คลาสไม่ใช้สมาชิกที่ต้องการของซูเปอร์คลาส


155

ดังนั้นผมจึงมีการปรับปรุงเพื่อ Xcode 6 เบต้า 5 วันนี้และสังเกตเห็นผมได้รับข้อผิดพลาดในรอบเกือบทั้งหมดของ subclasses ของฉันของการเรียนแอปเปิ้ล

สถานะข้อผิดพลาด:

Class 'x' ไม่ได้ใช้สมาชิกที่ต้องการของ superclass

นี่คือตัวอย่างหนึ่งที่ฉันเลือกเพราะคลาสนี้มีน้ำหนักเบาในขณะนี้ดังนั้นจึงง่ายต่อการโพสต์

class InfoBar: SKSpriteNode  { //Error message here

    let team: Team
    let healthBar: SKSpriteNode

    init(team: Team, size: CGSize) {
        self.team = team
        if self.team == Team.TeamGood {
            healthBar = SKSpriteNode(color: UIColor.greenColor(), size:size)
        }
        else {
            healthBar = SKSpriteNode(color: UIColor.redColor(), size:size)
        }
        super.init(texture:nil, color: UIColor.darkGrayColor(), size: size)

        self.addChild(healthBar)

    }

}

ดังนั้นคำถามของฉันคือทำไมฉันได้รับข้อผิดพลาดนี้และฉันจะแก้ไขได้อย่างไร ฉันไม่ได้ใช้อะไร ฉันกำลังเรียกเครื่องมือเริ่มต้นที่กำหนด

คำตอบ:


127

จากพนักงาน Apple บนฟอรัม Developer:

"วิธีประกาศคอมไพเลอร์และโปรแกรมที่สร้างขึ้นซึ่งคุณไม่ต้องการให้เข้ากันได้กับ NSCoding คือทำสิ่งนี้:"

required init(coder: NSCoder) {
  fatalError("NSCoding not supported")
}

หากคุณรู้ว่าคุณไม่ต้องการให้เป็นไปตามมาตรฐาน NSCoding นี่เป็นตัวเลือก ฉันใช้วิธีนี้ด้วยรหัส SpriteKit ของฉันมากมายฉันรู้ว่าฉันจะไม่โหลดมันจากกระดานเรื่องราว


อีกทางเลือกหนึ่งที่คุณสามารถทำได้ซึ่งใช้งานได้ค่อนข้างดีคือการใช้วิธีการนี้ให้เป็นความสะดวกสบายเช่น:

convenience required init(coder: NSCoder) {
    self.init(stringParam: "", intParam: 5)
}

selfหมายเหตุการโทรไปยังการเริ่มต้นใน สิ่งนี้ช่วยให้คุณต้องใช้ค่าดัมมี่สำหรับพารามิเตอร์ซึ่งตรงข้ามกับคุณสมบัติที่ไม่จำเป็นทั้งหมดขณะที่หลีกเลี่ยงการโยนข้อผิดพลาดร้ายแรง


ตัวเลือกที่สามของหลักสูตรคือการใช้วิธีการในขณะที่เรียก super และเริ่มต้นคุณสมบัติที่ไม่ใช่ตัวเลือกทั้งหมดของคุณ คุณควรใช้วิธีนี้หากวัตถุคือมุมมองที่ถูกโหลดจากกระดานเรื่องราว:

required init(coder aDecoder: NSCoder!) {
    foo = "some string"
    bar = 9001

    super.init(coder: aDecoder)
}

3
ตัวเลือกที่สองนั้นไม่มีประโยชน์ในกรณีส่วนใหญ่ในชีวิตจริง init(collection:MPMediaItemCollection)ใช้ตัวอย่างเช่นการเริ่มต้นที่ต้องการของฉัน คุณต้องจัดหาคอลเลกชันรายการสื่อจริง นั่นคือจุดของคลาสนี้ คลาสนี้ไม่สามารถสร้างอินสแตนซ์ได้หากไม่มีอย่างใดอย่างหนึ่ง มันจะวิเคราะห์การรวบรวมและเริ่มต้นตัวแปรอินสแตนซ์โหล นั่นคือจุดรวมของการเป็นผู้เริ่มต้นที่ได้รับมอบหมายเพียงคนเดียว! ดังนั้นจึงinit(coder:)ไม่มีความหมาย (หรือไม่มีความหมาย) MPMediaItemCollection ที่จะจัดหาที่นี่; เพียง แต่fatalErrorวิธีการที่ถูกต้อง
แมตต์

@ แก้ไขถูกต้องหนึ่งหรือตัวเลือกอื่นจะทำงานได้ดีขึ้นในสถานการณ์ที่แตกต่างกัน
Ben Kane

ถูกต้องและฉันค้นพบและพิจารณาตัวเลือกที่สองอย่างอิสระและบางครั้งมันก็สมเหตุสมผล ตัวอย่างเช่นฉันจะได้ประกาศ init(collection:MPMediaItemCollection!)di ที่จะอนุญาตให้init(coder:)ผ่านศูนย์ แต่ฉันก็รู้ว่า: ไม่ตอนนี้คุณแค่หลอกผู้แปล การผ่านศูนย์ไม่เป็นที่ยอมรับดังนั้นให้โยนfatalErrorและไปต่อ :)
แมตต์

1
ฉันรู้ว่าคำถามนี้และคำตอบของมันเก่าไปแล้ว แต่ฉันได้โพสต์คำตอบใหม่ซึ่งกล่าวถึงบางประเด็นที่ฉันคิดว่ามีความสำคัญต่อการเข้าใจข้อผิดพลาดที่ไม่ได้รับการแก้ไขจากคำตอบที่มีอยู่
nhgrif

คำตอบที่ดี. ฉันเห็นด้วยกับคุณว่าการเข้าใจว่าสวิฟท์ไม่ได้รับค่าเริ่มต้นที่ยอดเยี่ยมเป็นสิ่งสำคัญในการทำความเข้าใจรูปแบบนี้
Ben Kane

71

มีข้อมูลสำคัญเฉพาะสองอย่างของ Swift ที่หายไปจากคำตอบที่มีอยู่ซึ่งฉันคิดว่าช่วยให้ชัดเจนขึ้น

  1. หากโปรโตคอลระบุตัวกำหนดค่าเริ่มต้นเป็นวิธีการที่จำเป็นตัวเริ่มต้นนั้นจะต้องถูกทำเครื่องหมายโดยใช้requiredคำหลักของ Swift
  2. Swift มีกฎการสืบทอดชุดพิเศษเกี่ยวกับinitวิธีการ

TL; DRคือ:

หากคุณใช้ initializers ใด ๆ คุณจะไม่ได้รับค่าเริ่มต้นที่กำหนดไว้ของ superclass อีกต่อไป

initializers เพียงตัวเดียวที่คุณจะได้รับนั้นเป็น initializers อำนวยความสะดวกระดับซุปเปอร์ที่ชี้ไปที่ initializer ที่กำหนดซึ่งคุณเกิดขึ้นเพื่อแทนที่

ดังนั้น ... พร้อมแล้วสำหรับรุ่นยาว


Swift มีกฎการสืบทอดชุดพิเศษเกี่ยวกับinitวิธีการ

ฉันรู้ว่านี่เป็นครั้งที่สองที่ฉันทำ แต่เราไม่เข้าใจจุดแรกหรือทำไมrequiredคำหลักถึงมีอยู่จนกว่าเราจะเข้าใจจุดนี้ เมื่อเราเข้าใจประเด็นนี้แล้วอีกประเด็นก็ชัดเจน

ทุกปกข้อมูลผมในส่วนของคำตอบนี้นี้มาจากเอกสารของ Apple พบที่นี่

จากเอกสาร Apple:

แตกต่างจากคลาสย่อยใน Objective-C คลาสย่อย Swift ไม่ได้รับค่าเริ่มต้น superclass ของพวกเขาตามค่าเริ่มต้น วิธีการของ Swift ช่วยป้องกันสถานการณ์ที่ตัวเริ่มต้นอย่างง่ายจากซูเปอร์คลาสได้รับการสืบทอดโดยคลาสย่อยที่พิเศษกว่าและถูกใช้เพื่อสร้างอินสแตนซ์ใหม่ของคลาสย่อยที่ไม่ได้เริ่มต้นอย่างสมบูรณ์หรือไม่ถูกต้อง

เน้นการขุด

ดังนั้นตรงจากเอกสารของ Apple ที่นั่นเราจะเห็นว่าคลาสย่อย Swift จะไม่เสมอไป (และมักจะไม่) สืบทอดinitวิธีการของซูเปอร์คลาส

ดังนั้นพวกเขาจะได้รับมรดกจากซุปเปอร์คลาสของพวกเขาเมื่อไหร่?

มีกฎสองข้อที่กำหนดเมื่อคลาสย่อยสืบทอดinitเมธอดจากพาเรนต์ จากเอกสาร Apple:

กฎข้อที่ 1

หากคลาสย่อยของคุณไม่ได้กำหนดค่าเริ่มต้นที่กำหนดไว้ค่าเริ่มต้นจะรับค่าเริ่มต้นที่กำหนดระดับเริ่มต้นทั้งหมดของมันโดยอัตโนมัติ

กฎ 2

หากคลาสย่อยของคุณมีการนำไปใช้งานของตัวกำหนดค่าเริ่มต้นของซูเปอร์คลาสทั้งหมดไม่ว่าจะโดยการสืบทอดตามกฎ 1 หรือโดยการปรับใช้แบบกำหนดเองเป็นส่วนหนึ่งของคำจำกัดความ

กฎข้อที่ 2 ไม่ได้โดยเฉพาะอย่างยิ่งที่เกี่ยวข้องกับการสนทนานี้เพราะSKSpriteNode's init(coder: NSCoder)ไม่น่าจะเป็นวิธีการที่สะดวกสบาย

ดังนั้นคุณInfoBarชั้นสืบทอดrequiredinitializer init(team: Team, size: CGSize)ขวาขึ้นจนถึงจุดที่คุณเพิ่มเข้าไป

หากคุณไม่ได้ระบุinitวิธีการนี้และทำให้InfoBarคุณสมบัติที่เพิ่มเข้ามาของคุณเป็นทางเลือกหรือให้ค่าเริ่มต้นแทนคุณจะยังคงสืบทอดคุณสมบัติSKSpriteNodeต่อinit(coder: NSCoder)ไป อย่างไรก็ตามเมื่อเราเพิ่มตัวกำหนดค่าเริ่มต้นที่กำหนดเองของเราเองเราหยุดสืบทอดค่าเริ่มต้นที่กำหนดไว้ของ superclass (และตัวเริ่มต้นความสะดวกซึ่งไม่ได้ชี้ไปที่ตัวเริ่มต้นที่เราใช้)

ดังนั้นในฐานะที่เป็นตัวอย่างแบบง่าย ๆ ฉันขอนำเสนอ

class Foo {
    var foo: String
    init(foo: String) {
        self.foo = foo
    }
}

class Bar: Foo {
    var bar: String
    init(foo: String, bar: String) {
        self.bar = bar
        super.init(foo: foo)
    }
}


let x = Bar(foo: "Foo")

ซึ่งแสดงข้อผิดพลาดต่อไปนี้:

ไม่มีอาร์กิวเมนต์สำหรับพารามิเตอร์ 'bar' ในการโทร

ป้อนคำอธิบายรูปภาพที่นี่

ถ้านี่คือ Objective-C มันจะไม่มีปัญหาในการสืบทอด ถ้าเราเริ่มต้นBarกับinitWithFoo:ใน Objective-C ที่สถานที่ให้บริการก็จะself.bar nilมันอาจไม่ดี แต่มันเป็นความสมบูรณ์แบบที่ถูกต้องรัฐสำหรับวัตถุที่จะอยู่ใน. มันไม่ได้เป็นรัฐที่ถูกต้องสมบูรณ์สำหรับวัตถุสวิฟท์ที่จะอยู่ใน. ไม่ได้เป็นตัวเลือกและไม่สามารถself.barnil

อีกครั้งวิธีเดียวที่เรารับค่าเริ่มต้นคือการไม่ให้บริการของเราเอง ดังนั้นถ้าเราพยายามที่จะสืบทอดโดยการลบBarของinit(foo: String, bar: String)เช่น:

class Bar: Foo {
    var bar: String
}

ตอนนี้เรากลับไปรับมรดก (เรียงลำดับ) แต่สิ่งนี้จะไม่รวบรวม ... และข้อความแสดงข้อผิดพลาดอธิบายว่าทำไมเราไม่สืบทอดinitวิธีsuperclass :

ปัญหา: Class 'Bar' ไม่มี initializers

แก้ไขมัน:คุณสมบัติ 'บาร์' ที่เก็บไว้โดยไม่มี initializers ป้องกัน initializers สังเคราะห์

หากเราได้เพิ่มคุณสมบัติที่เก็บไว้ในคลาสย่อยของเราไม่มีวิธีที่รวดเร็วในการสร้างอินสแตนซ์ที่ถูกต้องของคลาสย่อยของเราด้วยตัวเริ่มต้น superclass ซึ่งอาจไม่รู้เกี่ยวกับคุณสมบัติที่เก็บไว้ของคลาสย่อยของเรา


โอเคทำไมฉันต้องใช้init(coder: NSCoder)เลย? ทำไมrequiredล่ะ

initวิธีการของ Swift อาจเล่นโดยกฎการสืบทอดชุดพิเศษ แต่ความสอดคล้องของโปรโตคอลยังคงสืบทอดมาจากสายโซ่ หากคลาสพาเรนต์เป็นไปตามโปรโตคอลคลาสย่อยนั้นจะต้องสอดคล้องกับโปรโตคอลนั้น

ตามปกติแล้วนี่ไม่ใช่ปัญหาเพราะโปรโตคอลส่วนใหญ่ต้องการเพียงวิธีการที่ไม่เล่นโดยกฎการรับมรดกพิเศษใน Swift ดังนั้นหากคุณสืบทอดจากคลาสที่สอดคล้องกับโปรโตคอลคุณก็จะได้รับมรดกทั้งหมดด้วย วิธีการหรือคุณสมบัติที่อนุญาตให้คลาสเพื่อตอบสนองความสอดคล้องกับโปรโตคอล

อย่างไรก็ตามโปรดจำไว้ว่าinitวิธีการของ Swift นั้นเล่นโดยกฎชุดพิเศษและไม่ได้รับการถ่ายทอดเสมอไป ด้วยเหตุนี้ในชั้นเรียนที่สอดคล้องกับโปรโตคอลซึ่งจะต้องมีพิเศษinitวิธีการ (เช่นNSCoding) ต้องว่าชั้นทำเครื่องหมายเหล่านั้นวิธีการเป็นinitrequired

ลองพิจารณาตัวอย่างนี้:

protocol InitProtocol {
    init(foo: Int)
}

class ConformingClass: InitProtocol {
    var foo: Int
    init(foo: Int) {
        self.foo = foo
    }
}

สิ่งนี้ไม่ได้รวบรวม มันสร้างคำเตือนต่อไปนี้:

ปัญหา:ความต้องการของเครื่องมือเริ่มต้น 'init (foo :)' สามารถทำได้โดยเครื่องมือกำหนดค่าเริ่มต้น 'ที่ต้องใช้' ใน ConformingClass ที่ไม่ใช่คลาสระดับสุดท้าย

แก้ไขมัน:ต้องใส่

มันต้องการให้ฉันทำinit(foo: Int)initializer ที่ต้องการ ฉันสามารถทำให้มีความสุขได้ด้วยการทำให้ชั้นเรียนfinal(ซึ่งหมายความว่าชั้นเรียนไม่สามารถสืบทอดได้)

แล้วจะเกิดอะไรขึ้นถ้าฉัน subclass จากจุดนี้ถ้าฉันคลาสย่อยฉันสบายดี ถ้าฉันจะเพิ่ม initializers ใด ๆ แม้ว่าผมจู่ ๆ init(foo:)ก็ไม่มีการสืบทอดอีกต่อไป InitProtocolนี่คือปัญหาเพราะตอนนี้ฉันไม่สอดคล้องกับ ฉันไม่สามารถคลาสย่อยจากคลาสที่สอดคล้องกับโพรโทคอลและจากนั้นก็ตัดสินใจว่าฉันไม่ต้องการที่จะปฏิบัติตามโพรโทคอลนั้นอีกต่อไป ฉันได้รับการสืบทอดโปรโตคอลที่สอดคล้องกัน แต่เนื่องจากวิธีการที่สวิฟท์ทำงานร่วมกับinitการสืบทอดวิธีการฉันจึงไม่ได้รับส่วนหนึ่งของสิ่งที่จำเป็นเพื่อให้สอดคล้องกับโปรโตคอลนั้นและฉันจะต้องใช้มัน


เอาล่ะทั้งหมดนี้สมเหตุสมผลแล้ว แต่ทำไมฉันไม่ได้รับข้อความแสดงข้อผิดพลาดที่เป็นประโยชน์มากกว่านี้?

เบบี้เกิดข้อผิดพลาดที่อาจจะมีความชัดเจนมากขึ้นหรือดีกว่าถ้ามันระบุว่าชั้นเรียนของคุณก็ไม่ได้เป็นไปตามสืบทอดโปรโตคอลและการที่จะแก้ไขได้คุณจำเป็นต้องใช้NSCoding init(coder: NSCoder)แน่ใจ

แต่ Xcode ไม่สามารถสร้างข้อความนั้นได้เพราะนั่นไม่ได้เป็นปัญหาจริงที่เกิดขึ้นกับการไม่ใช้หรือสืบทอดวิธีการที่จำเป็นเสมอไป มีเหตุผลอย่างน้อยหนึ่งเหตุผลในการทำinitวิธีการrequiredนอกเหนือจากความสอดคล้องของโปรโตคอลและนั่นคือวิธีการจากโรงงาน

ถ้าฉันต้องการเขียนวิธีการจากโรงงานที่เหมาะสมฉันต้องระบุประเภทการคืนสินค้าให้เป็นSelf(เทียบเท่ากับวัตถุประสงค์ของ C-Swift instanceType) แต่เพื่อที่จะทำสิ่งนี้ฉันต้องใช้requiredวิธีการเริ่มต้นจริง

class Box {
    var size: CGSize
    init(size: CGSize) {
        self.size = size
    }

    class func factory() -> Self {
        return self.init(size: CGSizeZero)
    }
}

สิ่งนี้สร้างข้อผิดพลาด:

การสร้างออบเจ็กต์ประเภทคลาส 'ตัวเอง' ด้วยค่าเมตาประเภทต้องใช้เครื่องมือเริ่มต้น 'จำเป็น'

ป้อนคำอธิบายรูปภาพที่นี่

มันเป็นปัญหาเดียวกัน ถ้าเรา subclass Box, subclasses factoryของเราจะได้รับมรดกวิธีการเรียน SubclassedBox.factory()ดังนั้นเราจึงสามารถเรียก อย่างไรก็ตามหากไม่มีrequiredคีย์เวิร์ดในinit(size:)เมธอดBoxคลาสย่อยจะไม่รับประกันว่าจะสืบทอดสิ่งself.init(size:)ที่factoryเรียก

ดังนั้นเราต้องทำวิธีนั้นrequiredถ้าเราต้องการวิธีการจากโรงงานเช่นนี้และนั่นหมายความว่าถ้าชั้นเรียนของเราใช้วิธีการเช่นนี้เราจะมีrequiredวิธีการเริ่มต้นและเราจะพบปัญหาเดียวกันกับที่คุณพบที่นี่ ด้วยNSCodingโปรโตคอล


ท้ายที่สุดแล้วมันก็ทำให้ความเข้าใจพื้นฐานที่ผู้เริ่มต้นของ Swift เล่นโดยกฎชุดการสืบทอดที่แตกต่างกันเล็กน้อยซึ่งหมายความว่าคุณไม่รับประกันว่าจะได้รับค่าเริ่มต้นจาก Superclass ของคุณ สิ่งนี้เกิดขึ้นเนื่องจาก initializer superclass ไม่สามารถรู้เกี่ยวกับคุณสมบัติใหม่ของคุณและพวกเขาไม่สามารถยกตัวอย่างวัตถุของคุณให้อยู่ในสถานะที่ถูกต้อง แต่ด้วยเหตุผลต่างๆเป็น superclass requiredอาจทำเครื่องหมายการเริ่มต้นเป็น เมื่อไรก็ตามที่เราทำเราสามารถใช้หนึ่งในสถานการณ์ที่เฉพาะเจาะจงโดยที่เราทำการสืบทอดrequiredวิธีการจริงหรือเราต้องดำเนินการเอง

ประเด็นหลักที่นี่คือถ้าเราได้รับข้อผิดพลาดที่คุณเห็นที่นี่นั่นหมายความว่าชั้นเรียนของคุณไม่ได้ใช้วิธีการทั้งหมด

อาจเป็นหนึ่งตัวอย่างสุดท้ายที่จะเจาะลึกในความจริงที่ว่าคลาสย่อย Swift ไม่ได้สืบทอดinitวิธีการของผู้ปกครองเสมอไป(ซึ่งฉันคิดว่าเป็นหัวใจสำคัญที่จะเข้าใจปัญหานี้อย่างสมบูรณ์) ลองพิจารณาตัวอย่างนี้:

class Foo {
    init(a: Int, b: Int, c: Int) {
        // do nothing
    }
}

class Bar: Foo {
    init(string: String) {
        super.init(a: 0, b: 1, c: 2)
        // do more nothing
    }
}

let f = Foo(a: 0, b: 1, c: 2)
let b = Bar(a: 0, b: 1, c: 2)

สิ่งนี้ล้มเหลวในการรวบรวม

ป้อนคำอธิบายรูปภาพที่นี่

ข้อความแสดงข้อผิดพลาดจะทำให้เข้าใจผิดเล็กน้อย:

อาร์กิวเมนต์พิเศษ 'b' อยู่ระหว่างการโทร

แต่ประเด็นก็คือBarไม่ได้รับมรดกใด ๆ ของFoo's initวิธีเพราะมันยังไม่ได้รับความพึงพอใจทั้งสองกรณีพิเศษสำหรับการสืบทอดinitวิธีการจากคลาสแม่ของมัน

หากนี่คือ Objective-C เราจะสืบทอดสิ่งนั้นinitโดยไม่มีปัญหาเพราะ Objective-C มีความสุขอย่างสมบูรณ์ที่จะไม่คืนค่าคุณสมบัติของวัตถุ (แม้ว่าในฐานะนักพัฒนาคุณไม่ควรมีความสุขกับสิ่งนี้) ใน Swift สิ่งนี้จะไม่ทำ คุณไม่สามารถมีสถานะที่ไม่ถูกต้องและการรับค่าเริ่มต้น superclass สามารถนำไปสู่สถานะวัตถุที่ไม่ถูกต้องเท่านั้น


คุณช่วยอธิบายความหมายของประโยคนี้หรือยกตัวอย่างได้ไหม? "(และ initializers ความสะดวกสบายซึ่งไม่ได้ชี้ไปที่ initializers ที่เราใช้งาน)"
Abbey Jackson

คำตอบที่ยอดเยี่ยม! ฉันต้องการมากขึ้นดังนั้นการโพสต์จะเกี่ยวกับเหตุผลเช่นนี้แทนที่จะเป็นเพียงวิธีการที่
Alexander Vasenin

56

เหตุใดปัญหานี้จึงเกิดขึ้น ความจริงธรรมดาก็คือมันมีความสำคัญเสมอ (เช่นใน Objective-C ตั้งแต่วันที่ฉันเริ่มเขียนโปรแกรม Cocoa กลับมาใน Mac OS X 10.0) เพื่อจัดการกับ initializers ที่ชั้นเรียนของคุณไม่พร้อมที่จะจัดการ เอกสารเหล่านี้ค่อนข้างชัดเจนเกี่ยวกับความรับผิดชอบของคุณในเรื่องนี้ แต่เรามีกี่คนที่อยากจะเติมเต็มพวกเขาอย่างสมบูรณ์และตามตัวอักษร? คงไม่มีพวกเรา! และคอมไพเลอร์ไม่บังคับใช้ มันเป็นเรื่องธรรมดาหมดจด

ตัวอย่างเช่นในคลาสย่อยคอนโทรลเลอร์มุมมอง Objective-C ของฉันด้วย initializer ที่กำหนดนี้:

- (instancetype) initWithCollection: (MPMediaItemCollection*) coll;

... มันเป็นสิ่งสำคัญที่เราจะต้องผ่านการรวบรวมรายการสื่อจริง: ตัวอย่างก็ไม่สามารถเกิดขึ้นได้หากไม่มี แต่ฉันไม่ได้เขียน "จุก" เพื่อป้องกันไม่ให้ใครบางคนเริ่มต้นฉันด้วยกระดูกเปลือยinitแทน ฉันควรจะเขียนหนึ่ง (จริง ๆ แล้วพูดอย่างถูกต้องฉันควรจะเขียนการดำเนินงานของinitWithNibName:bundle:initializer ที่กำหนดไว้ที่สืบทอด) แต่ฉันขี้เกียจเกินกว่าจะรำคาญเพราะฉัน "รู้" ฉันจะไม่เริ่มต้นชั้นเรียนของตัวเองอย่างไม่ถูกต้อง นี่ทำให้เกิดช่องโหว่ ใน Objective-C มีใครบางคนสามารถเรียกกระดูกเปลือยinitปล่อย ivars ของฉันไม่ได้เตรียมการและเราจะขึ้นห้วยโดยไม่ต้องพาย

สวิฟท์, มหัศจรรย์, บันทึกฉันจากตัวเองในกรณีส่วนใหญ่ ทันทีที่ฉันแปลแอพนี้เป็น Swift ปัญหาทั้งหมดก็หายไป Swift สร้างจุกสำหรับฉันอย่างมีประสิทธิภาพ! หากมีการกำหนดเพียงประกาศการเริ่มต้นในชั้นเรียนของฉันฉันไม่สามารถเริ่มต้นได้โดยการเรียกเปลือยกระดูกinit(collection:MPMediaItemCollection) init()มันเป็นปาฏิหาริย์!

สิ่งที่เกิดขึ้นในเมล็ดพันธุ์ที่ 5 เป็นเพียงการรวบรวมที่ได้ตระหนักว่าปาฏิหาริย์ไม่ทำงานในกรณีของ init(coder:)เพราะในทางทฤษฎีตัวอย่างของชั้นนี้อาจมาจากปลายปากกาและคอมไพเลอร์ไม่สามารถป้องกันได้ - และเมื่อ ปลายปากกาinit(coder:)จะถูกเรียก ดังนั้นคอมไพเลอร์ทำให้คุณเขียนสต๊อปเปอร์อย่างชัดเจน และค่อนข้างถูกด้วย


ขอบคุณสำหรับคำตอบอย่างละเอียด นี่นำความสว่างมาสู่เรื่อง
Julian Osorio

upvote to pasta12 สำหรับการบอกฉันถึงวิธีการทำให้คอมไพเลอร์ปิดตัวลง แต่เป็นการเพิ่มขึ้นของคุณเช่นกันสำหรับการพูดคุยกับฉันในสิ่งที่เกิดขึ้นในตอนแรก
Garrett Albright

2
อ้าปากค้างหรือไม่ฉันไม่เคยจะเรียกผู้เริ่มต้นนี้ดังนั้นจึงเป็นความผิดโดยสิ้นเชิงที่บังคับให้ฉันรวมมันเข้าด้วยกัน รหัสป่องเป็นค่าใช้จ่ายที่เราไม่ต้องการ ตอนนี้มันยังบังคับให้คุณเริ่มต้นคุณสมบัติของคุณทั้ง inits เช่นกัน ไม่มีจุดหมาย!
Dan Greenfield

5
@DanGreenfield ไม่มีก็ไม่ได้บังคับให้คุณเริ่มต้นอะไรเพราะถ้าคุณไม่เคยไปเรียกมันว่าคุณเพียงแค่ใส่ในfatalErrorอุดอธิบายไว้ในstackoverflow.com/a/25128815/341994 เพียงแค่ทำให้มันเป็น Code Snippet ของผู้ใช้และจากนี้ไปคุณสามารถเสียบเข้าไปในจุดที่ต้องการได้ ใช้เวลาครึ่งวินาที
แมตต์

1
@ nhgrif ดีที่จะเป็นธรรมคำถามไม่ได้ถามเรื่องหลังเต็ม มันเป็นเพียงวิธีการออกจากแยมนี้และไปต่อ เรื่องเต็มจะได้รับในหนังสือของฉัน: apeth.com/swiftBook/ch04.html#_class_initializers
แมตต์

33

เพิ่ม

required init(coder aDecoder: NSCoder!) {
  super.init(coder: aDecoder)
}

3
ใช้งานได้ แต่ฉันไม่คิดว่ามันเป็นข้อผิดพลาด initializers ไม่ได้รับการสืบทอดอย่างรวดเร็ว (เมื่อมีการประกาศตัวเริ่มต้นของคุณเอง) และสิ่งนี้จะถูกทำเครื่องหมายด้วยคำสำคัญที่ต้องการ ปัญหาเดียวก็คือตอนนี้ฉันต้องเริ่มต้นคุณสมบัติทั้งหมดของฉันในวิธีนี้สำหรับแต่ละคลาสของฉันซึ่งจะเป็นรหัสที่สูญเปล่ามากเพราะฉันไม่ได้ใช้สิ่งนี้เลย หรือฉันจะต้องประกาศคุณสมบัติทั้งหมดของฉันเป็นประเภททางเลือกที่ไม่ได้เปิดใช้โดยปริยายเพื่อข้ามการเริ่มต้นซึ่งฉันไม่ต้องการทำเช่นนั้น
Epic Byte

1
ได้! ฉันรู้ทันทีหลังจากพูดว่าอาจเป็นข้อผิดพลาดซึ่งจริง ๆ แล้วสมเหตุสมผล ฉันยอมรับว่ามันจะเป็นรหัสที่สูญเปล่าไปมากเพราะอย่างที่คุณไม่เคยใช้วิธีการเริ่มต้นนี้ ยังไม่แน่ใจเกี่ยวกับทางออกที่สง่างาม
กาแกนซิงห์

2
ฉันมีปัญหาเดียวกัน มันสมเหตุสมผลกับ "init init ที่จำเป็น" แต่ความรวดเร็วไม่ใช่ภาษาที่ "ง่าย" ที่ฉันหวังไว้ "ตัวเลือก" เหล่านี้ทำให้ภาษามีความซับซ้อนเกินความจำเป็น และไม่รองรับ DSL และ AOP ฉันผิดหวังมากขึ้นเรื่อย ๆ
user810395

2
ใช่ฉันเห็นด้วยอย่างยิ่ง คุณสมบัติของฉันมากมายถูกประกาศเป็นตัวเลือกเนื่องจากฉันถูกบังคับให้ทำเมื่อจริง ๆ แล้วพวกเขาไม่ควรได้รับอนุญาตให้เป็นศูนย์ บางตัวเป็นตัวเลือกเพราะพวกเขาควรจะเป็นตัวเลือกที่ถูกต้อง (หมายถึงไม่มีค่าที่ถูกต้อง) และในชั้นเรียนที่ฉันไม่ได้เป็นคลาสย่อยฉันไม่จำเป็นต้องใช้ตัวเลือกดังนั้นสิ่งต่าง ๆ มีความซับซ้อนมากและฉันไม่สามารถหารูปแบบการเข้ารหัสที่ถูกต้องได้ หวังว่า Apple จะเข้าใจบางสิ่ง
Epic Byte

5
ฉันคิดว่าพวกเขาหมายความว่าคุณสามารถสร้างความพึงพอใจให้กับ initializer ที่ต้องการได้โดยไม่ต้องประกาศ initializer ใด ๆ ของคุณเองซึ่งจะส่งผลให้ initializer ทั้งหมดได้รับการสืบทอด
Epic Byte
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.