前语
刚学完swift
编程语言,这儿直接搞一个swift版KVO
,趁便熟悉一下swift
里边讲了 swift
中 kvo
的根本运用,模仿 KVOController
的版别,还有 swift特点包装
小试
写的过程中也碰到了 swift
与 OC
各自 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-c
的 KVOController
而编写的,编写时削减了一层运用单例
统一处理监听和事情的场景,个人感觉不是必要的
其为一个主动开释监听的 KVO
结构,原理则是利用了引证计数的原理,当开释目标时,先开释父
目标,在开释子
目标,因而 KVOController
总是作为拥有类的子特点
而存在的,当持有 KVOController
的类开释时,监听会主动开释
留意:依据其开释机遇,最好一个控制器页面
或交互场景
运用一个 KVOController
实例变量,尽量避免多个控制器运用一个,除非有需求,且运用时,被调查目标模型特点仍然要加上 @objc dynamic
字段
KVOController
完成如下所示:
__LLKVOInfo
首先我界说了一个 __LLKVOInfo
根本数据结构,用于保存 被监听者
、回调
、键值
,便于后续回谐和移除运用
这儿承继了 NSObject
,并完成了 hash
和 isEqual
,后边两个则是在 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
中的 Dictionary
和Object-c
中的 Dictionary
,究竟不是每一个被调查者目标都遵循完成了哈希协议(例如:swift
中为 Hashable
协议)
而 NSMapTable
不需求考虑那么多,能够选用目标指针
作为哈希值key
,因而更为有用,而 value
保存的 __LLKVOInfo
类型,因而能够运用 NSSet
,swift
中的 Set
为结构体
,因而不适合,NSSet 处理重复运用的 hash
和 isEqual
,因而重写了这两个办法(也能够运用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
类型不太好用只能一个查找了(能够NSSet
换NSDictionary
,键值多时查找更敏捷)
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
,才干更好的行进