Swift三劍客:閉包卵迂、泛型和協(xié)議
一气破、閉包
如何看閉包,暈
參考文章:談一談閉包
1暑椰、閉包定義:
在計(jì)算機(jī)科學(xué)中霍转,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數(shù)閉包(function
closures)一汽,是引用了自由變量的函數(shù)避消。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外
2召夹、閉包的必要條件
閉包形成的必要條件
1岩喷、函數(shù)引用自由變量
2、函數(shù)的執(zhí)行環(huán)境和自由變量的聲明環(huán)境不同
兩個(gè)重點(diǎn)是自由變量,引用環(huán)境
3监憎、自由變量
值捕獲一:
var a = 3
func add5(b:Int) -> Int {
a += b
return a
}
print(add5(b: 2))
輸出5
值捕獲二:
func makeIncrementor(amount: Int) -> () -> Int {
var runningTotal = 0
withUnsafePointer(to: &runningTotal) {print($0)}#輸出變量地址
func incrementor() -> Int {
runningTotal += amount
withUnsafePointer(to: &runningTotal) {print($0)}
return runningTotal
}
return incrementor
}
let a = makeIncrementor(amount:10)
print(a())
print(a())
let b = makeIncrementor(amount:10)
print(b())
閉包外的runningTotal地址:0x000060400022fdf0
閉包內(nèi)的runningTotal地址:0x000060400022fdf0
10
第二次執(zhí)行:0x000060400022fdf0
20
--------------------------------------
閉包外的runningTotal地址:0x000060400022fe30
閉包內(nèi)的runningTotal地址:0x000060400022fe30
10
題外話:
函數(shù)或者閉包格式:() → 類型
去哪里都是這個(gè)格式
先去找最里面一層纱意,看哪一層符合這個(gè)格式,再擴(kuò)大尋找外面一層
4鲸阔、閉包作用
1偷霉、改變自由變量生命周期
通過概念和實(shí)例代碼, 很明顯閉包的存在改變了變量的生命周期, 大部分情況下它可以將自由變量的生命周期延遲到閉包函數(shù)的執(zhí)行, 而函數(shù)式中最重要的一個(gè)思想是盡可能多使用純函數(shù)(純函數(shù)是指對(duì)于相同的輸入必定有相同的輸入的函數(shù)), 在純函數(shù)中如果想要保持一個(gè)變量, 那閉包肯定是最佳選擇
2迄委、柯里化
什么是柯里化?
柯里化(英語:Currying)类少,是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù)叙身,并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。
let add = { (x, y) -> Int in
return x + y
}
print(add(10,20))
print(add(10,30))
print(add(10,50))
上面對(duì)add函數(shù)的調(diào)用其實(shí)都是10+y的形式, 很多時(shí)候我們?yōu)榱朔庋b, 又對(duì)10+y這樣的式子進(jìn)行封裝
var add = { (x) -> Int in
return x + 10
}
這里有一個(gè)不好的地方就是add10這個(gè)函數(shù)的封裝只能適用于10+y, 雖然實(shí)現(xiàn)了柯里化, 但是對(duì)于使用者來說靈活性不夠, 其實(shí)這里我們可以利用閉包對(duì)add函數(shù)稍加改造, 既方便使用又不失靈活性
let add = { (x) -> ((Int) -> Int) in
return {(y) -> Int in
return x+y
}
}
let add10 = add(10)
print(add10(30))
print(add10(30))
print(add10(50))
5硫狞、如何簡(jiǎn)略閉包信轿?
詳見文章:使用閉包簡(jiǎn)化語法
5、閉包種類
尾隨閉包
如果你需要將一個(gè)很長(zhǎng)的閉包表達(dá)式作為最后一個(gè)參數(shù)傳遞給函數(shù)残吩,可以使用尾隨閉包來增強(qiáng)函數(shù)的可讀性财忽。尾隨閉包是一個(gè)書寫在函數(shù)括號(hào)之后的閉包表達(dá)式,函數(shù)支持將其作為最后一個(gè)參數(shù)調(diào)用世剖。在使用尾隨閉包時(shí)定罢,你不用寫出它的參數(shù)標(biāo)簽:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函數(shù)體部分
}
// 以下是不使用尾隨閉包進(jìn)行函數(shù)調(diào)用
someFunctionThatTakesAClosure(closure: {
// 閉包主體部分
})
// 以下是使用尾隨閉包進(jìn)行函數(shù)調(diào)用
someFunctionThatTakesAClosure() {
// 閉包主體部分
}
逃逸閉包
當(dāng)一個(gè)閉包作為參數(shù)傳到一個(gè)函數(shù)中,但是這個(gè)閉包在函數(shù)返回之后才被執(zhí)行旁瘫,我們稱該閉包從函數(shù)中逃逸。當(dāng)你定義接受閉包作為參數(shù)的函數(shù)時(shí)琼蚯,你可以在參數(shù)名之前標(biāo)注 @escaping
酬凳,用來指明這個(gè)閉包是允許“逃逸”出這個(gè)函數(shù)的。
一種能使閉包“逃逸”出函數(shù)的方法是遭庶,將這個(gè)閉包保存在一個(gè)函數(shù)外部定義的變量中宁仔。舉個(gè)例子,很多啟動(dòng)異步操作的函數(shù)接受一個(gè)閉包參數(shù)作為 completion handler峦睡。這類函數(shù)會(huì)在異步操作開始之后立刻返回翎苫,但是閉包直到異步操作結(jié)束后才會(huì)被調(diào)用。在這種情況下榨了,閉包需要“逃逸”出函數(shù)煎谍,因?yàn)殚]包需要在函數(shù)返回之后被調(diào)用。例如:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
二龙屉、面向協(xié)議
1呐粘、總結(jié):
用完全新的眼光去看協(xié)議,遵守協(xié)議取代子類繼承
2转捕、協(xié)議語法:
2、Swift Protocol 詳解 - 協(xié)議&面向協(xié)議編程
3五芝、協(xié)議
// 協(xié)議的定義
protocol Pet{
// 對(duì)于屬性痘儡,不能有初始值
var name: String{ get set }// = "My Pet"
// 統(tǒng)一使用var關(guān)鍵字
var birthPlace: String{ get }
// 對(duì)于屬性,get,set隱藏了實(shí)現(xiàn)細(xì)節(jié)枢步,可以使用let實(shí)現(xiàn)只讀沉删,也可以使用只有g(shù)et的計(jì)算型屬性
// 對(duì)于方法渐尿,不能有實(shí)現(xiàn)
func playWith()
// 對(duì)于方法,不能有默認(rèn)參數(shù)(默認(rèn)參數(shù)就是一種實(shí)現(xiàn))
//func fed(food: String = "leftover")
func fed()
func fed(food: String)
}
// 協(xié)議的繼承
protocol PetBird: Pet{
var flySpeed: Double{ get }
var flyHeight: Double{ get }
}
// 協(xié)議的實(shí)現(xiàn)丑念,實(shí)現(xiàn)協(xié)議規(guī)定的所有屬性和方法即可
struct Dog: Pet{
var name: String
var birthPlace: String
func playWith() {
print("Wong!")
}
func fed(){
print("I want a bone.")
}
// 在具體實(shí)現(xiàn)上可以加默認(rèn)參數(shù)
func fed(food: String = "Bone"){
if food == "Bone"{
print("Happy")
}else{
print("I want a bone")
}
}
}
3涡戳、協(xié)議的最大用處:
1、回顧面向?qū)ο?/p>
class Animal {
var leg: Int { return 2 }
func eat() {
print("eat food.")
}
func run() {
print("run with \(leg) legs")
}
}
class Tiger: Animal {
override var leg: Int { return 4 }
override func eat() {
print("eat meat.")
}
}
let tiger = Tiger()
tiger.eat() // "eat meat"
tiger.run() // "run with 4 legs"
我們看到 Tiger
和 Animal
共享了一部分代碼脯倚,這部分代碼被封裝到了父類中渔彰,而除了 Tiger
的其他的子類也能夠使用 Animal
的這些代碼。這其實(shí)就是 OOP 的核心思想 - 使用封裝和繼承推正,將一系列相關(guān)的內(nèi)容放到一起恍涂。
雖然我們努力用面向?qū)ο髞沓橄蠛屠^承的方法進(jìn)行建模,但是實(shí)際的事物往往是一系列特質(zhì)的組合植榕,而不單單是以一脈相承并逐漸擴(kuò)展的方式構(gòu)建的再沧。
面向?qū)ο蟮娜齻€(gè)困境:
- 橫切關(guān)注點(diǎn)
- 菱形缺陷
- 動(dòng)態(tài)派發(fā)安全性
橫切關(guān)注點(diǎn):
面向?qū)ο?/p>
class ViewCotroller: UIViewController
{
// 繼承
// 新加
func myMethod() {
}
}
class AnotherViewController: UITableViewController
{
// 繼承
// 新加
func myMethod() {
}
}
由上看出,很難在不同繼承關(guān)系的類里共用代碼尊残。這里的問題用“行話”來說叫做“橫切關(guān)注點(diǎn)” (Cross-Cutting Concerns)炒瘸。我們的關(guān)注點(diǎn) myMethod
位于兩條繼承鏈 (UIViewController
-> ViewCotroller
和 UIViewController
-> UITableViewController
-> AnotherViewController
) 的橫切面上。
那么POP如何來解決寝衫?協(xié)議擴(kuò)展
protocol P {
func myMethod()
}
// class ViewController: UIViewController
extension ViewController: P {
func myMethod() {
doWork()
}
}
// class AnotherViewController: UITableViewController
extension AnotherViewController: P {
func myMethod() {
doWork()
}
}
所謂協(xié)議擴(kuò)展顷扩,就是我們可以為一個(gè)協(xié)議提供默認(rèn)的實(shí)現(xiàn)。對(duì)于 P
慰毅,可以在 extension P
中為 myMethod
添加一個(gè)實(shí)現(xiàn):
protocol P {
func myMethod()
}
extension P {
func myMethod() {
doWork()
}
}
有了這個(gè)協(xié)議擴(kuò)展后隘截,我們只需要簡(jiǎn)單地聲明 ViewController
和 AnotherViewController
遵守 P
,就可以直接使用 myMethod
的實(shí)現(xiàn)了:
extension ViewController: P { }
extension AnotherViewController: P { }
viewController.myMethod()
anotherViewController.myMethod()
動(dòng)態(tài)派發(fā)安全性
ViewController *v1 = ...
[v1 myMethod];
AnotherViewController *v2 = ...
[v2 myMethod];
NSArray *array = @[v1, v2];
for (id obj in array) {
[obj myMethod];
}
我們?nèi)绻?ViewController 和 AnotherViewController 中都實(shí)現(xiàn)了 myMethod 的話汹胃,這段代碼是沒有問題的婶芭。myMethod 將會(huì)被動(dòng)態(tài)發(fā)送給 array 中的 v1 和 v2。但是着饥,要是我們有一個(gè)沒有實(shí)現(xiàn) myMethod 的類型犀农,會(huì)如何呢?
?```
NSObject *v3 = [NSObject new]
// v3 沒有實(shí)現(xiàn) `myMethod`
NSArray *array = @[v1, v2, v3];
for (id obj in array) {
[obj myMethod];
}
// Runtime error:
// unrecognized selector sent to instance blabla
// 編譯依然可以通過贱勃,但是顯然井赌,程序?qū)⒃谶\(yùn)行時(shí)崩潰。Objective-C 是不安全的贵扰,編譯器默認(rèn)你知道某個(gè)方法確實(shí)有實(shí)現(xiàn)仇穗,這是消息發(fā)送的靈活性所必須付出的代價(jià)
struct Person: Greetable {
let name: String
func greet() {
print("你好 \(name)")
}
}
struct Cat: Greetable {
let name: String
func greet() {
print("meow~ \(name)")
}
}
let array: [Greetable] = [
Person(name: "Wei Wang"),
Cat(name: "onevcat")]
for obj in array {
obj.greet()
}
// 你好 Wei Wang
// meow~ onevcat
對(duì)于沒有實(shí)現(xiàn) Greetbale 的類型,編譯器將返回錯(cuò)誤戚绕,因此不存在消息誤發(fā)送的情況:
struct Bug: Greetable {
let name: String
}
// Compiler Error:
// 'Bug' does not conform to protocol 'Greetable'
// protocol requires function 'greet()'
協(xié)議拓展:
1纹坐、Swift的協(xié)議默認(rèn)實(shí)現(xiàn): Swift 的 extension 也比OC的強(qiáng)大的多,protocol 和 extension配合起來舞丛, 做到了更大的靈活性耘子,實(shí)現(xiàn)更加強(qiáng)大的功能果漾, 比起繼承和組合更加有效。
2谷誓、Swift開發(fā)指南:Protocols與Protocol Extensions的使用心法:這是Swift從面向?qū)ο蟮矫嫦騾f(xié)議轉(zhuǎn)變的關(guān)鍵绒障,畢竟,我們?cè)贠bjective C中也有protocols捍歪。那么為什么Objective C不曾考慮POP户辱,Swift卻開始使用呢?答案在于protocol extension糙臼。 就像我們?cè)谏弦还?jié)看到的那樣庐镐,我們可以在Swift中擴(kuò)展classes和structs以向它們添加功能。然而变逃,通過protocols讓extensions更加強(qiáng)大必逆,因?yàn)樗鼈冊(cè)试S你為協(xié)議提供預(yù)設(shè)功能,
泛型
總結(jié):
<Element> <T> 泛型揽乱,<>提前申明名眉,接下來把Element,T當(dāng)成一個(gè)類型使用就行
在Swift中凰棉,泛型可作用于幾個(gè)地方:
- Function
- Classess/Structs/Enums
- Protocol
- Extension
重要重要
泛型加where 來代替子類璧针。。渊啰。。申屹。绘证。。
參考文章:
2嚷那、泛型
四魏宽、閑話拓展Extension
拓展Extension
類比iOS:http://swift.gg/2016/05/16/using-swift-extensions/
作用域:https://www.cnblogs.com/tieria/p/4507261.html
五、可選值决乎、可選鏈
參考文章:
1队询、Swift3之細(xì)致理解Optional(可選類型)
3构诚、從枚舉類型看
5、可選映射
1范嘱、為什么使用可選值送膳?
引入一個(gè)顯式可選類型的意義是什么呢员魏?對(duì)于習(xí)慣了 Objective-C 的程序員來說,最初使用可選類型也許會(huì)覺得奇怪叠聋。Swift 的類型系統(tǒng)相當(dāng)嚴(yán)格:一旦我們有可選類型撕阎,就必須處理它可能是 nil 的問題。編程相對(duì)復(fù)雜碌补。在 Objective-C 中虏束,這一切更靈活。
- 場(chǎng)景1:字典取值 —— 可能你會(huì)想要區(qū)分失敗 (鍵不存在于字典) 和成功返回 nil (鍵存在于字典脑慧,但關(guān)聯(lián)值是 nil) 兩種情況魄眉。若要在 Objective-C 中做到這一點(diǎn),你只能使用 NSNull闷袒。
- 場(chǎng)景2:編譯時(shí)安全坑律,運(yùn)行時(shí)崩潰。項(xiàng)目最常見的就是Array囊骤,Dictionary晃择。項(xiàng)目中創(chuàng)建請(qǐng)求的參數(shù)
2、可選值是什么
可選類型其根源是一個(gè)枚舉型也物,里面有None和Some兩種類型宫屠。
可選值定義:
3、可選值的基本用法
1)滑蚯、顯式拆包(強(qiáng)制解析)
顯式拆包返回兩個(gè)值:
- 拆包的動(dòng)作其實(shí)就是將Some里面的值取出來浪蹂;
- 當(dāng)Optional沒有值時(shí),編譯不通過(保證運(yùn)行安全)
let a: String? = "1"
print(a!)
// 輸出 1
let a: String? = nil
print(a!)
// 編譯不通過
2)告材、自動(dòng)拆包
通過在聲明時(shí)的數(shù)據(jù)類型后面加一個(gè)感嘆號(hào)(!)坤次,編譯器自動(dòng)拆包
自動(dòng)拆包返回兩個(gè)值:
- 拆包的動(dòng)作其實(shí)就是將Some里面的值取出來;
- 當(dāng)Optional沒有值時(shí)斥赋,運(yùn)行時(shí)崩潰
壞處:運(yùn)行時(shí)不安全缰猴,謹(jǐn)慎使用
3)、對(duì)比
聲明定義 | 拆包 | 安全 | |
---|---|---|---|
顯示拆包 | 疤剑? | 滑绒! | 編譯不通過,運(yùn)行時(shí)安全 |
自動(dòng)拆包 | 隘膘! | 需要 | 運(yùn)行時(shí)崩潰 |
4疑故、玩轉(zhuǎn)可選值(如何拆包)
1)、可選綁定(Optional Binding) ——— if let
可選綁定返回兩個(gè)值:
- 如果目標(biāo)有值棘幸,調(diào)用就會(huì)成功焰扳,返回該值
- 如果目標(biāo)為nil,調(diào)用將返回nil
var name: String? = nil
//var name: String? = "jike"
if let name = name {
print("name is \(name)")
} else {
print("name is \(name)")
}
對(duì)比顯式拆包的兩個(gè)優(yōu)點(diǎn):
1、不必一直使用吨悍!來拆包
2扫茅、可以處理nil的情況
2)、可選鏈(Optional chaining)
一句話總結(jié):可選鏈?zhǔn)且粋€(gè)可選值育瓜,一個(gè)為nil葫隙,一個(gè)為非nil
可選鏈返回兩個(gè)值:
- 如果目標(biāo)有值,調(diào)用就會(huì)成功躏仇,返回該值
- 如果目標(biāo)為nil恋脚,調(diào)用將返回nil
用可選值變量的時(shí)候各種判斷有時(shí)候還需要if嵌套真的很麻煩,所以出現(xiàn)了可選鏈
可選鏈與強(qiáng)制解析對(duì)比
可選鏈 '?' | 感嘆號(hào)(!)強(qiáng)制展開方法焰手,屬性糟描,下標(biāo)腳本可選鏈 |
---|---|
? 放置于可選值后來調(diào)用方法,屬性书妻,下標(biāo)腳本 | ! 放置于可選值后來調(diào)用方法船响,屬性,下標(biāo)腳本來強(qiáng)制展開值 |
當(dāng)可選為 nil 輸出比較友好的錯(cuò)誤信息 | 當(dāng)可選為 nil 時(shí)強(qiáng)制展開執(zhí)行錯(cuò)誤 |
3)躲履、可選映射 map
public func map<U>(f: Wrapped -> U) -> U?
public func flatMap<U>(f: Wrapped -> U?) -> U?
/// If `self == nil`, returns `nil`.
/// Otherwise, returns `f(self!)`.
public func map<U>(@noescape f: (Wrapped) throws -> U)
rethrows -> U? {
switch self {
case .Some(let y):
return .Some(try f(y)) //區(qū)別點(diǎn)
case .None:
return .None
}
}
/// Returns `nil` if `self` is `nil`,
/// `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?)
rethrows -> U? {
switch self {
case .Some(let y):
return try f(y) //區(qū)別點(diǎn)
case .None:
return .None
}
}
-
f
函數(shù)一個(gè)返回U
见间,另一個(gè)返回U?
。 - 一個(gè)調(diào)用的結(jié)果直接返回工猜,另一個(gè)會(huì)把結(jié)果放到 .Some 里面返回米诉。
// flatMap降維和 map的對(duì)比示例
let a: String? = "1"
let am = a.map{ Int($0) } // 閉包結(jié)果類型 Int?
print( am )
// Optional(Optional(1)) map結(jié)果類型 Int?? .some( transform(y) )
let afm = a.flatMap{ Int($0) } // 閉包結(jié)果類型 Int篷帅?
print(afm)
// Optional(1) flatMap結(jié)果類型 Int?? transform(y)
// 原來類型:Int?, 返回值類型:String?
let b: Int? = 1
let bm = b.map { String("result = \($0)") } // 閉包結(jié)果類型 String
print(bm)
/// "Optional("result = 1")"
// 原optional沒有值, map和flatMap函數(shù)都直接返回nil
let c:Int? = nil
let cm = c.map { String("result = \($0)") } // 閉包結(jié)果類型 String
print(cm)
/// "nil" map結(jié)果類型 String?
4)史侣、空合運(yùn)算符 ??
let newName = name == nil ? "no name" : name!
let newName2 = name ?? "no name"
項(xiàng)目方面: