LRU在UIImageView的extension中简单应用

有时候简单写一个demo需要对UIImageView来一个根据url设置image的操作,单独pod install一下或者找个spm感觉太麻烦,隧根据URLSession写一个简单的extension,如下:

直接extension

extension UIImageView {
    
    

    func sg_setImage(_ urlString: String, placeholderName placeholder: String? = nil) {
    
    
        
        if let placeholder = placeholder, let placeholderImage = UIImage(named: placeholder) {
    
    
            self.image = placeholderImage
        }
        
        DispatchQueue.global().async {
    
    
            guard let url = URL(string: urlString) else {
    
     return }
            URLSession.shared.dataTask(with: url) {
    
     data, response, error  in
                if let data = data, let image = UIImage(data: data) {
    
    
                    DispatchQueue.main.async {
    
    
                        self.image = image
                    }
                } else {
    
    
                    print(error ?? "")
                }
            }.resume()
        }
        
    }
    
}

使用时,调用UIImageView实例对象的sg_setImage()方法即可,还额外实现了placeholder功能。

但是每次调用都会重新请求一下,如果一个CollectionView里面的cell有不少内容是重复的,那岂不是做了重复的工作?这是不可接受的,因此,自然而言地想到了使用hash字典来充当缓存。

因为加载重复image的首要条件就是url相同,那么就可以把url当作hash字段的key,这样,每次调用sg_setImage()方法时候根据url作为key先判断缓存里面是否存在image对象,如果有,直接显示即可,而没有的话,先走placeholder的路径,然后异步执行Session的任务,下载完后将image对象放入缓存里面,再设置image即可。内容如下:

extension + 字典缓存

extension UIImageView {
    
    
    
    private static var imageCaches: [String: Any] = {
    
     [: ] }()
    
    func sg_setImage(_ urlString: String, placeholderName placeholder: String? = nil) {
    
    
        if UIImageView.imageCaches[urlString] != nil {
    
    
            self.image = UIImageView.imageCaches[urlString] as? UIImage
            return
        }
        
        if let placeholder = placeholder, let placeholderImage = UIImage(named: placeholder) {
    
    
            self.image = placeholderImage
        }
        
        DispatchQueue.global().async {
    
    
            guard let url = URL(string: urlString) else {
    
     return }
            URLSession.shared.dataTask(with: url) {
    
     data, response, error  in
                if let data = data, let image = UIImage(data: data) {
    
    
                    DispatchQueue.main.async {
    
    
                        UIImageView.imageCaches[urlString] = image
                        self.image = image
                    }
                } else {
    
    
                    print(error ?? "")
                }
            }.resume()
        }
        
    }
    
}

然而随着深入使用发现了另一个问题,每个不同url的请求都会将image放入缓存中,那要不了多久内存就会被撑爆,这同样也是不可接受的。在<<操作系统>>课程里面有个很经典的算法叫做LRU(Latest Recently Used),叫做“最近最久使用”。该算法就是解决页面里面置换问题。操作系统里面的页面置换是有数组来管理的(实际上比数组复杂,可能为双向链表),那这里也可以用LRU去管理,YYCache我记得也是有LRU的应用的,这里不再赘述。

extension + LRU缓存

extension UIImageView {
    
    
    
    private static var sgImageCaches: Cache = {
    
     Cache(limitCount: 100) }()
    
    func sg_setImage(_ urlString: String, placeholderName placeholder: String? = nil) {
    
    
        if UIImageView.sgImageCaches.getObject(forKey: urlString) != nil {
    
    
            self.image = UIImageView.sgImageCaches.getObject(forKey: urlString) as? UIImage
            return
        }
        
        if let placeholder = placeholder, let placeholderImage = UIImage(named: placeholder) {
    
    
            self.image = placeholderImage
        }
        
        DispatchQueue.global().async {
    
    
            guard let url = URL(string: urlString) else {
    
     return }
            URLSession.shared.dataTask(with: url) {
    
     data, response, error  in
                if let data = data, let image = UIImage(data: data) {
    
    
                    DispatchQueue.main.async {
    
    
                        UIImageView.sgImageCaches.setObject(image, forKey: urlString)
                        self.image = image
                    }
                } else {
    
    
                    print(error ?? "")
                }
            }.resume()
        }
        
    }
    
}


class Node: NSObject {
    
    
    // 上一个节点
    var pre: Node?
    // 下一个节点
    var next: Node?
    var key: AnyHashable
    var value: Any
    
    init(value: Any, key: AnyHashable) {
    
    
        self.value = value
        self.key = key
        super.init()
    }
    
    override var description: String {
    
    
        return "\(key):\(value)"
    }
}

class LinkMap: NSObject {
    
    
    var headNode: Node?
    var tailNode: Node?
    var dict = [AnyHashable: Node]()
    
    var totalCount: UInt64 = 0
    
    /// 插入新元素
    ///
    /// - Parameter node: 元素节点
    func insert(_ node: Node) {
    
    
        totalCount += 1
        dict[node.key] = node
        
        if let head = headNode {
    
    
            node.next = head
            head.pre = node
            
            // 重新赋值头节点
            headNode = node
        } else {
    
    
            headNode = node
            tailNode = node
        }
    }
    
    /// 删除元素
    ///
    /// - Parameter node: 元素节点
    func removeNode(_ node: Node) {
    
    
        totalCount -= 1
        dict.removeValue(forKey: node.key)
        
        if let _ = node.pre {
    
    
            node.pre?.next = node.next
        }
        
        if let _ = node.next {
    
    
            node.next?.pre = node.pre
        }
        
        if headNode == node {
    
    
            headNode = node.next
        }
        
        if tailNode == node {
    
    
            tailNode = node.pre
        }
    }
    
    /// 把当前节点移动到首部
    ///
    /// - Parameter node: 元素节点
    func moveNodeToHead(_ node: Node) {
    
    
        if headNode == node {
    
    
            return
        }
        
        // 删除当前节点
        if tailNode == node {
    
    
            tailNode = node.pre
            tailNode?.next = nil
        } else {
    
    
            node.next?.pre = node.pre
            node.pre?.next = node.next
        }
        
        // 把当前节点移动到头部
        node.next = headNode
        node.pre = nil
        headNode?.pre = node
        
        // 重新赋值头节点
        headNode = node
    }
    
    /// 删除尾节点
    func removeTailNode() -> Node? {
    
    
        totalCount -= 1
        if let tail = tailNode {
    
    
            let key = tail.key
            dict.removeValue(forKey: key)
        }
        
        if headNode == tailNode {
    
    
            return nil
        } else {
    
    
            tailNode = tailNode?.pre
            tailNode?.next = nil
            return tailNode
        }
    }
    
    /// 删除所有元素节点
    func removeAllNode() {
    
    
        totalCount = 0
        
        headNode = nil
        tailNode = nil
        dict = [AnyHashable: Node]()
    }
}

class Cache: NSObject {
    
    
    var lru = LinkMap()
    var lock = NSLock()
    let limitCount: UInt64
    
    init(limitCount: UInt64 = 100) {
    
    
        self.limitCount = limitCount
        super.init()
    }
    
    func getObject(forKey key: AnyHashable) -> Any? {
    
    
        lock.lock()
        var node: Node?
        
        node = lru.dict[key]
        if let node = node {
    
    
            lru.moveNodeToHead(node)
        }
        lock.unlock()
        return node?.value
    }
    
    func setObject(_ value: Any, forKey key: AnyHashable) {
    
    
        lock.lock()
        
        if let node = lru.dict[key] {
    
    
            // 存在节点,则把节点移到头部
            // 如果值不相等,则把新值替换进去
            node.value = value
            lru.moveNodeToHead(node)
        } else {
    
    
            // 不存在节点,则插入新的节点
            let node = Node(value: value, key: key)
            lru.insert(node)
        }
        
        if lru.totalCount > limitCount {
    
    
            // 数量超过限制,则删除尾节点
            let _ = lru.removeTailNode()
        }
        
        lock.unlock()
    }
    
    func removeObjc(forKey key: AnyHashable) {
    
    
        lock.lock()
        
        if let node = lru.dict[key] {
    
    
            lru.removeNode(node)
        }
        
        lock.unlock()
    }
    
    override var description: String {
    
    
        var description = ""
        var node: Node? = lru.headNode
        while let current = node {
    
    
            description.append("\(current.description) ")
            node = current.next
        }
        
        return description
    }
}

这里说明一下:LRU的实现从网上直接copy其他博主的文章过来的,本来想放到链接但没找到

有了LRU的实现,就可以把普通的hash字典换成LRU缓存了,在设置完指定最大容量后,新的url请求过来就会把最久远未使用的image对象移除缓存,达到了动态使用、动态平衡的结果。

猜你喜欢

转载自blog.csdn.net/kicinio/article/details/129334404