2014-09-12 93 views
13

我有以下的例子在斯威夫特遊樂場,企圖實現雨燕拷貝構造函數:如何在Swift子類中實現複製構造函數?

class Shape : NSObject { 
    var color : String 

    override init() { 
     color = "Red" 
    } 

    init(copyFrom: Shape) { 
     color = copyFrom.color 
    } 
} 

class Square : Shape { 
    var length : Double 

    override init() { 
     super.init() 
     length = 10.0 
    } 

    init(copyFrom: Square) { /* Compilation error here! */ 
     super.init(copyFrom: copyFrom) 
     length = copyFrom.length 
    } 
} 

let s : Square = Square()  // {{color "Red"} length 10.0} 

let copy = Square(copyFrom: s) // {{color "Red"} length 10.0} 

s.color = "Blue"    // {{color "Blue"} length 10.0} 
s        // {{color "Blue"} length 10.0} 
copy       // {{color "Red"} length 10.0} 

的問題是,這實際上並不在其目前的形式編譯。論Square子類中的方法init(copyFrom: Square),報告這個錯誤:

Overriding method with selector 'initWithCopyFrom:' has incompatible type '(Square) -> Square'

這個問題將使意義,如果它不是一個構造,就好像它是一個普通func,你可以可能會傳入超類中預期的類型,但在子類中被覆蓋的類型更具限制性:

let mySquare : Shape = Square() // Note the var is a SHAPE 
mySquare.someShapeMethod("Test") // If Square overrides someShapeMethod() to expect Int, compiler errors out to protect us here. 

但是,構造函數構造函數使我相信我應該能夠覆蓋它並提供不同的方法簽名,因爲在編譯時絕對知道對象的類型是什麼。

如果我將Shape更改爲不再延伸NSObject,則此問題將消失。但是,由於包含現有的Objective-C代碼,它需要擴展NSObject

我如何更新我的拷貝構造函數允許Shape知道它是從一個Shape複製,並允許Square知道它是從一個Square複製?

回答

19

init(copyFrom: Square)init(copyFrom: Shape)的過載而不是覆蓋。我的意思是他們是不相關的方法,因爲他們接受不同的類型。在Swift中可以接受。在ObjC,這是非法的。 ObjC中沒有重載。

Swift初始化器不會自動繼承。所以在Swift中,你不能嘗試複製Shape作爲Square。初始化程序不可用。但在ObjC中,初始化程序做了自動繼承(並且不能阻止它們這樣做)。所以如果你有一個方法initWithCopyFrom:(*Shape),那就要求每個子類都願意接受它。這意味着你可以(在ObjC中)嘗試創建一個Circle作爲Square的副本。這當然是無稽之談。

如果這是一個NSObject子類,則應該使用NSCopying。這裏是你將如何去說:

import Foundation 

class Shape : NSObject, NSCopying { // <== Note NSCopying 
    var color : String 

    required override init() { // <== Need "required" because we need to call dynamicType() below 
    color = "Red" 
    } 

    func copyWithZone(zone: NSZone) -> AnyObject { // <== NSCopying 
    // *** Construct "one of my current class". This is why init() is a required initializer 
    let theCopy = self.dynamicType() 
    theCopy.color = self.color 
    return theCopy 
    } 
} 

class Square : Shape { 
    var length : Double 

    required init() { 
    length = 10.0 
    super.init() 
    } 

    override func copyWithZone(zone: NSZone) -> AnyObject { // <== NSCopying 
    let theCopy = super.copyWithZone(zone) as Square // <== Need casting since it returns AnyObject 
    theCopy.length = self.length 
    return theCopy 
    } 

} 

let s = Square()  // {{color "Red"} length 10.0} 

let copy = s.copy() as Square // {{color "Red"} length 10.0} // <== copy() requires a cast 

s.color = "Blue"    // {{color "Blue"} length 10.0} 
s        // {{color "Blue"} length 10.0} 
copy       // {{color "Red"} 

斯威夫特3

class Shape: NSObject, NSCopying { 

    required override init() { 
     super.init() 
    }  

    func copy(with zone: NSZone? = nil) -> Any { 
     let copy = type(of: self).init() 
     return copy 
    } 

} 

class Square: NSObject, NSCopying { 

    required override init() { 
     super.init() 
    }  

    func copy(with zone: NSZone? = nil) -> Any { 
     let copy = super.copy(with: zone) as! Square 
     copy.foo = self.foo 
     ...... 
     return copy 
    } 

} 
+0

感謝羅布 - 可靠的答案。 – 2014-09-12 13:32:41

+2

你也可能會發現這個討論對於在純Swift中製作副本很有用:http://stackoverflow.com/questions/25645090/protocol-func-returning-self – 2014-09-12 13:41:17

+0

對於Swift 2/Xcode 7,它會是'let theCopy = self。 dynamicType.init()',當然'as!'而不是'as'(僅僅提到,因爲相關的問題出現在這裏:http://stackoverflow.com/questions/31885231/using-object-initializers-in-迅速更換的-allocwithzone)。 – 2015-08-07 19:53:56

1

做到這一點,簡直是改變子類初始化器來init(copyFromSquare: Square)的名字,留下Square用最簡單的方法init(copyFrom: Shape)方法完好無損(因爲您已從Shape繼承)。

你當然可以覆蓋init(copyFrom: Shape),並測試copyFrom是否爲Square,在這種情況下,你採取行動(設定長度)的一門課程,否則不是。

還請注意,您需要設置self.length之前你叫超級。

class Shape : NSObject { 
    var color : String 

    override init() { 
     color = "Red" 
    } 

    init(copyFrom: Shape) { 
     color = copyFrom.color 
    } 
} 

class Square : Shape { 
    var length : Double 

    override init() { 
     self.length = 10.0 
     super.init() 
    } 

    override init(copyFrom: Shape) { 
     if copyFrom is Square { 
      self.length = (copyFrom as Square).length 
     } else { 
      self.length = 10.0 // default 
     } 
     super.init(copyFrom: copyFrom) 
    } 
} 
+0

+1我喜歡將方法名更改爲'copyFromSquare:'的想法,將其留給子類來命名。 'copyFromTriangle:'等 – 2014-09-12 13:28:28

+1

請注意,這會使copyFrom:ObjC中的Shape可用於所有子類,這將創建不正確的副本。如果您使用類命名方法,請確保您從超類中移除複製初始值設定項,或將其標記爲「必需」,以便所有子類被強制以某種方式實現它(即使僅通過拋出'precondition()') 。 – 2014-09-12 13:31:30