【问题标题】:Weak reference implementing a (generic) protocol实现(通用)协议的弱引用
【发布时间】:2021-08-10 16:06:33
【问题描述】:

我想创建一组弱引用来实现可观察功能的协议。 如果可能的话,这个协议应该有一个关联的类型,所以我不需要为每种类型创建相同的类型。

但是在 Swift 4 中这似乎不起作用,我想要的基本上是这样的:


protocol DelegateBase: AnyObject {
    associatedtype Item
    func onDataUpdated(data: [Item])
}

protocol Delegate : DelegateBase where Item == Int {
    // Will have func onDataUpdated(data: [Int])
}

// T should be a class implementing Delegate, but cannot find a way to 
// define T in a way that the Swift compiler accepts it
class WeakListener<T> {
    weak var listener : T?

    init(listener: T) {
        self.listener = listener
    }
}

class Implementation {
    val listeners = [WeakListener<Delegate>]()
}

如果我将所有内容都定义为非泛型,我可以让它工作,但我希望不必为我想要支持的每种类型复制粘贴具体版本的 DelegateBase。

// This works but is clunky

protocol DelegateA {
    func onDataUpdated(data: [Int])
}
// DelegateB with type Double, DelegateC with type X

class WeakListenerA {
    weak var listener : DelegateA?

    init(listener: DelegateA) {
        self.listener = listener
    }
}
// WeakListenerB with type Double, WeakListenerC with type X

class ImplementationA {
    val listeners = [WeakListenerA]()
}
// ImplementationB with type Double, ImplementationC with type X

编辑:预期用途

基本上我想实现远程支持的存储库,其中 UIViewControllers 可以监听存储库内发生的事件,即使它们没有主动请求数据。

我希望监听器是通用的,所以我不需要为每种类型克隆所有内容。我想要弱引用,从那时起,除了添加侦听器之外,不需要任何手动操作来管理它们。


protocol DelegateBase: AnyObject {
    associatedtype Item
    func onDataModified(data: [Item])
    func onDataRefreshed(data: [Item])
}

protocol IntDelegate : DelegateBase where Item == Int {
    // Will have
    //func onDataModified(data: [Int])
    //func onDataRefreshed(data: [Int])
}

// Listener should implement the delegate T, that extends DelegateBase 
// with a type
class WeakListener<T : DelegateBase> {
    weak var listener : T?

    init(listener: T) {
        self.listener = listener
    }
}

class IntRepository {
    var listeners = [WeakListener<IntDelegate>]()
    var data = [Int]()

    func addListener(_ listener: IntDelegate /* UIViewController implementing IntDelegate*/) {
        listeners.add(listener)
    }

    func add() {
        // data is updated, notify weak listeners
        data.append(1)
        for listener in listeners {
            listener.listener?.onDataModified(data: data)
        }
    }

    func refresh() {
        // data refreshed from a remote source
        for listener in listeners {
            listener.listener?.onDataRefreshed(data: data)
        }
    }

    fileprivate func cleanupListeners() {
        self.listeners = self.listeners.filter({$0.listener != nil})
    }

  // Singleton for example
    fileprivate static var _instance: IntImplementation!
    public class func shared() -> IntImplementation {
        if _instance == nil {
            _instance = IntImplementation()
        }

        return _instance!
    }
}

class IntViewController: UIViewController, IntDelegate {
    override func viewDidLoad() {
        IntImplementation.shared().addListener(self)
    }

    func onDataModified(data: [Int]) {
        // update UI
    }

    func onDataRefreshed(data: [Int]) {
        // update UI
    }
}

【问题讨论】:

  • 你打算用它做什么?预期的结果是什么?这让我想起了这个anwer我在类似的帖子上发的帖子
  • 我希望有一个委托来传达存储库中的更改,而不是单个可观察值。
  • 你能在你的问题中添加一个用法部分吗?展示你想如何使用你的委托。
  • 我添加了一个使用部分,感谢您花时间尝试帮助我
  • 好吧,我的answer 使用闭包,你使用协议,但原理相同:你的IntRepository 看起来很像我的Observed,你的WeakListener 看起来像我的Box 等等. @NewDev 的答案使用协议,看起来它也可以满足您的需求

标签: swift generics delegates protocols


【解决方案1】:

因为DelegateBase(以及扩展的Delegate)协议有一个关联的类型(不像DelegateA),你不能将它作为一个类型使用;你只能使用一些符合它的具体类型。

对于您要执行的操作,您实际上并不需要 Delegate 协议。对于Item 类型,您似乎想要一个WeakListener 泛型,您可以这样声明:

class WeakListener<T: DelegateBase> {
    weak var listener : T?

    init(listener: T) {
        self.listener = listener
    }
}

换句话说,以上内容适用于符合DelegateBase 的具体T 类型。

假设你有一个Int 的具体类型:

class ConcreteIntDelegateA: DelegateBase {
    func onDataUpdated(data: [Int]) {
        // ...
    }
}

然后您可以为ConcreteIntDelegateA 创建一个WeakListener,因为它符合DelegateBase 并进一步指定ItemInt

let listener: WeakListener<ConcreteIntDelegateA>

因此,它适用于具体类型,但您不能对协议 DelegateBase 执行此操作,因为 DelegateBase 不符合 DelegateBase(协议不符合协议,包括它们自己):

let listener: WeakListener<DelegateBase> // ERROR

这使您能够为特定的具体类型创建侦听器数组,例如[WeakListener&lt;ConcreteIntDelegateA&gt;][WeakListener&lt;ConcreteIntDelegateB&gt;],但不能混合使用,因为即使 ConcreteDelegateX 都符合 DelegateBase,这些类型也不相关。

要解决它是创建一个类型擦除类型,例如class AnyDelegate&lt;Item&gt;: DelegateBase,在其init 中采用具体类型:

class AnyDelegate<Item>: DelegateBase {
    private let onDataUpdated: ([Item]) -> Void

    init<C: DelegateBase>(_ c: C) where C.Item == Item {
        self.sonDataUpdated = c.onDataUpdated(data:)
    }

    func onDataUpdated(data: [Item]) {
        onDataUpdated(data)
    }
}
let anyIntA = AnyDelegate(ConcreteIntDelegateA())
let anyIntB = AnyDelegate(ConcreteIntDelegateB())
let anyIntListeners: [WeakListener<AnyDelegate<Int>>] = [anyIntA, anyIntB]

【讨论】:

  • 虽然这可行,但定义单独的具体类并不适合我正在尝试做的事情。我想做的是实现一个观察者系统,其中实现委托的弱引用列表可用于传达可观察对象内部的变化,并为委托报告的内容提供通用类型。
  • @MarkusM。您需要清楚地指定您正在尝试做什么,最好是在代码中(即使它不编译)以及您打算如何使用它。请记住,如果您有一个具有关联类型的协议,则不能再使用它来代替具体类型。
  • 我添加了一个使用部分,希望我的意图更清楚。感谢您花时间尝试帮助我。
  • @MarkusM.,好的,所以,在您的使用示例中,IntViewController 与此处的ConcreteIntDelegateA 相同。因为你不能有 IntDelegateWeakListener&lt;IntDelegate&gt; 类型,所以你需要创建一个类型擦除类型,如 AnyDelegate&lt;Item&gt;,就像我在答案的最后一部分中展示的那样。然后,您可以拥有[WeakListener&lt;AnyDelegate&lt;Int&gt;&gt;],并将您的具体类型添加为.shared.addListener(AnyDelegate(self))
  • 实现 ConcreteIntDelegateA 的类的扩展是什么样的?
【解决方案2】:

我最终做的只是扔掉了DelegateBase。为了提供某种类型一致性,我创建了一个通用基础存储库,并在运行时添加侦听器时强制执行委托类型

import UIKit

protocol IntDelegate : AnyObject {
    func onDataModified(data: [Int])
    func onDataRefreshed(data: [Int])
}

class WeakListener {
    weak var listener : AnyObject?

    init(listener: AnyObject) {
        self.listener = listener
    }
}


class RepositoryBase<T> {
    private var _listeners = [WeakListener]()
    var listeners: [T] {
        get {
            // Remove dead listeners
            self._listeners = self._listeners.filter({($0.listener as? T) != nil})
            return self._listeners.map({ $0.listener as! T })
        }
    }

    func addListener(_ listener: AnyObject) {
        assert(listener is T)
        _listeners.append(WeakListener(listener: listener))
    }
}

class IntRepository : RepositoryBase<IntDelegate> {
    var data = [Int]()

    func add() {
        // data is updated, notify weak listeners
        data.append(1)
        for listener in self.listeners {
            listener.onDataModified(data: data)
        }
    }

    func refresh() {
        // data refreshed from a remote source
        for listener in self.listeners {
            listener.onDataRefreshed(data: data)
        }
    }

  // Singleton for example
    fileprivate static var _instance: IntRepository!
    public class func shared() -> IntRepository {
        if _instance == nil {
            _instance = IntRepository()
        }

        return _instance!
    }
}

class IntViewController: IntDelegate {
    func viewDidLoad() {
        IntRepository.shared().addListener(self)
        IntRepository.shared().add()
    }

    func onDataModified(data: [Int]) {
        print("modified")
    }

    func onDataRefreshed(data: [Int]) {
        print("refreshed")
    }
}

IntViewController().viewDidLoad() // Prints "modified"

【讨论】:

    猜你喜欢
    • 2016-08-17
    • 2019-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-08
    • 1970-01-01
    • 2011-12-17
    • 2011-11-26
    相关资源
    最近更新 更多