Swift: guarda let vs se let

130

Eu tenho lido sobre os Opcionais no Swift e vi exemplos de onde if leté usado para verificar se um Opcional possui um valor e, no caso, ele faz - faça algo com o valor desembrulhado.

No entanto, vi que no Swift 2.0 a palavra guard let- chave é usada principalmente. Gostaria de saber se if letfoi removido do Swift 2.0 ou se ainda é possível usá-lo.

Devo alterar meus programas que contêm if letpara guard let?

lmiguelvargasf
fonte

Respostas:

164

if lete guard letservir a propósitos semelhantes, mas distintos.

O caso "else" de guarddeve sair do escopo atual. Geralmente isso significa que ele deve chamar returnou abortar o programa. guardé usado para fornecer retorno antecipado sem a necessidade de aninhamento do restante da função.

if letaninha seu escopo e não exige nada de especial. Pode returnou não.

Em geral, se o if-letbloco for o restante da função ou sua elsecláusula tiver um returnou abortar nele, você deverá usá-lo guard. Isso geralmente significa (pelo menos na minha experiência), quando em dúvida, guardgeralmente é a melhor resposta. Mas há muitas situações em que if letainda é apropriado.

Rob Napier
fonte
37
Use if letquando o non-nilcaso for válido. Use guardquando o nilcaso representa algum tipo de erro.
usar o seguinte
4
@BallpointBen Não concordo com isso. Existem muitos casos em que guardé apropriado, mesmo que não haja erro. Às vezes, significa apenas que não há nada a fazer. Por exemplo, um positionTitlemétodo pode guard if let title = title else {return}. O título pode ser opcional. Nesse caso, isso não é um erro. Mas guard letainda é apropriado.
Rob Napier
1
Sim; Eu quis dizer guarda deixar entrar o comentário.
Rob Napier
1
em outras palavras, "guard let" é usado quando o código tem 99% de certeza de não usar o condicional else; por outro lado, "if let" quando o código é 50 - 50 (exemplo) para usar outra condição.
Chino Pan
1
A variável vinculada por if leté visível apenas dentro do if let escopo. A variável vinculada por guard leté visível posteriormente. Portanto, faz sentido usar guarda para vincular valores opcionais também.
boweidmann 9/01
105

Guarda pode melhorar a clareza

Quando você usa o guarda, você tem uma expectativa muito maior de que o guarda seja bem - sucedido e é um pouco importante que, se não for bem-sucedido, você queira sair do escopo mais cedo . Como você guarda para ver se existe um arquivo / imagem, se uma matriz é Vazia ou não.

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
}

Se você escrever o código acima com if-let, ele transmite ao desenvolvedor de leitura que é mais do que 50-50. Mas se você usa o guarda, adiciona clareza ao seu código e isso implica que eu espero que funcione 95% das vezes ... se alguma vez falhou, não sei por que; é muito improvável ... mas use essa imagem padrão ou apenas afirme com uma mensagem significativa descrevendo o que deu errado!

  • Evite guards quando criarem efeitos colaterais, as proteções devem ser usadas como um fluxo natural . Evite guardas quando as elsecláusulas introduzirem efeitos colaterais. Os guardas estabelecem condições necessárias para que o código seja executado corretamente, oferecendo saída antecipada

  • Ao executar um cálculo significativo na ramificação positiva, refatorar de ifpara uma guardinstrução e retornar o valor de fallback na elsecláusula

De: livro Swift Style de Erica Sadun

Além disso, como resultado das sugestões acima e do código limpo, é mais provável que você queira / precise adicionar asserções a declarações de guarda com falha , isso apenas melhora a legibilidade e deixa claro para outros desenvolvedores o que você estava esperando.

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

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

De: livro Swift Style de Erica Sadun + algumas modificações

(você não usará declarações / condições prévias para if-lets. Simplesmente não parece certo)

Usar guardas também ajuda a melhorar a clareza, evitando a pirâmide da desgraça. Veja a resposta de Nitin .


Guard cria uma nova variável

Há uma diferença importante que acredito que ninguém explicou bem.

Ambos guard lete if let desembrulhar a variável no entanto

Com guard letvocê, você está criando uma nova variável que existirá fora da elseinstrução.

Como if letvocê não está criando nenhuma nova variável - após a instrução else, você só insere o bloco de código se o opcional for nulo. A variável recém-criada existe apenas dentro do bloco de código não depois!

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

Para obter mais informações if let, consulte: Por que a redeclaração da ligação opcional não cria um erro?


Guarda requer escopo saindo

(Também mencionado na resposta de Rob Napier):

Você DEVE ter guarddefinido dentro de uma função. Seu principal objetivo é abortar / retornar / sair do escopo, se uma condição não for atendida:

var str : String?

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

Para if letvocê não precisa tê-lo em nenhuma função:

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

guard vs if

Pena de notar que é mais apropriado para ver esta questão como guard letvs if lete guardvs if.

Um autônomo ifnão desembrulha, nem um autônomo guard. Veja o exemplo abaixo. Não sai mais cedo se um valor é nil. Não há valores opcionais. Ele sai cedo se uma condição não for atendida.

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]
}
Mel
fonte
46

Quando usar if-lete quando usar guardgeralmente é uma questão de estilo.

Digamos que você tenha func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Intuma matriz opcional de itens ( var optionalArray: [SomeType]?) e precise retornar 0se a matriz estiver nil(não definida) ou countse a matriz tiver um valor (definido).

Você pode implementá-lo assim usando if-let:

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

ou assim usando guard:

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

Os exemplos são funcionalmente idênticos.

Onde guardrealmente brilha é quando você tem uma tarefa como validar dados e deseja que a função falhe mais cedo se algo estiver errado.

Em vez de aninhar um monte de if-lets à medida que você se aproxima da conclusão da validação, o "caminho de sucesso" e os opcionais agora vinculados com êxito estão todos no escopo principal do método, porque todos os caminhos de falha já retornaram.

divergio
fonte
30

Vou tentar explicar a utilidade das declarações de guarda com algum código (não otimizado).

Você tem uma interface do usuário na qual está validando campos de texto para registro do usuário com nome, sobrenome, email, telefone e senha.

Se algum textField não contiver texto válido, esse campo deve ser firstResponder.

aqui está o código não otimizado:

//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()
    }
}

Você pode ver acima, que todas as strings (firstNameString, lastNameString etc) estão acessíveis apenas dentro do escopo da instrução if. portanto, cria essa "pirâmide da desgraça" e tem muitos problemas, incluindo legibilidade e facilidade de mudar as coisas (se a ordem dos campos for alterada, você precisará reescrever a maior parte desse código)

Com a declaração de guarda (no código abaixo), você pode ver que essas cadeias estão disponíveis fora {}e são usadas, se todos os campos forem válidos.

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

Se a ordem dos campos mudar, basta mover as respectivas linhas de código para cima ou para baixo, e você estará pronto.

Esta é uma explicação muito simples e um caso de uso. Espero que isto ajude!

Nitin Alabur
fonte
14

Diferença básica

Guarda deixe

  1. Processo de existência antecipada do escopo
  2. Exigir pontuação existente como retorno, Lançamento etc.
  3. Crie uma nova variável para acessar o escopo.

se deixar

  1. Não é possível acessar o escopo.
  2. não há necessidade de retornar a declaração. Mas nós podemos escrever

NOTA: Ambos são usados ​​para desembrulhar a variável Opcional.

Kiran K
fonte
2

A explicação mais clara que vi foi no Guia de Estilo do Github Swift :

if adiciona um nível de profundidade:

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

guard não:

guard n.isNumber else {
    return
}
// Use n here
picos selvagens
fonte
2

guarda

  • Uma guardinstrução é usada para transferir o controle do programa de um escopo se uma ou mais condições não forem atendidas.

  • O valor de qualquer condição em uma guardinstrução deve ser do tipo Bool ou do tipo com ponte Bool. A condição também pode ser uma declaração de ligação opcional.

Uma declaração de guarda tem o seguinte formato:

guard condition else {
    //Generally return
}

se deixar

if let roomCount = optionalValue {
    print("roomCount available")
} else {
    print("roomCount is nil")
}
Akshay
fonte
1

Aprendi isso rapidamente com Bob ..

Caso contrário típico

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

Problemas com Else-If

  1. Parênteses aninhados
  2. Tem que ler todas as linhas para identificar a mensagem de erro

Declaração de guarda Um bloco de proteção é executado apenas se a condição for falsa e sairá da função através do retorno. Se a condição for verdadeira, Swift ignorará o bloco de proteção. Ele fornece uma saída antecipada e menos colchetes.

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

Desempacotar Opcionais com Else-If

Uma declaração de guarda não é apenas útil para substituir um bloco condicional típico por uma outra declaração if, mas também é excelente para desembrulhar opcionais, minimizando o número de colchetes. Para comparar, vamos começar como desembrulhar vários opcionais com else-if. Primeiro, vamos criar três opcionais que serão desembrulhados.

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

O Pior Pesadelo

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

O código acima certamente funciona, mas viola o princípio DRY. É atroz. Vamos detalhar. +

Um pouco melhor O código abaixo é mais legível que o anterior.

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

Desembrulhar com proteção As instruções else-if podem ser substituídas por proteção.

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

Desembrulhe vários opcionais com o Else-If Até agora, você desembrulhou os opcionais um por um. Swift nos permite desembrulhar vários opcionais de uma só vez. Se um deles contiver nulo, ele executará o bloco 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")
  }
}

Esteja ciente de que quando você desembrulha vários opcionais de uma só vez, não consegue identificar qual contém

Desembrulhe várias opções opcionais com o Guard É claro que devemos usar o guard over 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
}
Ourang-Zeb Khan
fonte
volte e corrija a formatação / recuo do código!
pkamb 27/03