Sök…


Syntax

  • var closureVar: (<parameters>) -> (<returnType>) // Som variabel eller egenskapstyp
  • typealias ClosureType = (<parameters>) -> (<returnType>)
  • {[<captureList>] (<parameters>) <throwsness> -> <returnType> in <statements>} // Komplett stängningssyntax

Anmärkningar

Mer information om Swift-stängningar finns i Apples dokumentation .

Grunderna för stängning

Stängningar (även känd som block eller lambdas ) är kodkoder som kan lagras och skickas runt i ditt program.

let sayHi = { print("Hello") }
// The type of sayHi is "() -> ()", aka "() -> Void"

sayHi()  // prints "Hello"

Liksom andra funktioner kan stängningar acceptera argument och returnera resultat eller kasta fel :

let addInts = { (x: Int, y: Int) -> Int in
    return x + y
}
// The type of addInts is "(Int, Int) -> Int"

let result = addInts(1, 2)  // result is 3

let divideInts = { (x: Int, y: Int) throws -> Int in
    if y == 0 {
        throw MyErrors.DivisionByZero
    }
    return x / y
}
// The type of divideInts is "(Int, Int) throws -> Int"

Stängningar kan fånga värden från deras omfattning:

// This function returns another function which returns an integer
func makeProducer(x: Int) -> (() -> Int) {
    let closure = { x }  // x is captured by the closure
    return closure
}

// These two function calls use the exact same code,
// but each closure has captured different values.
let three = makeProducer(3)
let four = makeProducer(4)
three()  // returns 3
four()  // returns 4

Stängningar kan överföras direkt till funktioner:

let squares = (1...10).map({ $0 * $0 })  // returns [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
let squares = (1...10).map { $0 * $0 }

NSURLSession.sharedSession().dataTaskWithURL(myURL,
    completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) in
        if let data = data {
            print("Request succeeded, data: \(data)")
        } else {
            print("Request failed: \(error)")
        }
    }).resume()

Syntaxvarianter

Den grundläggande stängningssyntaxen är

{ [ capture list ] ( parametrar ) throw-ness -> return type in body } .

Många av dessa delar kan utelämnas, så det finns flera likvärdiga sätt att skriva enkla stängningar:

let addOne = { [] (x: Int) -> Int in return x + 1 }
let addOne = { [] (x: Int) -> Int in x + 1 }
let addOne = { (x: Int) -> Int in x + 1 }
let addOne = { x -> Int in x + 1 }
let addOne = { x in x + 1 }
let addOne = { $0 + 1 }

let addOneOrThrow = { [] (x: Int) throws -> Int in return x + 1 }
let addOneOrThrow = { [] (x: Int) throws -> Int in x + 1 }
let addOneOrThrow = { (x: Int) throws -> Int in x + 1 }
let addOneOrThrow = { x throws -> Int in x + 1 }
let addOneOrThrow = { x throws in x + 1 }
  • Fotograferingslistan kan utelämnas om den är tom.
  • Parametrar behöver inte typanteckningar om deras typer kan sluts.
  • Returtypen behöver inte anges om den kan dras.
  • Parametrar behöver inte namnges; istället kan de hänvisas till med $0 , $1 , $2 etc.
  • Om stängningen innehåller en enda uttryck, vars värde ska returneras, den return kan nyckelordet utelämnas.
  • Om slutsatsen dras att kasta ett fel, skrivs i ett sammanhang som förväntar sig att ett kast kastas eller inte kastar ett fel, kan throws .
// The closure's type is unknown, so we have to specify the type of x and y.
// The output type is inferred to be Int, because the + operator for Ints returns Int.
let addInts = { (x: Int, y: Int) in x + y }

// The closure's type is specified, so we can omit the parameters' type annotations.
let addInts: (Int, Int) -> Int = { x, y in x + y }
let addInts: (Int, Int) -> Int = { $0 + $1 }

Att överföra stängningar till funktioner

Funktioner kan acceptera stängningar (eller andra funktioner) som parametrar:

func foo(value: Double, block: () -> Void) { ... }
func foo(value: Double, block: Int -> Int) { ... }
func foo(value: Double, block: (Int, Int) -> String) { ... }

Baksidan syntax

Om en sista parameters sista parameter är en stängning, kan stängningsstödet { / } skrivas efter funktionskallandet:

foo(3.5, block: { print("Hello") })

foo(3.5) { print("Hello") }

dispatch_async(dispatch_get_main_queue(), {
    print("Hello from the main queue")
})

dispatch_async(dispatch_get_main_queue()) {
    print("Hello from the main queue")
}

Om en funktions enda argument är en stängning, kan du också utelämna paret inom parentes () när du kallar det med den bakre stängningssyntaxen:

func bar(block: () -> Void) { ... }

bar() { print("Hello") }

bar { print("Hello") }

@noescape parametrar

Stängningsparametrar som är markerade @noescape kommer garanterat att köras innan funktionssamtalet återgår, så med self. krävs inte inuti förslutningskroppen:

func executeNow(@noescape block: () -> Void) {
    // Since `block` is @noescape, it's illegal to store it to an external variable.
    // We can only call it right here.
    block()
}

func executeLater(block: () -> Void) {
    dispatch_async(dispatch_get_main_queue()) {
        // Some time in the future...
        block()
    }
}
class MyClass {
    var x = 0
    func showExamples() {
        // error: reference to property 'x' in closure requires explicit 'self.' to make capture semantics explicit
        executeLater { x = 1 }

        executeLater { self.x = 2 }  // ok, the closure explicitly captures self

        // Here "self." is not required, because executeNow() takes a @noescape block.
        executeNow { x = 3 }

        // Again, self. is not required, because map() uses @noescape.
        [1, 2, 3].map { $0 + x }
    }
}

Snabb 3-anmärkning:

Observera att i Swift 3 markerar du inte längre block som @noescape. Block flyr nu inte som standard. I Swift 3, i stället för att markera en stängning som icke-undkommande, markerar du en funktionsparameter som är en avslutande stängning som flyr med hjälp av nyckelordet "@escaping".


throws och rethrows

Stängningar, som andra funktioner, kan kasta fel :

func executeNowOrIgnoreError(block: () throws -> Void) {
    do {
        try block()
    } catch {
        print("error: \(error)")
    }
}

Funktionen kan naturligtvis överföra felet till dess uppringande:

func executeNowOrThrow(block: () throws -> Void) throws {
    try block()
}

Men om det blockerade passet inte kastar, är den som ringer fortfarande fast med en kastfunktion:

// It's annoying that this requires "try", because "print()" can't throw!
try executeNowOrThrow { print("Just printing, no errors here!") }

Lösningen är rethrows , som anger att funktionen bara kan kasta om dess stängningsparameter kastar :

func executeNowOrRethrow(block: () throws -> Void) rethrows {
    try block()
}

// "try" is not required here, because the block can't throw an error.
executeNowOrRethrow { print("No errors are thrown from this closure") }

// This block can throw an error, so "try" is required.
try executeNowOrRethrow { throw MyError.Example }

Många standardbiblioteksfunktioner använder rethrows , inklusive map() , filter() och indexOf() .

Fångar, starka / svaga referenser och behåller cykler

class MyClass {
    func sayHi() { print("Hello") }
    deinit { print("Goodbye") }
}

När en nedläggning fångar en referenstyp (en klassinstans) har den en stark referens som standard:

let closure: () -> Void
do {
    let obj = MyClass()
    // Captures a strong reference to `obj`: the object will be kept alive
    // as long as the closure itself is alive.
    closure = { obj.sayHi() }
    closure()  // The object is still alive; prints "Hello"
} // obj goes out of scope
closure()  // The object is still alive; prints "Hello"

Stängningens fångstlista kan användas för att specificera en svag eller obesökt referens:

let closure: () -> Void
do {
    let obj = MyClass()
    // Captures a weak reference to `obj`: the closure will not keep the object alive;
    // the object becomes optional inside the closure.
    closure = { [weak obj] in obj?.sayHi() }
    closure()  // The object is still alive; prints "Hello"
} // obj goes out of scope and is deallocated; prints "Goodbye"
closure()  // `obj` is nil from inside the closure; this does not print anything.
let closure: () -> Void
do {
    let obj = MyClass()
    // Captures an unowned reference to `obj`: the closure will not keep the object alive;
    // the object is always assumed to be accessible while the closure is alive.
    closure = { [unowned obj] in obj.sayHi() }
    closure()  // The object is still alive; prints "Hello"
} // obj goes out of scope and is deallocated; prints "Goodbye"
closure()  // crash! obj is being accessed after it's deallocated.

Mer information finns i minneshanteringsämnet och avsnittet Automatisk referensräkning i Swift-programmeringsspråket.

Behåll cykler

Om ett föremål håller fast vid en stängning, som också har en stark referens till föremålet, är detta en kvarhållningscykel . Såvida inte cykeln är trasig, kommer minnet som lagrar objektet och stängningen läckt ut (aldrig återvunnet).

class Game {
    var score = 0
    let controller: GCController
    init(controller: GCController) {
        self.controller = controller

        // BAD: the block captures self strongly, but self holds the controller
        // (and thus the block) strongly, which is a cycle.
        self.controller.controllerPausedHandler = {
            let curScore = self.score
            print("Pause button pressed; current score: \(curScore)")
        }

        // SOLUTION: use `weak self` to break the cycle.
        self.controller.controllerPausedHandler = { [weak self] in
            guard let strongSelf = self else { return }
            let curScore = strongSelf.score
            print("Pause button pressed; current score: \(curScore)")
        }
    }
}

Använda stängningar för asynkron kodning

Stängningar används ofta för asynkrona uppgifter, till exempel när man hämtar data från en webbplats.

3,0
func getData(urlString: String, callback: (result: NSData?) -> Void) {

    // Turn the URL string into an NSURLRequest.
    guard let url = NSURL(string: urlString) else { return }
    let request = NSURLRequest(URL: url)
    
    // Asynchronously fetch data from the given URL.
    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {(data: NSData?, response: NSURLResponse?, error: NSError?) in

        // We now have the NSData response from the website.
        // We can get it "out" of the function by using the callback 
        // that was passed to this function as a parameter.

        callback(result: data)
    }
        
    task.resume()
}

Den här funktionen är asynkron, så kommer inte att blockera tråden som den kallas på (den kommer inte att frysa gränssnittet om den kallas på huvudtråden i din GUI-applikation).

3,0
print("1. Going to call getData")

getData("http://www.example.com") {(result: NSData?) -> Void in

    // Called when the data from http://www.example.com has been fetched.
    print("2. Fetched data")
}

print("3. Called getData")

Eftersom uppgiften är asynkron kommer utdata vanligtvis att se ut så här:

"1. Going to call getData"
"3. Called getData"
"2. Fetched data"

Eftersom koden inuti stängningen, print("2. Fetched data") , kommer inte att anropas förrän data från URL: en hämtas.

Stängningar och typ alias

En stängning kan definieras med en typealias . Detta ger en platshållare av bekväm typ om samma stängningssignatur används på flera platser. Exempelvis gör vanliga återuppringningar av nätverksbegäran eller händelsehanterare av användargränssnitt fantastiska kandidater för att "namnges" med ett typalias.

public typealias ClosureType = (x: Int, y: Int) -> Int

Du kan sedan definiera en funktion med hjälp av typealias:

public func closureFunction(closure: ClosureType) {
    let z = closure(1, 2)
}
    
closureFunction() { (x: Int, y: Int) -> Int in return x + y }


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow