Swift'de mevcut bir başlatıcı uygulamak için en iyi uygulama


100

Aşağıdaki kodla basit bir model sınıfı tanımlamaya çalışıyorum ve bu sınıfın kullanılabilir başlatıcısı, bir (json-) sözlüğünü parametre olarak alıyor. nilKullanıcı adı orijinal json'da tanımlanmamışsa başlatıcı dönmelidir .

1. Kod neden derlenmiyor? Hata mesajı şunu söylüyor:

Bir sınıf örneğinin tüm depolanmış özellikleri, bir başlatıcıdan nil döndürülmeden önce başlatılmalıdır.

Bu mantıklı değil. Geri dönmeyi planladığımda neden bu özellikleri başlatmalıyım nil?

2. Yaklaşımım doğru mu yoksa hedefime ulaşmak için başka fikirler veya ortak modeller mi olacak?

class User: NSObject {

    let userName: String
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: NSDictionary) {
        if let value: String = dictionary["user_name"] as? String {
            userName = value
        }
        else {
           return nil
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            isSuperUser = value
        }

        someDetails = dictionary["some_details"] as? Array

        super.init()
    }
}

Benzer bir sorun yaşadım, benimki ile her sözlük değerinin beklenmesi gerektiği sonucuna vardım ve bu yüzden değerleri açmaya zorladım. Mülk orada değilse, hatayı yakalayabilirim. Ek olarak, canSetCalculablePropertiesbaşlatıcımın anında oluşturulabilen veya oluşturulamayan özellikleri hesaplamasına izin veren bir boole parametresi ekledim . Örneğin, eğer bir dateCreatedanahtar eksikse ve özelliği anında canSetCalculablePropertiesparametre true olduğu için ayarlayabilirsem, o zaman sadece geçerli tarihe ayarlıyorum.
Adam Carter

Yanıtlar:


71

Güncelleme: Gönderen Swift 2.2 Değişikliği Günlüğü'nde (2016 21 Mart yayınlandı):

Kullanılabilir veya fırlatma olarak bildirilen belirlenmiş sınıf başlatıcıları, nesne tam olarak başlatılmadan önce sırasıyla sıfır döndürebilir veya bir hata atabilir.


Swift 2.1 ve öncesi için:

Apple'ın belgelerine (ve sizin derleyici nilhatanıza ) göre, bir sınıf, kullanılabilir bir başlatıcıdan dönmeden önce tüm depolanan özelliklerini başlatmalıdır:

Bununla birlikte, sınıflar için, kullanılabilir bir başlatıcı, ancak bu sınıf tarafından sunulan tüm depolanan özellikler bir başlangıç ​​değerine ayarlandıktan ve herhangi bir başlatıcı yetkilendirmesi gerçekleştikten sonra bir başlatma hatasını tetikleyebilir.

Not: Aslında yapılar ve numaralandırmalar için iyi çalışır, sadece sınıflar için değil.

Başlatıcı başarısız olmadan önce başlatılamayan depolanan özellikleri işlemenin önerilen yolu, bunları örtük olarak sarmalanmamış isteğe bağlı öğeler olarak bildirmektir.

Dokümanlardan örnek:

class Product {
    let name: String!
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

Yukarıdaki örnekte, Product sınıfının name özelliği, dolaylı olarak sarmalanmamış isteğe bağlı bir dize türüne (Dize!) Sahip olarak tanımlanmıştır. İsteğe bağlı bir tür olduğundan, bu, name özelliğinin, başlatma sırasında belirli bir değer atanmadan önce varsayılan bir sıfır değerine sahip olduğu anlamına gelir. Bu varsayılan sıfır değeri, Product sınıfı tarafından tanıtılan tüm özelliklerin geçerli bir başlangıç ​​değerine sahip olduğu anlamına gelir. Sonuç olarak, Ürün için kullanılabilir başlatıcı, başlatıcı içindeki name özelliğine belirli bir değer atamadan önce boş bir dize geçirilirse başlatıcının başlangıcında bir başlatma hatasını tetikleyebilir.

Bununla birlikte, sizin durumunuzda, basitçe userNamea olarak tanımlamak String!derleme hatasını düzeltmez çünkü yine de temel sınıfınızdaki özellikleri başlatmak için endişelenmeniz gerekir NSObject,. Neyse ki, a olarak userNametanımlandığında String!, aslında temel sınıfınızı başlatacak ve derleme hatasını düzelten super.init()önünüzde arayabilirsin .return nilNSObject

class User: NSObject {

    let userName: String!
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: NSDictionary) {
        super.init()

        if let value = dictionary["user_name"] as? String {
            self.userName = value
        }
        else {
            return nil
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            self.isSuperUser = value
        }

        self.someDetails = dictionary["some_details"] as? Array
    }
}

1
Çok teşekkür ederim sadece doğru değil, aynı zamanda iyi açıklanmış
Kai Huppmann

9
swift1.2'de, Dokümanlardaki örnek bir hata veriyor "Bir sınıf örneğinin tüm saklanan özellikleri, bir başlatıcıdan sıfır döndürülmeden önce başlatılmalıdır"
jeffrey

2
@jeffrey Doğru, dokümantasyondan ( Productsınıf) alınan örnek , dokümanlar bunu yapabileceğini söylese bile, belirli bir değer atamadan önce bir başlatma hatasını tetikleyemez. Dokümanlar en son Swift sürümüyle senkronize değil. Bunu bir hale getirmek için tavsiye edilir varyerine artık için let. kaynak: Chris Lattner .
Arjan

1
Belgelerde bu kod parçası biraz farklıdır: önce özelliği ayarlarsınız ve ardından var olup olmadığını kontrol edersiniz. Bkz. "Sınıflar için Kullanılabilen Başlatıcılar", "Swift Programlama Dili". `` `class Product {let name: String! init? (ad: Dize) {self.name = name if name.isEmpty {return nil}}} ``
Misha Karpenko

Bunu Apple belgelerinde de okudum ancak bunun neden gerekli olduğunu anlamıyorum. Bir başarısızlık, yine de sıfır döndürmek anlamına gelir, o halde özelliklerin başlatılmış olup olmaması ne fark eder?
Alper

132

Bu mantıklı değil. Sıfır döndürmeyi planladığım halde neden bu özellikleri başlatmalıyım?

Chris Lattner'a göre bu bir hata. İşte söylediği:

Bu, sürüm notlarında belgelenen swift 1.1 derleyicisindeki bir uygulama sınırlamasıdır. Derleyici şu anda her durumda kısmen başlatılmış sınıfları yok edemiyor, bu nedenle olması gereken bir durumun oluşmasına izin vermiyor. Bunu bir özellik olarak değil, gelecekteki sürümlerde düzeltilecek bir hata olarak görüyoruz.

Kaynak

DÜZENLE:

Yani swift artık açık kaynak kodlu ve bu değişiklik günlüğüne göre artık hızlı 2.2 anlık görüntülerinde düzeltildi.

Kullanılabilir veya fırlatma olarak bildirilen belirlenmiş sınıf başlatıcıları, nesne tam olarak başlatılmadan önce sırasıyla sıfır döndürebilir veya bir hata atabilir.


2
Daha fazla ihtiyaç duyulmayacak özellikleri başlatma fikrinin pek makul görünmediğini belirttiğim için teşekkür ederim. Ve bir kaynağı paylaşmak için +1, bu da Chris Lattner'ın benim gibi hissettiğini kanıtlıyor;).
Kai Huppmann

22
Bilginize: "Gerçekten. Bu hala geliştirmek istediğimiz bir şey, ancak Swift 1.2 için kesinti yapmadık". - Chris Lattner 10 Şubat 2015
dreamlab

14
Bilginize: Swift 2.0 beta 2'de bu hala bir sorun ve aynı zamanda fırlatılan bir başlatıcı ile ilgili bir sorun.
aranasaurus

7

Mike S'nin cevabının Apple'ın tavsiyesi olduğunu kabul ediyorum, ancak bunun en iyi uygulama olduğunu düşünmüyorum. Güçlü tip bir sistemin tüm amacı, çalışma zamanı hatalarını derleme süresine taşımaktır. Bu "çözüm", bu amacı geçersiz kılar. IMHO, devam edip kullanıcı adını ilklendirip ""super.init () 'den sonra kontrol etmek daha iyi olur . Boş kullanıcı adlarına izin veriliyorsa, bir bayrak ayarlayın.

class User: NSObject {
    let userName: String = ""
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: [String: AnyObject]) {
        if let user_name = dictionary["user_name"] as? String {
            userName = user_name
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            isSuperUser = value
        }

        someDetails = dictionary["some_details"] as? Array

        super.init()

        if userName.isEmpty {
            return nil
        }
    }
}

Teşekkür ederim, ama güçlü tip sistemlerin fikirlerinin Mike'ın cevabıyla nasıl bozulduğunu anlamıyorum. Sonuç olarak, başlangıç ​​değerinin sıfır yerine "" olarak ayarlanması farkıyla aynı çözümü sunarsınız. Dahası, kodunuz bir kullanıcı adı olarak "" kullanmaktan da
vazgeçiyor

2
İnceleme üzerine, haklı olduğunuzu görüyorum, ancak bunun tek nedeni userName'in sabit olması. Bir değişken olsaydı, kabul edilen cevap benimkinden daha kötü olurdu çünkü userName daha sonra nil olarak ayarlanabilirdi.
Daniel T.

Bu cevabı beğendim. @KaiHuppmann, boş kullanıcı adlarına izin vermek istiyorsanız, basit bir Bool ihtiyaçlarınız da olabilirReturnNil. Değer sözlükte yoksa, needsReturnNil değerini true olarak ayarlayın ve userName değerini ne olursa olsun ayarlayın. Super.init () 'den sonra, needsReturnNil'i kontrol edin ve gerekirse sıfır döndürün.
Richard Venable

6

Sınırlamayı aşmanın başka bir yolu, ilklendirmeyi yapmak için sınıf işlevleriyle çalışmaktır. Hatta bu işlevi bir uzantıya taşımak isteyebilirsiniz:

class User: NSObject {

    let username: String
    let isSuperUser: Bool
    let someDetails: [String]?

    init(userName: String, isSuperUser: Bool, someDetails: [String]?) {

         self.userName = userName
         self.isSuperUser = isSuperUser
         self.someDetails = someDetails

         super.init()
    }
}

extension User {

    class func fromDictionary(dictionary: NSDictionary) -> User? {

        if let username: String = dictionary["user_name"] as? String {

            let isSuperUser = (dictionary["super_user"] as? Bool) ?? false
            let someDetails = dictionary["some_details"] as? [String]

            return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails)
        }

        return nil
    }
}

Kullanmak şöyle olur:

if let user = User.fromDictionary(someDict) {

     // Party hard
}

1
Bunu severim; Kurucuların ne istedikleri konusunda şeffaf olmalarını tercih ederim ve bir sözlüğe geçmek çok opaktır.
Ben Leggiero


1

Bunu öğrendim kutu Swift 1.2 yapılabilir

Bazı koşullar var:

  • Gerekli özellikler örtük olarak sarmalanmamış isteğe bağlı olarak bildirilmelidir
  • Tam olarak bir kez gerekli özelliklerinize bir değer atayın. Bu değer sıfır olabilir.
  • Sınıfınız başka bir sınıftan miras alıyorsa super.init () 'i çağırın.
  • Tüm gerekli mülklerinize bir değer atandıktan sonra , değerlerinin beklendiği gibi olup olmadığını kontrol edin. Değilse, sıfır döndür.

Misal:

class ClassName: NSObject {

    let property: String!

    init?(propertyValue: String?) {

        self.property = propertyValue

        super.init()

        if self.property == nil {
            return nil
        }
    }
}

0

Bir değer türü (yani, bir yapı veya numaralandırma) için kullanılabilen bir başlatıcı, başlatıcı uygulaması içindeki herhangi bir noktada bir başlatma hatasını tetikleyebilir

Bununla birlikte, sınıflar için, kullanılabilir bir başlatıcı, ancak bu sınıf tarafından sunulan tüm depolanan özellikler bir başlangıç ​​değerine ayarlandıktan ve herhangi bir başlatıcı yetkilendirmesi gerçekleştikten sonra bir başlatma hatasını tetikleyebilir.

Alıntı: Apple Inc. “ The Swift Programlama Dili. ”İBooks. https://itun.es/sg/jEUH0.l


0

Kolaylık initini kullanabilirsiniz :

class User: NSObject {
    let userName: String
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
        self.userName = userName
        self.isSuperUser = isSuperUser
        self.someDetails = someDetails
    }     

    convenience init? (dict: NSDictionary) {            
       guard let userName = dictionary["user_name"] as? String else { return nil }
       guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil }
       guard let someDetails = dictionary["some_details"] as? [String] else { return nil }

       self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails)
    } 
}
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.