Swift-鏈?zhǔn)介_(kāi)發(fā)思想

一. 什么是鏈?zhǔn)剑?/h2>

可以連續(xù)不斷地進(jìn)?方法調(diào)?用的一種語(yǔ)法形式。

二. 探究鏈?zhǔn)降氖褂门c實(shí)現(xiàn)本質(zhì)


示例1:打豆豆

有位科學(xué)家到了南極,碰到一群企鵝。他問(wèn)其中一個(gè):“你每天都干什么呀?”那企鵝說(shuō):“吃飯睡覺(jué)打豆豆?!?br> 他又問(wèn)另一個(gè):“你每天都干什么呀?”那企鵝也源說(shuō):“吃飯睡覺(jué)打豆豆?!?br> 他問(wèn)了許多許多的企鵝,都說(shuō):“吃飯睡覺(jué)打豆豆?!?br> 后來(lái)他碰到了一只小企鵝,很可愛(ài)的樣子,就問(wèn)它:“小朋友,你每天都干什么呀?”小企鵝說(shuō):“吃飯睡覺(jué)?!笨茖W(xué)家一愣,隨即問(wèn)到:“你怎么不打豆豆呀?”小企鵝委屈的說(shuō):“因?yàn)槲揖褪嵌苟?。?/p>

思考一下如何用代碼表述這些企鵝每天都做了什么?

普通的實(shí)現(xiàn)方式

// 其他企鵝的一天
let otherPenguin = Penguin()
otherPenguin.eat()
otherPenguin.sleep()
otherPenguin.strike(penguin: "豆豆")
print(otherPenguin.description)
// ->吃飯->睡覺(jué)->打豆豆

使用鏈?zhǔn)綄?shí)現(xiàn)

let longDay = Penguin.start { (make) in
    make.eat().sleep().strike("豆豆")
}
print(penguin.description)
// ->吃飯->睡覺(jué)->打豆豆
// 企鵝類(lèi)
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 += "->睡覺(jué)"
        return self
    }
    
    @discardableResult
    func strike(_ name: String) -> Penguin {
        description += "->打\(name)"
        return self
    }
}

通過(guò)這個(gè)示例可以發(fā)現(xiàn)鏈?zhǔn)奖磉_(dá)優(yōu)點(diǎn):精簡(jiǎn)代碼,提升代碼的閱讀性。

示例2:實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器功能

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的高級(jí)函數(shù)的使用

實(shí)現(xiàn)對(duì)數(shù)組去nil處理,并對(duì)數(shù)組排序的需求

let countArray = [1, 2, 4, 5, nil, 3]

let resultArr = countArray.compactMap { $0 }.sorted(by: <)
print(resultArr)
// [1, 2, 3, 4, 5]

查看使用的這兩個(gè)系統(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)compactMapsorted函數(shù)都返回一個(gè)數(shù)組類(lèi)型,滿足鏈?zhǔn)秸{(diào)用的條件。

可以將上面的鏈?zhǔn)讲鸾鉃?/p>

let arr1 = countArray.compactMap { $0 }
let arr2 = arr1.sorted(by: < )

通過(guò)示例2可以發(fā)現(xiàn)鏈?zhǔn)降牧硪粋€(gè)優(yōu)點(diǎn): 減少中間變量。

示例4:鏈?zhǔn)経I的使用

鏈?zhǔn)経I的介紹

移動(dòng)端的開(kāi)發(fā)工作離不開(kāi)對(duì)UI的操作(包含UI對(duì)象的聲明,UI對(duì)象的屬性配置,UI對(duì)象的添加,UI對(duì)象的約束布局等操作步驟)。這些操作需要大量的代碼來(lái)實(shí)現(xiàn),如果對(duì)代碼書(shū)寫(xiě)規(guī)范不嚴(yán)格遵守的話,相關(guān)代碼就有可能分散在文件的各個(gè)地方,影響代碼的整體結(jié)構(gòu)性和閱讀性。我們可以用鏈?zhǔn)剿枷雭?lái)解決這樣的問(wèn)題。

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"
    })

該段代碼實(shí)現(xiàn)了四個(gè)功能:

  • 初始化UILabel類(lèi)型的對(duì)象
  • 將該對(duì)象添加到父視圖上
  • 給這個(gè)對(duì)象添加約束布局
  • 給這個(gè)對(duì)象設(shè)置屬性

通過(guò)鏈?zhǔn)?/code>這種實(shí)現(xiàn)方式,把UI的相關(guān)的代碼都寫(xiě)在一起,方便管理和維護(hù),極大的提升了代碼的可讀性。

通過(guò)命名空間式擴(kuò)展避免命名沖突

前面我們給這些UIKit的類(lèi)通過(guò)擴(kuò)展的形式添加了這些方法 adhere , layout , config ,萬(wàn)一以后蘋(píng)果也使用了同樣的方法命名,我們就只能改方法的命名,非常不友好。

SnapKit 前幾個(gè)版本的是通過(guò)添加 snp_ 前綴的方式用來(lái)區(qū)分的。

view.snp_makeConstraints { (make) in
}

現(xiàn)在主流的Swift三方庫(kù)都支持采用命名空間式擴(kuò)展

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的其他一些說(shuō)明

我們的方法public func config(_ config: (T) -> Void) -> T。config 方法接收一個(gè)閉包作為參數(shù),所以也可以這樣用,增強(qiáng)復(fù)用性:

//  寫(xiě)一個(gè)配置的閉包
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)的代碼都強(qiáng)制寫(xiě)一起了,是不是為之后的更新代碼不便呢?

// 更新某一個(gè)需要更新的屬性配置
testLabel.bt.config {
    $0.textColor = UIColor.orange
}

// 當(dāng)然這樣也是可以的
testLabel.text = "改變紅色為橘色"

鏈?zhǔn)経I實(shí)現(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é)


通過(guò)以上的說(shuō)明和幾個(gè)簡(jiǎn)單的示例,發(fā)現(xiàn)鏈?zhǔn)骄哂幸韵聨讉€(gè)特點(diǎn):

  • 代碼簡(jiǎn)潔
  • 高復(fù)用性
  • 高可讀性
  • 減少中間變量

四. 鏈?zhǔn)秸{(diào)用的安全性


先來(lái)看一段約束布局

testLabel.snp.makeConstraints { (make) in
    make.width.equalTo(view.snp.left)
}

此段約束明顯是有問(wèn)題的,但是在編譯期不會(huì)報(bào)錯(cuò),只有真正運(yùn)行到此處才會(huì)報(bào)錯(cuò): 布局屬性的配對(duì)無(wú)效.
: 'NSLayoutConstraint for <UILabel>: Invalid pairing of layout attributes.'
往往這種潛在問(wèn)題都是致命的。接下來(lái)我們探究如何避免這種情況?

我們嘗試用代碼實(shí)現(xiàn)一下這個(gè)要求:

有句網(wǎng)絡(luò)名言是這樣的“同性才是真愛(ài),異性只為繁殖后代”

實(shí)現(xiàn)一個(gè)Love的協(xié)議,聲明 ManWomen 兩個(gè)類(lèi),并遵守該協(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("存在真愛(ài)")
    }
}
? 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)

雖然中間一個(gè)是man 一個(gè)是 women ,但是鏈?zhǔn)降淖詈笠粋€(gè)對(duì)象類(lèi)型是相同的。所以可以通過(guò)編譯。顯然不能滿足我們的需求。

?運(yùn)行能否成功?答案是肯定的。但是不符合我們的要求。
Validation.trueLove(left: man. man. man, right: man.women.man)

使用泛型約束繼續(xù)實(shí)現(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("存在真愛(ài)")
    }
}

最終使用的時(shí)候是這樣的

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)

可以通過(guò)這樣的方式,降低程序的錯(cuò)誤率提高代碼的安全性。

五. 可選鏈?zhǔn)秸{(diào)用


1. 概念

可選鏈?zhǔn)秸{(diào)用是指當(dāng)前值為可選類(lèi)型情況下,對(duì)當(dāng)前值執(zhí)行(獲取屬性、方法或下標(biāo))操作,會(huì)出現(xiàn)以下情況

  • 如果當(dāng)前的可選值為nil,調(diào)用失敗并返回nil;
  • 如果當(dāng)前的可選值有值,調(diào)用成功;

多個(gè)調(diào)用可以連接在一起形成調(diào)用鏈,當(dāng)其中任意一個(gè)節(jié)點(diǎn)返回nil,則整個(gè)調(diào)用鏈調(diào)用失敗返回nil。

2. 示例說(shuō)明

class Person {
    var residence: Residence?
}

class Residence {
    var rooms = [Room]()
    var numberOfRooms: Int {
        return rooms.count
    }
    
    // 通過(guò)下標(biāo)訪問(wèn)
    subscript(i: Int) -> Room {
        get {
            return rooms[i]
        }
        set {
            rooms[i] = newValue
        }
    }

    // 隱式返回類(lèi)型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è)置屬性
// 通過(guò)xiaoMing.residence來(lái)設(shè)定address屬性也會(huì)失敗,因?yàn)閤iaoMing.residence當(dāng)前為nil
let address = Address()
address.buildingName = "2.5產(chǎn)業(yè)園"
address.buildingNumber = "88號(hào)"
address.street = "dongchang Road"
xiaoMing.residence?.address = address
  • 通過(guò)可選鏈?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.
  • 通過(guò)可選鏈?zhǔn)秸{(diào)用訪問(wèn)下標(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.

六. 參考聲明

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容