Parameter penutupan lolos opsional Swift

162

Diberikan:

typealias Action = () -> ()

var action: Action = { }

func doStuff(stuff: String, completion: @escaping Action) {
    print(stuff)
    action = completion
    completion()
}

func doStuffAgain() {
    print("again")
    action()
}

doStuff(stuff: "do stuff") { 
    print("swift 3!")
}

doStuffAgain()

Apakah ada cara untuk membuat completionparameter (dan action) bertipe Action?dan juga menyimpannya @escaping?

Mengubah jenisnya memberikan kesalahan berikut:

Atribut @escaping hanya berlaku untuk tipe fungsi

Menghapus @escapingatribut, kode mengkompilasi dan menjalankan, tetapi tampaknya tidak benar karena completionpenutupan keluar dari ruang lingkup fungsi.

Lescai Ionel
sumber
21
"Menghapus @escapingatribut, kode mengkompilasi dan menjalankan" - Itu karena, seperti yang dijelaskan dalam SR-2444 , Action?secara default, melarikan diri. Jadi, melepas @escapingketika menggunakan penutupan opsional memenuhi apa yang Anda butuhkan.
Rob
type alias closures are escape
Masih
Inilah artikel yang sangat bagus dari Ole Begemann yang menjelaskan mengapa hal itu terjadi dan beberapa solusi jika Anda ingin parameter opsional menjadi @noescape.
Senseful

Jawaban:

122

Ada pelaporan SR-2552 yang @escapingtidak mengenali jenis fungsi alias. itu sebabnya kesalahan @escaping attribute only applies to function types. Anda bisa mengatasinya dengan memperluas jenis fungsi di tanda tangan fungsi:

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: (@escaping ()->())?) {
    print(stuff)
    action = completion
    completion?()
}

func doStuffAgain() {
    print("again")
    action?()
}

doStuff(stuff: "do stuff") {
    print("swift 3!")
}

doStuffAgain()

EDIT 1 ::

Saya sebenarnya di bawah versi beta xcode 8 di mana bug SR-2552 belum diselesaikan. memperbaiki bug itu, memperkenalkan bug baru (yang Anda hadapi) yang masih terbuka. lihat SR-2444 .

Solusinya @Michael Ilseman menunjuk sebagai solusi sementara adalah menghapus @escapingatribut dari tipe fungsi opsional, yang menjaga fungsi sebagai melarikan diri .

func doStuff(stuff: String, completion: Action?) {...}

EDIT 2 ::

The SR-2444 telah ditutup menyatakan secara eksplisit bahwa penutupan di posisi parameter tidak melarikan diri dan membutuhkan mereka harus ditandai dengan @escapinguntuk membuat mereka melarikan diri, tetapi parameter opsional yang secara implisit melarikan diri, karena ((Int)->())?merupakan sinonim dari Optional<(Int)->()>, penutupan opsional melarikan diri.

Jans
sumber
5
Sekarang menjadi@escaping may only be applied to parameters of function type func doStuff(stuff: String, completion: (@escaping ()->())?) {
Lescai Ionel
1
a temporary solution is remove the @escaping attribute from optional function type, that keep the function as escaping. Bisakah Anda menjelaskan ini lebih lanjut? Semantik default di swift 3 adalah non-escape. Walaupun ia mengkompilasi tanpa @escaping saya khawatir ini akan menyebabkan masalah dengan diperlakukan sebagai non-escaping. Apakah itu tidak benar?
Pat Niemeyer
49
Setelah membaca lebih lanjut, saya melihat bahwa SR-2444 mengatakan bahwa semua penutupan opsional diperlakukan sebagai pelarian, yang merupakan bug pelengkap :) Saya akan berasumsi bahwa ketika diperbaiki, kompilasi akan memperingatkan kita untuk melakukan perubahan.
Pat Niemeyer
Mungkin sedikit di luar topik; tetapi bagaimana cara kerjanya @autoclosure? Satu mendapat kesalahan yang sama di sana ...
Astaga.
ini bekerja tanpa @escaping __ func doStuff (barang: String, selesai: (() -> ())?) {
Феннур Мезитов
226

dari: milis swift-pengguna

Pada dasarnya, @escaping hanya valid pada penutupan pada posisi parameter fungsi. Aturan noescape-by-default hanya berlaku untuk penutupan ini pada posisi parameter fungsi, jika tidak maka mereka akan lolos. Agregat, seperti enum dengan nilai terkait (misalnya Opsional), tupel, struct, dll., Jika mereka memiliki penutupan, ikuti aturan default untuk penutupan yang tidak pada posisi parameter fungsi, yaitu mereka melarikan diri.

Jadi parameter fungsi opsional adalah @escaping secara default.
@noecape hanya berlaku untuk parameter fungsi secara default.

Dmitry Coolerov
sumber
7
Saya pikir ini menambahkan informasi paling penting ke subjek, itu harus diterima.
Damian Dudycz
Jawaban yang beralasan. Seberapa besar kemungkinan ini bisa berubah?
GoldenJoe
2
Ini masuk akal karena secara teknis mengatakan (()->Void)?itu sama dengan mengatakan Anda miliki Optional<()->Void>dan agar untuk Optionalmempertahankan kepemilikan itu harus hanya menerima @escapingfungsi. Saya akan ketiga bahwa ini harus menjadi jawaban yang diterima. Terima kasih.
Dean Kelly
22

Saya mengalami masalah yang sama karena pencampuran @escapingdan non @escaping-sangat membingungkan, terutama jika Anda harus melewati penutupan.

Saya akhirnya memberikan nilai default no-op ke parameter penutupan via = { _ in }, yang menurut saya lebih masuk akal:

func doStuff(stuff: String = "do stuff",
        completion: @escaping (_ some: String) -> Void = { _ in }) {
     completion(stuff)
}

doStuff(stuff: "bla") {
    stuff in
    print(stuff)
}

doStuff() {
    stuff in
    print(stuff)
}
Manusia bebas
sumber
Ini bersih & cantik dalam implementasinya.
Fred Faust
2
Bagaimana jika saya ingin memeriksa apakah blok ini kosong / kosong?
Vyachaslav Gerchicov
2
Nyebelin, ini tidak bekerja untuk metode protokol. "Argumen default tidak diizinkan dalam metode protokol" (Xcode 8.3.2).
Mike Taverne
17

Saya membuatnya bekerja di Swift 3 tanpa peringatan apa pun hanya dengan cara ini:

func doStuff(stuff: String, completion: (()->())? ) {
    print(stuff)
    action = completion
    completion?()
}
Igor
sumber
4

Yang penting untuk memahami dalam contoh adalah bahwa jika Anda mengubah Actionke Action?penutupan yang melarikan diri. Jadi, mari kita lakukan apa yang Anda usulkan:

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: Action?) {
    print(stuff)
    action = completion
    completion?()
}

Oke, sekarang kita akan menelepon doStuff:

class ViewController: UIViewController {
    var prop = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        doStuff(stuff: "do stuff") {
            print("swift 3!")
            print(prop) // error: Reference to property 'prop' in closure 
                        // requires explicit 'self.' to make capture semantics explicit
        }
    }
}

Nah, persyaratan itu hanya muncul untuk lolos dari penutupan. Jadi penutupannya keluar. Itu sebabnya Anda tidak menandainya melarikan diri - itu sudah melarikan diri.

matt
sumber