How Does Swift Memory Management Work

How does Swift memory management work?

Should we take this as a hint that Apple thinks we should be using structs as delegates? Or is this simply a bad example, and realistically, delegate protocols should be declared as class-only protocols so that the delegated object can hold a weak reference to its delegate?

Here's the thing. In real life Cocoa programming, the delegate is likely to be an existing class. It is a class because it exists for some other purpose that only a class can satisfy - because Cocoa demands it.

For example, very often, to take iOS as an example, one view controller needs to act as another view controller's delegate for purposes of arranging a message back and forth between them. Ownership of the view controllers is dictated by the view controller hierarchy and by nothing else. So, in Swift, just as in Objective-C, you had better make that delegate property weak, because it would be terrible if one view controller suddenly took memory management ownership of another view controller!

So, in the real world of the Cocoa framework, there is serious danger of incorrect ownership or of a retain cycle. And that is the problem that weak solves. But it only works, as you rightly say, with classes.

The example in the book, however, is about some objects living off in an abstract made-up artificial Swift-only world. In that world, as long as you aren't in danger of circularity (retain cycle), there's no reason not to use structs and there's no reason to worry about memory management. But that world is not the world you will usually be programming in! And that world is not the framework Cocoa world that your Objective-C delegate pattern comes from and belongs to.

What is the automatic memory management mechanism in Swift?

It uses ARC. From the programming guide (on iBooks):

We simplified memory management with Automatic Reference Counting (ARC).

Swift Memory Management

There is a system cache for images per the documentation of UIImage.init(named:); you cannot manually flush it but Apple's design intention is that you wouldn't ever get any benefit from doing so if you could — like all NSCache-type caches it'll automatically flush itself should memory ever become scarce. So in the meantime all a flush would achieve would be fewer cache hits and expenditure of the processor cycles required for the flush.

The documentation advises use of imageWithContentsOfFile: if you want to avoid the cache but if you're loading or being loaded from a XIB or Storyboard then you don't have sufficient direct control to assert influence.

@escaping completionHandler - How does memory management work for them?

I finally figured out!

  1. @escaping closure gets call even after class deinitialized, But won't it will get nil instance variable if you properly managed memory by using weak self.

  2. If we don't call @escaping closure at all it doesn't occupy any memory.

Sample Code

final class DataSource {

typealias RequestCompleted = (_ data:String?, _ error: NSError?) -> Void

// MARK: Shared Instance
static let sharedInstance = DataSource()

// MARK: Concurrent queue for thread-safe array

fileprivate let concurrentQ = DispatchQueue(label: "com.test.concurrentQueue",
qos: .userInitiated,
attributes: .concurrent)

// MARK:- Local Variable
fileprivate var _dataHandler: RequestCompleted?
fileprivate var dataHandler: RequestCompleted? {
get {
return concurrentQ.sync {
return _dataHandler
}
}

set {
concurrentQ.async(flags: .barrier){ [weak self] in
self?._dataHandler = newValue
}
}
}

fileprivate var result:(data: String?, error: NSError?) {
didSet {
if let handlr = dataHandler {
handlr(result.data, result.error)
self.isRequestInProgress = false
}
}
}

fileprivate var _isRequestInProgress = false
fileprivate var isRequestInProgress: Bool {
get {
return concurrentQ.sync {
return _isRequestInProgress
}
}

set {
concurrentQ.async(flags: .barrier){ [weak self] in
self?._isRequestInProgress = newValue
}
}
}

// MARK:- Private init()
private init() {

}

deinit {
print("Deinitialized")
}

internal func fetchData(_ onCompletion: @escaping RequestCompleted) {
self.dataHandler = onCompletion
if self.isRequestInProgress == true { print("TTT: In Progress")
return
} else {

self.isRequestInProgress = true

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(20)) {
// Code
self.result = ("Done", nil)
}
}
}
}

ViewController

class ViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
print("ViewController 1 Function")

DataSource.sharedInstance.fetchData { (name, error) in
print("ViewController 1 Handler")
}
}
}

ViewController2

class ViewController2: UIViewController {
var str = "Test"
var arr = [1, 2, 3]

override func viewWillAppear(_ animated: Bool) {
print("ViewController 2 Function")

DataSource.sharedInstance.fetchData { [weak self] (name, error) in
print("ViewController 2 Handler")
print("CCCC\(self?.arr ?? [0])")
print("SSSS\(self?.str ?? "Happy")")
}
}

deinit {
print("VC2.. deinit")
}
}

ViewController3

class ViewController3: UIViewController {
override func viewWillAppear(_ animated: Bool) {
print("ViewController 3 Function")

DataSource.sharedInstance.fetchData { (name, error) in
print("ViewController 3 Handler")
}
}

deinit {
print("VC3.. deinit")
}

}

And For Low memory warning-

Since in swift

collection types and tuple are value type,

I will either remove person objects or set tuple to nil in case of low memory warning. It won't impact data on my Controller view.

Swift's memory management

Your code prints the memory location of the kid1 variable,
and that does not change if you assign a new value to the variable.

If Kid is a reference type (class) then you can use
ObjectIdentifier to get a unique identifier for the class instance
that the variable references:

var kid1 = Kid(name: "A")
var kid2 = Kid(name: "B")

print(ObjectIdentifier(kid1)) // ObjectIdentifier(0x0000000100b06220)
print(ObjectIdentifier(kid2)) // ObjectIdentifier(0x0000000100b06250)

kid1 = kid2
print(ObjectIdentifier(kid1)) // ObjectIdentifier(0x0000000100b06250)

The object identifier happens to be the address of the pointed-to
instance, but that is an undocumented implementation detail.
If you need to convert an object reference to a real pointer
then you can do (compare How to cast self to UnsafeMutablePointer<Void> type in swift)

print(Unmanaged.passUnretained(kid1).toOpaque())

Swift memory management: creating views the right way and avoid memory leaks

So for those who end up here and wonder the same thing, CFGetRetainCount returns unreliable results as pointed out here.

To make sure memory is deallocated when using custom classes, you may call inside your class and test

deinit {
print("Memory deallocated")
}

If your object is not retained, the method above is automatically called. See documentation here: https://docs.swift.org/swift-book/LanguageGuide/Deinitialization.html

Other leaks may be tested using Instruments (in Xcode: Product > Profile > Leaks) or using the Debug Memory Graph tool

Sample Image

More explanations here: https://www.youtube.com/watch?v=1LnipXiSrSM

So answers would be as follows:

1+3) Do not use CFGetRetainCount. See explanation above.

2) This seems to be working fine and memory is deallocated when I test the app.



Related Topics



Leave a reply



Submit