“Önemli hata: İsteğe bağlı bir değer açılırken beklenmedik bir şekilde nil bulundu” ne anlama geliyor?


415

Swift programım çöküyor EXC_BAD_INSTRUCTIONve aşağıdaki benzer hatalardan biri. Bu hata ne anlama geliyor ve nasıl düzeltebilirim?

Önemli hata: İsteğe bağlı bir değer açılırken beklenmedik bir şekilde nil bulundu

veya

Önemli hata: Opsiyonel bir değeri dolaylı olarak açarken beklenmedik bir şekilde nil bulundu


Bu yazı, "beklenmedik bir şekilde bulunmuş nil" sorunlarına cevap toplamak için tasarlanmıştır, böylece dağılmamak ve bulmak zor değildir. Kendi cevabınızı ekleyebilir veya mevcut wiki cevabını düzenleyebilirsiniz .


8
@RobertColumbia, bu sorunun kapsamlı belgelerine sahip bir Topluluk Wiki Soru-Cevap çifti, bu sorunun bir MCVE eksikliği nedeniyle kapatılması gerektiğini düşünmüyorum.
JAL

Bunun gibi bir değişken yarat [var nameOfDaughter: String? ] ve bu değişkeni böyle kullanın [eğer _ = nameOfDaughter {}] en iyi yaklaşımdır.
iOS

Yanıtlar:


673

Bu cevap topluluk wiki'sidir . Daha iyi yapılabileceğini düşünüyorsanız, düzenlemekten çekinmeyin !

Arka Plan: İsteğe Bağlı Nedir?

Swift yılında Optionalbir olan genel tür (her türde) bir değeri içerebilir, ya da hiç bir değer.

Diğer birçok programlama dilinde, belirli bir "sentinel" değeri genellikle değer eksikliğini belirtmek için kullanılır . Örneğin Objective-C'de nil( boş gösterici ) bir nesnenin eksikliğini gösterir. Ancak bu, ilkel türlerle çalışırken daha zorlaşır - -1bir tamsayı, belki de INT_MINveya başka bir tam sayının yokluğunu göstermek için kullanılmalıdır ? Belirli bir değer "tamsayı yok" olarak seçilirse, bu artık geçerli olarak değerlendirilemez anlamına gelir değerin değer .

Swift, tür güvenli bir dildir, yani dil, kodunuzun çalışabileceği değer türleri hakkında net olmanıza yardımcı olur. Kodunuzun bir kısmı bir Dize bekliyorsa, security yazdığınızda kodu yanlışlıkla Int olarak iletmeniz engellenir.

Swift'te her tür isteğe bağlı yapılabilir . İsteğe bağlı bir değer, orijinal türden veya özel değerden herhangi bir değeri alabilir nil.

Opsiyoneller ?, tür üzerine bir sonekle tanımlanır :

var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int?    // `nil` is the default when no value is provided

İsteğe bağlı bir değerin olmaması aşağıdakilerle gösterilir nil:

anOptionalInt = nil

( Bunun Objective-C ile nilaynı olmadığını unutmayın nil. Objective-C'de nilgeçerli bir nesne işaretçisinin olmamasıdır ; Swift'te Optionals nesnelerle / referans türleriyle sınırlı değildir. İsteğe bağlı Haskell'ın Belki gibi davranır .)


Neden “ önemli hata: İsteğe bağlı bir değer açılırken beklenmedik bir şekilde sıfır bulunduiletisi aldım ?

(Hepsi de varsa) isteğe bağlı bir değerini erişmek için aşağıdakileri yapmanız gerekir kakışıyor bunu. İsteğe bağlı bir değer güvenli veya zorla açılabilir. İsteğe bağlı unwrap-zorlamak ve bu takdirde vermedi bir değere sahip, programınız yukarıdaki mesajla kilitlenmesine.

Xcode, bir kod satırını vurgulayarak çökmeyi gösterir. Sorun bu satırda oluşur.

çökmüş çizgi

Bu çökme, iki farklı tür güç açma ile ortaya çıkabilir:

1. Açık Kuvvet Açma

Bu, !isteğe bağlı olarak operatör ile yapılır . Örneğin:

let anOptionalString: String?
print(anOptionalString!) // <- CRASH

Önemli hata: İsteğe bağlı bir değer açılırken beklenmedik bir şekilde nil bulundu

Burada anOptionalStringolduğu gibi , nilaçmaya zorladığınız hatta bir çarpışma yaşarsınız.

2. Örtük Olarak Açılmamış Seçenekler

Bunlar türden sonra !değil, a ile tanımlanır ?.

var optionalDouble: Double!   // this value is implicitly unwrapped wherever it's used

Bu seçeneklerin bir değer içerdiği varsayılır. Bu nedenle, örtülü olarak açılmamış bir isteğe bağlı eriştiğinizde, sizin için otomatik olarak açılmaya zorlanacaktır. Bir değer içermiyorsa kilitlenir.

print(optionalDouble) // <- CRASH

Önemli hata: Opsiyonel bir değeri dolaylı olarak açarken beklenmedik bir şekilde nil bulundu

Hangi değişkenin çökmeye neden olduğunu bulmak için, tanımı göstermek üzere tıklatarak isteğe bağlı türü nerede bulabileceğinizi basılı tutabilirsiniz .

Özellikle IBOutlet'ler genellikle örtük olarak paketlenmemiş seçeneklerdir. Bunun nedeni, xib'inizin veya film şeridinizin başlatma işleminden sonra çalışma zamanında çıkışları birbirine bağlamasıdır . Bu nedenle çıkışlara yüklenmeden önce erişmediğinizden emin olmalısınız. Bağlantıların film şeridi / xib dosyanızdaki doğru olup olmadığını da kontrol etmelisiniz, aksi takdirde değerler nilçalışma zamanında olacak ve dolayısıyla örtük olarak açılmadıklarında çökecektir. . Bağlantıları düzeltirken, çıkışlarınızı tanımlayan kod satırlarını silmeyi deneyin, ardından tekrar bağlayın.


Ne zaman bir İsteğe bağlı paketini açmaya zorlamalıyım?

Açık kuvvet unwrapping

Genel bir kural olarak, hiçbir zaman !operatöre bağlı olarak isteğe bağlı bir paketin açılmasını zorlamamanız gerekir . Kullanıldığı durumlar olabilir! kabul edilebilir - ancak yalnızca isteğe bağlı bir değer içerdiğinden% 100 eminseniz kullanmalısınız.

Oradayken edebilir bir bildiğimiz gibi, kuvvet unwrapping kullanabileceğiniz bir fırsat olabilir aslında isteğe bağlı bir değer içerdiğini - bir yok tek yer nerede olamaz güvenle unwrap o isteğe yerine.

Örtük Olarak Açılmamış Seçenekler

Bu değişkenler, atamalarını daha sonra kodunuzda erteleyebileceğiniz şekilde tasarlanmıştır. Öyle senin buraya erişmek önce onlar bir değere sahip olmak için sorumluluk. Bununla birlikte, kuvvetin açılmasını içerirler, çünkü doğal olarak güvensizdirler - varsaydıkları gibi nil atamak geçerli olsa bile, değerinizin sıfır için .

Son çare olarak yalnızca örtük olarak açılmamış opsiyonelleri kullanmalısınız . Tembel bir değişken kullanabilir veya bir değişken için varsayılan bir değer sağlayabilirseniz , bunu örtük olarak kaydırılmamış bir isteğe bağlı kullanmak yerine yapmalısınız.

Bununla birlikte, örtük olarak açılmamış seçeneklerin yararlı olduğu birkaç senaryo vardır ve bunları aşağıda listelenen şekilde güvenli bir şekilde açmanın çeşitli yollarını kullanabilirsiniz - ancak bunları her zaman dikkatli bir şekilde kullanmalısınız.


Seçeneklerle nasıl güvenli bir şekilde başa çıkabilirim?

İsteğe bağlı bir değer içerip içermediğini kontrol etmenin en basit yolu, değeri karşılaştırmaktır nil.

if anOptionalInt != nil {
    print("Contains a value!")
} else {
    print("Doesn’t contain a value.")
}

Bununla birlikte, opsiyonellerle çalışırken zamanın% 99,9'u, eğer bir tane içeriyorsa, içerdiği değere erişmek istersiniz. Bunu yapmak için, İsteğe Bağlı Bağlama'yı kullanabilirsiniz .

İsteğe Bağlı Ciltleme

İsteğe Bağlı Bağlama, isteğe bağlı bir değer içerip içermediğini kontrol etmenizi sağlar ve kaydırılmamış değeri yeni bir değişkene veya sabite atamanıza olanak tanır. Sözdizimini kullanır if let x = anOptional {...}veyaif var x = anOptional {...} bağladıktan sonra yeni değişkenin değerini değiştirmeniz gerekiyorsa.

Örneğin:

if let number = anOptionalInt {
    print("Contains a value! It is \(number)!")
} else {
    print("Doesn’t contain a number")
}

Bunun yaptığı ilk önce isteğe bağlı bir değer olup olmadığını kontrol etmektir. O takdirde yapar , daha sonra 'Çizelgesi' değerinin yeni değişken (atanır numberdışı seçmeli sanki o zaman serbestçe kullanabileceği -). İsteğe bağlı değilse gelmez bir değer içeren beklediğiniz gibi, o zaman başka bir fıkra, çağrılır.

İsteğe bağlı ciltlemeyle ilgili düzgün olan şey, aynı anda birden fazla seçeneği açabilmenizdir. İfadeleri virgülle ayırabilirsiniz. Tüm seçeneklerin kaydırılmamış olması durumunda ifade başarılı olacaktır.

var anOptionalInt : Int?
var anOptionalString : String?

if let number = anOptionalInt, let text = anOptionalString {
    print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
    print("One or more of the optionals don’t contain a value")
}

Başka bir düzgün hile, açtıktan sonra değer üzerinde belirli bir koşulu kontrol etmek için virgül kullanabilmenizdir.

if let number = anOptionalInt, number > 0 {
    print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}

Bir if ifadesinde isteğe bağlı bağlama kullanmanın tek yakalaması, yalnızca ifadenin kapsamından açılmamış değere erişebilmenizdir. İfadenin kapsamı dışından değere erişmeniz gerekiyorsa, bir koruma ifadesi kullanabilirsiniz .

Bir koruma ifadesi , başarı için bir koşul tanımlamanıza olanak tanır ve geçerli kapsam yalnızca bu koşul yerine getirildiğinde yürütülmeye devam eder. Sözdizimi ile tanımlanırlar guard condition else {...}.

Bu nedenle, isteğe bağlı bir bağlama ile kullanmak için şunları yapabilirsiniz:

guard let number = anOptionalInt else {
    return
}

(Koruma gövdesi içinde, şu anda yürütülen kodun kapsamından çıkmak için kontrol aktarma deyimlerinden birini kullanmanız gerektiğini unutmayın ).

Eğer anOptionalIntbir değer içerir, bu Çizelgesi ve yeni atanmış olacak numbersabiti. Muhafızdan sonraki kod çalışmaya devam edecektir. Bir değer içermiyorsa - koruma kodu parantez içinde yürütür, bu da kontrolün aktarılmasına yol açar, böylece hemen sonraki kod yürütülmez.

Guard ifadeleriyle ilgili gerçek temiz şey, kaydırılmamış değerin artık ifadeyi izleyen kodda kullanılabilmesidir (gelecekteki kodun yalnızca isteğe bağlı bir değere sahipse çalışabileceğini bildiğimiz için ). Bu, birden fazla if ifadesini iç içe yerleştirerek oluşturulan 'kıyamet piramitlerini' ortadan kaldırmak için mükemmeldir .

Örneğin:

guard let number = anOptionalInt else {
    return
}

print("anOptionalInt contains a value, and it’s: \(number)!")

Muhafızlar ayrıca, if ifadesinin desteklediği aynı düzgün hileleri destekler; örneğin, aynı anda birden fazla seçeneğin paketinin açılması ve whereyan tümce kullanılması.

Bir if veya guard ifadesi kullanıp kullanmadığınız, gelecekteki herhangi bir kodun isteğe bağlı bir değer içermesini gerektirip gerektirmediğine bağlıdır .

Nil Birleştirme Operatörü

Nil Birleştirme Operatörü bir şık stenografi sürümü üçlü koşullu operatör öncelikle dışı seçeneklere için isteğe bağlı öğeleri dönüştürmek için tasarlanmıştır. Sözdizimine sahiptir a ?? b, burada aisteğe bağlı bir türdür ve baynı türdür a(genellikle isteğe bağlı olmasa da).

Temelde “ aBir değer içeriyorsa, paketini açın. bBunun yerine geri dönmezse ”. Örneğin, şu şekilde kullanabilirsiniz:

let number = anOptionalInt ?? 0

Bu , bir değer içeriyorsa değerini içeren başka bir tür numbersabitini tanımlayacaktır .IntanOptionalInt0

Sadece kısayol için:

let number = anOptionalInt != nil ? anOptionalInt! : 0

İsteğe Bağlı Zincirleme

Bir yöntemi çağırmak veya isteğe bağlı bir özelliğe erişmek için İsteğe Bağlı Zincirleme özelliğini kullanabilirsiniz . Bu, değişken adını ?kullanırken bir ile ekleyerek yapılır .

Örneğin, fooisteğe bağlı bir Fooörnek türünde bir değişkenimiz olduğunu varsayalım.

var foo : Foo?

Bir fooşey döndürmeyen bir yöntem çağırmak istersek, şunları yapabiliriz:

foo?.doSomethingInteresting()

Eğer foobir değer içerir, bu yöntem üzerinde çağrılır. Olmazsa, kötü bir şey olmaz - kod yürütmeye devam eder.

(Bu, nilObjective-C'de ileti göndermeye benzer davranıştır )

Bu nedenle bu, özellikleri ve çağrı yöntemlerini ayarlamak için de kullanılabilir. Örneğin:

foo?.bar = Bar()

Yine, hiçbir şey kötü burada da olacak fooolan nil. Kodunuz çalışmaya devam edecektir.

İsteğe bağlı zincirlemenin yapmasına izin veren bir başka düzgün hile, bir özellik ayarlamanın veya bir yöntemi çağırmanın başarılı olup olmadığını kontrol etmektir. Bunu, dönüş değerini ile karşılaştırarak yapabilirsiniz nil.

(Bunun nedeni, hiçbir şey döndürmeyen bir yöntem Void?yerine isteğe bağlı bir değerin Voiddöndürülmesidir)

Örneğin:

if (foo?.bar = Bar()) != nil {
    print("bar was set successfully")
} else {
    print("bar wasn’t set successfully")
}

Ancak, değer döndüren özelliklere veya çağrı yöntemlerine erişmeye çalışırken işler biraz daha zorlaşır. Çünkü fooisteğe bağlıdır, ondan dönen şey opsiyonel olacak. Bununla başa çıkmak için, yukarıdaki yöntemlerden birini kullanarak döndürülen seçeneklerin paketini açabilir veya fooyöntemlere erişmeden veya değerleri döndüren yöntemleri çağırmadan önce kendisini açabilirsiniz .

Ayrıca, adından da anlaşılacağı gibi, bu ifadeleri birbirine 'zincirleyebilirsiniz'. Bu foo, bir özelliği bazolan isteğe bağlı bir özelliğe sahipse qux, aşağıdakileri yazabileceğiniz anlamına gelir :

let optionalQux = foo?.baz?.qux

Yine foove bazisteğe bağlı olduğundan, kendisinden isteğe bağlı quxolup olmadığına bakılmaksızın, döndürülen değer her zaman isteğe quxbağlı olacaktır.

map ve flatMap

Optionals ile sıklıkla az kullanılan bir özellik mapve flatMapişlevlerini kullanma yeteneğidir . Bunlar isteğe bağlı olmayan dönüşümleri isteğe bağlı değişkenlere uygulamanızı sağlar. İsteğe bağlı bir değere sahipse, belirli bir dönüşümü uygulayabilirsiniz. Eğer bir değeri yoksa kalır nil.

Örneğin, isteğe bağlı bir dizeniz olduğunu varsayalım:

let anOptionalString:String?

mapFonksiyonu ona uygulayarak - stringByAppendingStringfonksiyonu başka bir dizeye birleştirmek için kullanabiliriz.

Çünkü stringByAppendingStringisteğe bağlı olmayan bir string argüman alıyor, doğrudan giriş opsiyonlu dize olamaz. Bununla birlikte, kullanarak map, bir değeri stringByAppendingStringvarsa izin kullanılmasını kullanabiliriz anOptionalString.

Örneğin:

var anOptionalString:String? = "bar"

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // Optional("foobar")

Ancak, anOptionalStringbir değeri mapyoksa dönecektir nil. Örneğin:

var anOptionalString:String?

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // nil

flatMapKapatma gövdesi içinden başka bir isteğe bağlı mapgeri dönmenize izin vermesi dışında, buna benzer şekilde çalışır . Bu, isteğe bağlı olmayan bir girdi gerektiren, ancak isteğe bağlı bir çıktı alabileceğiniz bir işleme isteğe bağlı girebileceğiniz anlamına gelir.

try!

Swift'in hata işleme sistemi Do-Try-Catch ile güvenle kullanılabilir :

do {
    let result = try someThrowingFunc() 
} catch {
    print(error)
}

Bir someThrowingFunc()hata atarsa, hata güvenli bir şekildecatch blokta .

errorEğer gördüğünüz sabit catchbloğu tarafımızca beyan edilmiş - otomatik araya getirilen catch.

Ayrıca errorkendinizi beyan edebilirsiniz , yararlı bir formata dökme avantajına sahiptir, örneğin:

do {
    let result = try someThrowingFunc()    
} catch let error as NSError {
    print(error.debugDescription)
}

tryBu şekilde kullanmak , fırlatma işlevlerinden kaynaklanan hataları denemenin, yakalamanın ve işlemenin doğru yoludur.

Orada da try?hata emdiği:

if let result = try? someThrowingFunc() {
    // cool
} else {
    // handle the failure, but there's no error information available
}

Ancak Swift'in hata işleme sistemi aşağıdakileri de "zorlamaya" zorlamanın bir yolunu sunar try!:

let result = try! someThrowingFunc()

Bu yayında açıklanan kavramlar burada da geçerlidir: bir hata atılırsa uygulama çökecektir.

Sadece try!sonucunun asla bağlamınızda başarısız olmayacağını kanıtlayabiliyorsanız kullanmalısınız - ve bu çok nadirdir.

Çoğu zaman try?, hatayı işlemenin önemli olmadığı nadir durumlarda , tam Do-Try-Catch sistemini ve isteğe bağlı olanı kullanırsınız .


kaynaklar


87
Şahsen, tüm bunları yazmak için çaba gösterdiğiniz için teşekkür ederim. Bunun yeni başlayanlar ve uzmanlar için hızlı bir şekilde yardımcı olacağını düşünüyorum.
Pranav Wadhwa

15
Yararlı bulduğuna sevindim. Bu cevap topluluk wiki'si, bu yüzden birçok insan arasında bir işbirliği (7, şimdiye kadar)!
jtbandes

1
Bu hata benim durumumda kesintili. Herhangi bir öneri? Kod stackoverflow.com/questions/50933681/...
py_ios_dev

1
Belki de bu cevabı kullanmak compactMap()yerine güncelleme zamanı flatMap().
Nicolas Miari

en iyi ve kısa çözümü "Nil Birleştirme Operatörü" sanırım, değil mi?
mehdigriche

65

TL; DR yanıtı

İle çok az istisna dışında , bu kural altındır:

Kullanmaktan kaçının !

Değişken isteğe bağlı ( ?), örtük olarak kaydırılmamış isteğe bağlı (IUO) ( !)

Başka bir deyişle, şunu kullanın:
var nameOfDaughter: String?

Onun yerine:
var nameOfDaughter: String!

if letVeya kullanarak isteğe bağlı değişkeni açguard let

Değişkenleri şu şekilde aç:

if let nameOfDaughter = nameOfDaughter {
    print("My daughters name is: \(nameOfDaughter)")
}

Veya bunun gibi:

guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")

Bu cevabın kısa olması amaçlandı, tam anlama için kabul edilen cevabı okuyun


kaynaklar


40

Bu soru TÜM ZAMANINDA SO geliyor. Yeni Swift geliştiricilerinin mücadele ettiği ilk şeylerden biri.

Arka fon:

Swift, bir değer içerebilecek veya içermeyecek değerlerle başa çıkmak için "İsteğe Bağlı" kavramını kullanır. C gibi diğer dillerde, değer içermediğini belirtmek için bir değişkende 0 değerini saklayabilirsiniz. Ancak, 0 geçerli bir değerse ne olur? O zaman -1 kullanabilirsiniz. -1 geçerli bir değerse ne olur? Ve bunun gibi.

Hızlı seçenek seçenekleri, geçerli bir değer içeren veya hiç değer içermeyen herhangi bir türde değişken ayarlamanıza olanak tanır.

Bir değişkeni ortalama olarak bildirdiğinizde (x türü veya değer yok) türden sonra soru işareti koyarsınız.

İsteğe bağlı olarak, belirli bir türde bir değişken içeren bir kap ya da hiçbir şey yoktur.

İçerideki değeri getirmek için isteğe bağlı bir "paketin açılması" gerekir.

"!" operatörü bir "kuvvet çözme" operatörüdür. "Güven bana. Ne yaptığımı biliyorum. Bu kod çalıştığında, değişken nil içermeyeceğini garanti ediyorum." Diyor. Eğer yanılıyorsanız, çökersiniz.

Gerçekten sürece do ne yaptığınızı biliyorum, kaçının "!" açma / kapama işleci. Muhtemelen Swift programcılarını başlatmak için en büyük çökme kaynağıdır.

Opsiyonellerle nasıl başa çıkılır:

Daha güvenli olan seçeneklerle başa çıkmanın birçok başka yolu vardır. İşte bazıları (kapsamlı bir liste değil)

Bu isteğe bağlı bir değer içeriyorsa, "isteğe bağlı bağlama" veya "eğer" diyelim "ifadesini kullanabilirsiniz. Bu değeri yeni, isteğe bağlı olmayan bir değişkene kaydedin. İsteğe bağlı bir değer içermiyorsa, bu if ifadesinin gövdesini atlayın ".

İşte isteğe bağlı bağlantımızla ilgili isteğe bağlı bir örnek foo:

if let newFoo = foo //If let is called optional binding. {
  print("foo is not nil")
} else {
  print("foo is nil")
}

İsteğe bağlı teklifi kullandığınızda tanımladığınız değişkenin, if ifadesinin gövdesinde yalnızca (yalnızca "kapsamda") olduğunu unutmayın.

Alternatif olarak, değişken nil ise işlevinizden çıkmanıza izin veren bir guard deyimi kullanabilirsiniz:

func aFunc(foo: Int?) {
  guard let newFoo = input else { return }
  //For the rest of the function newFoo is a non-optional var
}

Guard ifadeleri Swift 2'ye eklenmiştir. Guard, kodunuz aracılığıyla "altın yolu" korumanıza ve bazen "eğer izin verirse" isteğe bağlı bağlamanın kullanılmasından kaynaklanan sürekli artan iç içe düzeylerden kaçınmanıza olanak tanır.

"Nil birleştirici operatör" adı verilen bir yapı da vardır. "İsteğe bağlı_var ?? replacement_val" biçimini alır. İsteğe bağlı olarak bulunan verilerle aynı türde isteğe bağlı olmayan bir değişken döndürür. İsteğe bağlı nil içeriyorsa, ifadenin değerini "??" sembolü.

Böylece şöyle bir kod kullanabilirsiniz:

let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")

Deneme / yakalama veya koruma hatası işlemeyi de kullanabilirsiniz, ancak genellikle yukarıdaki diğer tekniklerden biri daha temizdir.

DÜZENLE:

Optionals ile biraz daha ince bir gotcha "örtük olarak sarılmış opsiyoneller. Foo beyan ettiğimizde şunu söyleyebiliriz:

var foo: String!

Bu durumda foo hala isteğe bağlıdır, ancak referans vermek için paketini açmanız gerekmez. Bu, her ne zaman foo'ya başvurmaya çalıştığınızda nil olursa çökersiniz.

Yani bu kod:

var foo: String!


let upperFoo = foo.capitalizedString

Foo'nun capitalizedString özelliğine referansla çökecek, ancak zorla sarma foo olmamasına rağmen. baskı iyi görünüyor, ama değil.

Böylece örtük olarak sarılmamış seçeneklere gerçekten dikkat etmek istersiniz. (ve seçeneklerle ilgili sağlam bir anlayışa sahip olana kadar onlardan tamamen kaçınabilirsiniz.)

Alt satır: Swift'i ilk öğrendiğinde, "!" karakter dilin bir parçası değil. Büyük olasılıkla sizi başınız belaya sokacak.


7
Bunu herkese açık bir wiki yapmayı düşünmelisiniz.
vacawama

3
Cevabınızı düzenleyin ve sağdaki düzenleme kutusunun altında bir topluluk wiki onay kutusu bulunur. Artık cevap için destek almayacaksınız, ama bunun zaten açık bir temsilci olmadığını biliyorum.
vacawama

6
Bu sorunun sitede milyonlarca kez sorulduğu göz önüne alındığında, soruna kanonik cevap olarak kendi kendine cevaplanan bir soru göndermek için zaman ayıracak olsaydım, cevabın bundan çok daha eksiksiz olduğunu umarım. guardMaddeler hakkında hiçbir şey yok . Kullanımı hakkında if vargeçerli bir yapı kadar hiçbir şey yoktur if let. Bağlama wherehakkında konuştuğumuzda bahsetmeye değer hissettiğim maddeler hakkında hiçbir şey yoktur if let(birçok durumda tüm iç içe geçme katmanını kaldırır). İsteğe bağlı zincirleme hakkında hiçbir şey yoktur.
nhgrif

6
Ve örtük olarak açılmamış opsiyonellerden bahsettiğinizde, örtük olarak açılmamış opsiyonelleri güvenle açmak için yukarıda belirtilen tüm isteğe bağlı hileleri kullanabileceğinizden bile bahsetmezsiniz. Aynı maddede birden fazla seçeneğin paketinin açılması da ele alınmıyor.
nhgrif

1
@nhgrif, "Deneme / yakalama veya koruma hatası işlemeyi de kullanabilirsiniz, ancak genellikle yukarıdaki diğer tekniklerden biri daha temizdir" dedim. Kesinlikle iyi puanların var. Bu oldukça büyük bir konu. Keskin nişan yorumları yapmak yerine yapmadığım şeyleri kapsayan kendi yanıtınıza katkıda bulunmaya ne dersiniz? Bu şekilde soru ve cevapları site için daha iyi bir varlık haline gelir.
Duncan C

13

Yukarıdaki yanıtlar, Seçeneklerle nasıl güvenli bir şekilde oynanacağını açıkça açıkladığından. Seçeneklerin gerçekten hızlı bir şekilde neler olduğunu açıklamaya çalışacağım.

İsteğe bağlı bir değişken bildirmenin başka bir yolu da

var i : Optional<Int>

İsteğe bağlı tür, iki durumlu bir numaralandırmadan başka bir şey değildir, yani

 enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none 
    case some(Wrapped)
    .
    .
    .
}

Yani bizim i 'değişkenimize nil atamak. Biz yapabiliriz var i = Optional<Int>.none veya bir değer atamak için, bazı değer geçecek var i = Optional<Int>.some(28)

Hızlıya göre, 'nil' değerin olmamasıdır. Ve ile başlatılan bir örnek oluşturmak için Tahmin nilettiğimizde ExpressibleByNilLiteralve büyük olarak adlandırılan bir protokole Optionalsuymak zorundayız, sadece ExpressibleByNilLiteraldiğer türlere uymak ve bunlara uymak cesaretini kırıyor.

ExpressibleByNilLiteralinit(nilLiteral:)Nil ile bir instans başlatan denilen tek bir yöntem var . Genellikle bu yöntemi çağırmazsınız ve hızlı belgelere göre, hazırlayıcı ile isteğe bağlı bir tür başlattığınızda derleyici çağırdığı için bu başlatıcıyı doğrudan çağırmak önerilmez nil.

Hatta kendimi bile kafamın etrafına sarmak zorunda kaldım (Seçenek yok: D Mutlu Swfting Hepsi) .


12

İlk olarak, İsteğe bağlı bir değerin ne olduğunu bilmelisiniz. Ayrıntılar için Hızlı Programlama Diline geçebilirsiniz .

İkincisi, isteğe bağlı değerin iki durumu olduğunu bilmelisiniz. Biri tam değer, diğeri sıfır değerdir. Bu nedenle, isteğe bağlı bir değer uygulamadan önce, bunun hangi durumda olduğunu kontrol etmelisiniz.

Sen kullanabilir if let ...veya guard let ... elsevb.

Başka bir yol, uygulamanızdan önce değişken durumunu kontrol etmek istemiyorsanız, var buildingName = buildingName ?? "buildingName"bunun yerine de kullanabilirsiniz .


8

Çıkışlarımı hazırlamak için segue yönteminden aşağıdaki gibi çıkışları ayarlamaya çalıştığımda bir kez bu hatayı aldım:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let destination = segue.destination as? DestinationVC{

        if let item = sender as? DataItem{
            // This line pops up the error
            destination.nameLabel.text = item.name
        }
    }
}

Sonra denetleyici henüz yüklenmediği veya başlatılmadığı için hedef denetleyici çıkışlarının değerlerini ayarlayamadığımı öğrendim.

Bu şekilde çözdüm:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let destination = segue.destination as? DestinationVC{

        if let item = sender as? DataItem{
            // Created this method in the destination Controller to update its outlets after it's being initialized and loaded
            destination.updateView(itemData:  item)
        }
    }
}

Hedef Denetleyici:

// This variable to hold the data received to update the Label text after the VIEW DID LOAD
var name = ""

// Outlets
@IBOutlet weak var nameLabel: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
    nameLabel.text = name
}

func updateView(itemDate: ObjectModel) {
    name = itemDate.name
}

Umarım bu cevap, işaretli cevabın seçeneklerin ve nasıl çalıştıklarının anlaşılması için harika bir kaynak olduğunu ancak sorunun doğrudan ele alınmadığını tespit ettiğimde aynı sorunla herkese yardımcı olur.


Sadece updateViewhedef kontrol
Ahmad F

@AhmadF iyi bir nokta. Oysa çağıran updateViewkullanıyorum gibi hedef denetleyicisi bu durumda gerekli değildir nameayarlamak için değişken nameLabel.textiçinde viewDidLoad. Ancak, çok fazla kurulum yapsaydık, başka bir işlev oluşturmak ve viewDidLoadbunun yerine onu çağırmak daha iyi olurdu .
Wissa

7

Temel olarak, derleyicinin orada asla sıfır değerinin olmayacağına güvenmesini ve böylece uygulamanızın derlenmesine izin vermesini söyleyerek Swift'in yalnızca sıfır olmayanlara izin verdiği yerlerde sıfır değer kullanmaya çalıştınız.

Bu tür ölümcül hatalara yol açan birkaç senaryo vardır:

  1. zorla açma:

    let user = someVariable!

    Eğer someVariablesıfırdır, o zaman bir kilitlenme alırsınız. Bir kuvvet çözme yaparak, nil çek sorumluluğunu derleyiciden size taşıdınız, temelde zorla bir çözme yaparak derleyiciye asla sıfır değerlerinin olmayacağını garanti ediyorsunuz. Ve bir şekilde sıfır değeri içeri girerse ne olur tahmin edin someVariable?

    Çözüm? İsteğe bağlı ciltleme kullanın (if-let olarak da bilinir), orada değişken işlemi yapın:

    if user = someVariable {
        // do your stuff
    }
  2. zorla (aşağı) atma sayısı:

    let myRectangle = someShape as! Rectangle

    Burada zorla döküm yaparak derleyiciye artık endişelenmemesini söylersiniz, çünkü her zaman Rectangleorada bir örneğiniz olur. Ve bu geçerli olduğu sürece endişelenmenize gerek yok. Projeden siz veya iş arkadaşlarınız dikdörtgen olmayan değerleri dolaşmaya başladığında sorunlar başlar.

    Çözüm? İsteğe bağlı ciltleme kullanın (if-let olarak da bilinir), orada değişken işlemi yapın:

    if let myRectangle = someShape as? Rectangle {
        // yay, I have a rectangle
    }
  3. Örtük olarak açılmamış isteğe bağlı seçenekler. Aşağıdaki sınıf tanımına sahip olduğunuzu varsayalım:

    class User {
        var name: String!
    
        init() {
            name = "(unnamed)"
        }
    
        func nicerName() {
            return "Mr/Ms " + name
        }
    }

    Şimdi, hiç kimse nameözelliği ayarlayarak bozmazsa nil, beklendiği gibi çalışır, ancak anahtar Useriçermeyen bir JSON'dan başlatılırsa name, özelliği kullanmaya çalışırken ölümcül hatayı alırsınız.

    Çözüm? Onları kullanmayın :) Mülkün kullanılması gerektiğinde her zaman sıfır olmayan bir değere sahip olacağından% 102 emin değilseniz. Çoğu durumda isteğe bağlı veya isteğe bağlı olmayan bir dönüştürme işlemi işe yarayacaktır. İsteğe bağlı olmayan bir hale getirmek, derleyicinin kaçırdığınız kod yollarını bu özelliğe bir değer vererek söyleyerek size yardımcı olmasına neden olur.

  4. Bağlantısız veya henüz bağlı olmayan prizler. Bu senaryo # 3 için özel bir durumdur. Temel olarak, kullanmak istediğiniz XIB yüklü bir sınıfınız var.

    class SignInViewController: UIViewController {
    
        @IBOutlet var emailTextField: UITextField!
    }

    Şimdi XIB editörünün çıkışını bağlamayı kaçırdıysanız, uygulama çıkışı kullanmak istediğiniz anda çökecektir. Çözüm? Tüm çıkışların bağlı olduğundan emin olun. Veya kullanmak ?onlara operatörünü: emailTextField?.text = "my@email.com". Ya da çıkışı isteğe bağlı olarak bildirin, ancak bu durumda derleyici sizi tüm kod boyunca açmaya zorlayacaktır.

  5. Objective-C'den gelen ve nullabilite ek açıklamalarına sahip olmayan değerler. Aşağıdaki Objective-C sınıfına sahip olduğumuzu varsayalım:

    @interface MyUser: NSObject
    @property NSString *name;
    @end

    Şimdi hiçbir açıklık belirtimi belirtilmezse (açıkça veya NS_ASSUME_NONNULL_BEGIN/ aracılığıyla NS_ASSUME_NONNULL_END), nameözellik Swift'e String!(bir IUO - örtük olarak paketlenmemiş isteğe bağlı) olarak aktarılır . Bazı hızlı kodlar değeri kullanmak isteyecek olursa name, nil ise çökecektir.

    Çözüm? Objective-C kodunuza sıfırlanabilirlik ek açıklamaları ekleyin. Yine de, Objective-C derleyicisi nullabilite söz konusu olduğunda biraz izin vericidir, bunları açıkça işaretlemiş olsanız bile nil değerlerle sonuçlanabilirsiniz nonnull.


2

Bu daha önemli bir yorumdur ve nedenleri, hata ayıklama nildeğerleri söz konusu olduğunda örtük olarak açılmamış seçeneklerin aldatıcı olabilmesidir .

Aşağıdaki kodu düşünün: Hata / uyarı olmadan derlenir:

c1.address.city = c3.address.city

Ancak çalışma zamanında aşağıdaki hatayı verir: Önemli hata: İsteğe bağlı bir değer açılırken beklenmedik bir şekilde nil bulundu

Bana hangi nesnenin olduğunu söyleyebilir misin nil?

Yapamazsın!

Tam kod şöyle olacaktır:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var c1 = NormalContact()
        let c3 = BadContact()

        c1.address.city = c3.address.city // compiler hides the truth from you and then you sudden get a crash
    }
}

struct NormalContact {
    var address : Address = Address(city: "defaultCity")
}

struct BadContact {
    var address : Address!
}

struct Address {
    var city : String
}

Kullanarak uzun hikaye kısa var address : Address! sen ediyoruz gizleme bir değişken olabilir ihtimalini nildiğer okuyuculardan. Ve çöktüğü zaman "ne cehennem address?!

Bu nedenle böyle yazmak daha iyidir:

c1.address.city = c2.address!.city  // ERROR:  Fatal error: Unexpectedly found nil while unwrapping an Optional value 

Şimdi bana bunun hangi nesne olduğunu söyleyebilir misiniz nil?

Bu sefer kod sizin için daha açık hale getirildi. Rasyonelleştirebilir ve addresszorla çözülmemiş bir parametre olduğunu düşünebilirsiniz .

Tam kod şöyle olacaktır:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var c1 = NormalContact()
        let c2 = GoodContact()

        c1.address.city = c2.address!.city
        c1.address.city = c2.address?.city // not compile-able. No deceiving by the compiler
        c1.address.city = c2.address.city // not compile-able. No deceiving by the compiler
        if let city = c2.address?.city {  // safest approach. But that's not what I'm talking about here. 
            c1.address.city = city
        }

    }
}

struct NormalContact {
    var address : Address = Address(city: "defaultCity")
}

struct GoodContact {
    var address : Address?
}

struct Address {
    var city : String
}

2

Hatalar EXC_BAD_INSTRUCTIONve fatal error: unexpectedly found nil while implicitly unwrapping an Optional valueen çok @IBOutlet, bir film şeridine bağlı değil, ancak .

Ayrıca , diğer cevaplarda bahsedilen Seçeneklerin nasıl çalıştığını da öğrenmelisiniz , ancak bu bana en çok görünen tek zamandır.


@IBOutletBu hatanın nedeni Önemli hataya sahip olmamalıdır : Hatanın isteğe bağlı değeri sürümünü dolaylı olarak açarken beklenmedik bir şekilde nil bulundu ?
pkamb

1
Bu doğru. Belki cevabı gönderdiğimde bunu kastediyorum ve ilk ölümcül hata mesajını kopyaladım. Hamiş'in yanıtı bu konuda çok eksiksiz görünüyor.
Ale Mohamad

2

CollectionView bu hatayı alırsanız, ayrıca CustomCell dosyası ve Custom xib oluşturmaya çalışın.

mainVC'deki ViewDidLoad () içine bu kodu ekleyin.

    let nib = UINib(nibName: "CustomnibName", bundle: nil)
    self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")

0

Bir tablo görünümü denetleyicisinden bir görünüm denetleyicisine segue yaparken bu hatayla karşılaştım çünkü ana film şeridinde görünüm denetleyicisi için özel sınıf adını belirtmeyi unutmuştum.

Her şeyin yolunda olup olmadığını kontrol etmeye değer basit bir şey

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.