通过扩展使现有Objective C类符合swift协议
我在项目中有两个版本的相同模型(并且我无法摆脱旧版本)。它是Customer
(遗留代码)和结构CustomerModel
- 现代Swift模型的实现。通过扩展使现有Objective C类符合swift协议
我有一个自定义UITableViewCell
它曾经有setup(withCustomer: CustomerModel)
方法。它适用于新模型,但现在我需要使用传统模型来设置相同的单元格。
我决定定义CustomerDisplayable
协议,并使两种模型都符合它。
下面是代码:
Customer.h
@interface Customer : NSObject
@property (nonatomic, strong) NSString* name;
@property (nonatomic, strong) NSString* details;
@end
CustomerModel.swift
struct CustomerModel {
let name: String
let details: String?
init(withJSON json: [String: Any]) {
name = json["name"] as! String
details = json["details"] as? String
}
}
CustomerDisplayable.swift
protocol CustomerDisplayable {
var name: String { get }
var details: String? { get }
var reviewCount: Int { get }
var reviewRating: Double { get }
}
extension Customer: CustomerDisplayable {
var reviewCount: Int { return 100 }
var reviewRating: Double { return 4.5 }
}
extension CustomerModel: CustomerDisplayable {
var reviewCount: Int { return 100 }
var reviewRating: Double { return 4.5 }
}
我预计随着Customer.h早已性质name
& details
- 这将符合本协议及以上的扩展将正常工作。但是在我的扩展中出现编译错误: 类型'客户'不符合协议'CustomerDisplayable'。
Xcode提供了一个快速修复 - 协议要求属性'名称'类型'字符串';你想添加一个存根。 如果我同意的Xcode添加存根我结束了name
和details
可计算的干将,但Xcode中显示新的编译错误:
extension Customer: CustomerDisplayable {
var details: String? {
return "test"
}
var name: String {
return "test"
}
var reviewCount: Int { return 100 }
var reviewRating: Double { return 4.5 }
}
- “细节”与目标自身的类型
- 消气为“名”内使用-C选择器'名称'与以前的声明冲突与相同的Objective-C选择器
任何想法如何解决这个问题?我真的想为这两种模型表示拥有这个协议和抽象接口。 我唯一的解决方案是重命名属性CustomerDisplayable
注意:真正的模型要复杂得多,但是这段代码正在演示这个问题。
嗯,这看起来像国际海事组织,就像可能应该被认为是Swift编译器中的一个错误。我建议在
http://bugs.swift.org处提交报告。
这里是似乎是怎么回事:
当你已经注意到,斯威夫特似乎并不在一个Objective-C选择满足追溯协议的要求,这是部分要注意我可能会提交一个bug。当你明确地尝试添加
name
和details
属性您的扩展,斯威夫特3通知书的扩展上NSObject
子类,并自动公开性质的Objective-C。 Objective-C当然不能使用同一个选择器的两种方法,所以你会看到你所看到的错误。 Swift 4不会自动将所有内容公开给Objective-C,所以你不会在这里得到这个错误,而在Swift 3中你可以通过添加@nonobjc
关键字来解决这个问题。但是然后:一旦你在扩展中添加属性,它会隐藏原始的Objective-C属性,从而很难获得属性中返回的正确值。
不幸的是,我不能想到一个干净的解决办法,虽然我可以想到一个丑陋,哈克一个涉及Objective-C运行的。我最喜欢的部分是我们必须使用基于字符串的NSSelectorFromString
,因为#selector
将会从@nonobjc
阴影属性中窒息。但是,它的工作原理:
extension Customer: CustomerDisplayable {
@nonobjc var details: String? {
return self.perform(NSSelectorFromString("details")).takeUnretainedValue() as? String
}
@nonobjc var name: String {
return self.perform(NSSelectorFromString("name")).takeUnretainedValue() as? String ?? ""
}
}
同样,我建议提交bug报告,这样我们就不必做垃圾这样的未来。
编辑:没关系所有这一切!我错了。无视我所说的一切。追溯协议不起作用的唯一原因是您的Objective-C类中没有可空性说明符,所以Swift不知道它们是否为零,因此将类型解释为String!
。应该已经注意到了,哦,哦,哦。无论如何,我不知道你是否能够编辑最初的Objective-C类的定义来添加可空性说明符,但是如果可以的话,原始协议可以很好地追溯到没有黑客行为的情况下。
@interface Customer : NSObject
@property (nonatomic, nonnull, strong) NSString* name;
@property (nonatomic, nullable, strong) NSString* details;
- (nonnull instancetype)initWithName:(nonnull NSString *)name details: (nonnull NSString *)details;
@end
这与我的答案一致,但您已经介绍了“我觉得太模糊的转换规则”。 'nonnull'和'nullable'属性属性被添加到Objective-C中,以便在Swift公开发布之后不久立即增加与Swift的兼容性:https://developer.apple.com/swift/blog/?id = 25 – BaseZen
在Objective-C NSString
是比天然夫特值类型String
不同。我发现的转换规则太难记忆了。在这种情况下出现的桥接在NSString *
带来如:ImplicitlyUnwrappedOptional<String>
所以,如果你不介意的传统Customer
类型支配你的银行代码某些方面,改变类型的name
和details
到String!
无处不在,一切都很好。
否则,您将不得不更改协议以使用新名称,如displayName
和displayDetails
,并引用基础属性。大概你可以*地做到这一点,无论如何你可以实现可能是一个重要的抽象层。然后只是在每个扩展块中列出所有四个属性的所有显而易见的实现。因为从String!
到String
或String?
的转换是自动的,代码看起来有点琐碎和臃肿,但它也可以正常工作。
没有鸭子快速打字,所以你确实必须重新命名你的领域。 – algrid