利用对象类型闭包和NSMapTable实现多播闭包

首先普及一个概念,就是多播代理,或者叫多播委托
我们知道swift回调有代理、通知、kvo和闭包
项目中多对多一般就用通知了,通知是有很多缺点的,例如难以维护,有些场景并不适合通知(举个例子:各大即时通讯的消息列表,都用的是多播代理)

多播代理的原理:
普通代理只能实现一对一,例如controller是tableview的代理,我们为了避免内存泄漏,delegate都是weak的。而多代理就是拿一个weak类型的数组把delegate存起来,这个数组就是NSHashTable
代码:

@objc
protocol AnimalPrtocol {
    func eat()
    func sleep()
}

class Controller: UIViewController {

// 没用过的注意options的weakMemory,资料很多不做赘述
       private let hashTable = NSHashTable<NSObject>.init(options: .weakMemory)
// 添加代理
       public func addDelegate(_ delegate: AnimalPrtocol) {
        hashTable.add(delegate)
      }

// 调用代理方法,饿了去吃
    private func hungry() {
        hashTable.allObjects.forEach { $0.eat() }
    }
}

已上就实现了多代理

而如果想把这个代理改成闭包呢?

第一、NSHashTable可以实现一个weak的数组,NSMapTable可以实现一个weak的字典
第二、要创建一个存储闭包的类型,例如

class Block {
  var stringCallback: ((String) -> ())?
  var intCallback: ((Int) -> ())?
  var modelCallback: ((XXXModel) -> ())?
}

缺点很明显,需要设置无数个回调
这里推荐使用王巍大大的Delegate类型
使用 protocol 和 callAsFunction 改进 Delegate
https://onevcat.com/2020/03/improve-delegate/
具体内容可以去看,本人不做赘述
以下是demo, 就这么三五十行。。要看效果的新建项目复制粘贴快很多.也可以去下载看看
https://gitee.com/nameUser/multicast-closure


import UIKit

class Model: NSObject {
    
    var name: String = ""
    convenience init(name: String) {
        self.init()
        self.name = name
    }
}

class ViewController: UIViewController {
    // 这里的valueOptions如果不在这里存直接释放所以用了strong,当key释放时,value自动释放。valueOptions根据自己是否保存过去选择weak或者strong,一般闭包只在这里引用一次
    var mapTable = NSMapTable<Model, Delegate<String, Void>>.init(keyOptions: .weakMemory, valueOptions: .strongMemory)
    var models: [Model] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        for i in 0..<10 {
            let model = Model.init(name: "\(i)")
            models.append(model)
            let comp: Delegate<String, Void> = .init()
            mapTable.setObject(comp, forKey: model)
        }
        
        for object in mapTable.keyEnumerator() {
            if let objc = object as? Model {
                mapTable.object(forKey: objc)?.delegate(on: self, closure: { (self, string) -> Void in
                    print(string)
                })
            }
        }
// 也可以直接取 value
        /*
         mapTable.objectEnumerator()?.allObjects.forEach {
            if let value = $0 as? Delegate<String, Void> {
                value.delegate(on: self, closure: { (self, string) -> Void in
                    print(string)
                })
            }
         }
         */
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        models.removeFirst()
        for object in mapTable.keyEnumerator() {
            if let objc = object as? Model {
                mapTable.object(forKey: objc)?.callAsFunction(objc.name)
            }
        }
        print(mapTable)
    }

}

以下是王巍大大的Delegate整合

import Foundation

public class Delegate<Input, Output> {
    
    public init() {}
    
    private var closure: ((Input) -> Output?)?
    
    /// 委托回调
    /// - Parameters:
    ///   - target: 目标对象
    ///   - closure: 回调闭包
    public func delegate<T: AnyObject>(on target: T, closure: @escaping ((T, Input) -> Output)) {
        // The `target` is weak inside block, so you do not need to worry about it in the caller side.
        self.closure = { [weak target] input in
            guard let target = target else { return nil }
            return closure(target, input)
        }
    }
    
    /// 取消代理回调
    public func cancell() {
        self.closure = nil
    }

    @discardableResult
    public func callAsFunction(_ input: Input) -> Output? {
        return closure?(input)
    }
}

extension Delegate where Input == Void {
    
    public func delegate<T: AnyObject>(on target: T, closure: @escaping ((T) -> Output)) {
        // The `target` is weak inside block, so you do not need to worry about it in the caller side.
        self.closure = { [weak target] _ in
            guard let target = target else { return nil }
            return closure(target)
        }
    }
    
    @discardableResult
    public func callAsFunction() -> Output? {
        return closure?(())
    }
}

public protocol OptionalProtocol {
    static var Nil: Self { get }
}

extension Optional: OptionalProtocol {
    
    public static var Nil: Optional<Wrapped> {
        return nil
    }
}

extension Delegate where Output: OptionalProtocol {
    
    @discardableResult
    public func callAsFunction(_ input: Input) -> Output {
        switch closure?(input) {
        case .some(let value):
            return value
            
        case .none:
            return .Nil
        }
    }
}

缺点就是每加一个方法就要加一个NSMapTable去记录,去callAsFunction,大于两个回调方法还是建议用多播代理,两个以内可以用这个

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Swift 介绍 简介 Swift 语言由苹果公司在 2014 年推出,用来撰写 OS X 和 iOS 应用程序 ...
    大L君阅读 8,536评论 3 25
  • 用到的组件 1、通过CocoaPods安装 2、第三方类库安装 3、第三方服务 友盟社会化分享组件 友盟用户反馈 ...
    SunnyLeong阅读 14,944评论 1 180
  • Swift 简介 查看Swift当前版本 简介 Swift 语言由苹果公司在 2014 年推出,用来撰写 OS X...
    mian小爬阅读 2,784评论 0 1
  • 随着苹果swift的开源,又开启了一段新的学习旅程,不过也是必然的,那现在开始整理一下swift的笔记吧. 以...
    蓝心儿的蓝色之旅阅读 6,798评论 0 4
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 13,585评论 0 11