Hi,SwiftGG 翻译组启用了新的域名:swiftgg.team今后翻译组的各项活动将会在新域名下开展,不要错过哦!

作者:shanks

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

本周整理问题如下:

对应的代码都放到了 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链接地址

//: ### 问题描述

/:
以下代码会报错,楼主的的意思是,想返回 Self 类型数组,可是会报错,怎么做才是对的呢?
/
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
}
文章目录
  1. 1. Question1: Understanding Enums with Functions as Associated Values in Swift
    1. 1.1. 问题描述
    2. 1.2. 问题解答
  2. 2. Question2: How to return [Self] from a Swift Protocol?
    1. 2.1. 问题解答
    2. 2.2. 问题思考
  3. 3. Question3: How to show a number “to the power of X” with string
    1. 3.1. 问题描述
    2. 3.2. 问题解答
  4. 4. Question4: Literals in Swift generics
    1. 4.1. 问题链接
    2. 4.2. 问题描述
    3. 4.3. 问题解答
  5. 5. Question5: Is there way to define compare (==) function automatically for struct in Swift?
    1. 5.1. 问题链接
    2. 5.2. 问题描述
    3. 5.3. 问题解答