前语

刚学完swift编程语言,这儿直接搞一个swift版KVO,趁便熟悉一下swift

里边讲了 swiftkvo 的根本运用,模仿 KVOController的版别,还有 swift特点包装 小试

写的过程中也碰到了 swiftOC 各自 API 结合过程中碰到的一些问题(究竟 UIKit仍是运用的OC的,且 OC 的一些类也能运用),一同给咱们分享一下

事例demo

KVO

KVO 根本是开发中必备技能之一,在一些内容更新中比较常见(修正个人信息,点赞等信息)

因为 Swift 运用的仍然是 Object-c 语言的 UIKit结构,在增加 UI 以及 点击事情的时候碰到了一些问题,且因为编程语言约束,增加点击事情的时候,不能像Object-C相同运用SEL了,而是需求时运用 #Selector()的办法设置指针调用

let btn = UIButton(frame: CGRect(x: 0, y: 100, width: self.view.bounds.size.width, height: 40))
btn.setTitle("点击测验一下KVO", for: .normal)
btn.setTitleColor(UIColor.black, for: .normal)
btn.addTarget(self, action: #selector(self.onClickToModify), for: .touchUpInside)
self.view.addSubview(btn)

且因为 swift 运用的仍然是 UIKit 结构(Object-c 编写的),因而函数需求标记 @objc 才干正常拜访

@objc func onClickToModify() {
    baseModel?.age = 200
}

根底KVO

了解其他KVO之前,先了解一下根本KVO的根本运用

被调查的模型类型如下所示,需求承继自 NSObject,且特点需求增加 @objc dynamic 标识,即 object-c 标识

class KVOBaseTestModel: NSObject {
    //必须要增加 @objc dynamic 参数才干够支持监听
    //且因为OC中没有可选类型,假如根本数据类型呈现了可选类型会报错,究竟根本类型不能赋值为nil
    @objc dynamic var age: Int = 0
    @objc dynamic var name: String?
}

增加监听办法addObsercer,回调函数observeValue

//增加监听的办法
baseModel.addObserver(self, forKeyPath: "name", options: [.new, .old], context: nil)
//响应的回调,经过 NSKeyValueChangeKey 能够拜访对应字典 change 内的数据
override func observeValue(forKeyPath keyPath: String?, of object: Any?, 
    change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    print(change as Any)
}

KVOController

这儿的 KVOController 是模仿 Object-cKVOController 而编写的,编写时削减了一层运用单例统一处理监听和事情的场景,个人感觉不是必要的

其为一个主动开释监听的 KVO 结构,原理则是利用了引证计数的原理,当开释目标时,先开释目标,在开释目标,因而 KVOController 总是作为拥有类的子特点而存在的,当持有 KVOController 的类开释时,监听会主动开释

留意:依据其开释机遇,最好一个控制器页面交互场景运用一个 KVOController 实例变量,尽量避免多个控制器运用一个,除非有需求,且运用时,被调查目标模型特点仍然要加上 @objc dynamic字段

KVOController 完成如下所示:

__LLKVOInfo

首先我界说了一个 __LLKVOInfo 根本数据结构,用于保存 被监听者回调键值,便于后续回谐和移除运用

这儿承继了 NSObject,并完成了 hashisEqual,后边两个则是在 NSSet 进行哈希值比照时需求用到的办法

class __LLKVOInfo: NSObject {
    weak var observer: AnyObject?
    var block: LLBlock
    var keyPath: String
    // block 需求设置 escaping 答应逃逸,究竟没有直接履行,而是保存了起来
    init(observer: AnyObject, block: @escaping LLBlock, keyPath: String) {
        self.observer = observer
        self.block = block
        self.keyPath = keyPath
    }
    func hash() -> Int {
        return Int(self.keyPath.hash)
    }
    override func isEqual(_ object: Any?) -> Bool {
        if let obj = object as? __LLKVOInfo {
            if obj.keyPath == self.keyPath {
                return true
            }
        }
        return false
    }
}

LLKVOController

LLKVOController 是调查的核心类,咱们的 KVOController为主动调查类,经过主动调查被调查者来完成主动开释的 KVO 逻辑

因为被调查者有多个键值,且或许存在多个被调查者,因而,保存调查者信息时,咱们以被调查者实例为键值,其被调查的多个键值存放在一个集合中,且确保不重复监听

经过考虑,选用了 Object-C 中的 NSMapTable 哈希表,至于没有选用 swift 中的 DictionaryObject-c 中的 Dictionary,究竟不是每一个被调查者目标都遵循完成了哈希协议(例如:swift中为 Hashable协议)

NSMapTable不需求考虑那么多,能够选用目标指针作为哈希值key,因而更为有用,而 value 保存的 __LLKVOInfo 类型,因而能够运用 NSSetswift 中的 Set结构体,因而不适合,NSSet 处理重复运用的 hashisEqual,因而重写了这两个办法(也能够运用NSDictionary,更好用,这儿仅仅一个事例)

如下所示,初始化了一个,NSMapTable和一个, 锁不多说,为了确保线程安全NSMapTable如下所示

//默许强引证,会引证被调查者,仅当KVOController开释时,其会被开释和移除调查
//设置弱引证调查者,弱引证被调查者开释时能够不免除监听,假如是单例,则或许会呈现多次监听问题
//设置弱引证适用于经常改写数据的视图,以削减内存开支
//实际并不推荐弱引证,虽然不开释没什么影响,但假如系统缓存了新生成的监听子类
//若监听的特点没开释,或许会有额定功能开支
init(_ isWeakObserved: Bool = false) {
    infosMap = NSMapTable(keyOptions: 
        [isWeakObserved ? .weakMemory : .strongMemory, .objectPointerPersonality],
        valueOptions: [.strongMemory, .objectPointerPersonality])
    semaphore = DispatchSemaphore(value: 1)
}

调查的代码如下所示

func observer(_ observedObj: AnyObject, _ keyPath: String, _ block: @escaping LLBlock) {
    //创建根本类型目标,一起用保存数据和数据比照
    let info = __LLKVOInfo(observer: observedObj, block: block, keyPath: keyPath)
    semaphore.wait()
    //获取指定目标的 value 集合
    var infoSet = infosMap.object(forKey: observedObj)
    if let set = infoSet {
        //这儿现已有 set 了,说明增加过监听
        if set.contains(info) {
            //现已增加调查了,不再增加
            semaphore.signal()
            return
        }
    }else {
        //创建 set 并参加 InfosMap
        infoSet = NSMutableSet()
        infosMap.setObject(infoSet, forKey: observedObj)
    }
    //增加新监听信息
    infoSet!.add(info)
    semaphore.signal()
    //增加调查
    observedObj.addObserver(self, forKeyPath: keyPath, options: [.new, .old], context: nil)
}

响应调查回调 observeValue,如下所示,UnsafeMutableRawPointer 类型不太好用只能一个查找了(能够NSSetNSDictionary,键值多时查找更敏捷)

override func observeValue(forKeyPath keyPath: String?, of object: Any?,
    change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    let obj = object as AnyObject
    if let infoSet = self.infosMap.object(forKey: obj) {
        for info in infoSet {
            if let info = info as? __LLKVOInfo {
                if (info.keyPath == keyPath) {
                    info.block(change?[NSKeyValueChangeKey.newKey] as Any, obj)
                    return
                }
            }
        }
    }
}

当咱们的 KVOController 开释时,主动开释和移除里边所有的监听

deinit {
    for observer in self.infosMap.keyEnumerator() {
        let observed = observer as AnyObject
        let obj = self.infosMap.object(forKey: observed)
        for info in obj! {
            observed.removeObserver(self, forKeyPath: (info as! __LLKVOInfo).keyPath)
        }
    }
    print("KVOController开释了")
}

swift特点包装之轻量型KVO

上面介绍了一个比较好用的 KVOController,这儿运用 swift 特性 特点包装,来处理 KVO 的问题,

优点:代码极少,功能较高,运用简略,且不用@objc dynamic修饰特点,只需求正常运用特点包装即可

缺陷:无法一起被多个目标监听,适用一个键值,一次监听的 KVO 运用,能够能够在完善

完成代码如下所示:

typealias LLObserverBlock = (Any) -> Void
@propertyWrapper
struct LLObserver<T> {
    private var observerValue: T? //记住设置初值,也能够经过init来设置
    private var block: LLObserverBlock?
    //默许的特点包装名称,参数名固定
    var wrappedValue: T? {
        get {observerValue}
        set {
            observerValue = newValue
            if let b = block {
                b(newValue as Any)
            }
        }
    }
    //特点映射名称,参数名固定(这儿模仿写入数据库操作),调用参数时前面加上$即可(obj.$number)
    var projectedValue: LLObserverBlock {
        get {
            block!
        }
        set {
            block = newValue
        }
    }
    init() {
        observerValue = nil
        block = nil
    }
}

运用更为简略,如下所示,则能够监听成功

observerModel = KVOObserverTestModel()
//设置监听
observerModel?.$name = { newValue in
    print("name", newValue)
}
observerModel?.$age = { newValue in
    print("age", newValue)
}

需留意运用特点包装

class KVOObserverTestModel: NSObject {
    @LLObserver
    var age: UInt?
    @LLObserver
    var name: String?
}

最后

这算是一次 swift 功能小试,且结合了 object-c 的一些常用类(Object-c的大多数类都还能正常运用),且发现 swift 根本内容功能缺乏(目前有些版别问题,只能仅仅swift新特性还不可),还仍需求 Object-C 中的一些根底功能来补全,因而,想开发好 ios, 只会 swift 编程语言是不可的,还要了解 Object-C,才干更好的行进