Swift'de neden kolaylık anahtar kelimesine ihtiyaç var?


132

Swift, yöntemi ve başlatıcı aşırı yüklemesini desteklediğinden, yan yana birden çok initöğe koyabilir ve hangisini uygun buluyorsanız onu kullanabilirsiniz:

class Person {
    var name:String

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

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

Öyleyse convenienceanahtar kelime neden var olsun ki? Aşağıdakileri önemli ölçüde daha iyi yapan nedir?

class Person {
    var name:String

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

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

13
Bunu belgelerde okuyordum ve bu konuda da kafam karıştı. : /
boidkan

Yanıtlar:


235

Mevcut cevaplar conveniencehikayenin sadece yarısını anlatıyor. Hikayenin diğer yarısı, mevcut cevapların hiçbirinin kapsamadığı yarısı, Desmond'un yorumlarda yayınladığı soruyu yanıtlıyor:

Swift, neden conveniencesadece ondan aramam gerektiği için beni başlatıcımın önüne koymaya zorlasın self.init? ''

Swift'in başlatıcı kurallarından birkaçını ayrıntılı olarak ele aldığım bu cevapta biraz değindim, ancak asıl odak noktası kelime üzerindeydi. Ancak bu cevap hala bu soru ve bu cevapla ilgili bir şeye hitap ediyordu. Swift başlatıcı mirasının nasıl çalıştığını anlamalıyız.required

Swift başlatılmamış değişkenlere izin vermediğinden, miras aldığınız sınıftan tüm başlatıcıları (veya herhangi birini) devralmanız garanti edilmez. Alt sınıfa ayrılır ve ilklendirilmemiş örnek değişkenlerini alt sınıfımıza eklersek, başlatıcıları devralmayı durdurmuş oluruz. Ve biz kendi başlatıcılarımızı ekleyene kadar, derleyici bize bağıracak.

Açık olmak gerekirse, başlatılmamış bir örnek değişkeni, varsayılan bir değer verilmeyen herhangi bir örnek değişkendir (isteğe bağlı ve örtük olarak kapatılmamış seçeneklerin otomatik olarak varsayılan bir değer aldığını unutmayın nil).

Yani bu durumda:

class Foo {
    var a: Int
}

abaşlatılmamış bir örnek değişkendir. aVarsayılan bir değer vermedikçe bu derlenmeyecektir :

class Foo {
    var a: Int = 0
}

veya abir başlatıcı yönteminde başlat:

class Foo {
    var a: Int

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

Şimdi, alt sınıfa ayrılırsak ne olacağını görelim, olur Foomu?

class Bar: Foo {
    var b: Int

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

Sağ? Bir değişken ekledik ve bderlenebilmesi için bir değer ayarlamak üzere bir başlatıcı ekledik . Hangi dilden geldiğinize bağlı olarak, bunun 'ın başlatıcısını Bardevralmasını bekleyebilirsiniz . Ama öyle değil. Ve nasıl olabilir? Nasıl yok 'ın biliyorum nasıl bir değer atamak için bu değişkenin ekledi? Öyle değil. Dolayısıyla, tüm değerlerimizi başlatamayan bir başlatıcıyla bir örneği başlatamayız.Fooinit(a: Int)Fooinit(a: Int)bBarBar

Bunların ne alakası var convenience?

Peki, başlatıcı mirasıyla ilgili kurallara bakalım :

Kural 1

Alt sınıfınız herhangi bir atanmış başlatıcı tanımlamıyorsa, otomatik olarak tüm üst sınıf atanmış başlatıcılarını devralır.

Kural 2

Alt sınıfınız, birinci sınıf atanmış başlatıcılarının tümünün bir uygulamasını sağlıyorsa - bunları kural 1'e göre miras alarak veya tanımının bir parçası olarak özel bir uygulama sağlayarak - o zaman tüm üst sınıf uygunluk başlatıcılarını otomatik olarak devralır.

Kolaylık başlatıcılardan bahseden Kural 2'ye dikkat edin.

Yani convenienceanahtar kelimenin yaptığı şey, bize hangi başlatıcıların varsayılan değerler olmadan örnek değişkenleri ekleyen alt sınıflar tarafından miras alınabileceğini belirtmektir .

Bu örnek Basedersi ele alalım :

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

convenienceBurada üç başlatıcımızın olduğuna dikkat edin . Bu, miras alınabilen üç başlatıcımız olduğu anlamına gelir. Ve bir atanmış başlatıcımız var (atanmış bir başlatıcı, basitçe bir kolaylık başlatıcı olmayan herhangi bir başlatıcıdır).

Temel sınıfın örneklerini dört farklı şekilde başlatabiliriz:

görüntü açıklamasını buraya girin

Öyleyse bir alt sınıf oluşturalım.

class NonInheritor: Base {
    let c: Int

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

Miras alıyoruz Base. Kendi örnek değişkenimizi ekledik ve buna varsayılan bir değer vermedik, bu yüzden kendi başlatıcılarımızı eklemeliyiz. Biz bir ilave, init(a: Int, b: Int, c: Int)ama imzası eşleşmiyor Basesınıfın başlatıcısı atanmış bir: init(a: Int, b: Int). Bu, aşağıdakilerden herhangi bir başlatıcı miras almadığımız anlamına gelir Base:

görüntü açıklamasını buraya girin

Öyleyse, miras alsaydık ne olurdu Base, ama devam ettik ve atanan başlatıcıyla eşleşen bir başlatıcı uyguladık 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)
    }
}

Şimdi, doğrudan bu sınıfta uyguladığımız iki başlatıcıya ek olarak, Basesınıfın atanmış başlatıcısıyla eşleşen bir başlatıcı uyguladığımız için , Basesınıfın tüm conveniencebaşlatıcılarını devralabiliriz :

görüntü açıklamasını buraya girin

Eşleşen imzaya sahip başlatıcının olarak işaretlenmiş convenienceolması burada hiçbir fark yaratmaz. Bu Inheritor, yalnızca bir belirlenmiş başlatıcıya sahip olduğu anlamına gelir . Öyleyse miras alırsak Inheritor, o atanmış başlatıcıyı uygulamamız gerekir ve sonra Inheritorkolaylık başlatıcısını devralırız , bu da tüm Baseatanmış başlatıcıları uyguladığımız ve onun conveniencebaşlatıcılarını devralabileceğimiz anlamına gelir .


16
Soruyu gerçekten cevaplayan ve dokümanları takip eden tek cevap. OP olsaydım kabul ederdim.
FreeNickname

12
Bir kitap
yazmalısınız

1
@SLN Bu yanıt , Swift başlatıcı kalıtımının nasıl çalıştığına dair birçok şeyi kapsar.
nhgrif

1
@SLN Çünkü bir Bar oluşturmak başlatılmamış init(a: Int)bırakacaktır b.
Ian Warburton

2
@IanWarburton Bu "neden" in cevabını bilmiyorum. Yorumunuzun ikinci bölümündeki mantığınız bana sağlam görünüyor, ancak belgeler bunun nasıl çalıştığını açıkça belirtiyor ve bir Oyun Alanında sorduğunuz şeyin bir örneğini ortaya koymak, davranışın belgelenenle eşleştiğini onaylıyor.
nhgrif

9

Çoğunlukla netlik. Senden ikinci örnek,

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

gereklidir veya belirtilmiştir . Tüm sabitlerinizi ve değişkenlerinizi başlatması gerekir. Kullanışlı başlatıcılar isteğe bağlıdır ve tipik olarak başlatmayı kolaylaştırmak için kullanılabilir. Örneğin, Kişi sınıfınızın isteğe bağlı bir cinsiyet değişkenine sahip olduğunu varsayalım:

var gender: Gender?

Toplumsal cinsiyetin bir sıralama olduğu yerde

enum Gender {
  case Male, Female
}

bunun gibi kolaylık başlatıcılara sahip olabilirsiniz

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

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

Kolaylık başlatıcıları, içlerinde belirtilen veya gerekli başlatıcıları çağırmalıdır . Sınıfınız bir alt sınıfsa, super.init() kendi başlatması içinde çağırması gerekir .


2
Bu nedenle, convenienceanahtar kelime olmadan bile birden çok başlatıcıyla ne yapmaya çalıştığımı derleyici için gayet açık olurdu ama Swift yine de bu konuda sorun yaşardı. Apple'dan beklediğim basitlik bu değil =)
Desmond Hume

2
Bu cevap hiçbir şeye cevap vermiyor. "Açıklık" dedin ama bunun bir şeyi nasıl daha net hale getirdiğini açıklamadın.
Robo Robok

7

Aklıma ilk gelen şey, kod organizasyonu ve okunabilirlik için sınıf mirasında kullanılmasıdır. PersonSınıfınızla devam ederken , bunun gibi bir senaryo düşünün

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}

Kolaylık başlatıcı Employee()ile değeri olmayan bir nesne oluşturabiliyorum , dolayısıylaconvenience


2
İle convenienceanahtar götürüldü değil Swift aynı kesin şekilde davranmasına yeterli bilgi alacağı?
Desmond Hume

Hayır, convenienceanahtar kelimeyi kaldırırsanız, Employeenesneyi herhangi bir argüman olmadan başlatamazsınız .
u54r

Özellikle, çağırmak, çağıran başlatıcıyı Employee()(miras alınan, nedeniyle convenience) init()çağırır self.init(name: "Unknown"). init(name: String), aynı zamanda için uygun bir başlatıcı Employee, belirlenen başlatıcıyı çağırır.
BallpointBen

1

Diğer kullanıcıların burada açıkladığı noktalardan ayrı olarak benim anlayışım.

Rahatlık başlatıcı ve uzantılar arasındaki bağlantıyı kuvvetle hissediyorum. Bana gelince, kolaylık başlatıcıları, mevcut bir sınıfın başlatılmasını değiştirmek (çoğu durumda kısaltmak veya kolaylaştırmak) istediğimde çok kullanışlıdır.

Örneğin, kullandığınız bazı üçüncü taraf sınıflarının initdört parametresi vardır , ancak uygulamanızda son ikisi aynı değere sahiptir. Daha fazla yazmaktan kaçınmak ve kodunuzu temiz hale getirmek için convenience init, yalnızca iki parametreyle bir tanımlayabilir ve içinde self.initen son varsayılan değerlere sahip parametreleri çağırabilirsiniz .


1
Swift, neden conveniencesadece ondan aramam gerektiği için beni başlatıcımın önüne koymaya zorlasın self.init? Bu gereksiz ve rahatsız edici görünüyor.
Desmond Hume

1

Göre Swift 2.1 belgelerinde , convenienceilklendiriciler bazı özel kurallara ya bağlı vardır:

  1. Bir conveniencebaşlatıcı yalnızca aynı sınıftaki başlatıcıları çağırabilir, süper sınıflarda değil (yalnızca karşısında, yukarı değil)

  2. Bir conveniencebaşlatıcı, zincirin herhangi bir yerinde belirlenmiş bir başlatıcıyı çağırmalıdır

  3. Bir conveniencebaşlatıcı, başka bir başlatıcıyı çağırmadan önce HERHANGİ BİR özelliği değiştiremez - ancak belirlenmiş bir başlatıcı , başka bir başlatıcıyı çağırmadan önce geçerli sınıf tarafından sunulan özellikleri başlatmalıdır.

convenienceAnahtar kelimeyi kullanarak, Swift derleyicisi bu koşulları kontrol etmesi gerektiğini bilir - aksi takdirde yapamaz.


Muhtemelen, derleyici bunu convenienceanahtar kelime olmadan çözebilir.
nhgrif

Üstelik üçüncü noktanız yanıltıcıdır. Bir kullanışlılık başlatıcı yalnızca özellikleri değiştirebilir (ve letözellikleri değiştiremez ). Özellikleri başlatamaz. Atanmış bir başlatıcı, belirlenmiş bir başlatıcıyı çağırmadan önce tanıtılan tüm özellikleri başlatma sorumluluğuna sahiptir super.
nhgrif

1
En azından kullanışlılık anahtar kelimesi geliştiriciye açıklık getirir, aynı zamanda önemli olan okunabilirliğidir (artı başlatıcıyı geliştiricinin beklentilerine göre kontrol etmek). İkinci fikriniz güzel, cevabımı buna göre değiştirdim.
TheEye

1

Bir sınıfın birden fazla atanmış başlatıcısı olabilir. Bir kolaylık başlatıcı, aynı sınıftan belirlenmiş bir başlatıcıyı çağırması gereken ikincil bir başlatıcıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.