Po co tworzyć "domyślnie rozpakowane opcje", ponieważ oznacza to, że wiesz, że istnieje wartość?

Dlaczego tworzysz "Unwrapped Optional" zamiast tworzyć zwykłą zmienną lub stałą? Jeśli wiesz, że można go z powodzeniem rozpakować, to po co w ogóle tworzyć opcję? Na przykład, dlaczego tak jest:

let someString: String! = "this is the string"

Będzie bardziej przydatny niż:

let someString: String = "this is the string"

Jeśli " opcje wskazują, że stała lub zmienna może mieć 'brak wartości', ale " czasami jest jasne ze struktury programu, że opcja zawsze będzie miała value after that value is first set", what is the point of making it an optional in the first place? Jeśli wiesz, że opcja zawsze będzie miała wartość, czy to nie znaczy, że nie jest opcjonalna?

Author: Gabriel Fonseca, 2014-06-03

10 answers

Rozważ przypadek obiektu, który podczas konstruowania i konfigurowania może mieć zerowe właściwości, ale później jest niezmienny i nie-zerowy (NSImage jest często traktowany w ten sposób, choć w jego przypadku nadal jest przydatne do mutacji). Domyślnie rozpakowane opcje wyczyściłyby jego kod, przy stosunkowo niskiej utracie bezpieczeństwa (tak długo, jak tylko posiadana jest jedna gwarancja, byłaby bezpieczna).

(Edit) dla jasności: zwykłe opcje są prawie zawsze preferowane.

 131
Author: Catfish_Man,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-03 04:15:58

Zanim opiszę przypadki użycia opcji domyślnie rozpakowanych, powinieneś już zrozumieć, jakie opcje i opcje domyślnie rozpakowane są w Swift. Jeśli nie, polecam najpierw przeczytać mój artykuł o optionals

Kiedy Korzystać Z Niejawnie Rozpakowanego Opcjonalnego

Istnieją dwa główne powody, dla których można by stworzyć domyślnie rozpakowaną opcję. Wszystko ma związek z definiowaniem zmiennej, która nigdy nie będzie dostępna, gdy nil, ponieważ w przeciwnym razie kompilator Swift zawsze zmusi cię do jawnego rozpakowania opcji.

1. Stała, Której Nie Można Zdefiniować Podczas Inicjalizacji

Każda stała członkowska musi mieć wartość przed zakończeniem inicjalizacji. Czasami stała nie może być zainicjalizowana poprawną wartością podczas inicjalizacji, ale nadal można zagwarantować, że ma wartość przed uzyskaniem dostępu.

Użycie opcjonalnej zmiennej rozwiązuje ten problem, ponieważ opcjonalna jest automatycznie zainicjalizowana za pomocą nil, a wartość, którą ostatecznie będzie zawierać, nadal będzie niezmienna. Jednak może to być ból, aby stale rozpakowywać zmienną, że wiesz na pewno nie jest zero. Domyślnie rozpakowane opcje osiągają te same korzyści, co Opcjonalne z dodatkową korzyścią, że nie trzeba go wyraźnie rozpakowywać wszędzie.

Doskonałym tego przykładem jest sytuacja, gdy zmienna Członkowska nie może być zainicjalizowana w podklasie UIView, dopóki widok nie zostanie załadowany:

class MyView: UIView {
    @IBOutlet var button: UIButton!
    var buttonOriginalWidth: CGFloat!

    override func awakeFromNib() {
        self.buttonOriginalWidth = self.button.frame.size.width
    }
}

Tutaj nie można obliczyć oryginalnej szerokości przycisku do momentu załadowania widoku, ale wiesz, że awakeFromNib zostanie wywołana przed jakąkolwiek inną metodą w widoku (inną niż inicjalizacja). Zamiast zmuszać wartość do jawnego rozpakowywania na całej klasie, możesz zadeklarować ją jako domyślnie rozpakowaną opcję.

2. Gdy aplikacja nie może odzyskać zmiennej nil

To powinno być niezwykle rzadkie, ale jeśli Twoja aplikacja może nie uruchamiać, jeśli zmienna jest nil po uzyskaniu dostępu, byłoby stratą czasu na testowanie jej pod kątem nil. Zwykle, jeśli masz warunek, który musi być absolutnie prawdziwy, aby Twoja aplikacja nadal działa, użyjesz assert. Domyślnie rozpakowana opcja ma wbudowane assert for nil. Nawet wtedy, często dobrze jest rozpakować opcjonalne i użyć bardziej opisowego twierdzenia, jeśli jest zerowe.

Kiedy Nie Używać Niejawnie Rozpakowanego Opcjonalnego

1. Leniwie Obliczone Zmienne Członkowskie

Czasami masz zmienną członkowską, która nigdy nie powinna być nil, ale nie może być ustawiona na prawidłową wartość podczas inicjalizacji. Jednym z rozwiązań jest użycie niejawnie rozpakowanego opcjonalnego, ale lepszym sposobem jest użycie leniwej zmiennej:

class FileSystemItem {
}

class Directory : FileSystemItem {
    lazy var contents : [FileSystemItem] = {
        var loadedContents = [FileSystemItem]()
        // load contents and append to loadedContents
        return loadedContents
    }()
}

Teraz zmienna członkowska contents nie jest inicjalizowana do momentu pierwszego dostępu do niej. Daje to klasie szansę na uzyskanie prawidłowego stanu przed obliczeniem początkowego wartość.

Uwaga: może się to wydawać sprzeczne z #1 z góry. Należy jednak dokonać ważnego rozróżnienia. buttonOriginalWidth powyżej musi być ustawiona podczas viewDidLoad, aby zapobiec zmianie szerokości przycisków przed uzyskaniem dostępu do właściwości.

2. Everywhere Else

W większości przypadków należy unikać domyślnie rozpakowanych opcji, ponieważ w przypadku błędnego użycia cała aplikacja ulegnie awarii, gdy jest dostępna podczas nil. Jeśli kiedykolwiek nie jesteś pewien niezależnie od tego, czy zmienna może być zerowa, zawsze domyślne jest użycie zwykłej opcji opcjonalnej. Rozpakowanie zmiennej, która nigdy nie jest nil z pewnością nie boli bardzo.

 461
Author: drewag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-10 05:05:11

Domyślnie rozpakowane opcje są przydatne do prezentowania właściwości jako nieobowiązkowe, gdy naprawdę musi być opcjonalne pod pokrywami. Jest to często konieczne do "zawiązania węzła" między dwoma powiązanymi obiektami, z których każdy potrzebuje odniesienia do drugiego. Ma to sens, gdy żadne odniesienie nie jest w rzeczywistości opcjonalne, ale jedno z nich musi być zerowe podczas inicjalizacji pary.

Na przykład:

// These classes are buddies that never go anywhere without each other
class B {
    var name : String
    weak var myBuddyA : A!
    init(name : String) {
        self.name = name
    }
}

class A {
    var name : String
    var myBuddyB : B
    init(name : String) {
        self.name = name
        myBuddyB = B(name:"\(name)'s buddy B")
        myBuddyB.myBuddyA = self
    }
}

var a = A(name:"Big A")
println(a.myBuddyB.name)   // prints "Big A's buddy B"

Każda instancja B powinna mieć zawsze poprawną myBuddyA Referencja, więc nie chcemy, aby użytkownik traktował ją jako opcjonalną, ale musimy ją traktować opcjonalnie, abyśmy mogli skonstruować B zanim będziemy mieli A do której będziemy się odwoływać.

Jednak! Ten rodzaj wzajemnego wymogu odniesienia jest często oznaką szczelnego połączenia i słabej konstrukcji. Jeśli polegasz na niejawnie rozpakowanych opcjach, prawdopodobnie powinieneś rozważyć refaktoryzację, aby wyeliminować współzależności.

 56
Author: n8gray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-28 18:30:25

Domyślnie rozpakowane opcje są pragmatycznym kompromisem, aby praca w środowisku hybrydowym, które musi współdzielić z istniejącymi frameworkami Cocoa i ich konwencjami, była przyjemniejsza, a jednocześnie pozwalała na stopniową migrację do bezpieczniejszego paradygmatu programowania - bez wskaźników null-wymuszonego przez kompilator Swift.

Swift book, in The Basics chapter, section implicite Unwrapped Optionals says:

Bezwarunkowo rozpakowany opcje są użyteczne, gdy wartość opcji zostanie potwierdzona natychmiast po pierwszym zdefiniowaniu opcji i można z pewnością założyć, że istnieje w każdym punkcie później. Domyślnie rozpakowane opcje w języku Swift są używane podczas inicjalizacji klasy, jak opisano w Unowned Referations i domyślnie rozpakowane opcjonalne właściwości.

Możesz myśleć o niejawnie rozpakowanym opcjonalnym jako zezwalającym na rozpakowanie opcjonalnego automatycznie za każdym razem, gdy jest używany. Zamiast umieszczać wykrzyknik po nazwie opcji za każdym razem, gdy jej używasz, umieszczasz wykrzyknik po typie opcji, gdy ją deklarujesz.

Sprowadza się to do przypadków użycia, w których non-nil-Ness właściwości jest ustalana przez Konwencję użycia i nie może być egzekwowana przez kompilator podczas inicjalizacji klasy. Na przykład, właściwości UIViewController, które są inicjowane ze stalówek lub Storyboardów, gdzie inicjalizacja jest podzielona na oddzielne fazy, ale po viewDidLoad() można założyć, że właściwości ogólnie istnieją. W przeciwnym razie, aby zadowolić kompilator, trzeba było użyć przymusowe rozpakowywanie , oprawa opcjonalna lub opcjonalne łańcuchowanie tylko po to, by zaciemnić główny cel kodu.

Powyższa część książki Swift odnosi się również do automatyczne liczenie referencji Rozdział :

Jest jednak trzecia scenariusz, w którym obie właściwości powinny zawsze mieć wartość, a żadna z nich nie powinna być nil po zakończeniu inicjalizacji. W tym scenariuszu, przydatne jest połączenie własności nieujawnionej na jednej klasie z niejawnie rozpakowaną właściwością opcjonalną na drugiej klasie.

Umożliwia to bezpośredni dostęp do obu właściwości (bez opcjonalnego rozpakowywania) po zakończeniu inicjalizacji, jednocześnie unikając cyklu odniesienia.

To sprowadza się do dziwactwa nie bycia śmieciowym językiem, dlatego łamanie cykli zatrzymywania jest na Tobie jako programistie i domyślnie rozpakowane opcje są narzędziem do ukrycia tego dziwactwa.

, który obejmuje "kiedy używać domyślnie rozpakowanych opcji w kodzie?"pytanie. Jako programista aplikacji napotkasz je głównie w podpisach metod bibliotek napisanych w Objective-C, które nie mają możliwości wyrażania opcjonalnych typów.

Z używanie Swifta z kakao i Objective-C, section Working with nil:

Ponieważ Objective-C nie daje żadnych gwarancji, że obiekt nie jest zerowy, Swift sprawia, że wszystkie klasy w typach argumentów i typy zwrotne są opcjonalne w importowanych API Objective-C. Przed użyciem obiektu Objective-C należy sprawdzić, czy go nie brakuje.

W niektórych przypadkach możesz być absolutnie pewien, że metoda lub właściwość Objective-C nigdy nie zwróci odniesienia do obiektu nil. Aby ułatwić pracę z obiektami w tym specjalnym scenariuszu, Swift importuje typy obiektów jako domyślnie rozpakowane opcje. Domyślnie rozpakowane typy opcjonalne obejmują wszystkie funkcje bezpieczeństwa typów opcjonalnych. Ponadto, możesz uzyskać dostęp do wartości bezpośrednio bez sprawdzania nil lub rozpakowywania jej samodzielnie. Gdy uzyskasz dostęp do wartości tego rodzaju opcjonalnego typu bez uprzedniego bezpiecznego rozpakowania jej, opcja domyślnie rozpakowana sprawdza, czy nie ma tej wartości. Jeśli brakuje tej wartości, wystąpi błąd runtime. W rezultacie, należy zawsze sprawdzić i rozpakować domyślnie rozpakowaną opcję samodzielnie, chyba że jesteś pewien, że nie można jej zabraknąć.

...and beyond here lay smoki

 37
Author: Palimondo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-16 15:39:27

Jednowierszowe (lub Wielowierszowe) proste przykłady nie pokrywają zbyt dobrze zachowania opcji-Tak, jeśli zadeklarujesz zmienną i podasz jej wartość od razu, opcjonalne nie ma sensu.

Najlepszym przypadkiem, jaki widziałem do tej pory, jest konfiguracja, która dzieje się po inicjalizacji obiektu, a następnie użycie, które jest "gwarantowane", aby podążać za tą konfiguracją, np. w kontrolerze widoku:

class MyViewController: UIViewController {

    var screenSize: CGSize?

    override func viewDidLoad {
        super.viewDidLoad()
        screenSize = view.frame.size
    }

    @IBAction printSize(sender: UIButton) {
        println("Screen size: \(screenSize!)")
    }
}

Wiemy, że printSize zostanie wywołana po załadowaniu widoku - jest to metoda akcji podłączona do Kontrola wewnątrz tego widoku i upewniliśmy się, że nie nazwaliśmy tego inaczej. Możemy więc oszczędzić sobie jakiegoś opcjonalnego sprawdzania / wiązania za pomocą !. Swift nie rozpoznaje tej gwarancji (przynajmniej dopóki Apple nie rozwiąże problemu z zatrzymaniem), więc mówisz kompilatorowi, że istnieje.

To jednak w pewnym stopniu łamie bezpieczeństwo typu. Gdziekolwiek masz domyślnie rozpakowany opcjonalny jest miejscem, w którym Twoja aplikacja może się zawiesić, jeśli twoja "gwarancja" nie zawsze się trzyma, więc jest to funkcja, z której można korzystać oszczędnie. Poza tym, za pomocą Cały czas brzmi to, jakbyś krzyczał, a nikt tego nie lubi.
 19
Author: rickster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-03 04:26:15

Apple daje świetny przykład w języku programowania Swift -> automatyczne liczenie referencji -> rozwiązywanie silnych cykli referencyjnych między instancjami klasy -> Unowned References and Unwrapped Optional Properties

class Country {
    let name: String
    var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var)
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

Inicjalizator dla City jest wywoływany z wewnątrz inicjalizatora dla Country. Jednak inicjalizator dla Country nie może przekazać self do inicjalizatora City aż do nowej instancji Country jest w pełni zainicjalizowana, jak opisano w dwufazowa Inicjalizacja.

Aby poradzić sobie z tym wymogiem, deklarujesz capitalCity właściwość Country jako niejawnie rozpakowaną właściwość opcjonalną.

 15
Author: fujianjin6471,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-04 06:19:11

Uzasadnienie dorozumianych opcji jest łatwiejsze do wyjaśnienia, najpierw patrząc na uzasadnienie wymuszonego rozpakowywania.

Wymuszone rozpakowywanie opcjonalnego (implicit or not), za pomocą! operator, oznacza, że jesteś pewien, że Twój kod nie ma błędów, a opcja ma już wartość, w której jest rozpakowywany. Bez ... operatorze, prawdopodobnie po prostu twierdzisz za pomocą opcjonalnego wiązania:

 if let value = optionalWhichTotallyHasAValue {
     println("\(value)")
 } else {
     assert(false)
 }

Który nie jest tak miły jak

println("\(value!)")

Teraz, implicit optionals niech ci express ma opcję, której spodziewasz się, że zawsze będzie miała wartość po rozpakowaniu we wszystkich możliwych przepływach. Więc to po prostu idzie o krok dalej, pomagając Ci-rozluźniając wymóg pisania ! aby rozpakować za każdym razem i upewnić się, że runtime nadal będzie błąd w przypadku, gdy twoje założenia dotyczące przepływu są błędne.

 3
Author: Danra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-03 22:59:36

Jeśli wiesz na pewno, wartość zwracana z opcjonalnego zamiast nil, domyślnie Unwraped Optionals używa do bezpośredniego przechwytywania tych wartości z optionals i non optionals can ' t.

//Optional string with a value
let optionalString: String? = "This is an optional String"

//Declaration of an Implicitly Unwrapped Optional String
let implicitlyUnwrappedOptionalString: String!

//Declaration of a non Optional String
let nonOptionalString: String

//Here you can catch the value of an optional
implicitlyUnwrappedOptionalString = optionalString

//Here you can't catch the value of an optional and this will cause an error
nonOptionalString = optionalString

Więc to jest różnica między użyciem

let someString : String! oraz let someString : String

 3
Author: enadun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-14 16:52:38

Myślę, że {[0] } to zła nazwa dla tej konstrukcji, która myli wielu początkujących.

Inne języki (na przykład Kotlin i C#) używają terminu Nullable, co znacznie ułatwia zrozumienie tego.

Nullable oznacza, że możesz przypisać wartość null do zmiennej tego typu. Więc jeśli jest Nullable<SomeClassType>, możesz przypisać do niego null, jeśli jest tylko SomeClassType, nie możesz. tak właśnie działa Swift.

Dlaczego ich używać? Cóż, czasami trzeba mieć null, dlatego. Na przykład, gdy wiesz, że chcesz mieć pole w klasie, ale nie możesz przypisać go do niczego podczas tworzenia instancji tej klasy, ale później to zrobisz. Nie będę podawał przykładów, bo ludzie już je tutaj podali. Piszę to, żeby oddać moje 2 centy.

Btw, proponuję przyjrzeć się jak to działa w innych językach, np. Kotlin i C#.

Oto link wyjaśniający tę funkcję w Kotlin: https://kotlinlang.org/docs/reference/null-safety.html

Inne języki, takie jak Java i Scala mają Optionals, ale działają inaczej niż Optional S w języku Swift, ponieważ typy Javy i Scali są domyślnie nullable.

Podsumowując, myślę, że ta funkcja powinna być nazwana Nullable w języku Swift, a nie Optional...

 0
Author: hey_you,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-13 10:44:45

Implicitly Unwrapped Optional jest cukrem składniowym Optional, który nie zmusza programisty do rozpakowania zmiennej. Może być użyta dla zmiennej, która nie może być zainicjalizowana podczas two-phase initialization process i implikuje non-nil. Zmienna ta zachowuje się jak non-nil , ale w rzeczywistości jest zmiennąopcjonalną . Dobrym przykładem jest-Interface Builder ' s outlets

Optional zazwyczaj są preferowane

var nonNil: String = ""
var optional: String?
var implicitlyUnwrappedOptional: String!

func foo() {
    //get a value
    nonNil.count
    optional?.count

    //Danderour - makes a force unwrapping which can throw a runtime error
    implicitlyUnwrappedOptional.count

    //assign to nil
//        nonNil = nil //Compile error - 'nil' cannot be assigned to type 'String'
    optional = nil
    implicitlyUnwrappedOptional = nil
}
 0
Author: yoAlex5,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-08 20:48:28