一. 什么是鏈?zhǔn)剑?/h2>
可以連續(xù)不斷地進?方法調(diào)?用的一種語法形式俊扭。
二. 探究鏈?zhǔn)降氖褂门c實現(xiàn)本質(zhì)
示例1:打豆豆
有位科學(xué)家到了南極妨退,碰到一群企鵝赠涮。他問其中一個:“你每天都干什么呀华弓?”那企鵝說:“吃飯睡覺打豆豆芭挽』希”
他又問另一個:“你每天都干什么呀?”那企鵝也源說:“吃飯睡覺打豆豆袜爪∪涑茫”
他問了許多許多的企鵝,都說:“吃飯睡覺打豆豆辛馆“陈”
后來他碰到了一只小企鵝,很可愛的樣子昙篙,就問它:“小朋友腊状,你每天都干什么呀?”小企鵝說:“吃飯睡覺瓢对∈僮茫”科學(xué)家一愣,隨即問到:“你怎么不打豆豆呀硕蛹?”小企鵝委屈的說:“因為我就是豆豆醇疼∷恫ⅲ”
思考一下如何用代碼表述這些企鵝每天都做了什么?
普通的實現(xiàn)方式
// 其他企鵝的一天
let otherPenguin = Penguin()
otherPenguin.eat()
otherPenguin.sleep()
otherPenguin.strike(penguin: "豆豆")
print(otherPenguin.description)
// ->吃飯->睡覺->打豆豆
使用鏈?zhǔn)綄崿F(xiàn)
let longDay = Penguin.start { (make) in
make.eat().sleep().strike("豆豆")
}
print(penguin.description)
// ->吃飯->睡覺->打豆豆
// 企鵝類
class Penguin {
fileprivate var description: String = ""
static func start(block: (Penguin) -> Void) -> String {
let penguin = Penguin()
block(penguin)
return penguin.description
}
}
extension Penguin {
@discardableResult
func eat() -> Self {
description += "->吃飯"
return self
}
@discardableResult
func sleep() -> Penguin {
description += "->睡覺"
return self
}
@discardableResult
func strike(_ name: String) -> Penguin {
description += "->打\(name)"
return self
}
}
通過這個示例可以發(fā)現(xiàn)鏈?zhǔn)奖磉_優(yōu)點:精簡代碼,提升代碼的閱讀性秧荆。
示例2:實現(xiàn)簡單的計算器功能
let result = Calculator.begin { (maker) in
maker.add(n: 2).subtract(n: 2).add(n: 3).divide(n: 0)
}
print(result)
public class Calculator {
public static func begin(caculateBlock:(CaculateMaker) -> ()) -> Double {
let caculator = CaculateMaker()
caculateBlock(caculator)
return caculator.result
}
}
public class CaculateMaker {
public var result: Double = 0
/// 加法
@discardableResult
public func add(n: Double) -> CaculateMaker {
result += n
return self
}
/// 減法
@discardableResult
public func subtract(n: Double) -> CaculateMaker {
result -= n
return self
}
/// 乘法
@discardableResult
public func multiply(n: Double) -> CaculateMaker {
result *= n
return self
}
/// 除法
@discardableResult
public func divide(n: Double) -> CaculateMaker? {
if n == 0 {
result = 0
} else {
result /= n
}
return self
}
}
示例3: Swift的高級函數(shù)的使用
實現(xiàn)對數(shù)組去nil處理倔毙,并對數(shù)組排序的需求
let countArray = [1, 2, 4, 5, nil, 3]
let resultArr = countArray.compactMap { $0 }.sorted(by: <)
print(resultArr)
// [1, 2, 3, 4, 5]
查看使用的這兩個系統(tǒng)方法的源碼
public struct Array<Element> {
@inlinable public func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
@inlinable public func sorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element]
}
發(fā)現(xiàn)compactMap
和sorted
函數(shù)都返回一個數(shù)組類型,滿足鏈?zhǔn)秸{(diào)用的條件乙濒。
可以將上面的鏈?zhǔn)讲鸾鉃?/p>
let arr1 = countArray.compactMap { $0 }
let arr2 = arr1.sorted(by: < )
通過示例2可以發(fā)現(xiàn)鏈?zhǔn)降牧硪粋€優(yōu)點: 減少中間變量陕赃。
示例4:鏈?zhǔn)経I的使用
鏈?zhǔn)経I的介紹
移動端的開發(fā)工作離不開對UI的操作(包含UI對象的聲明,UI對象的屬性配置颁股,UI對象的添加么库,UI對象的約束布局等操作步驟)。這些操作需要大量的代碼來實現(xiàn)甘有,如果對代碼書寫規(guī)范不嚴(yán)格遵守的話诉儒,相關(guān)代碼就有可能分散在文件的各個地方,影響代碼的整體結(jié)構(gòu)性和閱讀性亏掀。我們可以用鏈?zhǔn)剿枷雭斫鉀Q這樣的問題忱反。
let _ = UILabel()
.adhere(toSuperView: view)
.layout (snapKitMaker: { (make) in
make.top.equalToSuperview().offset(80)
make.centerX.equalToSuperview()
})
.config ({(label) in
label.backgroundColor = UIColor.clear
label.font = UIFont.systemFont(ofSize: 20)
label.textColor = UIColor.darkGray
label.text = "Label"
})
該段代碼實現(xiàn)了四個功能:
- 初始化UILabel類型的對象
- 將該對象添加到父視圖上
- 給這個對象添加約束布局
- 給這個對象設(shè)置屬性
通過鏈?zhǔn)?/code>這種實現(xiàn)方式,把UI的相關(guān)的代碼都寫在一起滤愕,方便管理和維護温算,極大的提升了代碼的可讀性。
通過命名空間式擴展避免命名沖突
前面我們給這些UIKit的類通過擴展的形式添加了這些方法 adhere
, layout
, config
间影,萬一以后蘋果也使用了同樣的方法命名注竿,我們就只能改方法的命名,非常不友好宇智。
SnapKit
前幾個版本的是通過添加 snp_
前綴的方式用來區(qū)分的蔓搞。
view.snp_makeConstraints { (make) in
}
現(xiàn)在主流的Swift三方庫都支持采用命名空間式擴展
view.snp.makeConstraints { (make) in
}
所以我們的鏈?zhǔn)経I可以改成這樣
let _ = UILabel()
.bt.add(toSuperView: testLabel)
.bt.layout ({
$0.center.equalToSuperview()
$0.width.height.equalTo(100)
})
.bt.config(config)
.bt.config ({
$0.backgroundColor = UIColor.green
})
關(guān)于鏈?zhǔn)経I的其他一些說明
我們的方法public func config(_ config: (T) -> Void) -> T
。config 方法接收一個閉包作為參數(shù)随橘,所以也可以這樣用喂分,增強復(fù)用性:
// 寫一個配置的閉包
let config = {(label: UILabel) in
label.backgroundColor = UIColor.red
label.font = UIFont.systemFont(ofSize: 14)
label.text = "label"
label.textColor = UIColor.white
}
let testLabel = UILabel()
.bt.add(toSuperView: view)
.bt.layout ({
$0.edges.equalToSuperview()
})
.bt.config(config)
let _ = UILabel()
.bt.add(toSuperView: testLabel)
.bt.layout ({
$0.center.equalToSuperview()
$0.width.height.equalTo(100)
})
.bt.config(config)
// 差異化設(shè)置通配屬性
.bt.config ({
$0.backgroundColor = UIColor.green
})
用鏈?zhǔn)経I的方式把UI相關(guān)的代碼都強制寫一起了,是不是為之后的更新代碼不便呢?
// 更新某一個需要更新的屬性配置
testLabel.bt.config {
$0.textColor = UIColor.orange
}
// 當(dāng)然這樣也是可以的
testLabel.text = "改變紅色為橘色"
鏈?zhǔn)経I實現(xiàn)源碼
public protocol NamespaceWrappable {
associatedtype BTWrapperType
var bt: BTWrapperType { get }
static var bt: BTWrapperType.Type { get }
}
public extension NamespaceWrappable {
var bt: NamespaceWrapper<Self> {
return NamespaceWrapper(value: self)
}
static var bt: NamespaceWrapper<Self>.Type {
return NamespaceWrapper.self
}
}
public struct NamespaceWrapper<T> {
public let wrappedValue: T
public init(value: T) {
self.wrappedValue = value
}
}
import SnapKit
extension NamespaceWrapper where T: UIView {
/// 添加在視圖
/// - Parameter toSuperView: 父視圖
public func add(toSuperView: UIView) -> T {
toSuperView.addSubview(wrappedValue)
return wrappedValue
}
@discardableResult
public func config(_ config: (T) -> Void) -> T {
config(wrappedValue)
return wrappedValue
}
@discardableResult
public func layout(_ snapKitMaker: (ConstraintMaker) -> Void) -> T {
wrappedValue.snp.makeConstraints { (make) in
snapKitMaker(make)
}
return wrappedValue
}
}
三. 階段性總結(jié)
通過以上的說明和幾個簡單的示例机蔗,發(fā)現(xiàn)鏈?zhǔn)骄哂幸韵聨讉€特點:
- 代碼簡潔
- 高復(fù)用性
- 高可讀性
- 減少中間變量
四. 鏈?zhǔn)秸{(diào)用的安全性
先來看一段約束布局
testLabel.snp.makeConstraints { (make) in
make.width.equalTo(view.snp.left)
}
此段約束明顯是有問題的蒲祈,但是在編譯期不會報錯,只有真正運行到此處才會報錯: 布局屬性的配對無效.
: 'NSLayoutConstraint for <UILabel>: Invalid pairing of layout attributes.'
往往這種潛在問題都是致命的萝嘁。接下來我們探究如何避免這種情況梆掸?
我們嘗試用代碼實現(xiàn)一下這個要求:
有句網(wǎng)絡(luò)名言是這樣的“同性才是真愛,異性只為繁殖后代”
實現(xiàn)一個Love
的協(xié)議牙言,聲明 Man
和 Women
兩個類酸钦,并遵守該協(xié)議。
protocol Love { }
class Man: Love { }
class Women: Love { }
extension Love {
var man: Man {
return Man()
}
var women: Women {
return Women()
}
}
struct Validation {
static func trueLove<T: Love>(left: T, right: T) {
print("存在真愛")
}
}
? Cannot invoke 'trueLove' with an argument list of type '(left: Man, right: Women)'
Validation.trueLove(left: man, right: women)
Validation.trueLove(left: man, right: man.women)
?
Validation.trueLove(left: man, right: man)
雖然中間一個是man
一個是 women
咱枉,但是鏈?zhǔn)降淖詈笠粋€對象類型是相同的卑硫。所以可以通過編譯徒恋。顯然不能滿足我們的需求。
?運行能否成功欢伏?答案是肯定的入挣。但是不符合我們的要求。
Validation.trueLove(left: man. man. man, right: man.women.man)
使用泛型約束繼續(xù)實現(xiàn)
protocol Love { }
class Man<X>: Love { }
class Women<X>: Love { }
extension Love {
var man: Man<Self> {
return Man()
}
var women: Women<Self> {
return Women()
}
}
struct Validation {
static func trueLove<T: Love>(left: T, right: T) {
print("存在真愛")
}
}
最終使用的時候是這樣的
let man = Man<Any>()
let women = Women<Any>()
?
Validation.trueLove(left: man, right: man.women.man)
Validation.trueLove(left: women.man, right: man.women.man)
?
Validation.trueLove(left: man, right: man)
Validation.trueLove(left: women.man, right: women.man)
可以通過這樣的方式硝拧,降低程序的錯誤率径筏,提高代碼的安全性。
五. 可選鏈?zhǔn)秸{(diào)用
1. 概念
可選鏈?zhǔn)秸{(diào)用是指當(dāng)前值為可選類型情況下障陶,對當(dāng)前值執(zhí)行(獲取屬性滋恬、方法或下標(biāo))操作,會出現(xiàn)以下情況
- 如果當(dāng)前的可選值為nil咸这,調(diào)用失敗并返回nil夷恍;
- 如果當(dāng)前的可選值有值,調(diào)用成功;
多個調(diào)用可以連接在一起形成調(diào)用鏈媳维,當(dāng)其中任意一個節(jié)點返回nil,則整個調(diào)用鏈調(diào)用失敗返回nil遏暴。
2. 示例說明
class Person {
var residence: Residence?
}
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
// 通過下標(biāo)訪問
subscript(i: Int) -> Room {
get {
return rooms[i]
}
set {
rooms[i] = newValue
}
}
// 隱式返回類型Void. -> Void
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}
class Room {
let name: String
init(name: String) { self.name = name }
}
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if let temp = buildingName {
return temp
}
if let temp1 = buildingNumber, let temp2 = street {
return temp1 + temp2
}
return nil
}
}
- 讀取屬性
let xiaoMing = Person()
if let roomCount = xiaoMing.residence?.numberOfRooms {
print("xiaoMing's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// Unable to retrieve the number of rooms.
- 設(shè)置屬性
// 通過xiaoMing.residence來設(shè)定address屬性也會失敗侄刽,因為xiaoMing.residence當(dāng)前為nil
let address = Address()
address.buildingName = "2.5產(chǎn)業(yè)園"
address.buildingNumber = "88號"
address.street = "dongchang Road"
xiaoMing.residence?.address = address
- 通過可選鏈?zhǔn)秸{(diào)用調(diào)用方法
if let _ = xiaoMing.residence?.printNumberOfRooms() {
print("It was possible to print the number of rooms.")
} else {
print("It was not possible to print the number of rooms.")
}
//It was not possible to print the number of rooms.
- 通過可選鏈?zhǔn)秸{(diào)用訪問下標(biāo)
if let firstRoomName = xiaoMing.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
//Unable to retrieve the first room name.