คำตอบที่มีอยู่บอกเรื่องราวได้เพียงครึ่ง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 ที่สะดวกซึ่งหมายความว่าเราได้ติดตั้งBase
initializers ที่กำหนดไว้ทั้งหมดและสามารถสืบทอดconvenience
ค่าเริ่มต้นได้