上一篇 設(shè)計模式(Swift) - 2.單例模式假丧、備忘錄模式和策略模式中講了三種常見的設(shè)計模式.
- 單例模式: 限制了類的實例化,一個類只能實例化一個對象,所有對單例對象的引用都是指向了同一個對象.
- 備忘錄模式: 我們可以把某個對象保存在本地,并在適當?shù)臅r候恢復(fù)出來,app開發(fā)中最常見的應(yīng)用就是用戶數(shù)據(jù)的本地緩存.
- 策略模式: 通過封裝業(yè)務(wù)分支來屏蔽業(yè)務(wù)細節(jié),只給出相關(guān)的策略接口作為切換.
1. 觀察者模式(Observer Pattern)
1. 觀察者模式概述
觀察者模式: 一個對象的變化,能夠被另一個對象知道.
本文除了介紹基于Runtime的KVO實現(xiàn)及其原理,還會自己動手去現(xiàn)實一套觀察者模式,畢竟在swift中使用Runtime并不被推薦.
- 被觀察對象(subject): 用來被監(jiān)聽的可觀察對象.
- 觀察者(observer): 用來監(jiān)聽被觀察對象.
2. 基于OC Runtime的觀察者模式實現(xiàn)
1. 實現(xiàn)一個繼承自NSObject的可觀察對象
// @objcMembers 為了給類中每個屬性添加 @objc 關(guān)鍵詞,
@objcMembers public class KVOUser: NSObject {
dynamic var name: String
public init(name: String) {
self.name = name
}
}
@objcMembers 為了給類中每個屬性添加 @objc 關(guān)鍵詞,在swift4中繼承NSObject的子類的屬性不會暴露給OC的Runtime,所以只能手動添加
swift本身是門靜態(tài)語言,添加@objc 關(guān)鍵詞是為了讓屬性具有動態(tài)特性,可以動態(tài)的生成set和get方法,因為KVO就需要去操作set方法.
// 注意kvoObserver的生命周期
var kvoObserver: NSKeyValueObservation?
let kvoUser = KVOUser(name: "Dariel")
// 監(jiān)聽kvoUser name屬性的變化
kvoObserver = kvoUser.observe(\.name, options: [.initial, .new]) {
(user, change) in
print("User's name is \(user.name)")
}
第一個參數(shù)是路徑,這邊是簡略寫法.name, swift會自己轉(zhuǎn)成全路徑; options是 NSKeyValueObservingOptions, 這邊傳入的表示初始化的值和新的值
2. 使用繼承自NSObject的可觀察對象
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
kvoUser.name = "John"
}
在任何地方改變kvoUser對象的name屬性,kvoObserver的observe方法都會回調(diào).
3. OC Runtime的觀察者模式實現(xiàn)原理
那么KVO是怎樣實現(xiàn)對對象屬性的監(jiān)聽的呢?
當給一個對象添加KVO之后,OC會通過Runtime將這個對象的isa指針指向(未設(shè)定KVO的對象的isa指針指向該對象的類對象)自己定義的一個原類的子類類對象(NSKVONotifying_xxx),這個子類類對象的isa指針指向原來對象的類對象,并調(diào)用這個類對象中的set方法,然后去通知監(jiān)聽器哪些值發(fā)生了改變.
在Swift4中,并沒有在語言層級上支持KVO,如果要使用需要導(dǎo)入Foundation和被觀察對象必須繼承自NSObject,這種實現(xiàn)方式顯然不夠優(yōu)雅.
4. 實現(xiàn)一個不基于Runtime的觀察者模式
KVO的觀察者模式本質(zhì)上還是通過拿到屬性的set方法去搞事情,基于這樣的原理我們可以自己實現(xiàn).直接貼代碼,新建一個Observable的swift文件
public class Observable<Type> {
// MARK: - Callback
fileprivate class Callback {
fileprivate weak var observer: AnyObject?
fileprivate let options: [ObservableOptions]
fileprivate let closure: (Type, ObservableOptions) -> Void
fileprivate init(
observer: AnyObject,
options: [ObservableOptions],
closure: @escaping (Type, ObservableOptions) -> Void) {
self.observer = observer
self.options = options
self.closure = closure
}
}
// MARK: - Properties
public var value: Type {
didSet {
removeNilObserverCallbacks()
notifyCallbacks(value: oldValue, option: .old)
notifyCallbacks(value: value, option: .new)
}
}
private func removeNilObserverCallbacks() {
callbacks = callbacks.filter { $0.observer != nil }
}
private func notifyCallbacks(value: Type, option: ObservableOptions) {
let callbacksToNotify = callbacks.filter { $0.options.contains(option) }
callbacksToNotify.forEach { $0.closure(value, option) }
}
// MARK: - Object Lifecycle
public init(_ value: Type) {
self.value = value
}
// MARK: - Managing Observers
private var callbacks: [Callback] = []
/// 添加觀察者
///
/// - Parameters:
/// - observer: 觀察者
/// - removeIfExists: 如果觀察者存在需要移除
/// - options: 被觀察者
/// - closure: 回調(diào)
public func addObserver(
_ observer: AnyObject,
removeIfExists: Bool = true,
options: [ObservableOptions] = [.new],
closure: @escaping (Type, ObservableOptions) -> Void) {
if removeIfExists {
removeObserver(observer)
}
let callback = Callback(observer: observer, options: options, closure: closure)
callbacks.append(callback)
if options.contains(.initial) {
closure(value, .initial)
}
}
public func removeObserver(_ observer: AnyObject) {
callbacks = callbacks.filter { $0.observer !== observer }
}
}
// MARK: - ObservableOptions
public struct ObservableOptions: OptionSet {
public static let initial = ObservableOptions(rawValue: 1 << 0)
public static let old = ObservableOptions(rawValue: 1 << 1)
public static let new = ObservableOptions(rawValue: 1 << 2)
public var rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
}
使用:
public class User {
// 被觀察的屬性需要是Observable類型
public let name: Observable<String>
public init(name: String) {
self.name = Observable(name)
}
}
// 用來管理觀察者
public class Observer {}
var observer: Observer? // 當observer置為nil的時候,可觀察對象會自動釋放.
let user = User(name: "Made")
observer = Observer()
user.name.addObserver(observer!, options: [.new]) { name, change in
print("name:\(name), change:\(change)")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
user.name.value = "Amel"
}
注意: 在使用過程中,如果改變value, addObserver方法不調(diào)用,很有可能是Observer對象已經(jīng)被釋放掉了.
5. 觀察者模式的使用場景
觀察者模式一般用在MVC模式中,控制器需要監(jiān)聽某個模型屬性的改變,而模型不需要知道控制器的類型,因此多個控制器可以監(jiān)聽一個模型對象.
2. 建造者模式(Buidler Pattern)
1. 建造者模式概述
建造者模式可以一步步分解復(fù)雜業(yè)務(wù)場景的實現(xiàn)過程.
- 管理者(Dircetor): 通常用來管理建造者,一般是個Controller
- 建造者(Builder): 通常是個類,用來管理Product的創(chuàng)建和數(shù)據(jù)輸入
- 產(chǎn)品(Product): 比較復(fù)雜的對象,可以是類或者結(jié)構(gòu)體,通常是個模型
1. 建造者模式舉例
1. Product
// MARK: - Product
public struct Person {
public let area: Area
public let character: Character
public let hobby: Hobby
}
extension Person: CustomStringConvertible {
public var description: String {
return area.rawValue
}
}
public enum Area: String { // 來自區(qū)域
case ShangHai
case ShenZhen
case HangZhou
case Toronto
}
public struct Character: OptionSet { // 性格
public static let independent = Character(rawValue: 1 << 1) // 2
public static let ambitious = Character(rawValue: 1 << 2) // 4
public static let outgoing = Character(rawValue: 1 << 3) // 8
public static let unselfish = Character(rawValue: 1 << 4) // 16
public static let expressivity = Character(rawValue: 1 << 5) // 32
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
}
public struct Hobby: OptionSet { // 愛好
public static let mountaineering = Hobby(rawValue: 1 << 1)
public static let boating = Hobby(rawValue: 1 << 2)
public static let climbing = Hobby(rawValue: 1 << 3)
public static let running = Hobby(rawValue: 1 << 4)
public static let camping = Hobby(rawValue: 1 << 5)
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
}
Person中定義了三個屬性,area地區(qū),character性格,hobby愛好,其中地區(qū)只能是一個值,性格和愛好可以支持多個值.Character和Hobby可以通過傳入一個值,設(shè)置多個值.
2. Builder
// MARK: - Builder
public class PersonStatistics {
public private(set) var area: Area = .HangZhou
public private(set) var characters: Character = []
public private(set) var hobbys: Hobby = []
private var outOfAreas: [Area] = [.Toronto]
public func addCharacter(_ character: Character) {
characters.insert(character)
}
public func removeCharacter(_ character: Character) {
characters.remove(character)
}
public func addHobby(_ hobby: Hobby) {
hobbys.insert(hobby)
}
public func removeHobby(_ hobby: Hobby) {
hobbys.remove(hobby)
}
public func setArea(_ area: Area) throws {
guard isAvailable(area) else { throw Error.OutOfArea }
self.area = area
}
public func build() -> Person {
return Person(area: area, character: characters, hobby: hobbys)
}
public func isAvailable(_ area: Area) -> Bool {
return !outOfAreas.contains(area)
}
public enum Error: Swift.Error {
case OutOfArea
}
}
通過builder統(tǒng)一對Product進行管理,設(shè)置完數(shù)據(jù)之后再去創(chuàng)建Person對象.
3. Director
public class ManagerStatistics {
public func createLiLeiData() throws -> Person {
let builder = PersonStatistics()
try builder.setArea(.HangZhou)
builder.addCharacter(.ambitious)
builder.addHobby([.climbing, .boating, .camping])
return builder.build()
}
public func createLucyData() throws -> Person {
let builder = PersonStatistics()
try builder.setArea(.Toronto)
builder.addCharacter([.ambitious, .independent, .outgoing])
builder.addHobby([.boating, .climbing, .camping])
return builder.build()
}
}
通過Director去設(shè)置builder中的數(shù)據(jù).
let manager = ManagerStatistics()
if let Lucy = try? manager.createLucyData() {
print(Lucy.description)
print(Lucy.character)
print(Lucy.hobby)
}else {
print("Out of area here")
}
if let Lilei = try? manager.createLiLeiData() {
print(Lilei.description)
print(Lilei.character)
print(Lilei.hobby)
}
2. 建造者模式的使用注意
建造者模式是用在創(chuàng)造比較復(fù)雜的Product,這個Product需要設(shè)置很多值,而這用構(gòu)造器又比較麻煩的情況下.如果Product比較簡單,那用構(gòu)造器就好了.
3. 總結(jié)
本篇主要講了用來對對象監(jiān)聽的觀察者模式和用在創(chuàng)建和管理復(fù)雜對象場景下的建造者模式.
參考:
The Swift Programming Language (Swift 4.1)
Objective-C編程之道
Design Patterns by Tutorials
如有疑問,歡迎留言 :-D