Поиск…


Синтаксис

  • var closVar: (<parameters>) -> (<returnType>) // В качестве переменной или типа свойства
  • typealias ClosureType = (<parameters>) -> (<returnType>)
  • {[<captureList>] (<parameters>) <throws-ness> -> <returnType> в <statements>} // Полный синтаксис закрытия

замечания

Дополнительную информацию о закрытии Swift см. В документации Apple .

Основы закрытия

Закрытие (также известное как блоки или лямбда ) - это фрагменты кода, которые можно хранить и передавать внутри вашей программы.

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

sayHi()  // prints "Hello"

Как и другие функции, замыкания могут принимать аргументы и возвращать результаты или бросать ошибки :

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"

Закрытие может захватывать значения из их объема:

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

Замыкания могут передаваться непосредственно в функции:

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

Варианты синтаксиса

Основной синтаксис закрытия

{ [ list list ] ( parameters ) throws-ness -> return type in body } .

Многие из этих частей могут быть опущены, поэтому существует несколько эквивалентных способов написания простых замыканий:

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 }
  • Список захвата может быть опущен, если он пуст.
  • Параметры не нужны аннотации типов, если их типы можно вывести.
  • Тип возвращаемого значения не нужно указывать, если это можно сделать.
  • Параметры не обязательно должны быть названы; вместо этого они могут ссылаться на $0 , $1 , $2 и т. д.
  • Если в закрытии содержится одно выражение, значение которого должно быть возвращено, ключевое слово return может быть опущено.
  • Если замыкание выведено для того, чтобы вызвать ошибку, оно записывается в контексте, который ожидает закрытия броска или не вызывает ошибку, 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 }

Передача замыканий в функции

Функции могут принимать замыкания (или другие функции) в качестве параметров:

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

Синтаксис закрытия трейлинга

Если последним параметром функции является замыкание, замыкающие скобки { / } могут быть записаны после вызова функции:

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

Если единственным аргументом функции является замыкание, вы можете также опустить пару круглых скобок () при вызове с помощью синтаксиса закрытия закрытия:

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

bar() { print("Hello") }

bar { print("Hello") }

Параметры @noescape

Параметры закрытия, отмеченные как @noescape , гарантированно выполняются до вызова функции, поэтому используйте self. не требуется внутри корпуса закрывания:

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

Свифт 3 Примечание:

Обратите внимание, что в Swift 3 вы больше не отмечаете блоки как @noescape. Блоки теперь не экранируются по умолчанию. В Swift 3 вместо того, чтобы отмечать закрытие как не-экранирование, вы отмечаете параметр функции, который является закрывающим закрытием, как escape-код, используя ключевое слово «@escaping».


throws и rethrows

Закрытие, как и другие функции, может вызывать ошибки :

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

Функция может, конечно, передать ошибку вместе со своим вызывающим:

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

Однако, если переданный блок не выбрасывает, вызывающий объект все еще застревает с функцией throwing:

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

Решением является rethrows , что rethrows , что функция может только бросать, если его параметр закрытия бросает :

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 }

Многие стандартные библиотечные функции используют rethrows , включая map() , filter() и indexOf() .

Захваты, сильные / слабые ссылки и циклы сохранения

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

Когда замыкание фиксирует ссылочный тип (экземпляр класса), по умолчанию имеет сильную ссылку:

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"

Список захвата закрытия можно использовать для указания слабой или неопубликованной ссылки:

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.

Для получения дополнительной информации см. Раздел « Управление памятью» и раздел « Автоматический подсчет ссылок » на языке «Быстрый язык программирования».

Сохранять циклы

Если объект удерживается на замыкании, который также содержит сильную ссылку на объект, это цикл сохранения . Если цикл не сломан, память, хранящая объект и закрытие, будет просочиться (никогда не будет исправлена).

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

Использование замыканий для асинхронного кодирования

Закрытие часто используется для асинхронных задач, например, при получении данных с веб-сайта.

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

Эта функция асинхронна, поэтому не будет блокировать поток, на который он вызывается (он не будет замораживать интерфейс, если вызван в основном потоке вашего приложения GUI).

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

Поскольку задача является асинхронной, вывод будет выглядеть следующим образом:

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

Поскольку код внутри закрытия, print("2. Fetched data") не будет вызываться до тех пор, пока не будут получены данные из URL-адреса.

Закрытие и псевдоним типа

Закрытие может быть определено с помощью typealias . Это обеспечивает удобный заполнитель типа, если одна и та же подпись закрытия используется в нескольких местах. Например, общие обратные вызовы сетевых запросов или обработчики событий пользовательского интерфейса делают отличные кандидаты для «имени» с псевдонимом типа.

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

Затем вы можете определить функцию с помощью 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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow