【问题标题】:Upcast non-generic ObjC Classes to parent class for Swift method parameter将非泛型 ObjC 类向上转换为 Swift 方法参数的父类
【发布时间】:2021-03-16 17:10:16
【问题描述】:

我在Objective-C 中定义了一些类,类似于:

@interface Type: NSObject {
    
}
@end

@interface SubType1: Type {
    
}
@end

@interface SubType2: Type {
    
}
@end

@interface Parent <T: __kindof Type *> : NSObject
@property (nonatomic, strong) NSArray <T> *anArray;
@property (nonatomic, strong) T anObject;
@end

@interface SubParent1: Parent<SubType1 *> {
    
}
@end

@interface SubParent2: Parent<SubType2 *> {
    
}
@end

我正在尝试创建一个Swift 函数,它可以采用Parent 的任何子类。我尝试了以下方法:

func callFunc(parent: Parent<Type>) {
                
}
callFunc(parent: SubParent1())

我得到了错误:Cannot convert value of type 'Parent&lt;SubType1&gt; to expected argument type 'Parent&lt;Type&gt;'

也试过了:

func callFunc<T>(parent: T) where T: Parent<Type>{

}
callFunc(parent: SubParent1())

我收到错误 Type of expression is ambiguous without more context

一般来说,我想要一个可以处理任何类型的子父类(SubParent1SubParent2)的方法,它具有父类类型(SubType1SubType2)的子类型,因为只有该方法需要访问Parent 上定义的属性。由于其他一些限制,切换Objective-C 类不是一个可能的选项,因此我正在寻找一种解决方案来保留Objective-C 中定义的类。不确定是否可能,但如果可能的话,如果参数预期为Parent,我可以在之后向下转换吗?

稍后编辑:另外,我如何允许函数在 Swift 中返回任何子父类型,例如:

enum AnEnum {
    case subParent1(_ : SubParent1)
    case subParent2(_ : SubParent2)
    
    func subParent<T: Type>() -> Parent<T>? {
        switch self {
        case .subParent1(let sub1):
            return sub1
        case .subParent2(let sub2):
            return sub2
        }
    }
}

编译器抱怨 sub1sub2 '表达式类型在没有更多上下文的情况下不明确'。

【问题讨论】:

    标签: swift objective-c generics downcast upcasting


    【解决方案1】:

    您正在参数化 Parent 而不是 Type,这是您实际更改的内容。你的意思是:

    func callFunc<T>(parent: Parent<T>) { ... }
    

    虽然您可以显式调用 T: Type,但这并不是必需的,因为 Parent 已经强制执行了。

    【讨论】:

    • 只是一个观察——在 Objective-C 中,你会在最高、最抽象的层次上工作,它最终会进行最深层次的调用,即使这会破坏系统。在 Swift 的安全环境中,您必须尽可能在最具体的最低级别上工作,并且 Swift 会调用尽可能浅的代码以保持安全。
    • 感谢您的解决方案。你能帮我完成“后期编辑”部分吗?
    • 该编辑应该是一个新问题,但答案是在一般情况下这是不可能的。仅使用编译时已知的信息,subParent() 的精确返回类型是什么?在这种特定情况下,更不可能,因为我可以传递 any 类型。我可以在程序的其他地方将CBPeripheralType 保持一致,而您必须返回Parent&lt;CBPeripheral&gt;。你会怎么做?您可能需要的是协议或类型擦除器,但这取决于实际用例。您应该打开一个问题来解释调用代码的外观。
    • @skaak 我不太确定你在这里的意思。在 ObjC 和 Swift 中传递非常高或非常低特异性的变量是很常见的。 Swift 的抽象代码往往比 ObjC 多得多。考虑针对非常抽象的协议(如 Collection)编写的代码与在 ObjC 中完成的代码。
    • @RobNapier 我实际上正在考虑一个基于stackoverflow.com/questions/66606803/… 为自己构建的特定示例,它看起来很像这个答案。没有我发布这个例子就很难继续这个讨论,但我的(具体的,不是一般的)结论是 Swift 迫使你尽可能具体地工作(为了保持安全?),即使这样它也会在安全方面犯错。哦,好吧,也许我会把这个例子作为一个问题发布,或者如果你真的想追求这个,我可以在聊天中展示给你
    【解决方案2】:

    这里的问题是您期望协方差,但 Swift 泛型通常不支持协方差。

    网络上有很多关于协方差的解释,这里就不一一解释了。但我将准确展示协方差与您的示例类型的不健全程度。考虑这段代码:

    let sp1 = SubParent1()
    let p = sp1 as Parent<Type>  // (1) Swift forbids this cast...
    let t: Type = SubType2()
    p.anObject = t               // (2) ...because this assigment would be unsound.
    

    假设 Swift 允许禁止的演员表 (1)。然后赋值 (2) 会将 SubType2 赋值给只能包含 SubType1 的属性。

    您可以通过使您的 callFunc 函数对 Parent 子类型和 Type 子类型都具有通用性来解决此问题:

    func callFunc<T, P>(parent: P) where T: Type, P: Parent<T> { }
    

    编辑:Rob Napier 的回答正确地表明您只需要对T 通用,而不是在P

    【讨论】:

    • 感谢您的解决方案。你能帮我完成“后期编辑”部分吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多