# Swift: Unit test ile strong reference cycle yakalama

Burada strong reference cycle'ın ne olduğunu anlatmayacağım ama kısaca bahsedecek olursam, bir reference type instance'ınız olsun `A`, bunun stored property'lerinden en az birinin kendisi veya nested bir yapı düşünürseniz ağacın yapraklarından biri geri dönüp tekrar `A`'ya strong reference tutuyorsa, `ARC` gidip de bu objelerin retain count'larını sıfırlayamıyor dolayısıyla bu objeler hiçbir zaman memory'den temizlenemiyorlar.

Bu durum crash log'lara çok yansımasa da uygulamanızın runtime'ında belirsiz davranışlara neden olacaktır. Örneğin bir view controller'ınız var diyelim, bu tip bir memory issue'sü yüzünden silinemiyorsa, gereksiz yere tekrar tekrar yaratılacaktır. Ek olarak diyelim ki bu controller'dan siz bazı analytics `event`'leri fırlatıyorsunuz. Aynı `event`i dashboard'unuzda tekrarlar biçimde göreceksiniz ama aslında user başına bir `event` bekliyordunuz. Ya da diğer bir senaryoda backend'e call yapıyorsunuz, bu call'lar tekrarlanacak ve boş yere sunuculara yük olacaktır. Yapacak en iyi şey bu cycle'ları tek tek temizlemektir fakat en iyisi en baştan hiç raise etmemek.

Bu yüzden potansiyel olarak reference cycle olabileceğini düşündüğünüz kısımları unit test ile cover etmek mantıklı. Bunun için çeşitli öneriler getirildi ancak ben `autoreleasepool` kullanan bir yardımcı method önereceğim.

``` swift
public func AssertNoStrongReferenceCycle<T: AnyObject>( // 1
   file: StaticString = #file, // 2
   line: UInt = #line, // 3
   act: () throws -> T // 4
 ) rethrows { // 9
   weak var system: T? // 5
   try autoreleasepool { // 6
     system = try act() // 7
   } 
  
   XCTAssertNil(system, "You've got a reference cycle in \(T.self).", file: file, line: line) // 8
 }
```

[`autoreleasepool`](https://developer.apple.com/documentation/foundation/nsautoreleasepool) aslında objc zamanlarından elimize kalan kullanışlı bir kavram, onu da burada çok açıklamayacağım dileyen orijinal dökümantasyonuna ve Bruno Rocha'nun [bloguna](https://swiftrocks.com/autoreleasepool-in-swift) göz atabilir. Çok kısaca yine özetlemem gerekirse `autoreleasepool` bir closure alıyor (bkz yorum 6), bu closure içinde allocate edilen objeler method sonunda release ediliyor (bkz yorum 7).

Bu bilgiyi nasıl kullanıyoruz? Eğer release edilen bir objeyi `weak` bir property'ye atamışsak (bkz yorum 5) sonucunda ilgili property'nin `nil` olmasını bekleriz (bkz yorum 8), olmuyorsa bir yerlerde birşeyler birbirini bırakmıyor demektir, yani strong reference cycle var ve kaldırmamız lazım.

Yardımcı methodun ayrıntılarına gelecek olursam, method `T` generic variable bekliyor ve bu type `T` `AnyObject`'e conform etmek zorunda (bkz yorum 1). `AnyObject` reference type tipini zorunlu kılan bir kısıt çünkü bildiğiniz gibi value type'larda reference tutulmadığı için memory leak problemi de yok. 

Method üç adet girdi alıyor, `file` (bkz yorum 2), `line` (bkz yorum 3) ve asıl önemli kısım olan `act` (bkz yorum 4). `file` ve `line`'ı daha önce testler için yardımcı methodlar yazmış olanlar bilir, Xcode'un hata mesajlarını doğru satırda vermesini sağlayan ayrıntılar, `act` ise kodunuzun asıl test etmek istediğiniz şüpheli kısım. `act` closure'u bazen içinde `try` içerebilir, örneğin `XCTUnwrap` kullanılabilir bu yüzden `throws` ekledim (bkz yorum 4), fakat içermeyebilir de, bu iki durumu aynı anda sağlayabilmek için ana methoda `rethrows` ekledim (bkz yorum 9).

Şimdi bu methodun örnek kullanımına geçeyim. İki adet class'ımız olsun `A` ve `B`, bunların birbirlerine referansları olsun. Bir tanesininki *optional* olsun ki sıra sıra init edebilelim (bkz: yorum 11).

``` swift
import XCTest

class A {
    weak var b: B?  // 10
    init(b: B?) {
        self.b = b
    }
}

class B {
    let a: A
    init(a: A) {
        self.a = a
    }
}

final class ReferenceTypeTests: XCTestCase {
    func testExample() {
        AssertNoStrongReferenceCycle { () -> A in
            let a = A(b: nil) // 11
            let b = B(a: a)
            a.b = b
            return a
        }
    }
}
```

Burada en bariz şekilde `A` ile `B` arasında strong reference cycle raise etmeyen bir ilişki kurdum, *etmeyen* dedim çünkü `A`'nın `B` ile olan ilişkisi `weak` modifier'ı ile işaretlenmiş (bkz: yorum 10). Eğer bu `weak`'i kaldırırsanız hata mesajı alacaksınız. Bu yöntemle karmaşık view controller'ları, reactive property'lerinizi `sink` ettiğiniz reference type'ları, ya da closure inject ettiğiniz herhangi bir class'ın strong reference raise edip etmediğini test edebilirsiniz.


### Referanslar

https://www.hackingwithswift.com/example-code/language/how-to-use-the-rethrows-keyword

https://gist.github.com/erkekin/0f44eefb62f62e9f79b8f544ca2dd46e

https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

https://developer.apple.com/documentation/foundation/nsautoreleasepool

https://swiftrocks.com/autoreleasepool-in-swift

