0
点赞
收藏
分享

微信扫一扫

iOS KVO预防崩溃处理

小龟老师 2021-09-19 阅读 80
日记本

KVO是iOS中常用的一种观察机制,具体用法这里不做过多描述。先说一下KVO的两种崩溃场景:
1.addObserver给同一个对象添加了相同的keypath;
2.removeObserver时,对象的keypath观察者重复移除,主要原因是add和remove的次数不匹配造成的。

再说一下实际开发中本人遇到的崩溃情况,之前做视频播放App,播放中可以自由切换列表播放源,在播放的过程中需要使用KVO对播放对象AVPlayerItem进行监听,具体监听如下:

//播放状态
addObserver(item, observer: self, keypath: "status", options: .new, context: nil)
//缓存进度
addObserver(item, observer: self, keypath: "loadedTimeRanges", options: .new, context: nil)
//当前缓存不够播放了
addObserver(item, observer: self, keypath: "playbackBufferEmpty", options: .new, context: nil)
//当前缓存可以播放
addObserver(item, observer: self, keypath: "playbackLikelyToKeepUp", options: .new, context: nil)

在切换到下一个播放源时,需要移除上一个AVPlayerItem对象上的Observer,再给新的AVPlayerItem添加上述监听。虽然代码层面上基本看不出什么问题,而且经过检查也确保
addObserver和removeObserver都是成对进行,然而理论终归是理论,在我们的App中,当用户量大了之后,崩溃记录上总会出现一下偶现的崩溃,防不胜防啊。
经过思考,我想到了一个解决办法,思路上通过一个中间类,在addObserver和removeObserver都加一层判断,具体代码如下:

import UIKit

class CRProxy: NSObject {

    var lock = NSLock()
    ///用来记录观察者、需要观察的对象、keypath的数组
    lazy var kvoInfo: [KVOObject] = {
        return [KVOObject]()
    }()
    
    func CR_addObserver(target:NSObject, observer: NSObject, keypath: String, options: NSKeyValueObservingOptions = [], context: UnsafeMutableRawPointer?)  {
        if keypath.isEmpty { return }
        //加个锁更安全
        lock.lock()
        //判断是否是同一个target添加重复的keypath,已存在相同的记录就直接返回,不作处理
        if kvoInfo.contains(where: { ($0.keypath == keypath && $0.target == target) }) {
            lock.unlock()
            return
        }
        kvoInfo.append(KVOObject(target: target, keypath: keypath, observer: observer))
        target.addObserver(observer, forKeyPath: keypath, options: options, context: context)
        
        lock.unlock()
    }
    
    func CR_removeObserver(target:NSObject, observer: NSObject, keypath: String) {
        if keypath.isEmpty { return }
        lock.lock()
        //判断要移除的观察者是否存在,存在就移除,否则直接返回
        if kvoInfo.isEmpty || !kvoInfo.contains(where: { ($0.keypath == keypath && $0.target == target) }) {
            lock.unlock()
            return
        }
        kvoInfo.removeAll(where: { $0.target == target && $0.keypath == keypath})
        lock.unlock()
    }
    
    struct KVOObject {
        var keypath = ""
        var observer: NSObject?
        var target: NSObject?
        
        init(target: NSObject, keypath: String, observer: NSObject) {
            self.target = target
            self.keypath = keypath
            self.observer = observer
        }
    }
}

调用的话直接创建一个CRProxy的全局变量的对象,最好和播放器,来调用即可,具体事例如下:

    //kov代理,预防kvo崩溃
     lazy var proxy: CRProxy = {
        return CRProxy()
    }()

    //监听PlayItem的缓存进度、播放状态等
    func addKVOObserver(item: AVPlayerItem) {
        //播放状态
        proxy.CR_addObserver(target: item, observer: self, keypath: "status", options: .new, context: nil)
        //缓存进度
        proxy.CR_addObserver(target: item, observer: self, keypath: "loadedTimeRanges", options: .new, context: nil)
        //当前缓存不够播放了
        proxy.CR_addObserver(target: item, observer: self, keypath: "playbackBufferEmpty", options: .new, context: nil)
        //当前缓存可以播放
        proxy.CR_addObserver(target: item, observer: self, keypath: "playbackLikelyToKeepUp", options: .new, context: nil)
    }
    
    //移除PlayItem上的观察者
    func removeKVOObserver(item:AVPlayerItem) {
        proxy.CR_removeObserver(target: item, observer: self, keypath: "status")
        proxy.CR_removeObserver(target: item, observer: self, keypath: "loadedTimeRanges")
        proxy.CR_removeObserver(target: item, observer: self, keypath: "playbackBufferEmpty")
        proxy.CR_removeObserver(target: item, observer: self, keypath: "playbackLikelyToKeepUp")
    }

即取即用,直接给CRProxy这个类写进项目中就行。亲测手动多次添加和移除相同的观察对象也不会崩溃,放心使用,有问题请及时留言。

举报

相关推荐

0 条评论