iOS iOS Swift Swift编程思想(四) - 链式表达结构 移动端小伙伴 2024-05-14 2024-05-14 一. 什么是链式? 可以连续不断地进⾏方法调⽤用的一种语法形式。
二. 探究链式的使用与实现本质 示例 1:打豆豆
有位科学家到了南极,碰到一群企鹅。他问其中一个:“你每天都干什么呀?”那企鹅说:“吃饭睡觉打豆豆。” 他又问另一个:“你每天都干什么呀?”那企鹅也源说:“吃饭睡觉打豆豆。” 他问了许多许多的企鹅,都说:“吃饭睡觉打豆豆。” 后来他碰到了一只小企鹅,很可爱的样子,就问它:“小朋友,你每天都干什么呀?”小企鹅说:“吃饭睡觉。”科学家一愣,随即问到:“你怎么不打豆豆呀?”小企鹅委屈的说:“因为我就是豆豆。”
思考一下如何用代码表述这些企鹅每天都做了什么 ❓
普通的实现方式
let otherPenguin = Penguin ()otherPenguin.eat() otherPenguin.sleep() otherPenguin.strike(penguin: "豆豆" ) print (otherPenguin.description)
使用链式实现
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 } }
通过这个示例可以发现链式表达优点:精简代码,提升代码的阅读性 。
示例 2:实现简单的计算器功能 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 的高级函数的使用 实现对数组去 nil 处理,并对数组排序的需求
let countArray = [1 , 2 , 4 , 5 , nil , 3 ]let resultArr = countArray.compactMap { $0 }.sorted(by: < )print (resultArr)
查看使用的这两个系统方法的源码
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 ] }
发现compactMap
和sorted
函数都返回一个数组类型,满足链式调用的条件。 可以将上面的链式拆解为
let arr1 = countArray.compactMap { $0 }let arr2 = arr1.sorted(by: < )
通过示例 2 可以发现链式的另一个优点: 减少中间变量 。
示例 4:链式 UI 的使用 链式 UI 的介绍 移动端的开发工作离不开对 UI 的操作(包含 UI 对象的声明,UI 对象的属性配置,UI 对象的添加,UI 对象的约束布局等操作步骤)。这些操作需要大量的代码来实现,如果对代码书写规范不严格遵守的话,相关代码就有可能分散在文件的各个地方,影响代码的整体结构性和阅读性。我们可以用链式思想来解决这样的问题。
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" })
该段代码实现了四个功能:
初始化 UILabel 类型的对象
将该对象添加到父视图上
给这个对象添加约束布局
给这个对象设置属性
通过链式
这种实现方式,把 UI 的相关的代码都写在一起,方便管理和维护,极大的提升了代码的可读性。
通过命名空间式扩展避免命名冲突 前面我们给这些 UIKit 的类通过扩展的形式添加了这些方法 adhere
, layout
, config
,万一以后苹果也使用了同样的方法命名,我们就只能改方法的命名,非常不友好。SnapKit
前几个版本的是通过添加 snp_
前缀的方式用来区分的。
view.snp_makeConstraints { (make) in }
现在主流的 Swift 三方库都支持采用命名空间式扩展
view.snp.makeConstraints { (make) in }
所以我们的链式 UI 可以改成这样
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 })
关于链式 UI 的其他一些说明 我们的方法public func config(_ config: (T) -> Void) -> T
。config 方法接收一个闭包作为参数,所以也可以这样用,增强复用性:
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) .bt.config ({ $0 .backgroundColor = UIColor .green })
用链式 UI 的方式把 UI 相关的代码都强制写一起了,是不是为之后的更新代码不便呢 ❓
testLabel.bt.config { $0 .textColor = UIColor .orange } testLabel.text = "改变红色为橘色"
链式 UI 的实现源码
import SnapKitextension NamespaceWrapper where T : UIView { @discardableResult 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.remakeConstraints { (make) in snapKitMaker(make) } return wrappedValue } }
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 } }
三. 阶段性总结 通过以上的说明和几个简单的示例,发现链式具有以下几个特点:
四. 链式调用的安全性 先来看一段约束布局
testLabel.snp.makeConstraints { (make) in make.width.equalTo(view.snp.left) }
此段约束明显是有问题的,但是在编译期不会报错,只有真正运行到此处才会报错: 布局属性的配对无效 .: 'NSLayoutConstraint for <UILabel>: Invalid pairing of layout attributes.'
往往这种潜在问题都是致命的。接下来我们探究如何避免这种情况?
我们尝试用代码实现一下这个要求:
有句网络名言是这样的“同性才是真爱,异性只为繁殖后代”
实现一个Love
的协议,声明 Man
和 Women
两个类,并遵守该协议。
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
,但是链式的最后一个对象类型是相同的。所以可以通过编译。显然不能满足我们的需求。
Validation .trueLove(left: man. man. man, right: man.women.man)
使用泛型约束
继续实现
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)
可以通过这样的方式,降低程序的错误率,提高代码的安全性 。
五. 可选链式调用 1. 概念 可选链式调用是指当前值为可选类型情况下,对当前值执行(获取属性、方法或下标)操作,会出现以下情况
如果当前的可选值为 nil,调用失败并返回 nil;
如果当前的可选值有值,调用成功;
多个调用可以连接在一起形成调用链,当其中任意一个节点返回 nil,则整个调用链调用失败返回 nil。
2. 示例说明 class Person { var residence: Residence ? } class Residence { var rooms = [Room ]() var numberOfRooms: Int { return rooms.count } subscript (i : Int ) -> Room { get { return rooms[i] } set { rooms[i] = newValue } } 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." ) }
let address = Address ()address.buildingName = "2.5产业园" address.buildingNumber = "88号" address.street = "dongchang Road" xiaoMing.residence? .address = address
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." ) }
if let firstRoomName = xiaoMing.residence? [0 ].name { print ("The first room name is \(firstRoomName) ." ) } else { print ("Unable to retrieve the first room name." ) }