Swifter 100个开发必备Tips的学习总结(一)

最近买了本王巍写的《Swifter 100个Swift2开发必备Tip》,之前看介绍以及看了此书序言后,确定此书应该是本不错的涉及Swift开发技巧的学习材料,故决定以此书讲述内容为题材,把自己对这些知识点的掌握以及由此引申出的知识及开发技巧归纳总结,一来便于自己巩固,二来也希望能帮到更多的喜欢Swift的开发者。

柯里化(Currying)


相信很多朋友都跟我一样,第一次听到这个名词,查了下关于该术语的解释:

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。

举个Swift中柯里化的实例,如下:

func addTwoNumbers(num: Int)(another: Int) -> Int {
    return num + another
}

let addThree = addTwoNumbers(3)
let result = addThree(another: 6)   //result is 9

从这个例子大家应该可以了解到柯里化在Swift中的用法,那么这个技巧在实际开发中有什么用呢?作者给我们举了个例子,就是在Swift中Selector只能通过字符串来生成,这个在编译期间是无法检查的,那为了控制风险,可以采用Ole Begemann提出的利用柯里化来对Target-Action模式进行封装,从而可以不用Selector方式来达到目的。

相信很多朋友和我一样,对这里所述内容不是很清楚,很有可能是因为大家接触Swift都相对比较晚,这时的Swift版本已经是2.2以后了。好,我就给大家讲一下Swift语言在这个知识点上的变化。在Swift2.2之前,Selector需要传入字符串,并且没有自动补全,这样开发者在输入时很容易出错。

let button = UIButton(type: .System)
button.addTarget(self, action: Selector(“buttonTapped:”), forControlEvents: .TouchUpInside)
...
func buttonTapped(sender: UIButton) { }

从Swift2.2开始,Selector写法更加安全了,有了自动补全,可以在编译阶段进行检查。写法如下:

button.addTarget(self, action: #selector(ViewController.buttonTapped(_:)), forControlEvents: .TouchUpInside)

好了,了解了Swift语言Selector语法的变化,我们再回来看Ole Begemann利用柯里化来对Target-Action模式进行的封装。首先给出了Swift中实例方法的本质

An instance method in Swift is just a type method that takes the instance as an argument and returns a function which will then be applied to the instance.

简单翻译就是:Swift中的实例方法就是一个类型方法,它以实例作为方法的第一个参数,并返回一个应用于该实例的函数。对这个概念也举了个例子如下:

class BankAccount {
    var balance: Double = 0.0

    func deposit(amount: Double) {
        balance += amount
    }
}

普通青年用法:

let account = BankAccount()
account.deposit(100) // balance is now 100

文艺青年用法:

let depositor = BankAccount.deposit
depositor(account)(100) // balance is now 200

depositor常量可以看做是类似C语言中实例方法的函数指针,也可以看做

let depositor: BankAccount -> (Double) -> ()

也就是说,这个函数有一个参数,就是BankAccount的实例,返回值为另一个函数,该函数接受一个Double类型的参数,没有返回值。

BankAccount.deposit(account)(100) // balance is now 300

我们如果连起来,再回头看开头介绍的实例方法的概念,就应该理解的更清楚了。

好明白了概念,我们再来看它是如何不依赖OC的消息分发机制(Selector方式),而用纯Swift的方式实现Target-Action模式的。

protocol TargetAction {
    func performAction()
}

struct TargetActionWrapper<T: AnyObject> : TargetAction {
    weak var target: T?
    let action: (T) -> () -> ()

    func performAction() -> () {
        if let t = target {
            action(t)()
        }
    }
}

enum ControlEvent {
    case TouchUpInside
    case ValueChanged
    // ...
}

class Control {
    var actions = [ControlEvent: TargetAction]()

    func setTarget<T: AnyObject>(target: T, action: (T) -> () -> (), controlEvent: ControlEvent) {
        actions[controlEvent] = TargetActionWrapper(target: target, action: action)
    }

    func removeTargetForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent] = nil
    }

    func performActionForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent]?.performAction()
    }
}

具体用法如下,在这里就不多介绍了。

class MyViewController {
    let button = Control()

    func viewDidLoad() {
        button.setTarget(self, action: MyViewController.onButtonTap, controlEvent: .TouchUpInside)
    }

    func onButtonTap() {
        println("Button was tapped")
    }
}

好了,介绍了这么多,大家应该对柯里化理解的比较深刻了,如果你准备利用这个特点在实际项目中大展拳脚,那我还是要给你泼一盆冷水。呵呵。原因如下:

Curried function declaration syntax func foo(x: Int)(y: Int) is of limited usefulness and creates a lot of language and implementation complexity. We should remove it.

在Swift3中柯里化特性已经被移除了,原因是用处有限且增加了语言实现的复杂性。不过大家学习柯里化这个概念还是值得的,说不定在今后的工作中还会遇到。

将protocol的方法声明为mutating


这个Tip说实在没有什么值得说的,看过Swift基本语法应该都知道,在protocol中声明的方法,class,struct,enum都可以实现,但是由于struct和enum是值类型,那么如果在方法中涉及成员变量修改的话,方法就需要被声明为mutating。而对于class来说,因为它是引用类型,无论是否修改其成员都不需要将方法声明为mutating,所以mutating对class来说相当于透明的。既然我们不知道定义的protocol方法将会被谁实现,为了避免错误,我们可以将protocol的方法都声明为mutating

Sequence


Swift中的for...in语法可以用在所有实现了SequenceType的类型上,那么SequenceType是如何实现的,我们还是看下类型声明。首先我们可以看到

public protocol SequenceType {...}

SequenceType被声明为一个public的protocol,其中定义了一些方法,为了能更好的理解protocol的定义,我们先来熟悉一些会看到的编译器指令。

  • @warn_unused_result:表示如果没有检查或者使用该方法的返回值,编译器就会报警告。
  • @noescape:用在函数的闭包参数上,意味着这个参数是唯一可被调用的(或者用在函数调用时以参数的方式出现),其意思是它的生命周期比函数调用的周期短,这有助于一些小小的性能优化,但最重要的是它屏蔽了闭包中对self.的需求。这使得函数的控制流比其他更加透明。
  • throws:可以抛出一个错误的函数或方法必需使用 throws 关键字标记。这些函数和方法被称为抛出异常函数(throwing functions)和抛出异常方法(throwing methods)。
  • rethrows:一个函数或方法可以使用 rethrows 关键字来声明,从而表明仅当这个函数或方法的一个函数参数抛出错误时这个函数或方法才抛出错误。这些函数和方法被称为重抛出异常函数(rethrowing functions)和重抛出异常方法(rethrowing methods)。重抛出异常函数或方法必需有至少一个抛出异常函数参数。

好,了解了这些编译器指令后,我们来把主要精力放在protocol具体声明上。首先声明如下:

associatedtype Generator : GeneratorType

从Swift2.2之后,protocol声明中会把typealias改为associatedtype关键字。原因是在Swift2.2之前,都用typealias,它主要有两种用法:

  • 为一个已经存在的类型取个别名
  • 在协议中作为一个类型的占位名称

举个例子:

protocal port {
    typealias Container: SequenceType
    typealias Element: Container.Generator.Element
}

如上这是两种不同的用法,Swift2.2之后,将第一种用法改用associatedtype关键字。

再回到SequenceType的声明上,首先声明了一个Generator的占位名称,类型是GeneratorType。从GeneratorType的声明中,我们可以看到它封装了枚举状态及遍历Sequence的方法。去掉编译器指令及注释后,声明代码如下:

public protocol GeneratorType {
    associatedtype Element
    public mutating func next() -> Self.Element?
}

王巍给了一个逆向遍历Sequence的实现GeneratorType的例子作为参考:

class ReverseGenerator: GeneratorType {
    typealias Element = Int     //注意这里在实现时我们用typealias
    
    var counter: Element
    
    init(start: Element) {
        self.counter = start
    }
    
    init<T>(array: [T]) {
        self.counter = array.count - 1
    }
    
    func next() -> Element? {
        return counter < 0 ? nil : counter--    //注意Swift3中 -- 语法会移除
    }
}

明白了GeneratorType如何实现后,我们再回头看下SequenceType的声明,必要实现的方法是

public protocol SequenceType {
    associatedtype Generator : GeneratorType
    public func generate() -> Self.Generator
}

我们以刚才定义的ReverseGenerator,来定义一个ReverseSequence。

class ReverseSequence<T>: SequenceType {
    typealias Generator = ReverseGenerator
    
    var array: [T]
    
    init(array: [T]) {
        self.array = array
    }
    
    func generate() -> Generator {
        return ReverseGenerator(array: array)
    }
}

这样我们就可以把ReverseSequence用for...in语法来遍历了,写个例子来验证一下

let arr = [0,1,2,3,4]

for i in ReverseSequence(array: arr) {
    print("Index \(i) is \(arr[i])")
}

结果如下:

Index 4 is 4
Index 3 is 3
Index 2 is 2
Index 1 is 1
Index 0 is 0

除了支持for...in语法,ReverseSequence还可以直接使用以下方法:

public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
public func filter(@noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element]

原因是这些方法已经在protocol extension中实现了。

多元组(Tuple)


这个Tip也很好理解,主要还是想介绍Swift新引入的Tuple的概念。对于方法返回值,利用元组这个概念,我们可以方便的返回多个不同类型的值。但是这样定义的方法适用于临时组织的数据,有时候定义类或者结构作为返回值会更适合。更多关于元组的语法,大家可以参考Apple官方Swift教程。

小结


本文按书中顺序介绍了4个Tips,有些概念可能比较陌生,所以本文做了很多延伸的介绍,并在下面给出了参考的链接。有些则展示了源码的声明,帮助更好理解Tip介绍的概念。 而其他一些Tips则相对简单,基本学习过Swift语法都应该是已经掌握的,故也不多做介绍。Swift语法的学习,国内已经有爱好者无偿做了高质量的翻译,下面也给出链接方便大家查阅。
本人也希望把此文做成一个系列,每篇介绍几个书中讲到的Tips,再加进一些本人的理解及相关知识的拓展延伸。希望对于没看过该书的朋友,可以学到书中所授知识,对于已读过的朋友,也能从本文中找到一些新的角度及解读。

参考文章

  1. Swift:Selector 语法糖
  2. Instance Methods are Curried Functions in Swift
  3. Swift 3.0 浅析 - 即将删除 currying 特性
  4. Swift 2.2 新特性前瞻
  5. The Swift Programming Language 中文版
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容