转载

每周 Swift 社区问答 2016-01-27

作者: shanks

本周共整理了 5 个问题,都是一些感觉很有意思的问题,分享给大家。看到很多入门问题就没有整理了。

本周整理问题如下:

  • Understanding Enums with Functions as Associated Values in Swift
  • How to return [Self] from a Swift Protocol?
  • How to show a number “to the power of X” with string
  • Literals in Swift generics
  • Is there way to define compare ( == ) function automatically for struct in Swift?

对应的代码都放到了 github 上,有兴趣的同学可以下载下来研究: 点击下载

Question1: Understanding Enums with Functions as Associated Values in Swift

Q1链接地址

问题描述

楼主的问题是,在 Swift 的枚举类型中,如何理解关联值参数允许传入函数类型(闭包)?楼主可以理解以下代码,关联值只包含一些基本数据结构:

enum Homework{
case InProgress(Int, Int)
case Complete
}

let load = Homework.InProgress(50, 100)

switch load {
case .InProgress(let done, let total):
print("/(done) out of /(total)")

case .Complete:
print("complete")
}

问题解答

函数在 Swift 是一等公民,可以在当做参数传入和右值使用。这是 Swift 函数式编程的基础。特别用在枚举类型的关联值中,能够做一些附加处理,见下面的例子:

enum Error: ErrorType {
case Temporary(message: String, recovery: () -> Void)
case Final(message: String)
}

func reconnect() {}

let err = Error.Temporary(
message: "Network down",
recovery: { reconnect() } // 重试操作
)

switch err {
case let .Temporary(message, recovery):
print(message)
recovery()
case let .Final(message):
print(message)
fatalError()
}

关于枚举的高级用法,可以参见我们翻译组的翻译的长文,值得一读: Swift 中枚举高级用法及实践

Question2: How to return [Self] from a Swift Protocol?

Q2链接地址

//: ### 问题描述

/

:

/

struct Database {

}protocol DatabaseInjectable {

static func deriveObjectFromDBRow(row: [String]) -> Self? // Method - 1  static func collectAllObjectsForDatabaseAction(action: (Database) -> Void) -> [Self]? // Method - 2 

}

class SampleClass: DatabaseInjectable {

init() {

}

static func deriveObjectFromDBRow(row: [String]) -> Self? {

return nil

}

// 报错:'Self' is only available in a protocol or as the result of a method in a class; static func collectAllObjectsForDatabaseAction(action: (Database) -> Void) -> [Self]? {         return nil } 

}

问题解答

第一个回答,Self 替换为协议名,如下:

protocol DatabaseInjectable {

static func deriveObjectFromDBRow(row: [String]) -> DatabaseInjectable? // Method - 1

static func collectAllObjectsForDatabaseAction(action: (Database) -> Void) -> [DatabaseInjectable]? // Method - 2
}

不满足需求,因为楼主希望指定具体的类。而不是协议。返回协议只能调用协议的方法。

第二个回答说,那你可以实现时候,加入 final 修饰类, Self 就可以替换成具体的类了。见下面代码:


final class SampleClass1: DatabaseInjectable {
init() {
}
static func deriveObjectFromDBRow(row: [String]) -> SampleClass1? {
return SampleClass1()
}

static func collectAllObjectsForDatabaseAction(action: (Database) -> Void) -> [SampleClass1]? {
let array = [SampleClass1]()
return array
}
}

可是这个回答不满足楼主的需求,加入了final 关键字,就不能当做父类继承了。

最后一个回答,我觉得是最好的,使用typealias来指定需要的类型,这样可以指定任何类型:


class Database1 {
var desc : String = "Default"
}

protocol DatabaseInjectable1 {
typealias MySelf

static func deriveObjectFromDBRow(row: [String]) -> MySelf?

static func collectAllObjectsForDatabaseAction(action: (Database1) -> Void) -> [MySelf]?
}

class MyClass : DatabaseInjectable1 {
typealias MySelf = MyClass

static func deriveObjectFromDBRow(row: [String]) -> MySelf? {
return MyClass()
}

static func collectAllObjectsForDatabaseAction(action: (Database1) -> Void) -> [MySelf]? {
return [MyClass(), MyClass()]
}
}

/* example */
let closure : (Database1) -> () = { print($0.desc) }
var arr : [MyClass]? = MyClass.collectAllObjectsForDatabaseAction(closure)
/* [MyClass, MyClass] */

问题思考

关于 Self 的详细解释,可以参见喵神的文章 接口和类方法中的 SELF , 从语义上来讲,Self代表的是类型本身,或者子类。但是放到数组里面去表示该类型的数组,编译器并不支持。即使是编译器允许这样做,那么应该如何去实现这样的方法呢?

如果没有特别强烈的需求,建议使用 typealias 来实现,不过马上 typealias 要被 associatedtype 取代了。有兴趣的可以看看:

Replace typealias keyword with associatedtype for associated type declarations

Question3: How to show a number “to the power of X” with string

Q3链接地址

问题描述

楼主的问题是,如何用自然的方式,显示 X 的 n 次方:

问题解答

这个问题比较普遍,在这个 帖子 中有代码实例,见以下代码:

import UIKit
let font:UIFont? = UIFont(name: "Helvetica", size:20)
let fontSuper:UIFont? = UIFont(name: "Helvetica", size:10)
let attString:NSMutableAttributedString = NSMutableAttributedString(string: "6.022*1023", attributes: [NSFontAttributeName:font!])
attString.setAttributes([NSFontAttributeName:fontSuper!,NSBaselineOffsetAttributeName:10], range: NSRange(location:8,length:2))
attString
//labelVarName.attributedText = attString;

在 playground 中, 请点击右边的眼睛按钮查看,显示出渲染后的效果。与问题描述中的一样。

关于 NSMutableAttributedString ,可以查看这里: NSAttributedString 详解 , 也可以查看官网的 Api 说明 ,不过最好还是结合实际的代码来看比较好理解。

Question4: Literals in Swift generics

问题链接

Q4链接地址

问题描述

楼主定义了一个泛型函数,但是报错了:

func sign<T> (value:T) -> T {
if value < 0.0 { // error: Binary operator '<' cannot be applied to operands of type 'T' and 'Double'
return -1.0
}
if value > 0.0 {
return 1.0
}
return 0.0
}

问题解答

此问题对于看过 Swift 官方文档的同学来讲,简直是小菜一碟,这个问题感觉可以作为一个面试题,如果回答不出来,就不要在 Swift 程序界混了吧:

import UIKit

protocol MyFloats : Comparable {
init(_ value: Double)
}

extension Double : MyFloats { }
extension Float : MyFloats { }
extension CGFloat : MyFloats { }

func sign<T: MyFloats> (value:T) -> T {
if value < T(0.0) {
return T(-1.0)
}
if value > T(0.0) {
return T(1.0)
}
return T(0.0)
}

Question5: Is there way to define compare ( == ) function automatically for struct in Swift?

问题链接

Q5链接地址

问题描述

楼主假设有一个巨大(包含很多属性的)的结构体,然后实现 “==” 操作就会很麻烦,因为要每个属性都比较一遍才行,见下面的代码。那么问题来了,有没有更好的办法可以简化 “==” 操作的定义呢?

struct SuperStruct {
var field1: Int = 0
var field2: String = ""
// lots of lines...
var field512: Float = 0.0
}

extension SuperStruct: Equatable {
}

func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
return
lhs.field1 == rhs.field1 &&
lhs.field2 == rhs.field2 &&
// lots of lines...
lhs.field512 == rhs.field512
}

问题解答

stackoverflow上的程序员们的智慧果然是无穷大的。核心的解决思路是:

  • 利用协议扩展,把所有用到的类型,都实现 isEqualTo 方法,用于比较类型是否相对。下面的例子只举了基本类型,自定义类型,也可以满足这个协议实现比较。
  • 利用 Mirror 函数,得到结构体中所有属性的 list,然后逐个比较
/* Let a heterogeneous protocol act as "pseudo-generic" type
for the different (property) types in 'SuperStruct' */

protocol MyGenericType {
func isEqualTo(other: MyGenericType) -> Bool
}
extension MyGenericType where Self : Equatable {
func isEqualTo(other: MyGenericType) -> Bool {
if let o = other as? Self { return self == o }
return false
}
}

/* Extend types that appear in 'SuperStruct' to MyGenericType */
extension Int : MyGenericType {}
extension String : MyGenericType {}
extension Float : MyGenericType {}
// ...

/* Finally, 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {

let mLhs = Mirror(reflecting: lhs).children.filter { $0.label != nil }
let mRhs = Mirror(reflecting: rhs).children.filter { $0.label != nil }

for i in 0..<mLhs.count {
guard let valLhs = mLhs[i].value as? MyGenericType, valRhs = mRhs[i].value as? MyGenericType else {
print("Invalid: Properties 'lhs./(mLhs[i].label!)' and/or 'rhs./(mRhs[i].label!)' are not of 'MyGenericType' types.")
return false
}
if !valLhs.isEqualTo(valRhs) {
return false
}
}
return true
}

/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true

如果不用协议扩展,代码会是这个样子, 类型越多, case 越多,显然不是一个很好的解决方案:


func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {

let mLhs = Mirror(reflecting: lhs).children.filter { $0.label != nil }
let mRhs = Mirror(reflecting: rhs).children.filter { $0.label != nil }

for i in 0..<mLhs.count {
switch mLhs[i].value {
case let valLhs as Int:
guard let valRhs = mRhs[i].value as? Int where valRhs == valLhs else {
return false
}
case let valLhs as String:
guard let valRhs = mRhs[i].value as? String where valRhs == valLhs else {
return false
}
case let valLhs as Float:
guard let valRhs = mRhs[i].value as? Float where valRhs == valLhs else {
return false
}
/* ... extend with one case for each type
that appear in 'SuperStruct' */

case _ : return false
}
}
return true
}
原文  http://swift.gg/2016/01/27/swift-qa-2016-01-27/
正文到此结束
Loading...