使现有的Objective C类通过扩展符合swift协议

时间:2017-09-04 16:23:12

标签: objective-c swift xcode protocols

我在项目中有两个相同型号的版本(我无法摆脱旧版本)。它是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添加存根,我最终得到namedetails可计算的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 }
}
  • '细节'在其自己的类型中使用
  • Getter for' name'使用Objective-C选择器' name'与使用相同Objective-C选择器的先前声明冲突

任何想法如何解决这个问题?我真的希望这两个模型表示都有这个协议和抽象接口。 我遇到的唯一解决方案是重命名CustomerDisplayable

中的属性

注意:真实模型要复杂得多,但这段代码证明了这个问题。

2 个答案:

答案 0 :(得分:1)

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

以下是正在发生的事情:

  1. 正如您所注意到的那样,Swift似乎没有注意到Objective-C选择器何时满足追溯协议要求,这是我可能提交的部分一个错误。

  2. 当您明确尝试向扩展程序添加namedetails属性时,Swift 3会注意到扩展程序位于NSObject子类上,并自动将属性公开给Objective -C。当然,Objective-C不能使用相同选择器的两种方法,因此您会收到您已经看到的错误。 Swift 4不再自动将所有内容暴露给Objective-C,因此您不会在那里得到此错误,而在Swift 3中,您可以通过添加@nonobjc关键字来解决此问题。但那时:

  3. 在扩展程序中添加属性后,它会隐藏原始的Objective-C属性,从而很难获得正确的值以返回属性。

  4. 不幸的是,我无法想到一个干净的解决方法,尽管我可以想到一个涉及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 ?? ""
        }
    }
    

    同样,我建议提交一份错误报告,以便我们将来不必像这样做。

    编辑:别介意这一切!我错了。无视我说的一切。追溯协议不起作用的唯一原因是因为您在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
    

答案 1 :(得分:0)

Objective-C中的

NSString与本机Swift值类型String不同。我发现的转换规则太难以记住。在这种情况下,桥接似乎会将NSString *带入:ImplicitlyUnwrappedOptional<String>

因此,如果您不介意指定Swift代码某些方面的遗留Customer类型,请将namedetails的类型更改为String!。一切都很好。

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