2016-10-15 21 views
5

Tôi muốn sử dụng tự trong các thông số init như vậy:tự trong init params

class A { 
    public init(finishBlock: ((_ operation: Self) -> Void)? = nil) {...} 
} 

Tôi biết tôi có thể sử dụng "A" ở nơi này, nhưng tôi muốn đạt được điều đó nếu một số lớp kế thừa từ A, sau đó nó được khởi tạo sẽ biết hoạt động vì nó là kiểu lớp và không phải là chỉ A. vì vậy, ví dụ nếu tôi đã viết:

class B: A { 
    public init(finishBlock: ((_ operation: Self) -> Void)? = nil) {...} 
    public func fooOnlyInB() {} 
} 

tôi sau đó có thể sử dụng:

let b = B { (operation) in 
    operation.fooOnlyInB() 
} 

Điều này có thể bằng cách nào đó không?

+0

Thú vị! Tôi muốn xem liệu điều này có thể làm việc bằng cách tạo ra một giao thức với một yêu cầu 'Self' trong' init', nhưng tôi đã gặp lỗi được đề cập trong [câu hỏi tương tự này] (http://stackoverflow.com/q/32999293/3769927). – kabiroberai

Trả lời

2

Thay vì sử dụng Self hoặc A trong mỗi trình khởi tạo, bạn có thể chỉ cần ghi đè từng trình khởi tạo của lớp con để sử dụng kiểu riêng của nó là operation.

này hoạt động vì initialiser A 's khẳng định rằng operation phải là một loại hình đó phù hợp với A, và khi bạn ghi đè lên nó, bạn có sự tự do để sử dụng một lớp con của A như operation để thay thế. Tuy nhiên, nếu bạn thay đổi operation thành loại không liên quan như String hoặc Int, trình biên dịch sẽ không ghi đè trình khởi tạo hiện tại.

Thứ nhất, xác định A với init của nó:

class A { 
    init(finishBlock: ((_ operation: A) -> Void)?) {...} 
} 

Hiện hành để tạo một lớp con, bạn phải ghi đè init bằng cách sử dụng lớp con loại như operation để thay thế. Trong cuộc gọi của bạn tới super.init, hãy tăng cường số operation ($0) vào loại của lớp con của bạn và gọi finishBlock với số này được đúc operation.

class B: A { 
    override init(finishBlock: ((_ operation: B) -> Void)?) { 
     // Perform custom initialisation... 
     super.init { finishBlock?($0 as! B) } 
    } 

    func fooOnlyInB() { 
     print("foo") 
    } 
} 

B 's initialiser nay đi B như operation, có nghĩa là bạn không cần phải cast nó cho mình nữa! Điều này là do bạn có thể ghi đè một số init với loại cụ thể hơn, trong trường hợp này là B.

let b = B { operation in 
    operation.fooOnlyInB() // prints "foo" 
} 
+0

Bạn nói đúng, đây là một số giải pháp, mặc dù không hoàn hảo. Tôi không thích thực tế là tôi cần phải ghi đè init một cách rõ ràng để làm việc này, và thay vào đó chỉ cần truyền finishBlock làm đối số, tôi cần phải tạo một khối khác để thực hiện finishBlock từ lớp con.Nhưng vẫn còn đây là một số giải pháp mà là tốt hơn sau đó không có gì. Tôi chắc chắn sẽ sử dụng nó, nhưng nó là một sự xấu hổ nó là cần thiết để làm một workaround như vậy. Cảm ơn bạn vì câu trả lời. –

1

Không, tôi không nghĩ điều đó là có thể vì bạn chỉ có thể sử dụng Self trong giao thức vì nó chỉ là trình giữ chỗ cho loại phù hợp với giao thức đó. Nó không phải là một loại thực như A hoặc B do đó bạn không thể sử dụng nó khi xác định các lớp như thể nó là Any hoặc AnyObject.

Vì vậy, hãy tạo ra một giao thức mà lực lượng phù hợp với các loại để thực hiện init(finishBlock:) initializer:

protocol BlockInitializable { 
    init(finishBlock: ((_ operation: Self) -> Void)?) 
} 

class A: BlockInitializable { 
    init() {} 

    convenience required init(finishBlock: ((_ operation: A) -> Void)?) { 
    self.init() 
    finishBlock?(self) 
    } 
} 

và bạn sẽ nhận được lỗi sau:

Protocol "Blocked" requirement init(finishBlock:) can not be satisifed by a non-final class (A) because it uses "Self" in a non-parameter, non-result type position.

và những gì tồi tệ hơn bạn sẽ mất các generic nhập Self của thông số hoạt động.

Để khắc phục điều này, bạn nên đánh dấu lớp của mình là cuối cùng hoặc sử dụng struct là loại cuối cùng. Tuy nhiên, bạn sẽ mất khả năng phân lớp các loại đó. Lý do tại sao bạn phải làm điều đó và lý do tại sao bạn không thể sử dụng Self trong các loại được phân loại được giải thích here vì vậy tôi khuyên bạn nên xem xét nó.

Tôi sẽ đi với các tùy chọn và sử dụng sau struct:

protocol Initializable { 
    init() 
} 

protocol BlockInitializable: Initializable { 
    init(finishBlock: ((_ operation: Self) -> Void)?) 
} 

extension BlockInitializable { 
    init(finishBlock: ((_ operation: Self) -> Void)?) { 
    self.init() 
    finishBlock?(self) 
    } 
} 

sau đó xác định AB như struct:

struct A: BlockInitializable { 

} 

struct B: BlockInitializable { 
    func fooOnlyInB() { 
    print("Only in B") 
    } 
} 

và bạn sẽ có thể làm như sau:

let blockA = A { operation in 
    print("Only in A") 
} 

let blockB = B { operation in 
    operation.fooOnlyInB() 
} 

Bạn có thể tải xuống sân chơi từ here.

+0

Toàn bộ điểm này là lớp này cần thừa kế, vì vậy tôi đoán không có gì tôi có thể làm về nó - tham số "hoạt động" cần phải được định nghĩa là A và trong các lớp con tôi cần đưa nó vào B ưa thích (hoạt động như? B). Quá tệ, sẽ rất tuyệt khi làm việc này. Cảm ơn bạn vì câu trả lời. –

+0

Vâng, đó là một con số thấp. Lý do cơ bản của điều đó là Swift nghiêm chỉnh cần phải biết loại 'Self' trong thời gian biên dịch. Liên kết khác tôi chia sẻ đang giải thích nó tốt hơn nhiều. – ozgur

1

Như những người khác đã nói, bạn không thể làm điều này trực tiếp, như Self hiện chỉ có sẵn trong các giao thức hoặc là kiểu trả về của một phương pháp trong một lớp học.

Tuy nhiên, bạn có thể workaround này bằng cách sử dụng một phần mở rộng giao thức để khai báo initialiser:

protocol InitialisableWithCompletionHandler { 
    init() 
} 

extension InitialisableWithCompletionHandler { 
    init(completion: ((Self) -> Void)? = nil) { 
     self.init() 
     completion?(self) 
    } 
} 

class A : InitialisableWithCompletionHandler { 
    required init() {} 
} 

class B : A { 
    func fooOnlyInB() { 
     print("foo in B") 
    } 
} 


let a = A(completion: { 
    print($0) // A 
}) 

let b = B(completion: { 
    $0.fooOnlyInB() // foo in B 
}) 

Đây không phải là một giải pháp hoàn hảo, vì nó gây khó khăn cho B để thêm riêng của mình các thuộc tính được lưu trữ, vì chúng sẽ phải có các giá trị mặc định để đáp ứng yêu cầu giao thức của init().

Ngoài ra còn có các gờ nhám khác xung quanh này, ví dụ Swift không cho phép bạn thêm các initialiser như một yêu cầu giao thức:

protocol InitialisableWithCompletionHandler { 
    init() 
    init(completion: ((Self) -> Void)?) 
} 

extension InitialisableWithCompletionHandler { 

    // compiler error: Protocol 'InitialisableWithCompletionHandler' requirement 
    // 'init(completion:)' cannot be satisfied by a non-final class ('A') because it uses 
    // 'Self' in a non-parameter, non-result type position 
    init(completion: ((Self) -> Void)? = nil) { 
     self.init() 
     completion?(self) 
    } 
} 

Nhưng thực sự, như xa như tôi biết, không có thực lý do kỹ thuật tại sao bạn không nên sử dụng Self làm tham số của đối số phương thức đóng trong một lớp.

Các tham số chức năng là contravariant, do đó (A) -> Void là một loại phụ của (B) -> Void. Do đó, ((A) -> Void) -> T có thể bị ghi đè bởi một ((B) -> Void) -> T (áp dụng contravariance một lần nữa), do đó việc sử dụng Self làm tham số của hàm được chuyển đến phương thức phải hoàn toàn hợp pháp.

Đó chỉ là ngôn ngữ không hỗ trợ đầy đủ (chưa).

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