作者: shanks
本周共整理了 5 个问题,都是一些感觉很有意思的问题,分享给大家。看到很多入门问题就没有整理了。
本周整理问题如下:
[Self]
from a Swift Protocol? ==
) function automatically for struct
in Swift? 对应的代码都放到了 github 上,有兴趣的同学可以下载下来研究: 点击下载
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 中枚举高级用法及实践
[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 declarationsQ3链接地址
楼主的问题是,如何用自然的方式,显示 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 说明 ,不过最好还是结合实际的代码来看比较好理解。
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)
}
==
) 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上的程序员们的智慧果然是无穷大的。核心的解决思路是:
/* 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
}