Mengapa kata kunci kenyamanan bahkan diperlukan di Swift?

132

Karena Swift mendukung metode dan initializer overloading, Anda dapat menempatkan beberapa di initsamping satu sama lain dan menggunakan mana pun yang Anda anggap nyaman:

class Person {
    var name:String

    init(name: String) {
        self.name = name
    }

    init() {
        self.name = "John"
    }
}

Jadi mengapa conveniencekata kunci bahkan ada? Apa yang membuat yang berikut ini jauh lebih baik?

class Person {
    var name:String

    init(name: String) {
        self.name = name
    }

    convenience init() {
        self.init(name: "John")
    }
}
Desmond Hume
sumber
13
Baru saja membaca ini dalam dokumentasi dan bingung juga. : /
boidkan

Jawaban:

235

Jawaban yang ada hanya menceritakan setengah dari conveniencecerita. Separuh cerita yang lain, separuh dari tidak ada jawaban yang ada, menjawab pertanyaan yang telah diposting Desmond dalam komentar:

Mengapa Swift memaksa saya untuk meletakkan conveniencedi depan inisialisasi saya hanya karena saya perlu menelepon self.initdari itu? `

Saya menyentuhnya sedikit dalam jawaban ini , di mana saya membahas beberapa aturan inisialisasi Swift secara detail, tetapi fokus utama ada pada requiredkata tersebut. Tetapi jawaban itu masih menyinggung sesuatu yang relevan dengan pertanyaan ini dan jawaban ini. Kita harus memahami cara kerja pewarisan initializer Swift.

Karena Swift tidak memungkinkan untuk variabel yang tidak diinisialisasi, Anda tidak dijamin akan mewarisi semua (atau apa pun) inisialisasi dari kelas yang Anda warisi. Jika kami subkelas dan menambahkan variabel instance yang tidak diinisialisasi ke dalam subkelas kami, kami telah berhenti mewarisi inisialisasi. Dan sampai kita menambahkan inisialisasi kita sendiri, kompiler akan meneriaki kita.

Agar lebih jelas, variabel instance yang tidak diinisialisasi adalah variabel instance apa saja yang tidak diberi nilai default (dengan mengingat bahwa opsional dan opsional yang tidak terbuka secara otomatis mengasumsikan nilai default nil).

Jadi dalam hal ini:

class Foo {
    var a: Int
}

aadalah variabel instance tidak diinisialisasi. Ini tidak akan dikompilasi kecuali kami memberikan anilai default:

class Foo {
    var a: Int = 0
}

atau inisialisasi adalam metode penginisialisasi:

class Foo {
    var a: Int

    init(a: Int) {
        self.a = a
    }
}

Sekarang, mari kita lihat apa yang terjadi jika kita subkelas Foo, ya?

class Bar: Foo {
    var b: Int

    init(a: Int, b: Int) {
        self.b = b
        super.init(a: a)
    }
}

Baik? Kami menambahkan variabel, dan kami menambahkan penginisialisasi untuk menetapkan nilai bsehingga akan dikompilasi. Bergantung pada bahasa apa Anda berasal, Anda mungkin berharap bahwa inisialisasi Barbawaan Foo,, init(a: Int). Tapi ternyata tidak. Dan bagaimana mungkin? Bagaimana Foo's init(a: Int)know bagaimana menetapkan nilai ke bvariabel yang Barditambahkan? Tidak. Jadi kita tidak dapat menginisialisasi aBar instance dengan penginisialisasi yang tidak dapat menginisialisasi semua nilai kami.

Apa hubungannya semua ini dengan convenience ?

Baiklah, mari kita lihat aturan tentang pewarisan initializer :

Aturan 1

Jika subkelas Anda tidak menentukan inisialisasi yang ditunjuk, subisialisasi Anda secara otomatis akan mewarisi semua inisialisasi yang ditentukan oleh superclass.

Aturan 2

Jika subclass Anda memberikan implementasi dari semua inisialisasi yang ditentukan oleh superclass-baik dengan mewarisinya sesuai aturan 1, atau dengan memberikan implementasi kustom sebagai bagian dari definisi-maka secara otomatis mewarisi semua inisialisasi kenyamanan superclass.

Perhatikan Aturan 2, yang menyebutkan inisialisasi kenyamanan.

Jadi apa yang conveniencekata kunci yang lakukan yaitu menunjukkan kepada kita yang initializers dapat diwariskan oleh subclass bahwa variabel add misalnya tanpa nilai default.

Mari kita ambil contoh Basekelas ini:

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)
    }
}

Perhatikan kita memiliki tiga convenience inisialisasi di sini. Itu berarti kami memiliki tiga inisialisasi yang dapat diwariskan. Dan kami memiliki satu penginisialisasi yang ditunjuk (penginisialisasi yang ditunjuk hanyalah penginisialisasi yang bukan penginisialisasi kenyamanan).

Kami dapat membuat instance instance dari kelas dasar dalam empat cara berbeda:

masukkan deskripsi gambar di sini

Jadi, mari kita membuat subkelas.

class NonInheritor: Base {
    let c: Int

    init(a: Int, b: Int, c: Int) {
        self.c = c
        super.init(a: a, b: b)
    }
}

Kami mewarisi dari Base. Kami menambahkan variabel instan kami sendiri dan kami tidak memberikan nilai default, jadi kami harus menambahkan inisialisasi kami sendiri. Kami menambahkan satu, init(a: Int, b: Int, c: Int)tapi itu tidak cocok dengan tanda tangan dari Basekelas yang ditunjuk initializer: init(a: Int, b: Int). Itu berarti, kita tidak mewarisi apapun initializers dari Base:

masukkan deskripsi gambar di sini

Jadi, apa yang akan terjadi jika kami mewarisi dari Base, tetapi kami melanjutkan dan menerapkan penginisialisasi yang cocok dengan penginisialisasi yang ditunjuk dari 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)
    }
}

Sekarang, selain dua inisialisasi yang kami terapkan langsung di kelas ini, karena kami menerapkan inisialisasi Basekelas pencocokan yang ditunjuk oleh initializer, kami harus mewarisi semua inisialisasi Basekelas convenience:

masukkan deskripsi gambar di sini

Fakta bahwa penginisialisasi dengan tanda tangan yang cocok ditandai sebagai conveniencetidak membuat perbedaan di sini. Ini hanya berarti bahwa Inheritorhanya memiliki satu inisialisasi yang ditunjuk. Jadi jika kita mewarisi dari Inheritor, kita hanya perlu menerapkan satu penginisialisasi yang ditunjuk, dan kemudian kita akan mewarisi Inheritorpenginisialisasi kenyamanan, yang pada gilirannya berarti kita telah menerapkan semua Baseinisialisasi yang ditunjuk dan dapat mewarisi convenienceinisialisasi.

nhgrif
sumber
16
Satu-satunya jawaban yang benar-benar menjawab pertanyaan dan mengikuti dokumen. Saya akan menerimanya jika saya adalah OP.
FreeNickname
12
Anda harus menulis buku;)
coolbeet
1
@SLN Jawaban ini mencakup banyak tentang cara kerja pewarisan initializer Swift.
nhgrif
1
@SLN Karena membuat Bar dengan init(a: Int)akan meninggalkan bdiinisialisasi.
Ian Warburton
2
@ IanWarburton Saya tidak tahu jawaban untuk "mengapa" ini. Logika Anda di bagian kedua dari komentar Anda tampaknya masuk akal bagi saya, tetapi dokumentasinya dengan jelas menyatakan bahwa ini adalah cara kerjanya, dan memunculkan contoh tentang apa yang Anda tanyakan di Playground menegaskan perilaku yang cocok dengan apa yang didokumentasikan.
nhgrif
9

Sebagian besar kejelasan. Dari Anda contoh kedua,

init(name: String) {
    self.name = name
}

diperlukan atau ditunjuk . Itu harus menginisialisasi semua konstanta dan variabel Anda. Inisialisasi kenyamanan adalah opsional, dan biasanya dapat digunakan untuk mempermudah inisialisasi. Misalnya, katakanlah kelas Person Anda memiliki jenis kelamin variabel opsional:

var gender: Gender?

dimana Gender adalah enum

enum Gender {
  case Male, Female
}

Anda bisa mendapatkan inisialisasi yang mudah seperti ini

convenience init(maleWithName: String) {
   self.init(name: name)
   gender = .Male
}

convenience init(femaleWithName: String) {
   self.init(name: name)
   gender = .Female
}

Inisialisasi kenyamanan harus memanggil inisialisasi yang ditunjuk atau diminta di dalamnya. Jika kelas Anda adalah subclass, ia harus memanggil super.init() inisialisasi itu.

Nate Mann
sumber
2
Jadi akan sangat jelas bagi kompiler apa yang saya coba lakukan dengan beberapa inisialisasi bahkan tanpa conveniencekata kunci tetapi Swift masih akan mengganggunya. Itu bukan jenis kesederhanaan yang saya harapkan dari Apple =)
Desmond Hume
2
Jawaban ini tidak menjawab apa pun. Anda mengatakan "kejelasan", tetapi tidak menjelaskan bagaimana itu membuat sesuatu lebih jelas.
Robo Robok
7

Nah, hal pertama yang terlintas dalam pikiran saya adalah bahwa ini digunakan dalam pewarisan kelas untuk organisasi kode dan keterbacaan. Melanjutkan Personkelas Anda , pikirkan skenario seperti ini

class Person{
    var name: String
    init(name: String){
        self.name = name
    }

    convenience init(){
        self.init(name: "Unknown")
    }
}


class Employee: Person{
    var salary: Double
    init(name:String, salary:Double){
        self.salary = salary
        super.init(name: name)
    }

    override convenience init(name: String) {
        self.init(name:name, salary: 0)
    }
}

let employee1 = Employee() // {{name "Unknown"} salary 0}
let john = Employee(name: "John") // {{name "John"} salary 0}
let jane = Employee(name: "Jane", salary: 700) // {{name "Jane"} salary 700}

Dengan initializer kenyamanan saya dapat membuat Employee()objek tanpa nilai, karenanya kataconvenience

u54r
sumber
2
Dengan conveniencekata kunci diambil, bukankah Swift mendapatkan informasi yang cukup untuk berperilaku dengan cara yang persis sama?
Desmond Hume
Tidak, jika Anda mengambil conveniencekata kunci, Anda tidak dapat menginisialisasi Employeeobjek tanpa argumen.
u54r
Secara khusus, panggilan Employee()panggilan conveniencepenginisialisasi (yang diwarisi, karena ) init(), yang memanggil self.init(name: "Unknown"). init(name: String), juga penginisialisasi kenyamanan untuk Employee, memanggil penginisialisasi yang ditunjuk.
BallpointBen
1

Terlepas dari poin yang dijelaskan pengguna lain di sini adalah sedikit pemahaman saya.

Saya sangat merasakan hubungan antara penginisialisasi kenyamanan dan ekstensi. Bagi saya inisialisasi kenyamanan paling berguna ketika saya ingin memodifikasi (dalam banyak kasus membuatnya pendek atau mudah) inisialisasi kelas yang ada.

Misalnya beberapa kelas pihak ketiga yang Anda gunakan memiliki initdengan empat parameter tetapi dalam aplikasi Anda dua yang terakhir memiliki nilai yang sama. Untuk menghindari lebih banyak mengetik dan membuat kode Anda bersih, Anda bisa mendefinisikan convenience initdengan hanya dua parameter dan di dalamnya memanggil self.initdengan terakhir ke parameter dengan nilai default.

Abdullah
sumber
1
Mengapa Swift memaksa saya untuk meletakkan conveniencedi depan inisialisasi saya hanya karena saya perlu menelepon self.initdari itu? Ini sepertinya berlebihan dan agak tidak nyaman.
Desmond Hume
1

Menurut dokumentasi Swift 2.1 , convenienceinisialisasi harus mematuhi beberapa aturan khusus:

  1. Sebuah convenienceinitializer hanya dapat memanggil intializers di kelas yang sama, tidak di kelas super (hanya di seberang, tidak sampai)

  2. Sebuah convenienceinitializer harus memanggil ditunjuk initializer di suatu tempat dalam rantai

  3. Sebuah convenienceinitializer tidak dapat mengubah APAPUN properti sebelum itu telah disebut initializer lain - sedangkan initializer yang ditunjuk harus menginisialisasi properti yang diperkenalkan oleh kelas saat sebelum memanggil initializer lain.

Dengan menggunakan conveniencekata kunci, kompiler Swift tahu bahwa ia harus memeriksa kondisi ini - jika tidak maka tidak akan bisa.

Mata
sumber
Bisa dibilang, kompiler mungkin bisa menyelesaikan masalah ini tanpa conveniencekata kunci.
nhgrif
Selain itu, poin ketiga Anda menyesatkan. Penginisialisasi kenyamanan hanya dapat mengubah properti (dan tidak dapat mengubah letproperti). Itu tidak dapat menginisialisasi properti. Inisialisasi yang ditunjuk memiliki tanggung jawab untuk menginisialisasi semua properti yang diperkenalkan sebelum memanggil ke superinisialisasi yang ditunjuk.
nhgrif
1
Setidaknya kata kunci kenyamanan menjelaskan kepada pengembang, itu juga keterbacaan yang diperhitungkan (ditambah memeriksa penginisialisasi terhadap harapan pengembang). Poin kedua Anda bagus, saya mengubah jawaban saya.
TheEye
1

Kelas dapat memiliki lebih dari satu penginisialisasi yang ditunjuk. Penginisialisasi kenyamanan adalah penginisialisasi sekunder yang harus memanggil inisialisasi yang ditunjuk dari kelas yang sama.

Abdul Yasin
sumber