Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/103.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios Swift:与协议的一致性,使用“通用方法”;其中;条款_Ios_Swift_Generics_Protocols_Swift Protocols - Fatal编程技术网

Ios Swift:与协议的一致性,使用“通用方法”;其中;条款

Ios Swift:与协议的一致性,使用“通用方法”;其中;条款,ios,swift,generics,protocols,swift-protocols,Ios,Swift,Generics,Protocols,Swift Protocols,摘要: 我想创建一个类,它将有一个相应的ClassDelegate协议,其中包含func 目标: 使用多个对象类重用单个对象和行为。接收已具有专用类的委托回调,而无需将对象强制转换为特定类来使用它 示例代码: 具有通用方法的协议: protocol GenericTableControllerDelegate: AnyObject { func controller<T>(controller: GenericTableController<T>, didSele

摘要:

我想创建一个
,它将有一个相应的
ClassDelegate
协议,其中包含
func

目标:

使用多个对象类重用单个对象和行为。接收已具有专用类的委托回调,而无需将对象强制转换为特定类来使用它

示例代码:

具有通用方法的协议:

protocol GenericTableControllerDelegate: AnyObject {
    func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}
GenericTableController的专用版本

final class SpecializedTableController: GenericTableController<NSObject> {}
SpecializedTableController的客户机
,具有“where”要求—唯一未编译的问题

final class AnotherClientOfTableController: UIViewController, GenericTableControllerDelegate {
    // Works OK
    func controller<T>(controller: GenericTableController<T>, didSelect value: T) where T: NSObject {
        // `value` is String
    }    
}
final类另一个ClientOftableController:UIViewController、GenericTableControllerDelegate{
//行吗
func控制器(控制器:GenericTableController,didSelect值:T),其中T:NSObject{
//'value'是字符串
}    
}
类型“AnotherClientOfTableController”不符合协议 “GenericTableControllerDelegate”是否要添加协议存根

是否有一种方法可以使协议具有通用方法,并且能够在该方法实现中具有具体(专用)类型?

是否有接近的替代方案可以满足类似的要求(具有泛型类,但能够在委托回调中处理具体类型)?


我认为这在你想要的意义上是行不通的。最接近的是与子类的组合。考虑以下事项:

protocol MagicProtocol {
    func dooMagic<T>(_ trick: T)
}

class Magician<TrickType> {
    private let listener: MagicProtocol
    private let tricks: [TrickType]
    init(listener: MagicProtocol, tricks: [TrickType]) { self.listener = listener; self.tricks = tricks }
    func abracadabra() { listener.dooMagic(tricks.randomElement()) }
}

class Audience<DataType>: MagicProtocol {

    var magician: Magician<DataType>?

    init() {
        magician?.abracadabra()
    }

    func doExplicitMagic(_ trick: DataType) {

    }

    func dooMagic<T>(_ trick: T) {
        doExplicitMagic(trick as! DataType)
    }

}
看起来这是非常安全的,它永远不会崩溃,但如果你看近一点,我们可以这样做:

func makeThingsGoWrong() {
    let myAudience = IntegerAudience()
    let evilMagician = Magician(listener: myAudience, tricks: ["Time to burn"])
    evilMagician.abracadabra() // This should crash the app
}
这里的
myAudience
对应于协议
MagicProtocol
,该协议可能不限于通用协议。但是
myAudience
仅限于
Int
。没有任何东西正在停止编译器,但如果它停止了,则会出现什么错误


不管怎样,只要你正确使用它,它就会工作。如果你不这样做,它就会崩溃。您可以选择展开,但我不确定是否合适。

解决这种情况的可能方法之一是使用回调而不是委派。通过不是传递闭包而是传递实例方法,它看起来与委托模式几乎相同:

open class GenericTableController2<DataType>: UITableViewController {
    var onSelect: ((DataType) -> Void)?
    var data = [DataType]()

    open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = data[indexPath.row]
        onSelect?(item)
    }
}

final class CallbackExample: GenericTableController2<NSObject> {
}

final class CallBackClient: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let vc = CallbackExample()
        vc.onSelect = handleSelection
    }

    func handleSelection(_ object: NSObject) {

    }
}
开放类GenericTableController2:UITableViewController{
var onSelect:((数据类型)->无效)?
变量数据=[DataType]()
打开重写func tableView(tableView:UITableView,didSelectRowAt indexPath:indexPath){
let item=data[indexPath.row]
onSelect?(项目)
}
}
最后一个类回调示例:GenericTableController2{
}
最终类CallBackClient:UIViewController{
重写func viewDidLoad(){
super.viewDidLoad()
设vc=CallbackExample()
vc.onSelect=handleSelection
}
func handleSelection(uObject:NSObject){
}
}

作为一个优点,该代码非常简单,不涉及Swift类型系统的任何高级变通方法,Swift类型系统在处理泛型和协议时经常出现一些问题。

您的错误在于协议:

protocol GenericTableControllerDelegate: AnyObject {
    func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}
然后您希望委托的数据类型与表视图的数据类型匹配。这让我们进入了PAT(带有相关类型的协议)、类型橡皮擦和(Swift中还没有)的世界,而事实上,它只是变得一团糟


虽然这是一个广义存在论特别适合的用例(如果它们曾被添加到Swift中),但在很多情况下,您可能无论如何都不希望这样。委托模式是在添加闭包之前开发的ObjC模式。过去在ObjC中传递函数非常困难,所以即使是非常简单的回调也会变成委托。在大多数情况下,我认为理查德·托普奇的方法是完全正确的。只需传递一个函数

但是如果你真的想保持代理风格呢?我们几乎可以做到。一个小问题是您不能拥有名为
delegate
的属性。你可以设置它,但你不能获取它

open class GenericTableController<DataType>: UITableViewController
{
    // This is the function to actually call
    private var didSelect: ((DataType) -> Void)?

    // We can set the delegate using any implementer of the protocol
    // But it has to be called `controller.setDelegate(self)`.
    public func setDelegate<Delegate: GenericTableControllerDelegate>(_ d: Delegate?)
        where Delegate.DataType == DataType {
            if let d = d {
                didSelect = { [weak d, weak self] in
                    if let self = self { d?.controller(controller: self, didSelect: $0) }
                }
            } else {
                didSelect = nil
            }
    }

    var data = [DataType]()

    // and here, just call our internal method
    open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = data[indexPath.row]
        didSelect?(item)
    }
}
有了它,您可以构建简单的策略,如“打印出来”:

或者更有趣的是,更新标签:

static func update(label: UILabel) -> SelectionHandler {
    return SelectionHandler { [weak label] in label?.text = "\($0)" }
}
然后你会得到这样的代码:

controller.selectionHandler = .update(label: self.nameLabel)
或者,更有趣的是,您可以构建高阶类型:

static func combine(_ handlers: [SelectionHandler]) -> SelectionHandler {
    return SelectionHandler {
        for handler in handlers {
            handler.didSelect($0)
        }
    }
}

static func trace(_ handler: SelectionHandler) -> SelectionHandler {
    return .combine([.printSelection(), handler])
}

controller.selectionHandler = .trace(.update(label: self.nameLabel))

这种方法比委托更有效,并开始发挥Swift的真正优势。

我绝对同意在这里使用回调而不是委托,但像这样传递方法通常会创建一个retain循环,需要非常小心
vc.onSelect=handleSelection
隐式捕获
self
作为强引用。在这些情况下,您通常需要使用
[弱自我]
。在这种特殊情况下,这不是因为
vc
是一个局部变量,但我怀疑在任何实际代码中,
vc
都会是一个属性。@RobNapier同意,这是一个很好的观点。你能不能改进一下这个想法,这样我就可以同时使用命名方法(不是匿名)和使
CallbackExample
controller捕获它?因此,如果我将对它的引用存储在“CallBackClient”中的某个地方,它仍然会被释放。我刚刚用一个简单的演示项目进行了检查,事实上,当存储对新创建的视图控制器的强引用时,同时存储回调会创建一个保留周期。我不相信有任何类似的语法,但我已经补充了一个答案,进一步说明了这一点。正如你所说,函数是IMO的必经之路。嗨,Rob,感谢你在文章和代码示例中对泛型和存在论进行了全面的概述,以及命令/策略封装的示例。看起来很有希望,但对于我想要的用例来说,这是一种过度的杀伤力。我心目中的目标之一是两者兼而有之,在调用站点上使用干净的语法,并且在内存管理方面没有任何问题
public protocol GenericTableControllerDelegate: AnyObject {
    associatedtype DataType
    func controller(controller: GenericTableController<DataType>, didSelect value: DataType)
}
open class GenericTableController<DataType>: UITableViewController
{
    // This is the function to actually call
    private var didSelect: ((DataType) -> Void)?

    // We can set the delegate using any implementer of the protocol
    // But it has to be called `controller.setDelegate(self)`.
    public func setDelegate<Delegate: GenericTableControllerDelegate>(_ d: Delegate?)
        where Delegate.DataType == DataType {
            if let d = d {
                didSelect = { [weak d, weak self] in
                    if let self = self { d?.controller(controller: self, didSelect: $0) }
                }
            } else {
                didSelect = nil
            }
    }

    var data = [DataType]()

    // and here, just call our internal method
    open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = data[indexPath.row]
        didSelect?(item)
    }
}
struct SelectionHandler<Element> {
    let didSelect: (Element) -> Void
}
extension SelectionHandler {
    static func printSelection() -> SelectionHandler {
        return SelectionHandler { print($0) }
    }
}
static func update(label: UILabel) -> SelectionHandler {
    return SelectionHandler { [weak label] in label?.text = "\($0)" }
}
controller.selectionHandler = .update(label: self.nameLabel)
static func combine(_ handlers: [SelectionHandler]) -> SelectionHandler {
    return SelectionHandler {
        for handler in handlers {
            handler.didSelect($0)
        }
    }
}

static func trace(_ handler: SelectionHandler) -> SelectionHandler {
    return .combine([.printSelection(), handler])
}

controller.selectionHandler = .trace(.update(label: self.nameLabel))