Palavra-chave da guarda de Swift

197

O Swift 2 introduziu a guardpalavra - chave, que pode ser usada para garantir que vários dados estejam configurados prontos para serem usados. Um exemplo que vi neste site demonstra uma função submitTapped:

func submitTapped() {
    guard username.text.characters.count > 0 else {
        return
    }

    print("All good")
}

Gostaria de saber se o uso guardé diferente de fazê-lo da maneira antiga, usando uma ifcondição. Oferece benefícios que você não poderia obter usando uma verificação simples?

David Snabel
fonte
Ver também guarda vs se-deixou questão
Mel
Consulte o link a seguir medium.com/@pvpriya7/swift-guard-18e59c50c624
Priyanka V

Respostas:

368

Ao ler este artigo , notei grandes benefícios usando o Guard

Aqui você pode comparar o uso de guarda com um exemplo:

Esta é a parte sem guarda:

func fooBinding(x: Int?) {
    if let x = x where x > 0 {
        // Do stuff with x
        x.description
    }

    // Value requirements not met, do something
}
  1. Aqui você está colocando o código desejado em todas as condições

    Você pode não encontrar imediatamente um problema com isso, mas pode imaginar o quão confuso isso poderia se tornar se estivesse aninhado com várias condições que todos precisavam ser atendidas antes de executar suas instruções

A maneira de limpar isso é fazer cada uma das suas verificações primeiro e sair se alguma não for atendida. Isso permite uma fácil compreensão de quais condições farão com que essa função saia.

Mas agora podemos usar a proteção e podemos ver que é possível resolver alguns problemas:

func fooGuard(x: Int?) {
    guard let x = x where x > 0 else {
        // Value requirements not met, do something
        return
    }

    // Do stuff with x
    x.description
}
  1. Verificando a condição que você deseja, não a que não deseja. Isso novamente é semelhante a uma afirmação. Se a condição não for atendida, a instrução else do guard é executada, o que interrompe a função.
  2. Se a condição for aprovada, a variável opcional aqui será desembrulhada automaticamente dentro do escopo em que a instrução guard foi chamada - nesse caso, a função fooGuard (_ :).
  3. Você está verificando casos ruins com antecedência, tornando sua função mais legível e fácil de manter

Esse mesmo padrão também é válido para valores não opcionais:

func fooNonOptionalGood(x: Int) {
    guard x > 0 else {
        // Value requirements not met, do something
        return
    }

    // Do stuff with x
}

func fooNonOptionalBad(x: Int) {
    if x <= 0 {
        // Value requirements not met, do something
        return
    }

    // Do stuff with x
}

Se você ainda tiver alguma dúvida, pode ler o artigo inteiro: Declaração de guarda rápida.

Empacotando

E, finalmente, lendo e testando, descobri que, se você usar o protetor para desembrulhar quaisquer opcionais,

esses valores desembrulhados ficam disponíveis para você usar no restante do seu bloco de código

.

guard let unwrappedName = userName else {
    return
}

print("Your username is \(unwrappedName)")

Aqui, o valor desembrulhado estaria disponível apenas dentro do bloco if

if let unwrappedName = userName {
    print("Your username is \(unwrappedName)")
} else {
    return
}

// this won't work – unwrappedName doesn't exist here!
print("Your username is \(unwrappedName)")
Jorge Casariego
fonte
3
Hey @Eric, você fez um excelente post! Obrigado por facilitar a compreensão!
Jorge Casariego
1
Estou usando o protetor para desembrulhar o NSError. Mas quando tento usá-lo no escopo de proteção (para passar um erro em algum retorno de chamada, por exemplo), ele diz "A variável declarada na condição de proteção não é utilizável em seu corpo". Faz algum sentido? Obrigado
GeRyCh
6
O desempacotamento de @GeRyCh em uma declaração de guarda torna essa variável disponível para após a declaração de guarda, não dentro dela. Levei um tempo para me acostumar com isso.
DonnaLea
2
Aqui está outro excelente artigo sobre o uso da proteção para desembrulhar os opcionais de maneira limpa. Resume bem.
Doches 01/01
let x = x where x > 0significa que você acoplou outra condição à sua ligação opcional? Quero dizer, é um pouco diferente deif let constantName = someOptional { statements }
Honey
36

Ao contrário if, guardcria a variável que pode ser acessada de fora do seu bloco. É útil desembrulhar muitos Optionals.

takebayashi
fonte
24

Há realmente dois grandes benefícios para guard. Um deles é evitar a pirâmide da destruição, como outros já mencionaram - muitas if letdeclarações irritantes aninhadas umas nas outras, movendo-se cada vez mais para a direita.

O outro benefício é geralmente a lógica que você deseja implementar é mais " if not let" do que " if let { } else".

Aqui está um exemplo: suponha que você queira implementar accumulate- um cruzamento entre mape reduceonde ele devolve uma série de reduções de execução . Aqui está com guard:

extension Sliceable where SubSlice.Generator.Element == Generator.Element {

    func accumulate(combine: (Generator.Element,Generator.Element)->Generator.Element) -> [Generator.Element] {
        // if there are no elements, I just want to bail out and
        // return an empty array
        guard var running = self.first else { return [] }

        // running will now be an unwrapped non-optional
        var result = [running]

        // dropFirst is safe because the collection
        // must have at least one element at this point
        for x in dropFirst(self) {
            running = combine(running, x)
            result.append(running)
        }
        return result
    }

}


let a = [1,2,3].accumulate(+)  // [1,3,6]
let b = [Int]().accumulate(+)  // []

Como você o escreveria sem guarda, mas ainda usando firstisso retorna um opcional? Algo assim:

extension Sliceable where SubSlice.Generator.Element == Generator.Element {

    func accumulate(combine: (Generator.Element,Generator.Element)->Generator.Element) -> [Generator.Element] {

        if var running = self.first  {
            var result = [running]

            for x in dropFirst(self) {
                running = combine(running, x)
                result.append(running)
            }
            return result
        }
        else {
            return []
        }
    }

}

O aninhamento extra é irritante, mas também não é lógico separar o ife o elsedistante. É muito mais legível ter a saída antecipada para o caso vazio e continuar com o restante da função como se isso não fosse uma possibilidade.

Velocidade da velocidade do ar
fonte
19

Quando uma condição é atendida, guardela expõe as variáveis ​​declaradas no guardbloco ao restante do bloco de código, trazendo-as para seu escopo. O que, como afirmado anteriormente, certamente será útil nas if letinstruções aninhadas .

Observe que o guarda requer um retorno ou um lançamento na sua declaração else.

Analisando JSON com o Guard

Abaixo está um exemplo de como alguém pode analisar um objeto JSON usando guard em vez de if-let. Este é um trecho de uma entrada de blog que inclui um arquivo de playground que você pode encontrar aqui:

Como usar o Guard no Swift 2 para analisar JSON

func parseJSONWithGuard(data : [String : AnyObject]) throws -> Developer {

    guard let firstname = data["First"] as? String  else {
        return Developer() // we could return a nil Developer()
    }

    guard let lastname = data["Last"] as? String else {
        throw ParseError.BadName // or we could throw a custom exception and handle the error
    }

    guard let website = data["WebSite"] as? String else {
        throw ParseError.BadName
    }

    guard let iosDev = data["iosDeveloper"] as? Bool else {
        throw ParseError.BadName
    }



    return Developer(first: firstname, last: lastname, site: website, ios: iosDev)

}

baixar playground: guarda playground

Mais informações:

Aqui está um trecho do The Swift Programming Language Guide:

Se a condição da declaração de guarda for atendida, a execução do código continuará após a chave de fechamento da declaração de guarda. Quaisquer variáveis ​​ou constantes às quais foram atribuídos valores usando uma ligação opcional como parte da condição estão disponíveis para o restante do bloco de código no qual a instrução guard aparece.

Se essa condição não for atendida, o código dentro da ramificação else será executado. Essa ramificação deve transferir o controle para sair do bloco de código em que a instrução de guarda aparece. Ela pode fazer isso com uma instrução de transferência de controle, como retornar, interromper ou continuar, ou pode chamar uma função ou método que não retorna, como como fatalError ().

Dan Beaulieu
fonte
7

Um benefício é a eliminação de muitas if letinstruções aninhadas . Veja o vídeo "What's New in Swift" da WWDC por volta das 15:30, na seção intitulada "Pirâmide da Perdição".

zaph
fonte
6

Quando usar guardas

Se você possui um controlador de exibição com alguns elementos UITextField ou algum outro tipo de entrada do usuário, notará imediatamente que deve desembrulhar o opcional textField.text para obter o texto interno (se houver!). isEmpty não fará nenhum bem a você, sem nenhuma entrada, o campo de texto simplesmente retornará nulo.

Portanto, você tem alguns desses que você desembrulha e, eventualmente, passa para uma função que os publica no terminal do servidor. Não queremos que o código do servidor tenha que lidar com valores nulos ou enviar valores inválidos por engano para o servidor, portanto, desembrulharemos esses valores de entrada primeiro.

func submit() {
    guard let name = nameField.text else {
        show("No name to submit")
        return
    }

    guard let address = addressField.text else {
        show("No address to submit")
        return
    }

    guard let phone = phoneField.text else {
        show("No phone to submit")
        return
    }

    sendToServer(name, address: address, phone: phone)
}

func sendToServer(name: String, address: String, phone: String) {
  ...
}

Você notará que nossa função de comunicação com o servidor usa valores não opcionais de String como parâmetros, portanto, a proteção é desembrulhada com antecedência. O desembrulhar é um pouco pouco intuitivo, porque estamos acostumados a desembrulhar com if deixe quais desembrulham valores para uso dentro de um bloco. Aqui, a declaração de guarda tem um bloco associado, mas na verdade é outro bloco - ou seja, o que você faz se o desembrulhar falhar - os valores são desembrulhados diretamente no mesmo contexto que a própria declaração.

// separação de preocupações

Sem guarda

Sem o uso de guarda, acabaríamos com uma grande pilha de código que se assemelha a uma pirâmide de destruição . Isso não é adequado para adicionar novos campos ao nosso formulário ou criar código muito legível. Pode ser difícil seguir o recuo, particularmente com tantas outras declarações em cada bifurcação.

func nonguardSubmit() {
    if let name = nameField.text {
        if let address = addressField.text {
            if let phone = phoneField.text {
                sendToServer(name, address: address, phone: phone)
            } else {
                show("no phone to submit")
            }
        } else {
            show("no address to submit")
        }
    } else {
        show("no name to submit")
    }
}

Sim, podemos até combinar todas essas instruções if let em uma única instrução separada por vírgulas, mas perderíamos a capacidade de descobrir qual instrução falhou e apresentar uma mensagem ao usuário.

https://thatthinginswift.com/guard-statement-swift/

Mel
fonte
5

Com o uso da guarda, nossa intenção é clara. não queremos executar o restante do código se essa condição específica não for atendida. aqui também podemos estender a cadeia, dê uma olhada no código abaixo:

guard let value1 = number1, let value2 = number2 else { return }
 // do stuff here
Narendra G
fonte
5

Declaração de guarda vai fazer. é par de diferente

1) é permitido reduzir a declaração aninhada
2) é aumentar meu escopo que minha variável acessível

Declaração if

func doTatal(num1 : Int?, num2: Int?) {
  // nested if statement
    if let fistNum = num1 where num1 > 0 {
        if let lastNum = num2 where num2 < 50 {

          let total = fistNum + lastNum
        }
    }
 // don't allow me to access out of the scope 
 //total = fistNum + lastNum 
}

Declaração de guarda

func doTatal(num1 : Int?, num2: Int?) {
   //reduce  nested if statement and check positive way not negative way 
    guard let fistNum = num1 where num1 > 0 else{
      return
    }
    guard  let lastNum = num2 where num2 < 50 else {
     return
    }
    // increase my scope which my variable accessible
    let total = fistNum + lastNum

}
Nazmul Hasan
fonte
3

Da documentação da Apple:

Declaração de guarda

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

Synatx:

guard condition else {
    statements
}

Vantagem:

1. Usando a guardinstrução, podemos nos livrar de condicionais profundamente aninhados, cujo único objetivo é validar um conjunto de requisitos.

2. Ele foi projetado especificamente para sair antecipadamente de um método ou função.

se você usar se deixe abaixo é o código que parece.

  let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

        if error == nil {
            if let  statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 {
                if let data = data {

                    //Process Data Here.
                    print("Data: \(data)")

                } else {
                    print("No data was returned by the request!")
                }
            } else {
                print("Your request returned a status code other than 2XX!")
            }
        } else {
            print("Error Info: \(error.debugDescription)")
        }
    }
    task.resume()

Usando o protetor, você pode transferir o controle de um escopo se uma ou mais condições não forem atendidas.

let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

            /* GUARD: was there an error? */
            guard (error == nil) else {
                print("There was an error with your request: \(error)")
                return
            }

            /* GUARD: Did we get a successful 2XX response? */
            guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else {
                print("Your request returned a status code other than 2XX!")
                return
            }

            /* GUARD: was there any data returned? */
            guard let data = data else {
                print("No data was returned by the request!")
                return
            }

            //Process Data Here.
            print("Data: \(data)")
}
task.resume()

Referência:

1. Swift 2: saia cedo com guarda 2. Udacity 3. Declaração de guarda

Ashok R
fonte
Mas você pode fazer o último com instruções if? if condition { return }grosseiro?
Oliver Dixon
2

Como uma instrução if, o guard executa instruções com base no valor booleano de uma expressão. Ao contrário de uma instrução if, as instruções de guarda são executadas apenas se as condições não forem atendidas. Você pode pensar em proteger mais como um Assert, mas em vez de travar, você pode sair normalmente.

consulte: http://ericcerney.com/swift-guard-statement/

Zgpeace
fonte
1

Realmente torna o fluxo de uma sequência com várias pesquisas e opcionais muito mais conciso e claro e reduz muito o aninhamento. Veja a publicação de Erica Sadun sobre a substituição de Ifs . .... Poderia se deixar levar, um exemplo abaixo:

    let filteredLinks = locationsLinkedToList.filter({$0.actionVerb == movementCommand})
    guard let foundLink = filteredLinks.first else {return ("<Person> cannot go in that direction.", nil, nil)}
    guard filteredLinks.count == 1 else {return ("<Person> cannot decide which route to take.", nil, nil)}
    guard let nextLocation = foundLink.toLocation else {return ("<Person> cannot go in that direction.", nil, nil)}

Veja se isso gruda.

DavidS
fonte
1

Simplificando, ele fornece uma maneira de validar campos antes da execução. Este é um bom estilo de programação, pois melhora a legibilidade. Em outros idiomas, pode ser assim:

func doSomething() {
    if something == nil {
        // return, break, throw error, etc.
    }
    ...
}

Mas como o Swift fornece opcionais, não podemos verificar se é nulo e atribuir seu valor a uma variável. Por outro lado, if letverifica se não é nulo e atribui uma variável para armazenar o valor real. É aqui que guardentra em jogo. Ele fornece uma maneira mais concisa de sair mais cedo usando os opcionais.

gunby
fonte
1

Fonte: Guarda em Swift

Vamos ver o exemplo para entender claramente

Exemplo 1:

func validate() {         
    guard 3>2 else {             
    print ("False")             
    return         
    }         
    print ("True") //True     
} 
validate()

No exemplo acima, vemos que 3 é maior que 2 e a instrução dentro da cláusula guard else é ignorada e True é impresso.

Exemplo 2:

func validate() {         
    guard 1>2 else {             
    print ("False")            //False 
    return         
    }         
    print ("True")      
} 
validate()

No exemplo acima, vemos que 1 é menor que 2 e a instrução dentro da cláusula guard else é executada e False é impresso seguido de retorno.

Example 3: gaurd let, unwrapping optionals through guard let

func getName(args myName: String?) {
     guard let name = myName, !name.isEmpty else {
     print ("Condition is false")          // Condition is false            return         
     }         
     print("Condition is met\(name)")     
} 
getName(args: "")

No exemplo acima, estamos usando o guard let para desembrulhar os opcionais. Na função getName, definimos uma variável do tipo string myName, que é opcional. Em seguida, usamos guard let para verificar se a variável myName é nula ou não, se não atribuir ao nome e verificar novamente, o nome não está vazio. Se ambas as condições forem qualificadas, ou seja, verdadeiras, o bloco else será ignorado e imprimirá “Condições são atendidas com o nome”.

Basicamente, estamos verificando duas coisas aqui separadas por vírgula, primeiro desembrulhando e opcional e verificando se isso satisfaz ou não a condição.

Aqui não estamos passando nada para a função, ou seja, string vazia e, portanto, Condition is false é print.

func getName(args myName: String?) {
     guard let name = myName, !name.isEmpty else {
     print ("Condition is false")          
     return         
     }        
     print("Condition is met \(name)") // Condition is met Hello    
} getName(args: "Hello")

Aqui estamos passando “Hello” para a função e você pode ver a saída impressa “Condition is met Hello Hello”.

Aditya
fonte