通过扩展使现有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添加存根我结束了namedetails可计算的干将,但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

注意:真正的模型要复杂得多,但是这段代码正在演示这个问题。

+1

没有鸭子快速打字,所以你确实必须重新命名你的领域。 – algrid

嗯,这看起来像国际海事组织,就像可能应该被认为是Swift编译器中的一个错误。我建议在 http://bugs.swift.org处提交报告。

这里是似乎是怎么回事:

  1. 当你已经注意到,斯威夫特似乎并不在一个Objective-C选择满足追溯协议的要求,这是部分要注意我可能会提交一个bug。

  2. 当你明确地尝试添加namedetails属性您的扩展,斯威夫特3通知书的扩展上NSObject子类,并自动公开性质的Objective-C。 Objective-C当然不能使用同一个选择器的两种方法,所以你会看到你所看到的错误。 Swift 4不会自动将所有内容公开给Objective-C,所以你不会在这里得到这个错误,而在Swift 3中你可以通过添加@nonobjc关键字来解决这个问题。但是然后:

  3. 一旦你在扩展中添加属性,它会隐藏原始的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 
+0

这与我的答案一致,但您已经介绍了“我觉得太模糊的转换规则”。 'nonnull'和'nullable'属性属性被添加到Objective-C中,以便在Swift公开发布之后不久立即增加与Swift的兼容性:https://developer.apple.com/swift/blog/?id = 25 – BaseZen

在Objective-C NSString是比天然夫特值类型String不同。我发现的转换规则太难记忆了。在这种情况下出现的桥接在NSString *带来如:ImplicitlyUnwrappedOptional<String>

所以,如果你不介意的传统Customer类型支配你的银行代码某些方面,改变类型的namedetailsString!无处不在,一切都很好。

否则,您将不得不更改协议以使用新名称,如displayNamedisplayDetails,并引用基础属性。大概你可以*地做到这一点,无论如何你可以实现可能是一个重要的抽象层。然后只是在每个扩展块中列出所有四个属性的所有显而易见的实现。因为从String!StringString?的转换是自动的,代码看起来有点琐碎和臃肿,但它也可以正常工作。