คำตอบที่มีอยู่บอกเรื่องราวได้เพียงครึ่งconvenienceเดียว อีกครึ่งหนึ่งของเรื่องครึ่งหนึ่งที่ไม่มีคำตอบใด ๆ ครอบคลุมตอบคำถามที่เดสมอนด์โพสต์ไว้ในความคิดเห็น:
เหตุใด Swift จึงบังคับให้ฉันวางconvenienceหน้าตัวเริ่มต้นเพียงเพราะฉันต้องการโทรself.initจากมัน”
ฉันพูดถึงมันเล็กน้อยในคำตอบนี้ซึ่งฉันครอบคลุมรายละเอียดกฎการเริ่มต้นของ Swift หลายข้อ แต่จุดสนใจหลักอยู่ที่requiredคำนั้น แต่คำตอบนั้นยังคงกล่าวถึงบางสิ่งที่เกี่ยวข้องกับคำถามนี้และคำตอบนี้ เราต้องเข้าใจว่าการสืบทอด Swift initializer ทำงานอย่างไร
เนื่องจาก Swift ไม่อนุญาตให้ใช้ตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นคุณจึงไม่รับประกันว่าจะรับค่าเริ่มต้นทั้งหมด (หรือใด ๆ ) จากคลาสที่คุณสืบทอดมา หากเราซับคลาสและเพิ่มตัวแปรอินสแตนซ์ที่ไม่ได้กำหนดค่าเริ่มต้นลงในคลาสย่อยของเราเราได้หยุดการรับค่าเริ่มต้น และจนกว่าเราจะเพิ่ม initializers ของเราเองคอมไพเลอร์จะตะโกนใส่เรา
เพื่อความชัดเจนตัวแปรอินสแตนซ์ที่ไม่ได้กำหนดค่าเริ่มต้นคือตัวแปรอินสแตนซ์ใด ๆ ที่ไม่ได้กำหนดค่าเริ่มต้น (โปรดทราบว่าตัวเลือกและตัวเลือกที่ไม่ได้ปิดโดยปริยายจะถือว่าเป็นค่าเริ่มต้นโดยอัตโนมัติnil)
ดังนั้นในกรณีนี้:
class Foo {
var a: Int
}
aเป็นตัวแปรอินสแตนซ์ที่ไม่ได้เริ่มต้น สิ่งนี้จะไม่รวบรวมเว้นแต่เราจะให้aค่าเริ่มต้น:
class Foo {
var a: Int = 0
}
หรือเริ่มต้นaด้วยวิธีการเริ่มต้น:
class Foo {
var a: Int
init(a: Int) {
self.a = a
}
}
ตอนนี้เรามาดูกันว่าจะเกิดอะไรขึ้นถ้าเราซับคลาสFooเราจะทำอย่างไร?
class Bar: Foo {
var b: Int
init(a: Int, b: Int) {
self.b = b
super.init(a: a)
}
}
ขวา? เราได้เพิ่มตัวแปรและเราได้เพิ่มตัวเริ่มต้นเพื่อกำหนดค่าbเพื่อที่จะรวบรวม ทั้งนี้ขึ้นอยู่กับสิ่งที่ภาษาที่คุณมาจากคุณอาจคาดหวังว่าBarได้รับการถ่ายทอดของการเริ่มต้น,Foo init(a: Int)แต่มันไม่ได้ แล้วจะทำได้อย่างไร? อย่างไรFoo's init(a: Int)ทราบวิธีการกำหนดค่าให้กับbตัวแปรที่Barเพิ่ม? มันไม่ ดังนั้นเราจึงไม่สามารถเริ่มต้นBarอินสแตนซ์ด้วยตัวเริ่มต้นที่ไม่สามารถเริ่มต้นค่าทั้งหมดของเราได้
สิ่งนี้เกี่ยวข้องกับconvenienceอะไร?
ลองดูกฎเกี่ยวกับการสืบทอดค่าเริ่มต้น :
กฎข้อ 1
หากคลาสย่อยของคุณไม่ได้กำหนดตัวเริ่มต้นที่กำหนดไว้คลาสย่อยจะสืบทอดตัวเริ่มต้นที่กำหนดระดับซูเปอร์คลาสทั้งหมดโดยอัตโนมัติ
กฎข้อ 2
หากคลาสย่อยของคุณมีการใช้งานตัวเริ่มต้นที่กำหนดระดับซูเปอร์คลาสทั้งหมดไม่ว่าจะโดยการสืบทอดตามกฎ 1 หรือโดยให้การใช้งานแบบกำหนดเองเป็นส่วนหนึ่งของคำจำกัดความจากนั้นคลาสจะรับค่าเริ่มต้นของความสะดวกระดับซุปเปอร์คลาสทั้งหมดโดยอัตโนมัติ
ประกาศกฎข้อ 2 ซึ่งกล่าวถึงตัวเริ่มต้นความสะดวก
ดังนั้นสิ่งที่เป็นconvenienceคำหลักที่ไม่ทำคือการแสดงให้เราซึ่ง initializers สามารถสืบทอดโดย subclasses ว่าตัวแปรเช่นเพิ่มโดยไม่ต้องเป็นค่าเริ่มต้น
ลองมาBaseเรียนตัวอย่างนี้:
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)
}
}
สังเกตว่าเรามีตัวconvenienceเริ่มต้นสามตัวที่นี่ นั่นหมายความว่าเรามีตัวเริ่มต้นสามตัวที่สามารถสืบทอดได้ และเรามีตัวเริ่มต้นที่กำหนดไว้หนึ่งตัว (ตัวเริ่มต้นที่กำหนดเป็นเพียงตัวเริ่มต้นใด ๆ ที่ไม่ใช่ตัวเริ่มต้นที่สะดวก)
เราสามารถสร้างอินสแตนซ์ของคลาสพื้นฐานได้สี่วิธี:

มาสร้างคลาสย่อยกัน
class NonInheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
}
Baseเรากำลังสืบทอดจาก เราเพิ่มตัวแปรอินสแตนซ์ของเราเองและเราไม่ได้ให้ค่าเริ่มต้นดังนั้นเราต้องเพิ่มตัวเริ่มต้นของเราเอง เราได้เพิ่มหนึ่งinit(a: Int, b: Int, c: Int)แต่มันไม่ตรงกับลายเซ็นของBaseระดับที่กำหนด init(a: Int, b: Int)initializer: นั่นหมายความว่าเราไม่ได้รับค่าเริ่มต้นใด ๆจากBase:

ดังนั้นสิ่งที่จะเกิดขึ้นถ้าเราสืบทอดมาจากBaseแต่เราเดินไปข้างหน้าและการดำเนินการการเริ่มต้นที่ตรงกับที่ได้รับมอบหมายจากการเริ่มต้น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)
}
}
ตอนนี้นอกจากตัวเริ่มต้นสองตัวที่เรานำมาใช้โดยตรงในคลาสนี้เนื่องจากเราติดตั้งตัวเริ่มต้นที่Baseกำหนดให้กับคลาสเริ่มต้นเราจะได้รับค่าเริ่มต้นทั้งหมดของBaseคลาสconvenience:

ความจริงที่ว่าตัวเริ่มต้นที่มีลายเซ็นตรงกันถูกทำเครื่องหมายว่าconvenienceไม่มีความแตกต่างที่นี่ หมายความว่าInheritorมีตัวเริ่มต้นที่กำหนดไว้เพียงตัวเดียว ดังนั้นหากเราสืบทอดมาInheritorเราก็แค่ต้องใช้ initializer ที่กำหนดไว้ตัวหนึ่งจากนั้นเราก็จะรับInheritorค่า initializer ที่สะดวกซึ่งหมายความว่าเราได้ติดตั้งBaseinitializers ที่กำหนดไว้ทั้งหมดและสามารถสืบทอดconvenienceค่าเริ่มต้นได้