About strong reference cycles for closures in Swift - ios

I have defined a class called Person. This is my code:
class Person {
var closure: (() -> ())?
var name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
then I use Person in class called ViewController:
class ViewController: UIViewController {
var person = Person(name: "john")
let aStr = "john is a cute boy"
override func viewDidLoad() {
super.viewDidLoad()
person.closure = {
print("\(self.aStr)")
}
person.closure!()
}
}
In my opinion, the picture of memory about my code like this :
So, from above picture, in my opinion, it will cause strong reference cycle between the three instances, but I can not get any leak from Instruments, so I have some confusion.
Does this code cause strong reference cycle?
If not, when will ARC deallocate the instance of Person? the method named deinit in Person class is never called.

Yes, this's a typical retain cycle.
To solve this problem use [weak self] in your closure
person.closure = { [weak self] in
guard let strongSelf = self else { return }
print("\(strongSelf.aStr)")
}
To really create a leak.
I create a demo App. Root is a navController.
The navController has a root controller. Let's call it buttonController.
When you click button in the buttonController, it create your ViewController and push to navController.
When you click back button in navigation bar, the navController pop your ViewController instance.
Profile it, then you will see the leak and the retain cycle in Instruments.
Xcode default template of iOS App use a single page, which always retain your ViewController instance. If the ViewController instance is still used by the system, it's actually not a leak yet.
So push & pop show that leak for you.

Related

How to pass data to the final view controller

I am new to Swift and am building an app to learn. Right now I am making the registration section of the app.
I thought the UX would be better if there were multiple VC's asking a single question, i.e. one for your name, one for your birthdate, etc as opposed to jamming all that into a single view controller. The final view controller collects all of that information and sends a dictionary as FUser object to be saved on Firebase.
I figured I could instantiate the final view controller on each of the previous five view controllers and pass that data directly to the end. I kept getting errors and figured out that the variables were nil. It works just fine if I pass the data directly to the next view controller but it doesn't seem to let me send it several view controllers down. Obviously there's a nuance to how the memory is being managed here that I'm not tracking.
Is there a way to do what I am trying to do or do I have to pass the data through each view controller along the way?
import UIKit
class FirstViewController: UIViewController {
//MARK: - IBOutlets
#IBOutlet weak var firstNameTextField: UITextField!
//MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - IBActions
#IBAction func continueToMiddleViewController(_ sender: Any) {
let vcFinal = storyboard?.instantiateViewController(withIdentifier:
"finalVC") as! finalViewController
vcFinal.firstName = firstNameTextField.text
let vc = storyboard?.instantiateViewController(withIdentifier:
"middleVC") as! middleViewController
vc.modalPresentationStyle = .fullScreen
present(vc, animated: false)
}
...
}
import UIKit
class FinalViewController: UIViewController {
var firstName: String?
...
//MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
}
...
}
TL;DR: The fastest one that would solve your problem is creating a singleton
There are many strategies for this. For a starter, it might be a good idea to read some begginer articles, like this one. I can update this answer if you don't find it useful, but it'd look just like the article
Viewcontroller's variable can't be initiated until any of the init method is called.
There are detailed answers on this thread.
Passing Data between ViewControllers
Another way to approach this problem could be to make use of closures. Note that personally I've moved away from using storyboards but I'll try to explain still. Closures are also referred to as callbacks, blocks, or in some context like here - completions.
You can declare a closure like let onSubmitInfo: (String?) -> Void below, it stores a reference to a block of code that can be executed at a later stage just like a function and it takes an optional string as a parameter just like a function can.
The closures are specified in the initialisers where a block of code is passed into the respective classes below and the closures are then called in the IBActions that will trigger the block of code that is defined where the below classes are initialised:
class First: UIViewController {
// MARK: - IBOutlets
#IBOutlet weak var firstNameTextField: UITextField!
// MARK: - Properties
private let onSubmitInfo: (String?) -> Void
init(onSubmitInfo: (String?) -> Void) {
self.onSubmitInfo = onSubmitInfo
}
// MARK: - IBActions
#IBAction func continue(_ sender: Any) {
onSubmitInfo(firstNameTextField.text)
}
}
class Second: UIViewController {
// MARK: - IBOutlets
#IBOutlet weak var lastNameTextField: UITextField!
// MARK: - Properties
private let onSubmitInfo: (String?) -> Void
init(onSubmitInfo: (String?) -> Void) {
self.onSubmitInfo = onSubmitInfo
}
// MARK: - IBActions
#IBAction func continue(_ sender: Any) {
onSubmitInfo(lastNameTextField.text)
}
}
To manage showing the above views and collecting the values returned by their closures (i.e. onSubmitInfo) we create a FlowController class that will also show the next view when the closure is called.
In FlowController we define the closures or blocks of code to be executed when it is called inside the IBAction in the respective First and Second classes above.
The optional string that is provided in the respective First and Second classes is used as the (firstName) and (secondName) closure properties below:
class FlowController: UIViewController {
private var fistName: String?
private var lastName: String?
...
private func showFirstView() {
let firstViewController = First(onSubmitInfo: { (firstName) in
self.firstName = firstName
showSecondView()
})
navigationController?.pushViewController(
firstViewController,
animated: true)
}
private func showSecondView() {
let secondViewController = Second(onSubmitInfo: { (lastName) in
self.lastName = lastName
showFinalView()
})
navigationController?.pushViewController(
secondViewController,
animated: true)
}
private func showFinalView() {
let finalViewController = Final(
firstName: firstName,
lastName: lastName)
navigationController?.pushViewController(
finalViewController,
animated: true)
}
}
The FlowController finally shows the Final view controller after it has collected the firstName form the First view controller and the lastName form the Second view controller in the showFinalView function above.
class Final: UIViewController {
let firstName: String
let lastName: String
...
}
I hope this is a shove in the right direction. I have moved away from storyboards because I find creating views in code is more verbose and clear on peer reviews and it was also easier for me to manage constraints and just to manage views in general.

Why is this a retain cycle?

I have basic understanding of ARC but in the following example I suddenly get really confused.
FeedViewController has a strong reference of NetworkHelper, then the NetworkHelper has a function which takes a closure and call it later.
So here's the confusion:
the closure is passed from FeedViewController to NetworkHelper, And this block is not being retained inside NetworkHelper, so why does NetworkHelper has a strong reference of NetworkHelper? this is stated in an article but I just could't figure out why. It makes sense to me only if NetworkHelper keep a strong reference to the block.
class NetworkHelper {
func getFeed(completion: #escaping ([FeedItem]) -> Void) {
Alamofire.request(…).responseJSON { (response) in
if let value = response.result.value {
if let json = JSON(value)[Constants.items].array {
completion(json.flatMap(FeedItem.init))
}
}
}
}
}
class FeedViewController {
var tableView: UITableViewController
var feedItems: [FeedItem]
var networkHelper: NetworkHelper
override func viewDidLoad() {
...
networkHelper.getFeed() { items in
self.feedItems = items
self.tableView.reloadData()
}
}
}
Technically, there is no cycle.
First of all, NetworkHelper never owns anything, it just passes a closure to Alamofire.
Alamofire holds to that closure, which retains a FeedViewController instance (as self). However, Alamofire is not owned by FeedViewController, therefore there is no cycle.
It's true that while the request is running, FeedViewController cannot be deallocated because the completion callback prevents that, but that could be an expected behavior and there is definitely no ownership cycle.

GCD keeps strong reference to 'self' even when defining a capture list

class MyClass {
var someProperty = 0
deinit {
// never gets called
}
func doSomething() {
DispatchQueue.global().async { [weak self] in
Thread.sleep(forTimeInterval: 3600)
self?.someProperty = 123
}
}
}
class MyViewController: UIViewController {
var myClass: MyClass?
deinit {
// MyViewController gets properly deallocated
}
override func viewDidLoad() {
super.viewDidLoad()
myClass = MyClass()
myClass?.doSomething()
}
}
When running the above code, MyClass never gets deallocated even when MyViewController is popped of the navigation stack or gets dismissed.
However, if I move the implementation of MyClass directly to MyViewController, everything works as expected:
class MyViewController: UIViewController {
var someProperty = 0
deinit {
// MyViewController still gets properly deallocated
}
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async { [weak self] in
Thread.sleep(forTimeInterval: 3600)
self?.someProperty = 123
}
}
}
I used the Debug Memory Graph to figure out what is still keeping a reference to the instance of MyClass and this is what I get:
Why is there still a reference? What am I missing here?
UPDATE
So I was trying to figure out why it works with MyViewController but not when having another instance in between. Seems like inheriting from NSObject makes a difference. When I inherit MyClass from NSObject, deinit is called and once the long operation is finished, self is then correctly set to nil.
The question now is, in what way are capture lists in Swift related to NSObject?

How to test deinit of viewController

I want to test if I remove all the key value observers in the deinit of my view controller.
In the test class I have defined following method to start view controller lifecycle
func startLifecycle() {
_ = viewController.view
}
In my test method I'm trying to invoke deinit by simply assigning nil to my view controller instance
testViewController = nil
XCTAssert for stuff...
But deinit is not called when I execute my test. I see no obvious retain cycles in my VC's code, what's more when I run the code in my app, not the test environment, deinit is called so it doesn't seem like something is keeping view controller in memory.
What is the correct way to release a view controller when testing?
I had the same problem.
When you examine the memory graph, you will see that an object of type UIStoryboardScene maintains a reference to your UIViewController via an #autorelease property 'sceneViewController'.
If you're unfamiliar with #autorelease, this is a helpful article: https://swiftrocks.com/autoreleasepool-in-swift. It explains that objects created with autorelease are released at the end of the current thread's run loop, but not before.
In Swift, we can use autoreleasepool to release UIStoryboardScene's reference to the UIViewController.
It might look something like this:
var testViewController: UIViewController?
autoreleasepool {
testViewController = UIStoryboard(name: "main", bundle: nil).instantiateInitialViewController()
}
Then, when you execute testViewController = nil, the UIViewController will actually deinit.
Try smth like this
class ViewController: UIViewController {
var deinitCalled: (() -> Void)?
deinit { deinitCalled?() }
}
class ViewControllerTest: XCTestCase {
func test_deinit() {
var instance: ViewController? = ViewController()
let exp = expectation(description: "Viewcontroller has deinited")
instance?.deinitCalled = {
exp.fulfill()
}
DispatchQueue.global(qos: .background).async {
instance = nil
}
waitForExpectations(timeout: 2)
}
}
In case someone else needs this i managed to do it this way.
// Given
var optionalSubject: UIViewController? = UIViewController()
// When
optionalSubject = nil // Call deinit
// Then
// TEST DEINIT

Is it memory leak in following case in Swift?

Is this a memory leak in following scenario in Swift Programming?
var string: String = "test-string-1" // statement-1
string = "test-string-2" // statement-2
Is it memory leak through execution from statement-1 to statement-2?
OR Do I this way?
var string: String? = "test-string-1"
string = nil
string = "test-string-2"
Please answer with proper description.
You don't need to do in the second way.
I guess the first scenario is safe enough.
After setting string to "test-string-2", the reference count of the "test-string-1" become 0. So, it will be deallocated by ARC.
You don't need to even think about a memory leak here, it is something very different from assigning a new value to a variable. It occurs when an instance is unable to be released from memory and is explained at length by Apple in the docs, but in brief here is the simplest scenario:
class Parent {
var child:Child?
}
class Child {
var parent: Parent?
}
var child = Child()
let parent = Parent()
child.parent = parent
parent.child = child // strong reference cycle created
A "strong reference cycle" has been created because the parent holds a reference to a child instance, and that same child instance holds a reference to the parent instance that holds a reference to it. The result is that it cannot be determined when to deinitialize either instance. This can be demonstrated by placing the code in a view controller and running it inside an app:
import UIKit
class Parent {
var child:Child?
deinit {
print("deinitialized")
}
}
class Child {
var parent: Parent?
deinit {
print("deinitialized")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let child = Child()
let parent = Parent()
child.parent = parent
parent.child = child // strong reference cycle created
}
}
Note that there are no console messages to indicate that deinitialization has taken place because it hasn't. Instead the instances both live on forever more. We call this a leak because the instances do not go out of existence but at the same time they are not reachable.
Now change one of the references to weak like so:
import UIKit
class Parent {
var child:Child?
deinit {
print("deinitialized")
}
}
class Child {
weak var parent: Parent?
deinit {
print("deinitialized")
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let child = Child()
let parent = Parent()
child.parent = parent
parent.child = child // strong reference cycle created
}
}
You will see two deinit messages. The parent alone is now responsible for keeping the child "alive", so it can release the child when it goes out of existence automatically via ARC. There is no ambiguity over who owns who, even when an owned instance has a reference to its owner, because it is now weak (rather than strong).

Resources