Swift @escaping ve Tamamlama İşleyicisi


101

Swift'in 'Kapanışını' daha net anlamaya çalışıyorum.

Ama @escapingve Completion Handleranlaşılması çok zor

Birçok Swift ilanını ve resmi belgeyi araştırdım, ancak yine de yeterli olmadığını hissettim.

Bu resmi belgelerin kod örneğidir

var completionHandlers: [()->Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
    completionHandlers.append(completionHandler)
}

func someFunctionWithNoneescapingClosure(closure: ()->Void){
    closure()
}

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

Kullanmanın iki yolu ve nedeni olduğunu duydum @escaping

Birincisi, bir kapağın depolanması için, ikincisi Async işletim amaçları içindir.

Aşağıdakiler sorularım :

İlk olarak, eğer doSomethingçalıştırılırsa daha sonra someFunctionWithEscapingClosurekapanma parametresi ile çalıştırılacak ve bu kapanış global değişken dizisine kaydedilecektir.

Bence kapanış {self.x = 100}

Nasıl selfgenel değişken kaydedilir {self.x = 100} içinde completionHandlersbağlanabilir instanceo nesne SomeClass?

İkincisi, someFunctionWithEscapingClosureböyle anlıyorum .

Yerel değişken kapanışını completionHandlerglobal değişken 'completeHandlers we using@ escaping` anahtar kelimesine depolamak için !

@escapinganahtar kelime someFunctionWithEscapingClosuredönüşleri olmadan yerel değişken completionHandlerbellekten kaldırılır

@escaping bu kapanışı hafızada tutmak mı

Bu doğru mu?

Son olarak, bu gramerin varlığını merak ediyorum.

Belki bu çok basit bir sorudur.

Bir işlevin belirli bir işlevden sonra yürütülmesini istiyorsak. Neden belirli bir işlev çağrısından sonra bazı işlevleri çağırmıyoruz?

Yukarıdaki kalıbı kullanmakla kaçan bir geri arama işlevi kullanmak arasındaki farklar nelerdir?

Yanıtlar:


125

Hızlı Tamamlama İşleyicisinden Kaçan ve Kaçmayan:

Bob Lee, Bob ile Swift'deki Completion Handlers adlı blog gönderisinde açıkladığı gibi :

Kullanıcının onu kullanırken bir uygulamayı güncellediğini varsayın. Kesinlikle bittiğinde kullanıcıyı bilgilendirmek istiyorsunuz. Muhtemelen, "Tebrikler, şimdi tam anlamıyla eğlenebilirsiniz!" Yazan bir kutuyu açmak isteyebilirsiniz.

Öyleyse, yalnızca indirme işlemi tamamlandıktan sonra bir kod bloğunu nasıl çalıştırırsınız? Ayrıca, yalnızca bir görünüm denetleyicisi bir sonrakine taşındıktan sonra belirli nesneleri nasıl canlandırırsınız? Peki, bir patron gibi nasıl tasarlanacağını bulacağız.

Geniş kelime listeme göre, tamamlama işleyicileri şu anlama gelir:

İşler bittiğinde bir şeyler yapın

Bob'un gönderisi, tamamlama işleyicileri hakkında netlik sağlar (geliştirici bakış açısından, anlamamız gereken şeyi tam olarak tanımlar).

@escaping kapanışları:

Biri işlev bağımsız değişkenlerinde bir kapanış geçirdiğinde, işlevin gövdesi çalıştırıldıktan sonra onu kullanmak ve derleyiciyi geri döndürür. İşlev sona erdiğinde, geçirilen kapanmanın kapsamı var olur ve kapanış çalıştırılıncaya kadar bellekte var olur.

İçeren işlevde kapanıştan kaçmanın birkaç yolu vardır:

  • Depolama: Kapanışı global değişken, özellik veya çağıran işlevin geçmiş belleğinde bulunan herhangi bir başka depoda saklamanız gerektiğinde çalıştırılır ve derleyiciyi geri döndürür.

  • Eşzamansız yürütme: Sevk kuyruğunda kapatma işlemini eşzamansız olarak yürüttüğünüzde, kuyruk sizin için bellekte kapanmayı tutacaktır, ileride kullanılabilir. Bu durumda kapatmanın ne zaman yürütüleceğine dair hiçbir fikriniz yok.

Bu senaryolarda kapanışı kullanmaya çalıştığınızda Swift derleyicisi hatayı gösterecektir:

hata ekran görüntüsü

Bu konu hakkında daha fazla netlik için Medium'daki bu gönderiye göz atabilirsiniz .

Her ios geliştiricisinin anlaması gereken bir nokta daha eklemek:

  1. Kaçan Kapanış : Kaçan bir kapanış, döndürülen işlevden sonra çağrılan bir kapanmadır. Başka bir deyişle, geçtiği işlevden daha uzun ömürlüdür.
  2. Kaçan olmayan kapanış : Geçildiği işlev içinde, yani dönmeden önce çağrılan bir kapanış.

@shabhakar, ya bir kapanışı saklarsak ama daha sonra aramasak? Ya da method iki kez çağrıldıysa ama biz closure'ı sadece bir kez çağırdık. Sonucun aynı olduğunu bildiğimiz için.
user1101733

@ user1101733 Kapanıştan kaçmaktan bahsettiğinizi düşünüyorum, siz aramayana kadar Closure çalışmayacaktır. Yukarıdaki örnekte doSomething yöntemini 2 kez çağırırsanız, 2 completeHandler nesnesi completeHandlers dizisine eklenir. CompleteHandlers dizisinden ilk nesneyi alır ve çağırırsanız, çalıştırılır ancak completeHandlers dizi sayısı aynı kalır (2).
Deepak

@Deepak, Evet kaçış kapanışı hakkında. en son çağrıyı neyi yürüteceğimizden dolayı, dizi kullanmadığımızı ve kapanış referansını saklamak için normal değişkeni kullanmadığımızı varsayalım. Asla aranmayacak olan önceki kapanışlar tarafından bir hafıza işgal edilecek mi?
user1101733

1
@ user1101733 Kapanış referans tipidir (sınıf gibi), değişkene yeni kapanışlar atadığınızda özellik / değişken yeni kapanışı gösterecek ve böylece ARC önceki kapanışlar için belleği serbest bırakacaktır.
Deepak

29

İşte kendime @escaping'in nasıl çalıştığını hatırlatmak için kullandığım küçük bir örnek sınıfı.

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}

2
Bu kod doğru değil. @escapingElemeler eksik .
Rob

Bunu en çok sevdimi.e. escape the scope of this function.
Gal
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.