我在项目中有两个相同型号的版本(我无法摆脱旧版本)。它是Customer
(遗留代码)和结构CustomerModel
- 模型的现代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提供快速解决方案 - 协议要求属性'名称'类型'字符串&#39 ;;你想添加一个存根。
如果我同意Xcode添加存根,我最终得到name
和details
可计算的getter,但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 }
}
任何想法如何解决这个问题?我真的希望这两个模型表示都有这个协议和抽象接口。
我遇到的唯一解决方案是重命名CustomerDisplayable
注意:真实模型要复杂得多,但这段代码证明了这个问题。
答案 0 :(得分:1)
嗯,这看起来像IMO,可能应该被认为是Swift编译器中的一个错误。我建议在http://bugs.swift.org提交报告。
以下是正在发生的事情:
正如您所注意到的那样,Swift似乎没有注意到Objective-C选择器何时满足追溯协议要求,这是我可能提交的部分一个错误。
当您明确尝试向扩展程序添加name
和details
属性时,Swift 3会注意到扩展程序位于NSObject
子类上,并自动将属性公开给Objective -C。当然,Objective-C不能使用相同选择器的两种方法,因此您会收到您已经看到的错误。 Swift 4不再自动将所有内容暴露给Objective-C,因此您不会在那里得到此错误,而在Swift 3中,您可以通过添加@nonobjc
关键字来解决此问题。但那时:
在扩展程序中添加属性后,它会隐藏原始的Objective-C属性,从而很难获得正确的值以返回属性。
不幸的是,我无法想到一个干净的解决方法,尽管我可以想到一个涉及Objective-C运行时的丑陋,hacky。我最喜欢的部分是我们必须使用基于字符串的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 ?? ""
}
}
同样,我建议提交一份错误报告,以便我们将来不必像这样做。
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
答案 1 :(得分:0)
NSString
与本机Swift值类型String
不同。我发现的转换规则太难以记住。在这种情况下,桥接似乎会将NSString *
带入:ImplicitlyUnwrappedOptional<String>
因此,如果您不介意指定Swift代码某些方面的遗留Customer
类型,请将name
和details
的类型更改为String!
。一切都很好。
否则,您必须更改协议以使用新名称,例如displayName
和displayDetails
,并引用基础属性。大概你可以自由地做到这一点,无论如何你都可以实现一个重要的抽象层。然后尽职地列出每个扩展块中所有四个属性的所有明显实现。由于从String!
到String
或String?
的转换是自动的,因此代码看起来有点琐碎和臃肿,但它也可以正常工作。