init模式
初始化是准备要使用的类,结构体或枚举的实例的过程。
逐一成员构造器 (只有结构体有) 编译器会为结构体自动生成一个逐一成员构造器。生成的构造器将包含除拥有默认值的常量属性之外的所有属性,包括可选型;它的可访问性是 internal
,所以对其他模块不可见。
只要结构体的任何一个存储属性是 private 的,那么结构体的默认逐一成员构造器也将是 private 的。同样,如果结构体的任何一个存储属性是 file private 的,那么逐一成员构造器也将是 file private 的。其他情况,这个构造器的访问级别是 internal。
失败构造器 有些情况下你发现事情不对,你不想因此创建出糟糕的或者不合理的对象。比如你想要滤掉一堆点中的原点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct Point { let x: Int let y: Int init? (x : Int , y : Int ) { if x == 0 && y == 0 { return nil } self .x = x self .y = y } } let p1 = Point (x: 0 , y: 0 ) let p2 = Point (x: 1 , y: 1 )
枚举遵循 RawRepresentable
协议,它们可以通过 rawValue 来构造,这也是一个失败构造器模式的例子。
1 2 3 4 5 6 7 8 enum Color : String { case red case blue case yellow } let c1 = Color (rawValue: "orange" ) let c2 = Color (rawValue: "red" )
类初始化 类并没有逐一成员构造器。它们是引用类型,加上继承逻辑,为类自动生成逐一成员构造器这件事要复杂很多。
默认构造器 如果你为包括可选型在内的所有存储属性提供了默认值,你会获得一个自动生成的 internal
访问级别的默认构造器。
1 2 3 4 5 class Point { let x: Int = 1 let y: Int = 1 } let p1 = Point ()
或者像这样:
1 2 3 4 5 6 7 class Point { let x: Int = 0 let y: Int = 0 var key: Int ! let label: String ? = "zero" } let p1 = Point ()
指定构造器
指定构造器是一个类的主构造器
指定构造器是没有用 convenience
标记的构造器,一个类可以有多个指定构造器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Point { let x: Int let y: Int init (x : Int , y : Int ) { self .x = x self .y = y } } class NamedPoint : Point { let label: String ? init (x : Int , y : Int , label : String ?) { self .label = label super .init (x: x, y: y) } init (point : Point , label : String ?) { self .label = label super .init (x: point.x, y: point.y) } } let p1 = NamedPoint (x: 1 , y: 1 , label: "first" )let p2 = NamedPoint (point: Point (x: 1 , y: 1 ), label: "second" )
指定构造器必须调用它的直接父类的某个指定构造器,所以你需要向上代理构造链。但在这之前,根据初始化的第一条规则,我们必须先确保当前类的所有存储属性被初始化。
便利构造器 1 2 3 4 5 6 7 8 9 10 11 12 13 class Point { let x: Int let y: Int init (x : Int , y : Int ) { self .x = x self .y = y } convenience init (z : Int ) { self .init (x: z, y: z) } } let p1 = Point (z: 1 )
便利构造器必须调用同一个类层级里的其他构造器,最终调用到指定构造器。
结构体也可以有“便利”构造器,但你不需要写出 “convenience” 关键字。实际上它们的 init 方法略有不同,因为你可以在一个 init 方法里调用其他的 init 方法,外部看起来跟类的那套机制很相似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct Point { let x: Int let y: Int init (x : Int , y : Int ) { self .x = x self .y = y } init (z : Int ) { self .init (x: z, y: z) } } var p1 = Point (z: 1 )
必需构造器 如果你在一个类里面用 required
标记构造器。那你将不得不在类继承体系里每个层级都用上它。因为子类也必须实现这种构造器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Point { let x: Int let y: Int required init (x : Int , y : Int ) { self .x = x self .y = y } } class NamedPoint : Point { let label: String ? required init (x : Int , y : Int ) { self .label = nil super .init (x: x, y: y) } } let p1 = NamedPoint (x: 1 , y: 1 )
重写构造器 在 Swift 中,构造器默认是不被子类继承的。如果你想要为子类提供一个父类已经存在的构造器,你需要用到 override
关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Point { let x: Int let y: Int init (x : Int , y : Int ) { self .x = x self .y = y } } class NamedPoint : Point { let label: String ? override init (x : Int , y : Int ) { self .label = nil super .init (x: x, y: y) } } let p1 = NamedPoint (x: 1 , y: 1 )
构造器继承有两条规则:
如果你的子类没有定义任何指定构造器,它将自动继承其父类的所有指定构造器。
如果你的子类提供其父类的指定构造器的实现——无论是通过规则1继承得到,或者自定义实现的,那么它都会自动继承父类的所有便利构造器。
懒加载
在计算机编程中,懒加载是一种在创建对象,或者计算某个值,或者其他某些开销昂贵的处理过程中延迟执行的策略。这些过程只在第一次被需要的时候才会真正执行。
当一个属性只有在某些时刻才被需要,你可以用 lazy
关键字放在它前头。这样它就被排除出初始化过程。它的默认值一经请求,就会赋值。这个特性对于创建开销昂贵或者创建过程耗时的类型很有用。
用懒加载变量来优化代码十分方便,但它们只能用于结构体和类,也不能用于计算属性。
下面是一个睡美人的传说。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class SleepingBeauty { init () { print ("zzz...沉睡中..." ) sleep(2 ) print ("睡美人准备好了!" ) } } class Castle { var princess = SleepingBeauty () init () { print ("城堡准备好了!" ) } } print ("一座新的城堡即将出现..." )let castle = Castle ()
这段代码的输入如下,你可以看到公主睡了很久,还导致城堡被阻塞。
1 2 3 4 一座新的城堡即将出现... zzz...沉睡中... 睡美人准备好了! 城堡准备好了!
现在,让我们通过添加 lazy
关键字来把事情提速吧,以便我们的英雄有时间去屠龙,同时我们的继续沉睡,直到剧情需要她醒来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class SleepingBeauty { init () { print ("zzz...沉睡中..." ) sleep(2 ) print ("睡美人准备好了!" ) } } class Castle { lazy var princess = SleepingBeauty () init () { print ("城堡准备好了!" ) } } print ("一座新的城堡即将出现..." )let castle = Castle ()castle.princess
这下好多了!城堡会被立即创建,以便战斗立即开始,打败恶龙后王子唤醒公主。他们幸福地生活在一起,happy ending。
1 2 3 4 一座新的城堡即将出现... 城堡准备好了! zzz...沉睡中... 睡美人准备好了!
用懒加载来避免可选型 还可以用懒加载来抹除对象中的可选型。
当你在处理 UIView
的派生类时这会很有用。举个例子,如果你的视图层级需要用到一个UILabel
,通常你会需要声明这个视图的可选型属性或者隐式解包可选型属性。这种情况下,我们就可以利用 lazy
关键字消灭邪恶的可选型需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class ViewController : UIViewController { lazy var label: UILabel = UILabel (frame: .zero) override func loadView () { super .loadView() self .view.addSubview(self .label) } override func viewDidLoad () { super .viewDidLoad() self .label.textColor = .black self .label.font = UIFont .systemFont(ofSize: 16 , weight: .bold) } }
使用懒加载闭包 基于存储属性做懒加载的主要好处是你的语句块只有那个属性发生读取操作时才会执行。看一下实践代码:
1 2 3 4 5 6 7 8 9 10 class ViewController : UIViewController { lazy var label: UILabel = { let label = UILabel (frame: .zero) label.translatesAutoresizingMaskIntoConstraints = false label.textColor = .black label.font = UIFont .systemFont(ofSize: 16 , weight: .bold) return label }() }
使用工厂进行懒加载初始化 工厂方法 如果你不喜欢上面那种闭包,你还可以把你的代码放进工厂方法,再通过懒加载变量来使用。就像下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 class ViewController : UIViewController { lazy var label: UILabel = self .createCustomLabel() private func createCustomLabel () -> UILabel { print ("被调用" ) let label = UILabel (frame: .zero) label.translatesAutoresizingMaskIntoConstraints = false label.textColor = .black label.font = UIFont .systemFont(ofSize: 16 , weight: .bold) return label } }
这里的工厂方法看起来就懒加载属性的私有构造器,还有进一步提升可复用性的空间。
静态工厂 如果你希望在应用的多个部分复用代码,那么把懒加载构造器的代码外包给静态工厂会是一种好的实践。举个例子,自定义 view
的初始化过程就很适合采用这个方式。况且,严格说来创建自定义视图不应该是 view controller
的任务,在这个例子中责任可以分担出去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class ViewController : UIViewController { lazy var label: UILabel = UILabel .createCustomLabel() } extension UILabel { static func createCustomLabel () -> UILabel { let label = UILabel (frame: .zero) label.translatesAutoresizingMaskIntoConstraints = false label.textColor = .black label.font = UIFont .systemFont(ofSize: 16 , weight: .bold) return label } }
使用静态工厂属性或者方法你还能额外收获一些好处,比如实现缓存或者返回特定的子类型。
迭代器 什么是迭代器设计模式?
让你能够迭代一个合集里的元素。
提供一种方法,访问一个容器对象中的各个元素,而又不暴露该对象的内部细节。
简单来说,迭代器为你提供一个接口,让你可以遍历合集,不用关心这个合集是怎么实现的。下面是一个用字符串迭代器演示上面定义的粒子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import Foundationprotocol StringIterator { func next () -> String ? } class ArrayStringIterator : StringIterator { private let values: [String ] private var index: Int ? init (_ values : [String ]) { self .values = values } private func nextIndex (for index : Int ?) -> Int ? { if let index = index, index < self .values.count - 1 { return index + 1 } if index == nil , ! self .values.isEmpty { return 0 } return nil } func next () -> String ? { if let index = self .nextIndex(for: self .index) { self .index = index return self .values[index] } return nil } } protocol Iterable { func makeIterator () -> StringIterator } class DataArray : Iterable { private var dataSource: [String ] init () { self .dataSource = [" " , " " , " " , " " , " " , " " , " " , " " , " " ] } func makeIterator () -> StringIterator { return ArrayStringIterator (self .dataSource) } } let data = DataArray ()let iterator = data.makeIterator()while let next = iterator.next() { print (next) }
Swift中的自定义序列 Swift有一个内建的序列协议,可以帮助你创建迭代器。实现你自己的序列其实就是做一件事:通过创建自定义的迭代器隐藏内部的数据结构体。你只需要存储当前的索引,然后在每一次 next 函数被调用时返回下一个元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import Foundationstruct Emojis : Sequence { let animals: [String ] func makeIterator () -> EmojiIterator { return EmojiIterator (self .animals) } } struct EmojiIterator : IteratorProtocol { private let values: [String ] private var index: Int ? init (_ values : [String ]) { self .values = values } private func nextIndex (for index : Int ?) -> Int ? { if let index = index, index < self .values.count - 1 { return index + 1 } if index == nil , ! self .values.isEmpty { return 0 } return nil } mutating func next () -> String ? { if let index = self .nextIndex(for: self .index) { self .index = index return self .values[index] } return nil } } let emojis = Emojis (animals: [" " , " " , " " , " " , " " , " " , " " , " " , " " ])for emoji in emojis { print (emoji) }
因此,Sequence
协议相当于前一个例子中的那个 Iterable
协议,而 IteratorProtocol
则类似于对应的 StringIterator
,不过更像 Swift 风格,并且更通用。
Swift 中的自定义 Collection Collection 是比序列更进一步的东西,里面的元素可以通过下标访问。
举个例子,如果你想消除可选型。想象一个分类收藏的机制,对于每个分类都有一组收藏。你需要处理空的情况和不存在的情况。利用自定义 Collection
,你可以在你的自定义数据结构里隐藏额外的代码,只暴露一个干净的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 class Favorites { typealias FavoriteType = [String : [String ]] private(set) var list: FavoriteType public static let shared = Favorites () private init () { self .list = FavoriteType () } } extension Favorites : Collection { typealias Index = FavoriteType .Index typealias Element = FavoriteType .Element var startIndex: Index { return self .list.startIndex } var endIndex: Index { return self .list.endIndex } subscript (index : Index ) -> Iterator .Element { return self .list[index] } func index (after i : Index ) -> Index { return self .list.index(after: i) } } extension Favorites { subscript (index : String ) -> [String ] { return self .list[index] ?? [] } func add (_ value : String , category : String ) { if var values = self .list[category] { guard ! values.contains(value) else { return } values.append(value) self .list[category] = values } else { self .list[category] = [value] } } func remove (_ value : String , category : String ) { guard var values = self .list[category] else { return } values = values.filter { $0 == value } if values.isEmpty { self .list.removeValue(forKey: category) } else { self .list[category] = values } } } Favorites .shared.add("apple" , category: "fruits" )Favorites .shared.add("pear" , category: "fruits" )Favorites .shared.add("apple" , category: "fruits" )Favorites .shared["fruits" ]Favorites .shared.remove("apple" , category: "fruits" )Favorites .shared.remove("pear" , category: "fruits" )Favorites .shared.list
单例 单例之所以名声不好是因为他们共享全局可变状态。可以从程序的任何地方访问全局变量,所以当你的类使用这些变量时,它们是有状态的,不安全的,紧耦合并且难以调试的。
1 2 3 4 5 6 7 8 9 10 11 12 var global = 0 func square (_ x : Int ) -> Int { global = x return x * x } global = 1 ; var result = square(5 )result += global print (result)
利用单例来管理那些会持续整个应用生命周期的状态。Swift 默认是非线程安全的
何时使用单例类? 举个例子,UIApplication
最有可能是单例,因为应用本来就只能有一个实例,并且它需要存续到应用被关闭。这是一个绝佳的单例范例。 另外一个用例是 Logger
类。它也很适合用单例,因为开或者不开 logger
都不会影响到应用状态。没有别人会持有或者管理这个logger
,只有你自己传递信息给它,因此状态不会被搞乱。 结论:控制台和 logger
类是很适合单例的场景。
1 Console .default.notice("Hello,我是一个单例!" )
在Apple
的框架中有大量的单例场景,下面这份清单或许能给你一些灵感:
1 2 3 4 5 6 7 8 9 10 HTTPCookieStorage .sharedURLCredentialStorage .sharedURLSessionConfiguration .defaultURLSession.sharedFileManager .defaultBundle.mainUserDefaults.standardNotificationCenter .defaultUIScreen.mainUIDevice.currentUIApplication .sharedMPMusicPlayerController.systemMusicPlayerGKLocalPlayer .localPlayer()SKPaymentQueue .default()WCSession .defaultCKContainer .default()
如果你想要把某样东西变成单例,先问自己几个问题:
有没有其他人可能持有,管理或者对这个东西负责?
是真的只有一个实例吗?
可以用全局状态变量代替吗?
是否存续在整个应用生命周期?
有没有其他选项?
Swift中如何创建单例? 1 2 3 4 5 6 7 8 9 class Singleton { static let shared = Singleton () private init () { } } let singleton = Singleton .shared
如何消除单例? 单例最常用的替代选项是依赖注入 。首先,你应该将单例方法抽象为协议,然后用单例作为这个协议的默认实现 。现在,你可以将单例或者重构的对象注入合适的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 typealias DataCompletionBlock = (Data ?) -> Void protocol Session { func make (request : URLRequest , completionHandler : @escaping DataCompletionBlock ) } extension URLSession : Session { func make (request : URLRequest , completionHandler : @escaping DataCompletionBlock ) { let task = self .dataTask(with: request) { data, _ , _ in completionHandler(data) } task.resume() } } class ApiService { var session: Session init (session : Session = URLSession .shared) { self .session = session } func load (_ request : URLRequest , completionHandler : @escaping DataCompletionBlock ) { self .session.make(request: request, completionHandler: completionHandler) } } class MockedSession : Session { func make (request : URLRequest , completionHandler : @escaping DataCompletionBlock ) { completionHandler("Mocked data response" .data(using: .utf8)) } } func test () { let api = ApiService (session: MockedSession ()) let request = URLRequest (url: URL (string: "https://localhost/" )! ) api.load(request) { data in print (String (data: data! , encoding: .utf8)! ) } } test()
简单工厂 用 switch-case 来实现简单工厂 简单工厂模式的目标是封装一些经常变化的事情。
想象一个调色板应用,你需要根据设计师的日常习惯,比如最近的常用的颜色来改变调色板的默认颜色。如果要手工搜索和替换每一处创建的颜色实例会很麻烦。让我们在 Swift 中创建一个简单工厂,它基于给定的样式返回不同的颜色。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class ColorFactory { enum Style { case text case background } func create (_ style : Style ) -> UIColor { switch style { case .text: return .black case .background: return .white } } } let factory = ColorFactory ()let textColor = factory.create(.text)let backgroundColor = factory.create(.background)
简单工厂十分有用,特别是在对象的构造过程很复杂的情况下。你也可以定义一个协议,在它的实现中通过 switch-case
块返回各种不同的实例类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 protocol Environment { var identifier: String { get } } class DevEnvironment : Environment { var identifier: String { return "dev" } } class LiveEnvironment : Environment { var identifier: String { return "live" } } class EnvironmentFactory { enum EnvType { case dev case live } func create (_ type : EnvType ) -> Environment { switch type { case .dev: return DevEnvironment () case .live: return LiveEnvironment () } } } let factory = EnvironmentFactory ()let dev = factory.create(.dev)print (dev.identifier)
它通过分离构造和使用逻辑实现松散耦合。
它只是一种对于经常变化的事情的包装器。 ♂️
Swift 里的简单工厂可以采用 enum 和 switch-case 来实现。
如果你想返回不同的对象,可以使用协议。(POP )
保持简单
这个设计模式将创建过程和实际使用分离开,将创建的责任移给特定的角色。如果创建过程有变化,只需要改动工厂即可,其他代码可以完全不变。