3

I have gone through Swift Closures and ARC in Swift and I got little confused.

I have simple scenario of calling web service and using response data.
Here is my basic implementation:

class WebServices: NSObject {
  func requestDataFromServer(completion: @escaping (_ data: Data?) -> Void) {
    //web service call here
    completion(Data())
  }
  deinit {
    print("WebServices deinitializer...")
  }
}

class Controller: NSObject {
  private let webService = WebServices()
  private func useResponseData(_ data: Data) {
    print("Response Data: \(data)")
  }
  func fetchData() {
    webService.requestDataFromServer { (data) in
      if let responseData = data {
        self.useResponseData(responseData)//direct use of self
      }
    }
  }
  deinit {
    print("Controller deinitializer...")
  }
}

var controller: Controller? = Controller()
controller!.fetchData()
controller = nil

Console output is:

Response Data: 0 bytes
Controller deinitializer...
WebServices deinitializer...

My question is even I'm using selfdirectly inside closure why this implementation is not causing Reference Retain Cycle?
If I use unowned or weak then also same behavior.


And what can cause reference retain cycle in above scenario?(I don't want to cause, rather want to be aware of mistakes)

D4ttatraya
  • 3,344
  • 1
  • 28
  • 50

2 Answers2

3

There is no issue in your code as requestDataFromServercall the completion handler directly (no async). So the caller can't have been released during you call.

But when you will implement your webservice's real call, it will be async. So user can switch page before your webservice answer. In that case, you will retain a strong reference to your controller and it will never be released. You should use a [weak self]in your closure (and so call self?.useResponseData(responseData) with self as optional).

unowned is used in case that you are sure your reference will not be nil (it's not an optional)

Renaud Cousin
  • 114
  • 1
  • 1
  • 6
0

Ok, I guess the problem is what you are doing here is not actually async, so it executes one after another, this code causes memory leak:

import Foundation

class WebServices {
    func requestDataFromServer(completion: @escaping (_ data: Data?) -> Void) {
        //web service call here
        print("called")
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
            print("called async")
            completion(Data())
        })
    }
    deinit {
        print("WebServices deinitializer...")
    }
}

class Controller: UIViewController {
    private let webService = WebServices()
    private func useResponseData(_ data: Data) {
        print("Response Data: \(data)")
    }
    func fetchData() {
        webService.requestDataFromServer { (data) in
            print("called")
            if let responseData = data {
                self.useResponseData(responseData)//direct use of self
            }
        }
        self.dismiss(animated: true, completion: nil)
    }
    deinit {
        print("Controller deinitializer...")
    }
}

var controller: Controller? = Controller()
controller!.fetchData()
JuicyFruit
  • 2,638
  • 2
  • 18
  • 35
  • Even this case with async also both the instances are de-initializing! – D4ttatraya Jun 13 '17 at 09:34
  • @DashAndRest edited, my mistake, now it is example with `UIViewContoller` like behaviour, `dismiss` instead of `nil` – JuicyFruit Jun 13 '17 at 10:40
  • But how this is still de-initializing? and how it is leaking memory? – D4ttatraya Jun 13 '17 at 10:51
  • @DashAndRest my guess is this has something to do with `UIViewController` lifecycle, it is not directly assigned to nil, when `viewDidDisappear`, so when it still has reference after it, it would never be `deinit`, and when you do `weak self` it has none. – JuicyFruit Jun 14 '17 at 05:57