Các câu trả lời hiện tại chỉ nói một nửa convenience
câu chuyện. Nửa còn lại của câu chuyện, một nửa mà không có câu trả lời nào hiện có, trả lời câu hỏi Desmond đã đăng trong các bình luận:
Tại sao Swift buộc tôi phải đặt convenience
trước trình khởi chạy của mình chỉ vì tôi cần gọi self.init
từ nó? '
Tôi đã chạm vào nó một chút trong câu trả lời này , trong đó tôi trình bày chi tiết một số quy tắc khởi tạo của Swift, nhưng trọng tâm chính là required
từ này. Nhưng câu trả lời đó vẫn đang giải quyết một cái gì đó có liên quan đến câu hỏi này và câu trả lời này. Chúng ta phải hiểu cách kế thừa trình khởi tạo Swift hoạt động.
Vì Swift không cho phép các biến chưa được khởi tạo, nên bạn không được đảm bảo kế thừa tất cả (hoặc bất kỳ) trình khởi tạo nào từ lớp mà bạn kế thừa từ đó. Nếu chúng ta phân lớp và thêm bất kỳ biến đối tượng chưa được khởi tạo nào vào lớp con của chúng ta, chúng ta đã dừng kế thừa các công cụ khởi tạo. Và cho đến khi chúng tôi thêm các trình khởi tạo của riêng mình, trình biên dịch sẽ la mắng chúng tôi.
Để rõ ràng, một biến đối tượng chưa được khởi tạo là bất kỳ biến đối tượng nào không được cung cấp một giá trị mặc định (lưu ý rằng các tùy chọn và các tùy chọn không được bao bọc hoàn toàn tự động giả sử giá trị mặc định là nil
).
Vì vậy, trong trường hợp này:
class Foo {
var a: Int
}
a
là một biến thể chưa được khởi tạo. Điều này sẽ không biên dịch trừ khi chúng tôi đưa ra a
một giá trị mặc định:
class Foo {
var a: Int = 0
}
hoặc khởi tạo a
trong một phương thức khởi tạo:
class Foo {
var a: Int
init(a: Int) {
self.a = a
}
}
Bây giờ, hãy xem điều gì xảy ra nếu chúng ta phân lớp Foo
, phải không?
class Bar: Foo {
var b: Int
init(a: Int, b: Int) {
self.b = b
super.init(a: a)
}
}
Đúng? Chúng tôi đã thêm một biến và chúng tôi đã thêm một trình khởi tạo để đặt một giá trị để b
nó sẽ biên dịch. Tùy thuộc vào ngôn ngữ bạn đến từ ngôn ngữ nào, bạn có thể mong đợi rằng nó Bar
đã được kế thừa trình Foo
khởi tạo , init(a: Int)
. Nhưng nó không. Và làm thế nào nó có thể? Làm thế nào để Foo
's init(a: Int)
bí quyết làm thế nào để gán giá trị cho các b
biến mà Bar
thêm vào? Nó không. Vì vậy, chúng tôi không thể khởi tạo một Bar
thể hiện bằng một trình khởi tạo không thể khởi tạo tất cả các giá trị của chúng tôi.
Những điều này có liên quan gì convenience
?
Chà, hãy xem các quy tắc về thừa kế trình khởi tạo :
Quy tắc 1
Nếu lớp con của bạn không xác định bất kỳ trình khởi tạo được chỉ định nào, nó sẽ tự động kế thừa tất cả các trình khởi tạo được chỉ định của lớp cha.
Quy tắc 2
Nếu lớp con của bạn cung cấp một triển khai của tất cả các trình khởi tạo được chỉ định của siêu lớp của nó bằng cách kế thừa chúng theo quy tắc 1 hoặc bằng cách cung cấp một triển khai tùy chỉnh như một phần của định nghĩa của nó thì nó sẽ tự động kế thừa tất cả các trình khởi tạo tiện lợi của siêu lớp.
Lưu ý Quy tắc 2, trong đó đề cập đến khởi tạo thuận tiện.
Vì vậy, những gì các convenience
từ khóa không làm là chỉ ra cho chúng ta mà initializers có thể được thừa hưởng bởi lớp con rằng các biến add dụ không có giá trị mặc định.
Hãy lấy Base
lớp ví dụ này :
class Base {
let a: Int
let b: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
}
convenience init() {
self.init(a: 0, b: 0)
}
convenience init(a: Int) {
self.init(a: a, b: 0)
}
convenience init(b: Int) {
self.init(a: 0, b: b)
}
}
Lưu ý rằng chúng tôi có ba convenience
khởi tạo ở đây. Điều đó có nghĩa là chúng tôi có ba trình khởi tạo có thể được kế thừa. Và chúng tôi có một trình khởi tạo được chỉ định (trình khởi tạo được chỉ định đơn giản là bất kỳ trình khởi tạo nào không phải là trình khởi tạo tiện lợi).
Chúng ta có thể khởi tạo các thể hiện của lớp cơ sở theo bốn cách khác nhau:
Vì vậy, hãy tạo một lớp con.
class NonInheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
}
Chúng tôi đang thừa hưởng từ Base
. Chúng tôi đã thêm biến cá thể của riêng mình và chúng tôi không cung cấp cho nó một giá trị mặc định, vì vậy chúng tôi phải thêm các công cụ khởi tạo của riêng mình. Chúng tôi đã thêm một, init(a: Int, b: Int, c: Int)
nhưng nó không khớp với chữ ký của trình Base
khởi tạo được chỉ định của lớp : init(a: Int, b: Int)
. Điều đó có nghĩa là, chúng tôi không kế thừa bất kỳ công cụ khởi tạo nào từ Base
:
Vậy, điều gì sẽ xảy ra nếu chúng ta được thừa hưởng từ Base
, nhưng chúng ta đã đi trước và triển khai một trình khởi tạo khớp với trình khởi tạo được chỉ định từ Base
?
class Inheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
convenience override init(a: Int, b: Int) {
self.init(a: a, b: b, c: 0)
}
}
Bây giờ, ngoài hai trình khởi tạo mà chúng tôi đã triển khai trực tiếp trong lớp này, bởi vì chúng tôi đã triển khai trình khởi tạo Base
được chỉ định của lớp khởi tạo, chúng tôi có thể kế thừa tất cả các Base
trình convenience
khởi tạo của lớp :
Thực tế là trình khởi tạo với chữ ký phù hợp được đánh dấu là convenience
không có sự khác biệt ở đây. Nó chỉ có nghĩa là chỉ có Inheritor
một trình khởi tạo được chỉ định. Vì vậy, nếu chúng ta kế thừa từ đó Inheritor
, chúng ta chỉ cần thực hiện một trình khởi tạo được chỉ định đó, và sau đó chúng ta sẽ kế thừa trình Inheritor
khởi tạo tiện lợi, điều đó có nghĩa là chúng ta đã thực hiện tất cả các trình Base
khởi tạo được chỉ định và có thể kế thừa trình convenience
khởi tạo của nó .