Swift sınıfında hata: Özellik super.init çağrısında başlatılmadı


219

İki dersim var ShapeveSquare

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

Yukarıdaki uygulama ile hatayı alıyorum:

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

Aramadan self.sideLengthönce neden ayarlamam gerekiyor super.init?


bunun gerçek bir teknik kısıtlama ile değil, iyi programlama uygulamaları ile ilgisi olduğundan emin olabilirsiniz. Shape, Square'in geçersiz kıldığı bir işlevi çağıracaksa, Square sideLength kullanmak isteyebilir, ancak henüz başlatılmamıştır. Swift muhtemelen sadece temel sınıfı çağırmadan önce örneklerinizi başlatmaya zorlayarak bunu kaza yapmanıza izin vermez.
cwharris

3
Kitaptaki örnekler kötü. Aşağıda açıklanan tüm özelliklerin başlatıldığından emin olmak için her zaman super.init () öğesini çağırmanız gerekir.
Pascal

Yanıtlar:


173

Sorunuzu cevaplayan Swift Programlama Dili'nden alıntı:

“Swift'in derleyicisi, iki aşamalı başlatmanın hatasız olarak tamamlandığından emin olmak için dört yararlı güvenlik kontrolü gerçekleştirir:”

Güvenlik kontrolü 1 “Atanmış bir başlatıcı,“ sınıfının getirdiği tüm özelliklerin, bir üst sınıf başlatıcıya kadar yetki vermeden önce başlatılmasını ”sağlamalıdır.

Alıntı: Apple Inc. “Hızlı Programlama Dili.” iBooks. https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11


47
Şimdi bu C ++, C # veya Java ile karşılaştırıldığında şaşırtıcı bir değişiklik.
MDJ

12
@MDJ Elbette. Dürüstçe ya da katma değer görmüyorum.
Ruben

20
Aslında bir tane var gibi görünüyor. Diyelim ki C # 'da, üst sınıf yapıcı, geçersiz kılınmış (sanal) yöntemleri çağırmamalıdır, çünkü kimse tam olarak başlatılmamış alt sınıfla nasıl tepki vereceğini bilmez. Swift'te tamam, üst sınıf yapıcı çalışırken alt sınıf ekstra durumu iyi olduğundan. Dahası, Swift'te son yöntemler hariç hepsi geçersizdir.
MDJ

17
Özellikle can sıkıcı buluyorum çünkü bu, kendi alt görünümlerini oluşturan bir UIView alt sınıfı yapmak istiyorsam, bu alt görünümleri başlamak için çerçevesiz başlatmalı ve daha sonra kareleri ekleyemediğim için kareleri daha sonra eklemeliyim super.init çağrıldıktan SONRA kadar sınırlar.
Kül

5
@Janos özelliği isteğe bağlı yaparsanız, onu başlatmanız gerekmez init.
JeremyP

105

Swift, başlatıcılarda yapılan çok açık, özel bir işlem sırasına sahiptir. Bazı temel örneklerle başlayalım ve genel bir duruma geçelim.

A nesnesini ele alalım. Bunu aşağıdaki gibi tanımlayacağız.

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

A'nın bir üst sınıfa sahip olmadığına dikkat edin, bu nedenle varolmadığı için bir super.init () işlevi çağıramaz.

Tamam, şimdi A'yı B adlı yeni bir sınıfla alt sınıflandıralım.

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

Bu, [super init]tipik olarak her şeyden önce ilk çağrılacak olan Objective-C'den ayrılmadır. Swift'te öyle değil. Arama yöntemleri (üst sınıf 'başlatıcıyı içeren) de dahil olmak üzere başka bir şey yapmadan önce örnek değişkenlerinizin tutarlı bir durumda olmasını sağlamaktan siz sorumlusunuz.


1
bu son derece yararlı, açık bir örnek. teşekkür ederim!
FullMetalFist

Y'yi hesaplamak için değere ihtiyacım olursa, örneğin: init (y: Int) {self.y = y * self.x super.init ()}
6rod9

1
init (y: Int, x: Int = 0) {self.y = y * x; self.x = x; super.init (x: x)}, ayrıca yukarıdaki örneğe referansla süper sınıf için doğrudan boş
yapıcıyı çağıramazsınız

43

Gönderen docs

Güvenlik kontrolü 1

Atanmış bir başlatıcı, bir üst sınıf başlatıcıya kadar yetki vermeden önce, sınıfının getirdiği tüm özelliklerin başlatılmasını sağlamalıdır.


Neden böyle bir güvenlik kontrolüne ihtiyacımız var?

Bunu cevaplamak için hızlı bir şekilde başlatma sürecine geçelim.

İki Fazlı Başlatma

Swift'te sınıf başlatma iki aşamalı bir süreçtir. İlk aşamada, saklanan her özelliğe, onu tanıtan sınıf tarafından bir başlangıç ​​değeri atanır. Depolanan her özellik için başlangıç ​​durumu belirlendikten sonra, ikinci aşama başlar ve her sınıfa, yeni örneğin kullanıma hazır olduğu düşünülmeden önce depolanan özelliklerini daha da özelleştirme fırsatı verilir.

İki aşamalı bir başlatma işleminin kullanılması, bir sınıf hiyerarşisindeki her sınıfa tam esneklik sağlarken başlatmayı da güvenli kılar. İki aşamalı başlatma, özellik değerlerine başlatılmadan önce erişilmesini engeller ve özellik değerlerinin başka bir başlatıcı tarafından beklenmedik bir şekilde farklı bir değere ayarlanmasını engeller.

Bu nedenle, iki adımlı başlatma işleminin yukarıda tanımlandığı gibi yapıldığından emin olmak için dört güvenlik kontrolü vardır, bunlardan biri,

Güvenlik kontrolü 1

Atanmış bir başlatıcı, bir üst sınıf başlatıcıya kadar yetki vermeden önce, sınıfının getirdiği tüm özelliklerin başlatılmasını sağlamalıdır.

Şimdi, iki aşamalı başlatma hiçbir zaman siparişten bahsetmez, ancak bu güvenlik kontrolü, super.inittüm özelliklerin başlatılmasından sonra sipariş verilmesini sağlar.

İki aşamalı başlatma özelliği , bu güvenlik kontrolü 1 olmadan başlatılmadan önce özellik değerlerine erişilmesini önlediği için güvenlik kontrolü 1 önemsiz görünebilir .

Bu örnekteki gibi

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.initkullanılmadan önce her özelliği başlattı. Güvenlik kontrolü 1 önemsiz görünüyor,

Ama sonra başka bir senaryo, biraz karmaşık olabilir,

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)

Çıktı :

Shape Name :Triangle
Sides :3
Hypotenuse :12

Burada super.initönce ayarını çağırmış olsaydık hypotenuse, super.initçağrı daha sonra çağrılmış olacaktı printShapeDescription()ve bu geçersiz kılındığından, önce Üçgen sınıfı uygulamasına geri dönecekti printShapeDescription(). printShapeDescription()Üçgen sınıf erişim hypotenuseolmayan bir isteğe bağlı özellik hala başlatıldı edilmediğini. İki fazlı başlatma, özellik değerlerine başlatılmadan önce erişilmesini engellediğinden buna izin verilmez

Bu nedenle, İki fazlı başlatmanın tanımlandığı gibi yapıldığından emin olun, belirli bir çağrı sırası olmalıdır super.initve bu, selfsınıf tarafından sunulan tüm özellikleri başlattıktan sonra , bir Güvenlik kontrolüne ihtiyacımız var 1


1
Büyük açıklama, neden kesinlikle en üst cevaba eklenmelidir.
Guy Daher

Temelde söylüyorsun Yani üst sınıf en çünkü init edebilir o fonksiyon erişim alt sınıfları özellik, daha sonra değerleri ayarlamak olmaması önlemek için hangi bir (geçersiz kılındı) işlevini ... çağırmak, çağrı supertüm değerlerin sonra gerçekleşmesi gerekir kümesidir. Tamam mantıklı. Öyleyse Objective-C'nin bunu nasıl yaptığını ve neden superilk önce aramak zorunda kaldınız?
Bal

Esasen ne kadar işaret ediyoruz olduğunu benzer yerleştirilmesi: için printShapeDescription() önce self.sides = sides; self.name = named; bu hatası oluşturuyor hangi: use of 'self' in method call 'printShapeDescription' before all stored properties are initialized. OP hatası, çalışma zamanı hatası olasılığını azaltmak için verilir.
Bal

Özellikle 'olasılık' kelimesini kullandım, çünkü eğer `` print ("hiçbir şey") gibi bir şeye benzemeyen printShapeDescriptionbir işlev selfolsaydı, o zaman hiçbir sorun olmazdı. (Yine de derleyici bir hata fırlatacaktı, çünkü süper akıllı değil )
Honey

Peki objc güvenli değildi. Swift yazım güvenlidir, bu nedenle isteğe bağlı olmayan nesnelerin gerçekten nil olması gerekir!
Daij-Djan

36

Tüm örnek değişkenlerinizi başlattıktan sonra "super.init ()" çağrılmalıdır.

Apple'ın "Intermediate Swift" videosunda (bunu Apple Developer video kaynak sayfasında https://developer.apple.com/videos/wwdc/2014/ adresinde bulabilirsiniz ), yaklaşık 28:40 saatinde, tüm başlatıcıların Örnek değişkenlerinizi başlattıktan SONRA super sınıfı çağrılmalıdır.

Objective-C'de, tam tersi oldu. Swift'te, tüm özelliklerin kullanılmadan önce başlatılması gerektiğinden, önce özellikleri başlatmamız gerekir. Bunun amacı, öncelikle özellikleri başlatmadan, süper sınıfın "init ()" yönteminden geçersiz kılınan işleve yapılan bir çağrıyı önlemek içindir.

Yani "Square" uygulaması şöyle olmalıdır:

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

1
Asla tahmin edemezdim. Süper ile başlatma ilk başta benim düşüncem bu olacaktı. !! hm. hızlı bir değişiklik.
mythicalcoder

Bunun neden peşine düşmesi gerekiyor? Lütfen teknik bir neden
belirtin

14

Çirkin biçimlendirme için özür dileriz. Beyandan sonra bir soru karakteri koyun ve her şey yoluna girecek. Bir soru derleyiciye değerin isteğe bağlı olduğunu söyler.

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

Edit1:

Bu hatayı atlamanın daha iyi bir yolu var. Jmaschad'ın yorumuna göre, isteğe bağlı olarak kullanmanız için hiçbir neden yoktur, çünkü opsiyonlar kullanımda rahat değildir ve erişmeden önce isteğe bağlı olarak nil olup olmadığını her zaman kontrol etmeniz gerekir. Tek yapmanız gereken beyandan sonra üyeyi başlatmaktır:

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

Edit2:

İki eksi bu cevabı aldıktan sonra daha iyi bir yol buldum. Sınıf üyesinin yapıcıda başlatılmasını istiyorsanız, yapıcı içinde ve super.init () çağrısından önce ona başlangıç ​​değeri atamanız gerekir. Bunun gibi:

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

Swift'i öğrenmede iyi şanslar.


Sadece geçin super.init(name:name)ve self.sideLength = sideLength. sideLengthİsteğe bağlı olarak bildirmek yanlış yönlendirmedir ve daha sonra paketini açmaya zorlamanız gerektiğinde ek güçlük çıkarır.
Johannes Luong

Evet, bu bir seçenek. Teşekkürler
fnc12

Aslında var sideLength: Doublebir başlangıç ​​değeri atamanıza gerek yok
Jarsen

Gerçekten isteğe bağlı bir sabitim varsa. Bununla ne yapacağım? Yapıcıda başlatmam gerekiyor mu? Bunu neden yapmanız gerektiğini anlamıyorum, ancak derleyici Swift 1.2
Van Du

1
Mükemmel ! tüm 3 çözümler "?", "String ()" çalıştı, ama benim için sorun ben mülkiyet bir 'atamadı' oldu, ve ne zaman o çalıştı! Teşekkürler dostum
Naishta

9

swift her üye varlığını kullanmadan / kullanmadan önce başlatmanızı sağlar. Süper dönüş olduğunda ne olacağından emin olamadığı için hata veriyor: üzgünümden daha güvenli


1
Bu IMO anlamsızdır, çünkü üst sınıf, çocuklarında bildirilen özelliklere ilişkin hiçbir görünürlüğe sahip olmamalıdır!
Andy Hin

1
Öyle değil ama bir şeyleri geçersiz kılabilir ve kendini 'süper yapmadan önce' kullanmaya başlayabilirsiniz
Daij-Djan

Burada ve ardından gelen yorumları görebiliyor musunuz? Sanırım tam olarak ne dediğini söylüyorum yani ikimiz de derleyicinin üzgün olmak yerine güvenli olmak istediğini söylüyoruz, tek sorum şu: objektif-c bu sorunu nasıl çözdü? Yoksa olmadı mı? Eğer yapmadıysa neden hala super.initilk satıra yazmanızı gerektiriyor ?
Bal

7

Edward

Örneğinizdeki kodu şu şekilde değiştirebilirsiniz:

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

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

Bu, örtük olarak açılmamış bir isteğe bağlı kullanıyor.

Belgelerde şunları okuyabiliriz:

"Seçeneklerde olduğu gibi, örtük olarak kaydırılmamış isteğe bağlı bir değişken veya özellik bildirdiğinizde bir başlangıç ​​değeri sağlamazsanız, bu değer otomatik olarak varsayılan değerdir."


Bence bu en temiz seçenek. İlk Swift girişimimde, doğrudan somutlaştırılamayan AVCaptureDevice türünde bir üye değişkenim vardı, bu nedenle init () kodu gerekliydi. Ancak, ViewController birden çok başlatıcı gerektirir ve init () 'den ortak bir başlatma yöntemi çağıramazsınız.
sunetos


6

Bildirinin sonuna nil ekleyin.


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

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

Bu benim durumum için çalıştı, ama senin için çalışmayabilir


İyi bir, protokol ile UIViewController ile UIView ile kullanırken
abdul sathar

1

Sadece yanlış sıraya giriyorsun.

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

0

Muhtemelen bazı downvotes alacağım, ama dürüst olmak gerekirse, hayat bu şekilde daha kolay:

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

-4

Şaşırtıcı derecede aptalca bir tasarım.

Bunun gibi bir şey düşünün:

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

Bu, yukarıda belirtildiği gibi geçersizdir. Ama öyle de:

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

Çünkü 'benlik' başlatılmamıştır.

Umarım bu hata yakında çözülür.

(Evet, boş bir nesne oluşturabileceğimi ve sonra boyutu ayarlayabileceğimi biliyorum ama bu sadece aptalca).


2
Bu bir hata değil, OOP'ların yeni programlama temelidir.
Hitendra Solanki
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.