Swift vs. Kotlin

Koruma döngüleri nedeniyle, iOS geliştiricilerinin bazen basit şeyler için Android geliştiricilerinden daha karmaşık kodlar yazması gerekir. Bunun en iyi örneği bir kapak (Swift) ve lambda (Android) kullanmaktır.

Swift, sınıf mirası, protokol uygunlukları, jeneriklik ve aşırı yükleme gibi birçok özelliğe sahip zengin bir tip sistemiyle çok etkileyici bir dildir.
Swift, sınıf mirası, protokol uygunlukları, jeneriklik ve aşırı yükleme gibi birçok özelliğe sahip zengin bir tip sistemiyle çok etkileyici bir dildir.

Çöp toplama

Otomatik bellek yönetimi olarak da bilinen Çöp Toplama işlemi, dinamik olarak ayrılmış belleğin otomatik geri dönüşümüdür . Hala anlamadın mı? Tamam, bunu bazı resimler ve basitleştirilmiş bir örnek kullanarak açıklamama izin verin.

Bu bellek yığını (ile karıştırmayın olduğunu yığını uygulamanıza atanan). Şu anda boş.

1) Boş bellek yığını

Uygulamanız başladığında, çalışma zamanındaki nesnelere (başvuru türleri) bellek ayırır.

2) Tahsis edilmiş hafıza bloklarına sahip hafıza yığını (mavi dikdörtgenler)

Bir süre sonra, bazı nesneler çöp toplama için uygun hale gelir ve ardından bellek yığınından kaldırılır.

3) Koyu mavi dikdörtgenler, çöp toplama için uygun olan ve daha sonra bellek öbeğinden kaldırılan nesneleri belirtir.

Bir nesne, referans alınmadığında çöp toplama için iyi bir aday olur. Örneğin, bir TODO uygulamanız var. Bir görev listesi ekranı ve bir görev ayrıntıları ekranı vardır. İlk olarak, uygulamanız başlatılırken görev listesini görüntülemek için bellek ayırır. Kullanıcı listedeki bir öğeye tıkladığında, ayrıntılar ekranı görüntülenir. Bu işlem için, uygulamanız yığına dinamik olarak ek bellek ayırır. Kullanıcı görev ayrıntıları ekranını kapattığında, bununla ilgili tüm nesneler kaldırılmalıdır. Sebep basittir. Sınırsız hafızanız yok ve geri almak zorundasınız. Bu nedenle sistem erişilemeyen nesneleri kaldırıyor. Yararları şüphe gölgesi olmadan açıktır. Bu, kullanılmayan nesnelerin silinmesinden programcının sorumlu olmadığı anlamına gelir. Hem Kotlin hem de Swift bu işlemi C, C ++ ‘dan farklı olarak otomatikleştirmiştir. Yani, Bu özelliği ücretsiz olarak karşımıza çıkarsa ve her şey sihirli bir şekilde gerçekleşirse, umrunda mı olmalısınız? Evet, yapmalısın çünkü bu, hata yapamayacağın anlamına gelmez. Dahası, bu bilgi çok daha sorunsuz çalışan daha iyi uygulamalar yazmanıza izin verir. Mükemmel bir Android veya iOS geliştiricisi olmak istiyorsanız, bellek yönetiminde bilgili olmalısınız.

Swift ve Kotlin’in programcıları bir uygulama için belleği boşaltma görevinden kurtarmasına rağmen, bunu farklı şekillerde yapıyorlar. Bu ileri düzey konuyu mümkün olduğunca açıklamaya çalışacağım. Ayrıntılara odaklanmayacağım, çünkü bu makaleyi herkes için anlaşılabilir tutmak istiyorum. Daha fazla bilgi edinmek isteyenler için bazı referanslar bıraktım. Kotlin ile başlayalım.

Kotlin’de hafıza yönetimi (Android)

Android, CMS algoritmasıyla çöp toplama izlemesi olarak bilinen en yaygın çöp toplama türünü kullanır. Cms anlamına gelirEşzamanlı Mark-Sweep. İhtiyaçlarımız için “C” harfini görmezden gelebilirsiniz. Burada temel işaret ve tarama algoritmasının nasıl çalıştığını anlamak daha önemlidir.

İlk adım, Çöp Toplama Köklerini tanımlamaktır. Statik değişkenler, aktif iplikler (örneğin Android’de bir UI Thread) veya burada listelenenlerin bir kısmı olabilir . Bu yapıldığında, GC bir işaret aşamasına başlar . Bu amaçla, GC tüm nesneler ağacından geçer. Oluşturulan her nesne, varsayılan olarak 0’a ayarlanmış bir işaret biti içerir. Bir nesneyi işaret aşamasında ziyaret ettiğinde, işaret biti 1 olarak ayarlanır – bu, erişilebilir olduğu anlamına gelir.

GC Root

Yukarıdaki resimde, bu aşamadan sonra gri kalan nesnelere erişilemez, bu nedenle uygulamamız artık onlara ihtiyaç duymuyor. Ancak, daha ileri gitmeden önce, lütfen resme tekrar bakın. Birbirine işaret eden nesneleri fark ediyor musunuz? iOS geliştiricileri onları döngüleri sürdürmek olarak adlandırıyor. Bu sorun Android dünyasında yok. GC Köküne hiçbir yol olmadığında “çevrimler” kaldırılır.

Marka aşaması hakkında bahsetmeye değer başka bir şey, onun gizli maliyetidir. Dünyayı Durdur terimine aşina olmalısınız . Her toplama döngüsünden önce, GC, nesneler ağacından geçerken yeni nesnelerin tahsis edilmesini önlemek için uygulamamızı duraklatır. Duraklatma süresi ulaşılabilir nesnelerin sayısına bağlıdır. Toplam nesne veya yığın boyutu önemli değil. Bu yüzden birçok “canlı” gereksiz nesne yaratmak acı vericidir – örneğin, bir döngü içinde otomatik kutulama. GC, bellek yığını neredeyse dolduğunda işlemi başlatır. Böylece, pek çok gereksiz nesne oluşturduğunuzda, daha fazla GC çevrimi ve daha fazla kare damlası üreten bellek yığını daha hızlı doldurursunuz, çünkü her duraklama uygulamanızın zamanını kullanır.

Şimdi çıkarmadan bahsedelim. Herhangi bir atıktan kurtulmak için, GC bir sonraki aşamaya geçer – süpürme . Bu durumda, GC, bir işaret biti 0’a ayarlanmış tüm nesneleri bulmak için bellek öbeğini arar. Son adımda, bunlar kaldırılır ve erişilebilen tüm nesnelerin işaret bitleri 0’a sıfırlanır. . Bununla birlikte, bu çözümün bir sakıncası vardır. Bellek yığını parçalanmasına yol açabilir. Bu, yığınınızın toplamda oldukça fazla boş alana sahip olabileceği anlamına gelir (boş hafıza), ancak bu boşluk küçük bloklara bölünmüştür. Sonuç olarak, 2 MB’lık bir nesneyi 4 MB boş belleğe ayırmaya çalışırken sorun yaşayabilirsiniz. Neden? Çünkü, en büyük tek blok 2 MB sığdırmak için yeterli olmayabilir. Android’in Compact adlı gelişmiş bir sürümünü kullanmasının nedeni budur . Kompaktvaryantın bir ek adımı daha vardır. Tarama evresinden kurtulan nesneler, bellek yığınının başlangıcına hareket ettirilir – resmi kontrol edin.

Bedava öğle yemeği diye bir şey yoktur. Bu geliştirme, GC duraklamasını arttırır.

Yani Android’de bellek yönetimi kısaca. Tabii ki, bütün yönleri kapsamadım. Örneğin, yığın oluşturma konusunu atladım. 

Tamam, şimdi Swift zamanı.

Swift’de bellek yönetimi (iOS)

Swift basit bir çöp toplama mekanizması kullanır. Buna ARC ( Otomatik Referans Sayma ) denir . Bu yaklaşım, diğer nesneler tarafından tutulan bir nesneye yapılan güçlü referansların izlenmesine dayanır. Bir sınıfın yarattığı her yeni örnek ekstra bilgi depolar – bir referans sayacı. Bir özelliğe bir değişkene, değişkene veya sabit bir nesneyi atadığınızda (ona güçlü bir referans yaparak), referanslar sayaç değerini artırırsınız. Bu değer 0’a eşit olmadıkça nesneniz güvendedir ve yerinden çıkarılamaz. Ancak referanslar sayacı 0’a girer girmez, nesne hemen duraklatılır *, duraklama olmadan ve GC toplama döngüsünü başlatmadan. Çöp toplama türüne göre büyük bir avantaj.

Tamam, “hemen” yanına bir yıldız işareti koydum. Bunu yaptım çünkü internette bellek yönetimi efsanelerinden biri olduğuna dair bazı bilgiler bulabilirsiniz . Bu ilginç tartışmayı incelemenizi tavsiye ederim 🙂

Tabii ki, madalyonun diğer tarafı var – daha önce belirtilen tutma döngüleri. Her şeyden önce, nasıl bir koruma döngüsü oluşturulacağına ve iOS geliştiricilerin bellek sızıntılarını önlemek için ne yapmaları gerektiğine bakalım. İki benzer sınıf düşünelim:

class Person {    let name: String
    var dog: Dog?    init(name: String) {
        self.name = name
    }
}class Dog {
    
    let name: String
    var owner: Person?
    
    init(name: String) {
        self.name = name
    }
}

Her iki sınıfın da bir adı ve isteğe bağlı bir özelliği vardır – bir kişi için bir köpek, çünkü bir kişi her zaman bir köpeğe sahip olamayabilir ve bir köpek için bir kişi, çünkü bir köpek her zaman bir sahibine sahip olmayabilir – çok üzücü 🙁

Bir sonraki kod snippet’i, her bir sınıfın örneklerini oluşturur ve aynı zamanda referans sayacını her ikisi için de 1 olarak ayarlar:

var joe : Kişi ?  = Kişi (isim : "Joe") 
var lassie : Köpek ?  = Köpek (isim : "Lassie")

Joe ve Lassie sırasıyla Kişi ve Köpek örneğine güçlü referanslar. Çok uzak çok iyi. Eğer bir atarsanız nil için joe değişkeni , artık Kişi örneğine hiçbir güçlü gönderme var, çünkü bellek geri.

Güçlü bir referans döngüsü yani bir tutma döngüsü oluşturmak için, iki örneği birbirine bağlamanız yeterli.

Joe! . dog = lassie 
lassie! . sahip = joe

Lütfen referans sayaçlarına dikkat edin. İkisi de aynı değerde 2.

Şimdi, eğer joe ve lassie tarafından tutulan güçlü referansları kırırsanız , referans sayıcılar 0’a sıfırlanmaz.

joe = nil 
lassie = nil

ARC, tutma çevrimi nedeniyle örnekleri serbest bırakamaz.

Elbette, bunun bir çözümü var. Güçlü referans döngüsünü çözmek için, zayıf veya istenmeyen referanslar kullanmalısınız . Bir değişkenden önce sadece özel bir anahtar kelime eklersiniz ve ardından bu değişkene bir nesne atadığınızda, nesnenin referans sayacı artmaz.

Hafıza yönetimi kodlama şeklimizi nasıl etkiler?

Açıkçası, iOS öğrenmeye başladığımda , iOS ve Android’de zayıf bir referansın aynı şekilde çalıştığını düşündüm . Tabii ki bu doğru değil.

Kullanılması zayıf iOS anahtar kelimeyi, normal bir şeydir ve yaygın kullandığınızda bile iyi bir uygulama kabul edilebilir Heyeti deseni . Android’e gelince, hala AsyncTasks kullanmıyorsanız, genel bir uygulama değildir (umarım).

Koruma döngüleri nedeniyle, iOS geliştiricilerinin bazen basit şeyler için Android geliştiricilerinden daha karmaşık kodlar yazması gerekir. Bunun en iyi örneği bir kapak (Swift) ve lambda (Android) kullanmaktır.

Android:

class UpdateHandler {
    var actionAfterUpdate: (() -> Unit) = {} // Lambdafun update() {
        // do work
        actionAfterUpdate()
    }
}

iOs:

class UpdateHandler {
    var actionAfterUpdate: () -> Void = {} // Closure
    
    func update() {
        // do work
        actionAfterUpdate()
    }
}

ActionAfterUpdate yürütülecek güncelleme () metodu tamamlandı. Şimdi, UpdateHandler’ın nasıl kullanılacağını kontrol edelim.

Android:

class MyObject {
    val updateHandler = UpdateHandler()

    fun doSomething() {
        // do important thing
    }

    fun timeToUpdate() {
        updateHandler.actionAfterUpdate = { doSomething() }
        updateHandler.update()
    }
}

iOS:

class MyObject {
    let updateHandler = UpdateHandler()
    
    func doSomething() {
        // do important thing
    }
    
    func timeToUpdate() {
        updateHandler.actionAfterUpdate = { self.doSomething() }
        updateHandler.update()
    }
}

Gördüğünüz gibi, UpdateHandler’ı kullanmak basittir. Update () yöntemini çağırmadan önce , bu güncellemeden sonra ne olması gerektiğini beyan edersiniz. Her şey yolunda gözüküyor, ancak… iOS sürümü çok büyük bir hataya sahip… Bu korkunç bir şey çünkü bellek sızıntısına neden oluyor. Sorun ne? Bu var actionAfterUpdate güçlü bir başvuru içerir kapatma, öz . Self , UpdateHandler’a da başvuruda bulunan bir MyObject örneğidir – bir tutma döngüsü! Bellek sızıntısını önlemek için , kapağın içinde zayıf (veya sahipsiz, bu durumda yeterli olan) bir anahtar kelime kullanmamız gerekir:

updateHandler.actionAfterUpdate = { [weak self] in self?.doSomething() 
}

Diğer bir problem ise Lapsed Listener problemidir . Kısacası, bir dinleyiciyi kaydettiğinizde ve bunun kaydını silmeyi unutursanız, bunun sonucunda uygulamanızda bir bellek sızıntısı olur.

Bu solucan kutularını daha ayrıntılı olarak tartışmak için daha önce kullandığım örnekleri değiştirdim.

Şu anda, UpdateHandler ömrü bizim app ömrü kadar uzun olan bir singleton. Güncelleme almak için önce bir dinleyiciyi kaydetmeniz gerekir.

Android:

object UpdateHandler {
    private var listener: OnUpdateListener? = null

    fun registerUpdateListener(listener: OnUpdateListener) {
        this.listener = listener
    }

    fun update() {
        // do work
        listener?.onUpdateComplete()
    }
}

interface OnUpdateListener {
    fun onUpdateComplete()
}

iOS:

class UpdateHandler {
    
    static let sharedInstance = UpdateHandler()
    
    private var listener: OnUpdateListener? = nil
    
    func registerUpdateListener(listener: OnUpdateListener) {
        self.listener = listener
    }
    
    func update() {
        // do work
        listener?.onUpdateComplete()
    }
}

protocol OnUpdateListener: class {
    func onUpdateComplete()
}

Ve MyObject sınıfındaki bazı değişiklikler .

Android:

sınıf MyObject: OnUpdateListener { 
    override fun onUpdateComplete () {...} 
}

iOs:

sınıf MyObject: OnUpdateListener { 
    func onUpdateComplete () {...} 
}

Gördüğünüz gibi, MyObject bir güncelleme dinleyicisi ve güncelleme tamamlandığında belirli bir işlem gerçekleştiriyor.

Sorunu vurgulamak için, bu kodu uygulamadan çıkıp yeniden başlattığınızda her zaman çağrılan bir yere koyarım. Ancak, bu örneğin üretim kodunda bir anlamı olmadığını lütfen unutmayın. Bu sadece basit bir örnek 🙂

Android ( MainActivity.kt) :

override fun onStart() {
    super.onStart()
    val myObject = MyObject()
    UpdateHandler.registerUpdateListener(myObject)
    UpdateHandler.update()
}

iOS (AppDelegate.swift):

func applicationWillEnterForeground(_ application: UIApplication) {
    let myObject = MyObject()
    UpdateHandler.sharedInstance.registerUpdateListener(listener: myObject)
    UpdateHandler.sharedInstance.update()
}

Böylece, bir myObject oluşturdum , onu UpdateHandler’a güncelleme dinleyicisi olarak ilettim ve update () yöntemi olarak adlandırdım . Güncelleştirme () metodu çağırarak, bitmiş işler hakkında dinleyicisi bildirir onUpdateComplete () yöntemini ( onUpdateComplete () metodu içine yürütülür MyObject ).

OnStart () / applicationWillEnterForegorund (…) tamamlandığında, MyObject sınıfının örneği kaldırılmalıdır , çünkü yöntemlerde oluşturulan nesneler, yöntem süresi yürütüldüğü sürece hayatta kalır ve bu süreden sonra çöp toplama için uygun olur. Ancak bu durumda, UpdateHandler sonsuza dek MyObject örneğine bir referans tutar . Potansiyel bir bellek sızıntısını nasıl yönetebilirsiniz? Muhtemelen tüm iOS geliştiricileri “zayıf bir referans kullanın!” Derdi ve haklılar. UpdateHandler sınıfının içinde bir dinleyici değişkeni olan zayıf bir anahtar kelime kullanmak aşağıdakileri yapar:

class UpdateHandler {
    ...
    private weak var listener: OnUpdateListener? = nil
    ...
}

Bu sayede ARC bizim için dinleyiciyi kaldırabilir.

Peki ya Android? Birkaç Android geliştiricisi de aynı şeyi söyler: “Bir dinleyiciyi bir WeakReference! Tamam, yardımcı olabilir… bazen… ama eminim sorunlarınızın başlangıcıdır 🙂 Bilmelisiniz, geri aramalarınızı her zaman bir Zayıf Referans olarak tuttuğunuzda bir yavru kedi ölür.

WeakReference çöp toplama için nesne hak kılar. Bu yüzden düşündüğünüzden daha erken kaldırılabilir. Bu duruma tek ve tek çözüm bir unregisterUpdateListener yöntemi eklemek ve dinleyiciyi elle temizlemek .

Benzer henüz farklı

Sonuna kadar sebat eden herkesi tebrik ediyoruz!

Bu yazı birisinin bu karmaşık konuyu anlamasına yardımcı olursa memnun olurum. Beşinci adım adım olduğunu açıklamaya çalıştım. Görünüşte iki benzer programlama dili, başlık altında birçok farklılığı gizler ve bunun farkında olmanızı istiyorum. Bazen Android’den yaygın bir uygulama iOS’ta çalışmaz ve bunun tersi de geçerlidir. Bir platformdan diğerine geçmek göründüğü kadar basit değildir.

Lütfen dikkat, şunu yazdım:

“Swift basit bir GC toplama mekanizması kullanıyor”

Yanıltıcı olabilir ve bu benim kötü. ARC’nin çöp toplayıcısı olduğunu kastetmedim. Çöp toplama işlemini atıklardan (kullanılmayan nesnelerden) kurtulma süreci olarak kastediyordum. ARC, atıklardan kurtulmanın başka bir mekanizması / tekniğidir. Ayrıca, Android’deki çöp toplama işleminin uygulamanızın çalışma zamanında çalıştığını, ARC’nin de derleme zamanında sağlandığını söylemedim.

Comments

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

GIPHY App Key not set. Please check settings

Yükleniyor…

0

Ne düşünüyorsun?

32 Points
Upvote Downvote

Alexa Nedir? Alexa Sıralaması Gerçekten Önemli Mi?

Huawei’nin kendi işletim sistemi bu yıl hazır olabilir