Szybki: strażnik niech vs jeśli pozwolę


132

Czytałem o Optionals w Swift i widziałem przykłady, w których if letjest używany do sprawdzania, czy Argument opcjonalny przechowuje wartość, a jeśli tak - zrób coś z nieopakowaną wartością.

Jednak widziałem, że w Swift 2.0 słowo kluczowe guard letjest używane głównie. Zastanawiam się, czy if letzostał usunięty ze Swift 2.0, czy nadal można go używać.

Czy powinienem zmienić programy, które zawierają if letna guard let?

Odpowiedzi:


164

if leti guard letsłużą podobnym, ale odmiennym celom.

Przypadek „else” guardmusi wyjść z bieżącego zakresu. Ogólnie oznacza to, że musi wywołać returnlub przerwać program. guardjest używany do zapewnienia wczesnego powrotu bez konieczności zagnieżdżania reszty funkcji.

if letzagnieżdża swój zakres i nie wymaga od niego niczego specjalnego. Może returnlub nie.

Ogólnie rzecz biorąc, jeśli if-letblok elsemiałby być resztą funkcji lub jego klauzula zawierałaby a returnlub abort, powinieneś guardzamiast tego użyć . To często oznacza (przynajmniej z mojego doświadczenia), że w razie wątpliwości guardjest zwykle lepszą odpowiedzią. Ale jest wiele sytuacji, w których if letnadal jest to właściwe.


38
Użyj, if letgdy non-nilsprawa jest ważna. Użyj, guardgdy nilprzypadek przedstawia jakiś rodzaj błędu.
BallpointBen

4
@BallpointBen Nie zgadzam się z tym. Jest wiele przypadków, w których guardjest to właściwe, nawet jeśli nie ma błędu. Czasami oznacza to po prostu, że nie ma nic do zrobienia. Na przykład positionTitlemetoda może guard if let title = title else {return}. Tytuł może być opcjonalny, w takim przypadku nie jest to błąd. Ale guard letnadal jest odpowiednie.
Rob Napier

1
Tak; Chodziło mi o to, że strażnik dopuścił komentarz.
Rob Napier

1
innymi słowy, „guard let” jest używane, gdy kod jest na 99% pewien, że nie użyje warunku else; z drugiej strony „if let”, gdy kod ma wartość 50 - 50 (przykład), aby użyć warunku else.
Chino Pan

1
Zmienna związana if letjest widoczna tylko w if let zasięgu. Zmienna związana guard letjest później widoczna. Dlatego sensowne jest używanie guard do wiązania wartości opcjonalnych.
boweidmann

105

Straż może poprawić przejrzystość

Kiedy używasz strażnika, masz znacznie większe oczekiwania, że ​​strażnik odniesie sukces i jest dość ważne, że jeśli to się nie powiedzie, po prostu chcesz wcześniej wyjść z lunety . Tak jak Ty pilnujesz, aby sprawdzić, czy plik / obraz istnieje, czy tablica jest pusta, czy nie.

func icon() -> UIImage {
    guard let image = UIImage(named: "Photo") else {
        return UIImage(named: "Default")! //This is your fallback
    }
    return image //-----------------you're always expecting/hoping this to happen
}

Jeśli napiszesz powyższy kod za pomocą if-let, przekaże on programiście czytającemu, że jest to bardziej 50-50. Ale jeśli używasz guard, dodajesz klarowności do swojego kodu i oznacza to, że spodziewam się, że to zadziała w 95% przypadków ... jeśli kiedykolwiek się nie powiedzie, nie wiem, dlaczego miałoby to działać; jest to bardzo mało prawdopodobne ... ale zamiast tego po prostu użyj tego domyślnego obrazu lub po prostu potwierdź za pomocą sensownej wiadomości opisującej, co poszło nie tak!

  • Unikaj, guardgdy powodują efekty uboczne, osłony powinny być używane jako naturalny przepływ. Unikaj strażników, gdy elseklauzule wprowadzają skutki uboczne. Strażnicy ustanawiają wymagane warunki do prawidłowego wykonania kodu, oferując wczesne wyjście

  • Kiedy wykonujesz znaczące obliczenia w dodatniej gałęzi, refaktoryzuj instrukcję z ifdo guardi zwraca wartość rezerwową w elseklauzuli

Od: Książka Swift Style Erica Sadun

Również w wyniku powyższych sugestii i czystego kodu jest bardziej prawdopodobne, że będziesz chciał / musiał dodać potwierdzenia do nieudanych instrukcji ochrony, po prostu poprawia czytelność i wyjaśnia innym programistom, czego się spodziewasz.

guard​ ​let​ image =UIImage(named: selectedImageName) else { // YESSSSSS
     assertionFailure("Missing ​​\(​selectedImageName​)​​ asset") 
     return
} 

guard​ ​let​ image =UIImage(named: selectedImageName) else { // NOOOOOOO
​     ​return 
}

Od: Książka Swift Style Erica Sadun + kilka modyfikacji

(nie będziesz używać potwierdzeń / warunków wstępnych dla if-lets. To po prostu nie wydaje się właściwe)

Korzystanie ze strażników pomaga również zwiększyć przejrzystość, omijając piramidę zagłady. Zobacz odpowiedź Nitina .


Guard tworzy nową zmienną

Jest jedna ważna różnica, której moim zdaniem nikt dobrze nie wyjaśnił.

Jednak oba guard leti if let rozpakuj zmienną

Wraz z guard lettobą tworzysz nową zmienną, która będzie istniała poza elseinstrukcją.

Ze if letty nie tworzysz każdy nowy zmiennej po else, wystarczy tylko wprowadzić blok kodu jeśli opcjonalny nie jest zerowe. Nowo utworzona zmienna istnieje tylko wewnątrz bloku kodu, a nie po!

guard let:

func someFunc(blog: String?) {

    guard let blogName = blog else {
        print("some ErrorMessage")
        print(blogName) // will create an error Because blogName isn't defined yet
        return
    }
    print(blogName) // You can access it here ie AFTER the guard statement!!

    //And if I decided to do 'another' guard let with the same name ie 'blogName' then I would create an error!
    guard let blogName = blog else { // errorLine: Definition Conflicts with previous value.
        print(" Some errorMessage")
        return
    }
    print(blogName)
}

if-let:

func someFunc(blog: String?) {


    if let blogName1 = blog {
        print(blogName1) // You can only access it inside the code block. Outside code block it doesn't exist!
    }
    if let blogName1 = blog { // No Error at this line! Because blogName only exists inside the code block ie {}
        print(blogName1)
    }
}

Aby uzyskać więcej informacji if let, zobacz: Dlaczego ponowna deklaracja opcjonalnego powiązania nie powoduje błędu


Ochrona wymaga wyjścia z lunety

(Wspomniane również w odpowiedzi Roba Napiera):

MUSISZ guardzdefiniować wewnątrz func. Jego głównym celem jest przerwanie / powrót / wyjście z zakresu, jeśli warunek nie jest spełniony:

var str : String?

guard let blogName1 = str else {
    print("some error")
    return // Error: Return invalid outside of a func
}
print (blogName1)

Na if letnie trzeba go mieć w środku każdej func:

var str : String?    
if let blogName1 = str {
   print(blogName1) // You don't get any errors!
}

guard vs if

Warto zauważyć, że jest to bardziej właściwe, aby zobaczyć jak to pytanie guard letvs if leti guardkontra if.

Samodzielny ifnie rozpakowuje się, podobnie jak samodzielny guard. Zobacz przykład poniżej. Nie kończy się wcześniej, jeśli wartość to nil. Brak wartości opcjonalnych. Po prostu wychodzi wcześnie, jeśli warunek nie jest spełniony.

let array = ["a", "b", "c"]
func subscript(at index: Int) -> String?{
   guard index > 0, index < array.count  else { return nil} // exit early with bad index
   return array[index]
}

46

Kiedy if-leti kiedy używać, guardczęsto zależy od stylu.

Powiedzmy, że masz func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Inti opcjonalną tablicę elementów ( var optionalArray: [SomeType]?), i musisz zwrócić albo 0jeśli tablica jest nil(nie jest ustawiona), albo countjeśli tablica ma wartość (jest ustawiona).

Możesz to zaimplementować w ten sposób, używając if-let:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        if let array = optionalArray {
            return array.count
        }
        return 0
    }

lub w ten sposób używając guard:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        guard let array = optionalArray else {
            return 0
        }
        return array.count
    }

Przykłady są funkcjonalnie identyczne.

Gdzie guardnaprawdę błyszczy jest, gdy masz zadanie jak sprawdzanie poprawności danych, a chcesz funkcja niepowodzenie wcześnie, jeśli coś jest nie tak.

Zamiast zagnieżdżać kilka elementów w if-letmiarę zbliżania się do zakończenia walidacji, „ścieżka sukcesu” i teraz pomyślnie powiązane opcje znajdują się w głównym zakresie metody, ponieważ wszystkie ścieżki błędów już wróciły.


30

Spróbuję wyjaśnić użyteczność instrukcji guard za pomocą (niezoptymalizowanego) kodu.

Masz interfejs użytkownika, w którym weryfikujesz pola tekstowe do rejestracji użytkownika za pomocą imienia, nazwiska, adresu e-mail, telefonu i hasła.

Jeśli jakiekolwiek pole textField nie zawiera poprawnego tekstu, to pole powinno być pierwszeResponder.

oto niezoptymalizowany kod:

//pyramid of doom

func validateFieldsAndContinueRegistration() {
    if let firstNameString = firstName.text where firstNameString.characters.count > 0{
        if let lastNameString = lastName.text where lastNameString.characters.count > 0{
            if let emailString = email.text where emailString.characters.count > 3 && emailString.containsString("@") && emailString.containsString(".") {
                if let passwordString = password.text where passwordString.characters.count > 7{
                    // all text fields have valid text
                    let accountModel = AccountModel()
                    accountModel.firstName = firstNameString
                    accountModel.lastName = lastNameString
                    accountModel.email = emailString
                    accountModel.password = passwordString
                    APIHandler.sharedInstance.registerUser(accountModel)
                } else {
                    password.becomeFirstResponder()
                }
            } else {
                email.becomeFirstResponder()
            }
        } else {
            lastName.becomeFirstResponder()
        }
    } else {
        firstName.becomeFirstResponder()
    }
}

Jak widać powyżej, wszystkie ciągi znaków (firstNameString, lastNameString itp.) Są dostępne tylko w zakresie instrukcji if. więc tworzy tę "piramidę zagłady" i ma z nią wiele problemów, w tym czytelność i łatwość przenoszenia elementów (jeśli zmieni się kolejność pól, musisz przepisać większość tego kodu)

Za pomocą instrukcji guard (w poniższym kodzie) możesz zobaczyć, że te ciągi są dostępne poza {}polami i są używane, jeśli wszystkie pola są prawidłowe.

// guard let no pyramid of doom
func validateFieldsAndContinueRegistration() {

guard let firstNameString = firstName.text where firstNameString.characters.count > 0 else {
            firstName.becomeFirstResponder()
            return
        }
guard let lastNameString = lastName.text where lastNameString.characters.count > 0 else {
            lastName.becomeFirstResponder()
            return
        }
guard let emailString = email.text where 
        emailString.characters.count > 3 &&
        emailString.containsString("@") && 
        emailString.containsString(".") else {
            email.becomeFirstResponder()
            return
        }
guard let passwordString = password.text where passwordString.characters.count > 7 else {
            password.becomeFirstResponder()
            return
        }

// all text fields have valid text
    let accountModel = AccountModel()
    accountModel.firstName = firstNameString
    accountModel.lastName = lastNameString
    accountModel.email = emailString
    accountModel.password = passwordString
    APIHandler.sharedInstance.registerUser(accountModel)
}

Jeśli zmieni się kolejność pól, po prostu przesuń odpowiednie wiersze kodu w górę lub w dół i gotowe.

To bardzo proste wyjaśnienie i przypadek użycia. Mam nadzieję że to pomoże!


14

Podstawowa różnica

Niech strażnik

  1. Wczesny proces z zakresu
  2. Wymagaj istniejącego wyniku, takiego jak powrót, rzut itp.
  3. Utwórz nową zmienną, do której będzie można uzyskać dostęp poza zakresem.

jeśli niech

  1. Brak dostępu do zakresu.
  2. nie ma potrzeby zwracania oświadczenia. Ale możemy pisać

UWAGA: Oba są używane do rozpakowania zmiennej opcjonalnej.


2

Najjaśniejsze wyjaśnienie, jakie widziałem, było w przewodniku po stylach Github Swift :

if dodaje poziom głębi:

if n.isNumber {
    // Use n here
} else {
    return
}

guard nie:

guard n.isNumber else {
    return
}
// Use n here

2

strzec

  • guardOświadczenie jest używany do transferu out programu sterującego w zakresie, jeżeli nie są spełnione jeden lub więcej warunków.

  • Wartość dowolnego warunku w guardinstrukcji musi być typu Bool lub typu, do którego jest mostkowany Bool. Warunek może być również opcjonalną wiążącą deklaracją.

Oświadczenie strażnika ma następującą postać:

guard condition else {
    //Generally return
}

jeśli niech

  • Popularny również jako oprawy opcjonalne .
  • Aby uzyskać dostęp do opcjonalnego obiektu, używamy if let.
if let roomCount = optionalValue {
    print("roomCount available")
} else {
    print("roomCount is nil")
}

1

Nauczyłem się tego od szybkiego z Bobem.

Typowe inaczej, jeśli

 func checkDrinkingAge() {
      let canDrink = true

     if canDrink {
        print("You may enter")
       // More Code
        // More Code
      // More Code

         } else {
         // More Code
    // More Code
    // More Code
    print("Let me take you to the jail")
          }
     }

Problemy z Else-If

  1. Nawiasy zagnieżdżone
  2. Trzeba przeczytać każdą linię, aby znaleźć komunikat o błędzie

Instrukcja Guard Blokada działa tylko wtedy, gdy warunek jest fałszywy i wyjdzie z funkcji poprzez powrót. Jeśli warunek jest spełniony, Swift ignoruje blokadę ochronną. Zapewnia wczesne wyjście i mniej nawiasów. +

func checkDrinkProgram() {
       let iCanDrink = true

           guard iCanDrink else {
        // if iCanDrink == false, run this block
         print("Let's me take you to the jail")
          return
        }

         print("You may drink")
           // You may move on
                  // Come on.
                 // You may leave
                // You don't need to read this.
                 // Only one bracket on the bottom: feeling zen.
       }

Unwrap Optionals with Else-If

Instrukcja guard jest przydatna nie tylko do zastępowania typowego bloku warunkowego instrukcją else-if, ale także doskonale nadaje się do rozpakowywania opcji opcjonalnych poprzez minimalizację liczby nawiasów. Aby porównać, zacznijmy najpierw od rozpakowywania wielu opcji za pomocą else-if. Najpierw utwórzmy trzy opcje, które zostaną rozpakowane.

var publicName: String? = "Bob Lee"
var publicPhoto: String? = "Bob's Face"
var publicAge: Int? = nil

Najgorszy koszmar

func unwrapOneByOne() {
         if let name = publicName {
              if let photo = publicPhoto {
                     if let age = publicAge {
                        print("Bob: \(name), \(photo), \(age)")
                                  } else {
                          print("age is mising")
                           }
                  } else {
                      print("photo is missing")
                         }
                  } else {
                        print("name is missing")
                         }
                  }

Powyższy kod z pewnością działa, ale narusza zasadę DRY. To okropne. Złóżmy to. +

Trochę lepiej Poniższy kod jest bardziej czytelny niż powyżej. +

func unwrapBetter() {
         if let name = publicName {
       print("Yes name")
                   } else {
               print("No name")
        return
      }

         if let photo = publicPhoto {
             print("Yes photo")
            } else {
           print("No photo")
       return
             }

        if let age = publicAge {
            print("Yes age")
                      } else {
                print("No age")
            return
                           }
     }

Rozpakowywanie z Guard Instrukcje else-if można zastąpić wyrażeniem guard. +

 func unwrapOneByOneWithGuard() {
             guard let name = publicName else {
                  print("Name missing")
              return
                                        }

              guard let photo = publicPhoto else {
              print("Photo missing")
                return
                                            }

                  guard let age = publicAge else {
                   print("Age missing")
                                     return
                                                 }
                 print(name)
                 print(photo)
                 print(age)
         }

Unwrap Multiple Optionals with Else-If Do tej pory rozpakowywałeś opcje jeden po drugim. Swift pozwala nam jednocześnie rozpakować wiele opcji. Jeśli jeden z nich zawiera nil, wykona blok else.

func unwrap() {
  if let name = publicName, let photo = publicPhoto, let age = publicAge {
    print("Your name is \(name). I see your face right here, \(photo), you are \(age)")
  } else {
    // if any one of those is missing
    print("Something is missing")
  }
}

Należy pamiętać, że gdy rozpakowujesz wiele opcji jednocześnie, nie możesz zidentyfikować, który zawiera zero

Unwrap Multiple Optionals with Guard Oczywiście, powinniśmy użyć guard zamiast else-if. +

func unwrapWithGuard() {
  guard let name = publicName, let photo = publicPhoto, let age = publicAge else {
    // if one or two of the variables contain "nil"
    print("Something is missing")
    return
  }

  print("Your name is \(name). I see your, \(photo). You are \(age).")
  // Animation Logic
  // Networking
  // More Code, but still zen
}

wróć i popraw formatowanie / wcięcia kodu!
pkamb
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.