首先普及一個概念闪彼,就是多播代理,或者叫多播委托
我們知道swift回調有代理协饲、通知畏腕、kvo和閉包
項目中多對多一般就用通知了,通知是有很多缺點的茉稠,例如難以維護描馅,有些場景并不適合通知(舉個例子:各大即時通訊的消息列表,都用的是多播代理)
多播代理的原理:
普通代理只能實現一對一而线,例如controller是tableview的代理铭污,我們?yōu)榱吮苊鈨却嫘孤琩elegate都是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的字典
第二烤黍、要創(chuàng)建一個存儲閉包的類型,例如
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竿奏,大于兩個回調方法還是建議用多播代理袄简,兩個以內可以用這個