545 Stimmen

Was bedeutet ein Ausrufezeichen in der Swift-Sprache?

Der Leitfaden zur Swift-Programmiersprache hat das folgende Beispiel:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { println("\(name) wird deinitialisiert") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { println("Apartment Nr. \(number) wird deinitialisiert") }
}

var john: Person?
var number73: Apartment?

john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)

// Aus dem Leitfaden "The Swift Programming Language" von Apple (https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html)

Dann, wenn das Apartment der Person zugewiesen wird, benutzen sie ein Ausrufezeichen, um die Instanz "auszupacken":

john!.apartment = number73

Was bedeutet es, eine Instanz "auszupacken"? Warum ist es notwendig? Wie unterscheidet es sich davon, einfach folgendes zu tun:

john.apartment = number73

Ich bin ganz neu in der Swift-Sprache. Versuche nur, die Grundlagen zu verstehen.

UPDATE:
Das große Puzzlestück, das mir fehlte (nicht direkt in den Antworten angegeben - zumindest nicht zum Zeitpunkt des Verfassens dieses Beitrags), ist, dass wenn man Folgendes tut:

var john: Person?

das NICHT bedeutet, dass "john vom Typ Person ist und vielleicht nil sein könnte", wie ich ursprünglich dachte. Ich habe einfach missverstanden, dass Person und Person? vollständig separate Typen sind. Sobald ich das verstanden hatte, ergaben alle anderen ?, ! Verwirrungen und die großartigen Antworten unten viel mehr Sinn.

551voto

Ashley Punkte 5805

Was bedeutet es, eine Instanz "auszupacken"? Warum ist es notwendig?

Soweit ich das herausfinden kann (das ist für mich auch sehr neu)...

Der Begriff "verpackt" impliziert, dass wir eine optionale Variable als ein Geschenk betrachten sollen, das in glänzendes Papier eingewickelt ist und möglicherweise (leider!) leer sein könnte.

Wenn "verpackt", ist der Wert einer optionalen Variable ein Enum mit zwei möglichen Werten (ähnlich einem Boolean). Dieses Enum beschreibt, ob die Variable einen Wert enthält (Some(T)) oder nicht (None).

Wenn ein Wert vorhanden ist, kann dieser durch "Auspacken" der Variable erhalten werden (Erhalten des T von Some(T)).

Was ist der Unterschied zwischen john!.apartment = number73 und john.apartment = number73? (Neuformuliert)

Wenn Sie den Namen einer optionalen Variable schreiben (z. B. den Text john ohne das !), bezieht sich dies auf das "verpackte" Enum (Some/None), nicht auf den Wert selbst (T). Also ist john keine Instanz von Person und hat kein apartment-Element:

john.apartment
// 'Person?' hat kein Element namens 'apartment'

Der tatsächliche Wert von Person kann auf verschiedene Arten ausgepackt werden:

  • "erzwungenes Auspacken": john! (gibt den Person-Wert zurück, wenn er vorhanden ist, Laufzeitfehler, wenn er nil ist)
  • "optionales Binden": if let p = john { println(p) } (führt das println aus, wenn der Wert vorhanden ist)
  • "optionale Verkettung": john?.learnAboutSwift() (führt diese erfundene Methode aus, wenn der Wert vorhanden ist)

Ich vermute, dass Sie je nachdem, was im nil-Fall passieren soll und wie wahrscheinlich das ist, eine dieser Methoden zum Auspacken wählen. Dieses Sprachdesign zwingt dazu, den nil-Fall explizit zu behandeln, was die Sicherheit im Vergleich zu Obj-C erhöht (wo es leicht ist, den nil-Fall zu vergessen).

Aktualisierung:

Das Ausrufezeichen wird auch in der Syntax für die Deklaration von "Implicitly Unwrapped Optionals" verwendet.

In den bisherigen Beispielen wurde die Variable john als var john:Person? deklariert und ist optional. Wenn Sie den tatsächlichen Wert dieser Variablen benötigen, müssen Sie ihn auf eine der oben genannten Arten auspacken.

Wenn es stattdessen als var john:Person! deklariert wäre, wäre die Variable ein Implizit Entpacktes Optional (siehe den Abschnitt mit diesem Titel im Apple-Buch). Diese Art von Variable muss nicht beim Zugriff auf den Wert entpackt werden, und john kann ohne zusätzliche Syntax verwendet werden. Aber das Apple-Buch sagt:

Implizit entpackte Optionale sollten nicht verwendet werden, wenn die Möglichkeit besteht, dass eine Variable später nil wird. Verwenden Sie immer einen normalen optionalen Typ, wenn Sie während der Lebensdauer einer Variablen nach einem nil-Wert suchen müssen.

Aktualisierung 2:

Der Artikel "Interessante Swift-Features" von Mike Ash gibt einige Gründe für optionale Typen. Ich finde, er schreibt sehr klar.

Aktualisierung 3:

Ein weiterer nützlicher Artikel über die Verwendung des implizit entpackten Optionals für das Ausrufezeichen: "Swift und der letzte Kilometer" von Chris Adamson. Der Artikel erklärt, dass dies eine pragmatische Maßnahme von Apple ist, um die Typen zu deklarieren, die ihre Objective-C-Frameworks enthalten könnten, die nil enthalten. Die Deklaration eines Typs als optional (mit ?) oder implizit entpackt (mit !) ist "ein Kompromiss zwischen Sicherheit und Bequemlichkeit". In den in dem Artikel gegebenen Beispielen hat Apple sich dafür entschieden, die Typen als implizit entpackt zu deklarieren, um den Aufrufcode bequemer, aber weniger sicher zu machen.

Vielleicht wird Apple in Zukunft ihre Frameworks durchsuchen und die Ungewissheit von implizit entpackten ("wahrscheinlich nie nil") Parametern beseitigen und sie durch optionale ("könnte sicherlich in bestimmten (hoffentlich dokumentierten!) Umständen nil sein") oder Standard-nicht-optionale ("ist nie nil") Deklarationen ersetzen, basierend auf dem genauen Verhalten ihres Objective-C-Codes.

130voto

Amr Punkte 2076

Hier ist, was ich denke, der Unterschied ist:

var john: Person?

Bedeutet, dass john null sein kann

john?.apartment = number73

Der Compiler wird diese Zeile interpretieren als:

if john != nil {
    john.apartment = number73
}

Während

john!.apartment = number73

Der Compiler wird diese Zeile einfach interpretieren als:

john.apartment = number73

Daher wird durch Verwendung von ! die if-Anweisung ausgepackt und führt schneller aus, aber wenn john null ist, wird ein Laufzeitfehler auftreten.

Also bedeutet "unwrap" hier nicht, dass es speicherumwickelt ist, sondern es bedeutet, dass es code-umwickelt ist, in diesem Fall ist es mit einer if-Anweisung umwickelt, und weil Apple während der Laufzeit viel Wert auf Leistung legt, möchten sie Ihnen eine Möglichkeit geben, Ihre App mit der bestmöglichen Leistung laufen zu lassen.

Update:

Nach 4 Jahren komme ich zu dieser Antwort zurück, da ich den höchsten Ruf dafür auf Stackoverflow erhalten habe :) Ich habe damals die Bedeutung des Auspackens ein wenig falsch verstanden. Jetzt, nach 4 Jahren, glaube ich, dass die Bedeutung des Auspackens hier ist, den Code von seiner ursprünglichen kompakten Form zu erweitern. Es bedeutet auch, die Unklarheiten um dieses Objekt zu entfernen, da wir per Definition nicht sicher sind, ob es null ist oder nicht. Genauso wie die Antwort von Ashley oben, denken Sie daran wie ein Geschenk, das nichts enthält. Aber ich denke immer noch, dass das Auspacken Codeauspacken ist und nicht auf Speicher basiertes Auspacken, wie bei der Verwendung von enum.

71voto

Alex Nolasco Punkte 17867

TL;DR

Was bedeutet ein Ausrufezeichen in der Swift-Sprache?

Das Ausrufezeichen sagt effektiv: „Ich weiß, dass dieses Optionale definitiv einen Wert hat; bitte benutze ihn.“ Dies wird als erzwungenes Entpacken des Werts des Optionals bezeichnet:

Beispiel

let possibleString: String? = "Ein optionaler String."
print(possibleString!) // erfordert ein Ausrufezeichen, um auf seinen Wert zuzugreifen
// gibt "Ein optionaler String." aus

let assumedString: String! = "Ein implizit entpackter optionaler String."
print(assumedString)  // kein Ausrufezeichen benötigt, um auf seinen Wert zuzugreifen
// gibt "Ein implizit entpackter optionaler String." aus

Quelle: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_399

37voto

Ben Gottlieb Punkte 84876

Wenn John eine optionale Variable wäre (so deklariert)

var john: Person?

dann wäre es möglich, dass john keinen Wert hat (in der ObjC-Sprache, nil-Wert)

Das Ausrufezeichen sagt im Grunde dem Compiler: "Ich weiß, dass hier ein Wert ist, du brauchst nicht darauf zu testen". Wenn du es nicht verwenden möchtest, könntest du bedingt darauf testen:

if let otherPerson = john {
    otherPerson.apartment = number73
}

Der Inhalt davon wird nur ausgewertet, wenn john einen Wert hat.

35voto

Paul Cantrell Punkte 8855

Einige großbildliche Perspektiven, um den anderen nützlichen, aber detailzentrierten Antworten hinzuzufügen:

In Swift erscheint das Ausrufezeichen in verschiedenen Kontexten:

  • Forced Unwrapping: let name = nameLabel!.text
  • Implicitly unwrapped Optionals: var logo: UIImageView!
  • Forced Casting: logo.image = thing as! UIImage
  • Unerwünschte Ausnahmen: try! NSJSONSerialization.JSONObjectWithData(data, [])

Jedes dieser Konstrukte ist eine andere Sprachkonstruktion mit einer anderen Bedeutung, aber sie haben alle drei wichtige Dinge gemeinsam:

1. Ausrufezeichen umgehen die Fehlerbehandlungsprüfungen von Swift zur Kompilierzeit.

Wenn Sie in Swift ! verwenden, sagen Sie im Grunde genommen: "Hey, Compiler, ich weiß, du denkst, dass hier möglicherweise ein Fehler passieren könnte, aber ich weiß mit absoluter Sicherheit, dass es nie passieren wird."

(Bearbeitung zur Klarstellung: Das erzwungene Entpacken mit ! ist immer noch "sicher" im Sinne des Sprachdesigns, dass es kein undefiniertes Verhalten ist und garantiert in einer sauberen, vorhersehbaren Weise abstürzt (im Gegensatz zum Dereferenzieren eines Null-Zeigers in C oder C++). Es ist jedoch nicht "sicher" im Sinne des App-Entwicklers, dass die Sprache sicherstellt, dass Ihre App nicht plötzlich wegen Ihrer falschen Annahmen abstürzt.)

Nicht alle gültigen Codes passen in das Schema des statischen Typsystems von Swift – oder in das statische Typenprüfung irgendeiner Sprache, um genau zu sein. Es gibt Situationen, in denen Sie logisch beweisen können, dass ein Fehler nie passieren wird, aber Sie können es dem Compiler nicht beweisen. Deshalb haben die Designer von Swift diese Funktionen von Anfang an hinzugefügt.

Jedoch, wenn Sie ! verwenden, schließen Sie aus, einen Wiederherstellungsweg für einen Fehler zu haben, was bedeutet, dass…

2. Ausrufezeichen sind potenzielle Abstürze.

Ein Ausrufezeichen sagt auch: "Hey Swift, ich bin so sicher, dass dieser Fehler niemals passieren kann, dass es besser für dich ist, meine ganze App zum Absturz zu bringen, als dass ich einen Wiederherstellungsweg dafür programmiere."

Das ist eine gefährliche Behauptung. Es kann die richtige sein: In kritischen Codebereichen, in denen Sie sorgfältig über die Invarianten Ihres Codes nachgedacht haben, kann es sein, dass fehlerhafte Ausgaben schlimmer sind als ein Absturz.

Jedoch, wenn ich ! in freier Wildbahn sehe, wird es selten so bewusst verwendet. Stattdessen bedeutet es allzu oft: "Dieser Wert war optional und ich habe nicht wirklich darüber nachgedacht, wieso er nil sein könnte oder wie ich diese Situation ordnungsgemäß behandeln sollte, aber durch das Hinzufügen von ! wurde es kompiliert … also ist mein Code korrekt, oder?"

Hüten Sie sich vor der Arroganz des Ausrufezeichens. Stattdessen…

3. Ausrufezeichen sollten sparsam verwendet werden.

Jedes dieser !-Konstrukte hat ein Gegenstück mit ?, das Sie zwingt, mit dem Fehler/nil-Fall umzugehen:

  • Bedingtes Entpacken: if let name = nameLabel?.text { ... }
  • Optionals: var logo: UIImageView?
  • Bedingte Casts: logo.image = thing as? UIImage
  • Nil-bei-Fehler Ausnahmen: try? NSJSONSerialization.JSONObjectWithData(data, [])

Wenn Sie versucht sind, ! zu verwenden, ist es immer gut, sorgfältig zu überlegen, warum Sie nicht stattdessen ? verwenden. Ist das Abstürzen Ihres Programms wirklich die beste Option, wenn die !-Operation fehlschlägt? Warum ist dieser Wert optional/fehlbar?

Gibt es einen vernünftigen Wiederherstellungsweg, den Ihr Code im nil/Fehlerfall nehmen könnte? Wenn ja, programmieren Sie es.

Wenn es unmöglich ist, dass es nil ist, dass der Fehler niemals passieren kann, gibt es dann einen vernünftigen Weg, Ihre Logik umzuarbeiten, so dass der Compiler das weiß? Falls ja, tun Sie es; Ihr Code wird fehleranfälliger sein.

Es gibt Zeiten, in denen es keinen vernünftigen Weg gibt, mit einem Fehler umzugehen, und den Fehler zu ignorieren – und somit mit falschen Daten fortzufahren – wäre schlimmer als abzustürzen. Das sind die Zeiten, in denen Sie das erzwungene Entpacken verwenden sollten.

Ich suche regelmäßig meinen gesamten Codebestand nach ! und überprüfe jede Verwendung davon. Sehr wenige Verwendungen halten einer Überprüfung stand. (Zum Zeitpunkt des Schreibens hat das gesamte Siesta-Framework genau zwei Instanzen davon.)

Das heißt nicht, dass Sie nie ! in Ihrem Code verwenden sollten – nur, dass Sie es bewusst verwenden sollten und es niemals zur Standardoption machen sollten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X