2014-09-12 15 views
13

Tôi có ví dụ sau đây trong một sân chơi Swift, trong một nỗ lực để thực hiện một constructor sao chép trong Swift:Làm cách nào để triển khai hàm tạo bản sao trong lớp con 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} 

Vấn đề là điều này không thực sự biên dịch trong hình thức hiện tại của nó. Trên init(copyFrom: Square) phương pháp trong Square lớp con, lỗi này được báo cáo:

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

Vấn đề này sẽ có ý nghĩa nếu nó không phải là một constructor, như thể nó là một thường xuyên func, bạn có thể có khả năng vượt qua trong một loại được mong đợi trong siêu lớp, nhưng điều đó đã bị ghi đè trong lớp con sẽ hạn chế hơn:

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. 

Nhưng thực tế là nó ' s a constructor dẫn tôi tin rằng tôi có thể ghi đè lên nó và cung cấp một chữ ký phương thức khác, vì nó hoàn toàn được biết tại thời gian biên dịch loại đối tượng là gì.

Sự cố này sẽ biến mất nếu tôi thay đổi Shape để không còn mở rộng NSObject. Tuy nhiên, do việc đưa vào mã Objective-C hiện tại, nó cần phải mở rộng NSObject.

Làm cách nào để cập nhật bản sao của bản sao của tôi để cho phép Shape biết nó đang sao chép từ Shape và cho phép Square biết nó đang sao chép từ Square?

Trả lời

19

init(copyFrom: Square) là tình trạng quá tải, không phải là ghi đè, của init(copyFrom: Shape). Điều tôi ngụ ý là chúng là các phương pháp không liên quan bởi vì chúng chấp nhận các kiểu khác nhau. Trong Swift có thể chấp nhận được. Trong ObjC, điều đó là bất hợp pháp. Không có quá tải trong ObjC.

Trình khởi chạy nhanh không tự động kế thừa. Vì vậy, trong Swift, bạn không thể cố gắng sao chép ngẫu nhiên Shape dưới dạng Square. Trình khởi tạo không khả dụng. Nhưng trong ObjC, initializers do tự động kế thừa (và bạn không thể ngăn chúng làm như vậy). Vì vậy, nếu bạn có một phương thức initWithCopyFrom:(*Shape), thì mọi lớp con đều sẵn sàng chấp nhận nó. Điều đó có nghĩa là bạn có thể (trong ObjC) cố gắng tạo một bản sao của một vòng tròn như một hình vuông. Đó là tất nhiên vô nghĩa.

Nếu đây là lớp con NSObject, bạn nên sử dụng NSCopying. Đây là cách bạn sẽ đi về điều đó:

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"} 

Swift 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

Cảm ơn Rob - câu trả lời chắc chắn. –

+2

Bạn cũng có thể thấy thảo luận này hữu ích về việc tạo bản sao trong Swift thuần túy: http://stackoverflow.com/questions/25645090/protocol-func-returning-self –

+0

Đối với Swift 2/Xcode 7, nó sẽ là 'let theCopy = self. dynamicType.init() ', và dĩ nhiên' as! 'thay vì' as' (chỉ đề cập đến, bởi vì một câu hỏi liên quan xuất hiện ở đây: http://stackoverflow.com/questions/31885231/using-object-initializers-in- swift-to-replace-allocwithzone). –

1

Cách đơn giản nhất để làm điều đó sẽ chỉ đơn giản là thay đổi tên của initialiser lớp con để init(copyFromSquare: Square), để lại Square với init(copyFrom: Shape) phương pháp còn nguyên vẹn (như bạn đã ký hợp đồng bằng cách kế thừa từ Shape).

Bạn có thể ghi đè init(copyFrom: Shape) và kiểm tra xem copyFromSquare, trong trường hợp đó bạn thực hiện một hành động (đặt độ dài), nếu không thì không.

Cũng lưu ý rằng bạn cần đặt self.length trước bạn gọi là siêu.

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 Tôi thích ý tưởng chỉ thay đổi tên phương thức thành 'copyFromSquare:', để nó lên các lớp con để đặt tên cho nó. 'copyFromTriangle:', vv –

+1

Lưu ý rằng điều này lá copyFrom: Hình dạng có sẵn trong ObjC cho tất cả các lớp con, mà sẽ tạo ra một bản sao không chính xác. Nếu bạn sử dụng phương pháp đặt tên lớp, hãy đảm bảo bạn xóa trình khởi tạo bản sao khỏi siêu lớp, hoặc đánh dấu nó là "bắt buộc" để tất cả các lớp con buộc phải thực hiện nó bằng cách nào đó (ngay cả khi chỉ bằng cách ném 'điều kiện tiên quyết()') . –

Các vấn đề liên quan