Các câu trả lời hiện tại chỉ nói một nửa conveniencecâ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 conveniencetrước trình khởi chạy của mình chỉ vì tôi cần gọi self.inittừ 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à requiredtừ 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
}
alà 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 amột giá trị mặc định:
class Foo {
var a: Int = 0
}
hoặc khởi tạo atrong 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ị để bnó 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 Fookhở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 bbiến mà Barthêm vào? Nó không. Vì vậy, chúng tôi không thể khởi tạo một Barthể 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 conveniencetừ 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 Baselớ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 conveniencekhở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 Basekhở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 Basetrình conveniencekhở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à conveniencekhông có sự khác biệt ở đây. Nó chỉ có nghĩa là chỉ có Inheritormộ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 Inheritorkhở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 Basekhởi tạo được chỉ định và có thể kế thừa trình conveniencekhởi tạo của nó .