Kesalahan di kelas Swift: Properti tidak diinisialisasi pada panggilan super.init

218

Saya punya dua kelas, ShapedanSquare

class Shape {
    var numberOfSides = 0
    var name: String
    init(name:String) {
        self.name = name
    }
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

class Square: Shape {
    var sideLength: Double

    init(sideLength:Double, name:String) {
        super.init(name:name) // Error here
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Dengan implementasi di atas saya mendapatkan kesalahan:

property 'self.sideLength' not initialized at super.init call
    super.init(name:name)

Mengapa saya harus mengatur self.sideLengthsebelum menelepon super.init?

JuJoDi
sumber
cukup yakin ini ada hubungannya dengan praktik pemrograman yang baik, bukan pembatasan teknis yang sebenarnya. Jika Shape memanggil fungsi yang ditimpa Square, Square mungkin ingin menggunakan sideLength, tetapi belum diinisialisasi. Swift mungkin hanya membatasi Anda dari melakukan hal ini secara tidak sengaja dengan memaksa Anda untuk menginisialisasi instance Anda terlebih dahulu sebelum memanggil kelas dasar.
cwharris
3
Contoh-contoh dalam buku ini buruk. Anda seharusnya selalu memanggil super.init () terakhir untuk memastikan bahwa semua properti telah diinisialisasi, dijelaskan di bawah.
Pascal

Jawaban:

173

Kutipan dari Bahasa Pemrograman Swift, yang menjawab pertanyaan Anda:

"Kompiler Swift melakukan empat pemeriksaan keamanan yang membantu untuk memastikan bahwa inisialisasi dua fase selesai tanpa kesalahan:"

Pemeriksaan keamanan 1 "Penginisialisasi yang ditunjuk harus memastikan bahwa semua" properti yang diperkenalkan oleh kelasnya diinisialisasi sebelum didelegasikan hingga penginisialisasi superclass. "

Kutipan dari: Apple Inc. “Bahasa Pemrograman Swift.” iBooks. https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11

Ruben
sumber
47
Nah, itu perubahan yang mengejutkan dibandingkan dengan C ++, C # atau Java.
MDJ
12
@MDJ Tentu. Jujur saya juga tidak melihat nilai tambahnya.
Ruben
20
Sebenarnya sepertinya ada satu. Katakanlah dalam C #, konstruktor superclass seharusnya tidak memanggil metode (virtual) yang dapat ditimpa, karena tidak ada yang tahu bagaimana mereka akan bereaksi dengan subkelas yang tidak sepenuhnya diinisialisasi. Di Swift tidak apa-apa, karena keadaan tambahan subkelas baik-baik saja, ketika konstruktor superclass sedang berjalan. Selain itu, di Swift, semua kecuali metode final dapat ditimpa.
MDJ
17
Saya khususnya merasa menjengkelkan karena itu berarti, jika saya ingin membuat subkelas UIView yang membuat subview sendiri, saya harus menginisialisasi subview tersebut tanpa bingkai untuk memulai dan menambahkan frame kemudian karena saya tidak dapat merujuk pada tampilan. batas hingga SETELAH memanggil super.init.
Ash
5
@ Joan jika Anda menjadikan properti ini opsional, Anda tidak harus menginisialisasi init.
JeremyP
105

Swift memiliki urutan operasi yang sangat jelas dan spesifik yang dilakukan pada inisialisasi. Mari kita mulai dengan beberapa contoh dasar dan menyelesaikan kasus umum.

Mari kita ambil objek A. Kita akan mendefinisikannya sebagai berikut.

class A {
    var x: Int
    init(x: Int) {
        self.x = x
    }
}

Perhatikan bahwa A tidak memiliki superclass, sehingga tidak dapat memanggil fungsi super.init () karena tidak ada.

OK, jadi sekarang mari kita subkelas A dengan kelas baru bernama B.

class B: A {
    var y: Int
    init(x: Int, y: Int) {
        self.y = y
        super.init(x: x)
    }
}

Ini adalah keberangkatan dari Objective-C di mana [super init]biasanya akan dipanggil dulu sebelum yang lainnya. Tidak demikian halnya dengan Swift. Anda bertanggung jawab untuk memastikan bahwa variabel instan Anda dalam keadaan konsisten sebelum Anda melakukan hal lain, termasuk metode pemanggilan (yang mencakup penginisialisasi superclass Anda).

Hitendra Solanki
sumber
1
ini sangat membantu, contoh yang jelas. Terima kasih!
FullMetalFist
Bagaimana jika saya perlu nilai untuk menghitung y, misalnya: init (y: Int) {self.y = y * self.x super.init ()}
6rod9
1
gunakan sesuatu seperti, init (y: Int, x: Int = 0) {self.y = y * x; self.x = x; super.init (x: x)}, Anda juga tidak dapat langsung memanggil konstruktor kosong untuk kelas super dengan merujuk pada contoh di atas, karena nama kelas super A tidak memiliki konstruktor kosong
Hitendra Solanki
43

Dari dokumen

Pemeriksaan keamanan 1

Penginisialisasi yang ditunjuk harus memastikan bahwa semua properti yang diperkenalkan oleh kelasnya diinisialisasi sebelum didelegasikan ke penginisialisasi superclass.


Mengapa kita perlu pemeriksaan keamanan seperti ini?

Untuk menjawab ini mari kita lanjutkan proses inisialisasi dalam cepat.

Inisialisasi Dua Fase

Inisialisasi kelas di Swift adalah proses dua fase. Pada fase pertama, setiap properti yang disimpan diberi nilai awal oleh kelas yang memperkenalkannya. Setelah keadaan awal untuk setiap properti yang disimpan telah ditentukan, fase kedua dimulai, dan setiap kelas diberi kesempatan untuk menyesuaikan properti yang disimpannya lebih lanjut sebelum instance baru dianggap siap untuk digunakan.

Penggunaan proses inisialisasi dua fase membuat inisialisasi aman, sambil tetap memberikan fleksibilitas lengkap untuk setiap kelas dalam hirarki kelas. Inisialisasi dua fase mencegah nilai properti diakses sebelum mereka diinisialisasi , dan mencegah nilai properti diatur ke nilai yang berbeda oleh penginisialisasi lain secara tak terduga.

Jadi, untuk memastikan proses inisialisasi dua langkah dilakukan sebagaimana didefinisikan di atas, ada empat pemeriksaan keamanan, salah satunya adalah,

Pemeriksaan keamanan 1

Penginisialisasi yang ditunjuk harus memastikan bahwa semua properti yang diperkenalkan oleh kelasnya diinisialisasi sebelum didelegasikan ke penginisialisasi superclass.

Sekarang, inisialisasi dua fase tidak pernah berbicara tentang pesanan, tetapi pemeriksaan keamanan ini, diperkenalkan super.inituntuk dipesan, setelah inisialisasi semua properti.

Pemeriksaan keamanan 1 mungkin tampak tidak relevan karena, inisialisasi dua fase mencegah nilai properti diakses sebelum diinisialisasi dapat dipenuhi, tanpa pemeriksaan keamanan ini 1.

Seperti dalam sampel ini

class Shape {
    var name: String
    var sides : Int
    init(sides:Int, named: String) {
        self.sides = sides
        self.name = named
    }
}

class Triangle: Shape {
    var hypotenuse: Int
    init(hypotenuse:Int) {
        super.init(sides: 3, named: "Triangle") 
        self.hypotenuse = hypotenuse
    }
}

Triangle.inittelah diinisialisasi, setiap properti sebelum digunakan. Jadi pemeriksaan Keselamatan 1 tampaknya tidak relevan,

Tapi kemudian mungkin ada skenario lain, sedikit rumit,

class Shape {
    var name: String
    var sides : Int
    init(sides:Int, named: String) {
        self.sides = sides
        self.name = named
        printShapeDescription()
    }
    func printShapeDescription() {
        print("Shape Name :\(self.name)")
        print("Sides :\(self.sides)")
    }
}

class Triangle: Shape {
    var hypotenuse: Int
    init(hypotenuse:Int) {
        self.hypotenuse = hypotenuse
        super.init(sides: 3, named: "Triangle")
    }

    override func printShapeDescription() {
        super.printShapeDescription()
        print("Hypotenuse :\(self.hypotenuse)")
    }
}

let triangle = Triangle(hypotenuse: 12)

Output:

Shape Name :Triangle
Sides :3
Hypotenuse :12

Di sini jika kita telah memanggil super.initpengaturan sebelumnya hypotenuse, super.initpanggilan itu akan memanggil printShapeDescription()dan karena itu telah ditimpa akan pertama kali mundur ke implementasi kelas Segitiga printShapeDescription(). Kelas printShapeDescription()Triangle mengakses hypotenuseproperti non-opsional yang masih belum diinisialisasi. Dan ini tidak diperbolehkan karena inisialisasi dua fase mencegah nilai properti diakses sebelum diinisialisasi

Jadi pastikan inisialisasi Dua fase dilakukan seperti yang didefinisikan, perlu ada urutan panggilan tertentu super.init, dan itu adalah, setelah menginisialisasi semua properti yang diperkenalkan oleh selfkelas, maka kita perlu pemeriksaan Keselamatan 1

BangOperator
sumber
1
Penjelasan yang bagus, mengapa harus ditambahkan pada jawaban teratas.
Guy Daher
Jadi pada dasarnya Anda mengatakan, karena superclass's init dapat memanggil fungsi (overriden) ... di mana fungsi itu mengakses properti subclass, maka untuk menghindari tidak memiliki nilai yang ditetapkan, panggilan untuk superharus terjadi setelah semua nilai ditetapkan. OK masuk akal. Bertanya-tanya bagaimana Objective-C melakukannya saat itu dan mengapa Anda harus menelepon superdulu?
Sayang
Pada dasarnya apa yang Anda menunjuk ke adalah sama dengan: menempatkan printShapeDescription() sebelum self.sides = sides; self.name = named; yang akan menghasilkan kesalahan ini: use of 'self' in method call 'printShapeDescription' before all stored properties are initialized. Kesalahan OP diberikan untuk mengurangi 'kemungkinan' kesalahan runtime.
Sayang
Saya secara khusus menggunakan kata 'kemungkinan', karena jika printShapeDescriptionfungsi yang tidak merujuk selfyaitu yaitu sesuatu seperti `cetak (" tidak ada ") maka tidak akan ada masalah. (Namun bahkan untuk itu kompiler akan membuat kesalahan, karena itu tidak super pintar)
Honey
Well, objc tidak aman. Swift adalah tipe aman sehingga objek yang bukan opsional benar-benar harus nihil!
Daij-Djan
36

"Super.init ()" harus dipanggil setelah Anda menginisialisasi semua variabel instan Anda.

Dalam video "Intermediate Swift" Apple (Anda dapat menemukannya di halaman sumber video Pengembang Apple https://developer.apple.com/videos/wwdc/2014/ ), sekitar 28:40, secara eksplisit dikatakan bahwa semua inisialisasi di kelas super harus disebut SETELAH Anda menginisialisasi variabel instan Anda.

Di Objective-C, itu kebalikannya. Di Swift, karena semua properti harus diinisialisasi sebelum digunakan, kita perlu menginisialisasi properti terlebih dahulu. Ini dimaksudkan untuk mencegah panggilan ke fungsi utama dari metode "init ()" kelas super, tanpa menginisialisasi properti terlebih dahulu.

Jadi implementasi "Square" harus:

class Square: Shape {
    var sideLength: Double

    init(sideLength:Double, name:String) {
        self.sideLength = sideLength
        numberOfSides = 4
        super.init(name:name) // Correct position for "super.init()"
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}
Shuyang
sumber
1
Tidak akan pernah menduga. Inisialisasi dengan super harus menjadi pernyataan pertama yang akan saya pikirkan. !! hm perubahan cukup cepat.
mitos pembuat
Mengapa ini perlu dicari? Harap berikan alasan teknis
Masih
14

Maaf untuk pemformatan jelek. Cukup masukkan karakter pertanyaan setelah deklarasi dan semuanya akan beres. Sebuah pertanyaan memberi tahu kompiler bahwa nilainya opsional.

class Square: Shape {
    var sideLength: Double?   // <=== like this ..

    init(sideLength:Double, name:String) {
        super.init(name:name) // Error here
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Sunting1:

Ada cara yang lebih baik untuk melewati kesalahan ini. Menurut komentar jmaschad, tidak ada alasan untuk menggunakan opsional dalam kasus Anda karena opsional tidak nyaman digunakan dan Anda selalu harus memeriksa apakah opsional tidak nol sebelum mengaksesnya. Jadi yang harus Anda lakukan adalah menginisialisasi anggota setelah deklarasi:

class Square: Shape {
    var sideLength: Double=Double()   

    init(sideLength:Double, name:String) {
        super.init(name:name)
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Sunting2:

Setelah dua minus mendapat jawaban ini saya menemukan cara yang lebih baik. Jika Anda ingin anggota kelas diinisialisasi dalam konstruktor Anda, Anda harus menetapkan nilai awal untuk itu di dalam konstruktor dan sebelum super.init () panggilan. Seperti ini:

class Square: Shape {
    var sideLength: Double  

    init(sideLength:Double, name:String) {
        self.sideLength = sideLength   // <= before super.init call..
        super.init(name:name)
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Semoga beruntung dalam belajar Swift.

f12
sumber
Beralih saja super.init(name:name)dan self.sideLength = sideLength. Mendeklarasikan sideLengthsebagai opsional adalah salah arah dan memperkenalkan kerumitan tambahan nanti saat Anda harus memaksanya membuka bungkusannya.
Johannes Luong
Ya, ini pilihan. Terima kasih
fnc12
Anda sebenarnya bisa saja memiliki var sideLength: Double, tidak perlu menetapkannya nilai awal
Jarsen
Bagaimana jika saya benar-benar memiliki konstanta opsional. Apa yang harus saya lakukan dengannya? Apakah saya harus menginisialisasi dalam konstruktor? Saya tidak mengerti mengapa Anda harus melakukannya, tetapi kompilernya mengeluh di Swift 1.2
Van Du Tran
1
Sempurna! semua 3 solusi berhasil "?", "String ()", tetapi masalah bagi saya adalah saya belum 'menugaskan' salah satu properti, dan ketika saya melakukannya itu berhasil! Terima kasih sobat
Naishta
9

swift memaksa Anda untuk menginisialisasi setiap anggota sebelum itu pernah / mungkin pernah digunakan. Karena tidak dapat memastikan apa yang terjadi ketika giliran makan malam berubah, itu kesalahan: lebih baik aman daripada menyesal

Daij-Djan
sumber
1
Ini tidak masuk akal IMO karena kelas induk seharusnya tidak memiliki visibilitas ke properti yang dinyatakan dalam anak-anak itu!
Andy Hin
1
Tidak, tetapi Anda dapat mengganti hal-hal dan mulai menggunakan diri 'sebelum super berhasil'
Daij-Djan
Bisakah Anda melihat di sini dan komentar yang mengikutinya? Saya pikir saya mengatakan persis apa yang Anda katakan yaitu kita berdua mengatakan kompiler ingin aman daripada maaf, satu-satunya pertanyaan saya adalah, jadi bagaimana tujuan-c menyelesaikan masalah ini? Atau tidak? Jika tidak, mengapa masih mengharuskan Anda untuk menulis super.initdi baris pertama?
Sayang
7

Edward,

Anda dapat mengubah kode dalam contoh Anda seperti ini:

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}

Ini menggunakan opsional yang terbuka secara implisit.

Dalam dokumentasi kita dapat membaca:

"Seperti halnya opsional, jika Anda tidak memberikan nilai awal ketika Anda mendeklarasikan variabel opsional atau properti opsional yang terbuka, nilainya secara otomatis default menjadi nihil."

Pavel Gubarev
sumber
Saya pikir ini adalah opsi terbersih. Dalam upaya Swift pertama saya, saya memiliki variabel anggota jenis AVCaptureDevice, yang tidak dapat di-instantiate secara langsung, sehingga diperlukan kode init (). Namun, ViewController memerlukan beberapa inisialisasi, dan Anda tidak dapat memanggil metode inisialisasi umum dari init (), jadi jawaban ini tampaknya menjadi satu-satunya pilihan yang menghindari menyalin / menempel kode duplikat di setiap inisialisasi.
suneto
6

Swift tidak akan memungkinkan Anda untuk menginisialisasi kelas super tanpa menginisialisasi properti, kebalikan dari Obj C. Jadi Anda harus menginisialisasi semua properti sebelum memanggil "super.init".

Silakan buka http://blog.scottlogic.com/2014/11/20/swift-initialisation.html . Ini memberikan penjelasan yang bagus untuk masalah Anda.

jishnu bala
sumber
6

Tambahkan nil ke akhir deklarasi.


// Must be nil or swift complains
var someProtocol:SomeProtocol? = nil

// Init the view
override init(frame: CGRect)
    super.init(frame: frame)
    ...

Ini berfungsi untuk kasus saya, tetapi mungkin tidak bekerja untuk Anda

Michael
sumber
Bagus, saat menggunakan dengan UIView dengan UIViewController dengan protokol
abdul sathar
1

Anda hanya memulai dengan urutan yang salah.

     class Shape2 {
        var numberOfSides = 0
        var name: String
        init(name:String) {
            self.name = name
        }
        func simpleDescription() -> String {
            return "A shape with \(numberOfSides) sides."
        }
    }

    class Square2: Shape2 {
        var sideLength: Double

        init(sideLength:Double, name:String) {

            self.sideLength = sideLength
            super.init(name:name) // It should be behind "self.sideLength = sideLength"
            numberOfSides = 4
        }
        func area () -> Double {
            return sideLength * sideLength
        }
    }
ylgwhyh
sumber
0

Saya akan menerima beberapa downvotes mungkin, tetapi jujur, hidup lebih mudah dengan cara ini:

class CSListServerData<ListItem: CSJsonData>: CSServerData {
    var key: String!
    var type: ListItem.Type!
    var property: CSJsonDataList<ListItem>!

    func construct(_ key: String, _ type: ListItem.Type) -> Self {
        self.key = key
        self.type = type
        property = CSJsonDataList(self, type, key)
        return self
    }

    func construct(_ type: ListItem.Type) { construct("list", type) }

    var list: [ListItem] { property.list }
}
Renetik
sumber
-4

Ini adalah desain yang sangat bodoh.

Pertimbangkan sesuatu seperti ini:

.
.
.
var playerShip:PlayerShip
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100))
    playerLayerNode.addChild(playerShip)        
}
.
.
.

Ini tidak valid, seperti disebutkan di atas. Tapi begitu juga:

.
.
.
var playerShip:PlayerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100))
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}
.
.
.

Karena 'diri' belum diinisialisasi.

Saya sangat berharap bug ini segera diperbaiki.

(Ya saya tahu saya bisa membuat objek kosong dan kemudian mengatur ukuran tapi itu hanya bodoh).

Edward Kenworthy
sumber
2
Ini bukan bug, dasar pemrograman baru dari OOP.
Hitendra Solanki