2017-09-04 90 views
1

我在項目中有兩個版本的相同模型(並且我無法擺脫舊版本)。它是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

回答

1

嗯,這看起來像國際海事組織,就像可能應該被認爲是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

0

在Objective-C NSString是比天然夫特值類型String不同。我發現的轉換規則太難記憶了。在這種情況下出現的橋接在NSString *帶來如:ImplicitlyUnwrappedOptional<String>

所以,如果你不介意的傳統Customer類型支配你的銀行代碼某些方面,改變類型的namedetailsString!無處不在,一切都很好。

否則,您將不得不更改協議以使用新名稱,如displayNamedisplayDetails,並引用基礎屬性。大概你可以自由地做到這一點,無論如何你可以實現可能是一個重要的抽象層。然後只是在每個擴展塊中列出所有四個屬性的所有顯而易見的實現。因爲從String!StringString?的轉換是自動的,代碼看起來有點瑣碎和臃腫,但它也可以正常工作。