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
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.
Swift iOS memory usage increasing during runtime
Profile Memory Allocation
I made a test iOS application to show how you could profile memory allocation. After creating a new iOS project I added the following to the AppDelegate
:
var test: [String] = []
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
while true {
test.append("blasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdfblasdgpjsogfisogfihsofighäkneäijifdf")
}
return true
}
This should simulate your problem. Then we longpress the build button and choose profile.
We choose Allocations as profiling template.
We press the record button.
We once again select Allocations.
We select Call Trees.
And probably we already find the problematic stack trace on the right side.
Or you scan the call tree by yourself.
This should give you a starting point for further debugging to find the infinite loop :)
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.
Will struct in Swift cause memory issue if passed around a lot?
Theoretically there could be memory concerns if you pass around very large struct
s causing them to be copied. A couple of caveats/observations:
In practice, this is rarely an issue, because we’re frequently using native “extensible” Swift properties, such as
String
,Array
,Set
,Dictionary
,Data
, etc., and those have “copy on write” (COW) behavior. This means that if you make a copy of thestruct
, the whole object is not necessarily copied, but rather they internally employ reference-like behavior to avoid unnecessary duplication while still preserving value-type semantics. But if you mutate the object in question, only then will a copy be made.This is the best of both worlds, where you enjoy value-semantics (no unintended sharing), without unnecessary duplication of data for these particular types.
Consider:
struct Foo {
private var data = Data(repeating: 0, count: 8_000)
mutating func update(at: Int, with value: UInt8) {
data[at] = value
}
}The private
Data
in this example will employ COW behavior, so as you make copies of an instance ofFoo
, the large payload won’t be copied until you mutate it.Bottom line, you asked a hypothetical question and the answer actually depends upon what types are involved in your large payload. But for many native Swift types, it’s often not an issue.
Let’s imagine, though, that you’re dealing with the edge case where (a) your combined payload is large; (b) your
struct
was composed of types that don’t employ COW (i.e., not one of the aforementioned extensible Swift types); and (c) you want to continue to enjoy value semantics (i.e. not shift to a reference type with risk of unintended sharing). In WWDC 2015 video Building Better Apps with Value Types they show us how to employ COW pattern ourselves, avoiding unnecessary copies while still enforcing true value-type behavior once the object mutates.Consider:
struct Foo {
var value0 = 0.0
var value1 = 0.0
var value2 = 0.0
...
}You could move these into a private reference type:
private class FooPayload {
var value0 = 0.0
var value1 = 0.0
var value2 = 0.0
...
}
extension FooPayload: NSCopying {
func copy(with zone: NSZone? = nil) -> Any {
let object = FooPayload()
object.value0 = value0
...
return object
}
}You could then change your exposed value type to use this private reference type and then implement COW semantics in any of the mutating methods, e.g.:
struct Foo {
private var _payload: FooPayload
init() {
_payload = FooPayload()
}
mutating func updateSomeValue(to value: Double) {
copyIfNeeded()
_payload.value0 = value
}
private mutating func copyIfNeeded() {
if !isKnownUniquelyReferenced(&_payload) {
_payload = _payload.copy() as! FooPayload
}
}
}The
copyIfNeeded
method does the COW semantics, usingisKnownUniquelyReferenced
to only copy if that payload isn’t uniquely referenced.That’s can be a bit much, but it illustrates how to achieve COW pattern on your own value types if your large payload doesn’t already employ COW. I’d only suggest doing this, though, if (a) your payload is large; (b) you know that the relevant payload properties don’t already support COW, and (c) you’ve determined you really need that behavior.
If you happen to be dealing with protocols as types, Swift automatically employs COW, itself, behind the scenes, Swift will only make new copies of large value types when the value type is mutated. But, if your multiple instances are unchanged, it won’t create copies of the large payload.
For more information, see WWDC 2017 video What’s New in Swift: COW Existential Buffers:
To represent a value of unknown type, the compiler uses a data structure that we call an existential container. Inside the existential container there's an in-line buffer to hold small values. We’re currently reassessing the size of that buffer, but for Swift 4 it remains the same 3 words that it's been in the past.
If the value is too big to fit in the in-line buffer, then it’s allocated on the heap.And heap storage can be really expensive. That’s what caused the performance cliff that we just saw. So, what can we do about it? The answer is cow buffers, existential COW buffers...
... COW is an acronym for “copy on write”.
You may have heard us talk about this before because it’s a key to high performance with value semantics. With Swift 4, if a value is too big to fit in the inline buffer, it's allocated on the heap along with a reference count. Multiple existential containers can share the same buffer as long as they’re only reading from it.And that avoids a lot of expensive heap allocation. The buffer only needs to be copied with a separate allocation if it’s modified while there are multiple references to it. And Swift now manages the complexity of that for you completely automatically.
For more information about existential containers and COW, I’d refer you to WWDC 2016 video Understanding Swift Performance.
Related Topics
F# Asynchronous Http Request - Parse JSON Response
Generating Random Doable Math Problems Swift
Problem with Frameworks in Command Line Tool
Swift Set UIbutton Setbordercolor in Storyboard
What Is The Advantage of Closure Stored Property Initialisation
Create Custom Action in a Class for Use in Interface Builder
Non-Translucent UItabbar Creates Strange Grey Bar
How to Create Viewcontrollers Without Storyboard and Set One as Delegate of The Other One
Uibutton Background Color Overlaps Text on Highlight
How to Refer to a Global Type from Within a Class That Has a Nested Type with The Same Name
Arkit/Scenekit on iOS 14 Throws New Warning (Metal)
How to Pause an Animation in Swiftui
Swift: Simple Dispatchqueue Does Not Run & Notify Correctly
I Opened My App in Xcode 10 and Now I Have Errors in 9.4.1: Sdkapplicationdelegate (Facebookcore)
Weird Toolbar with Nested Conditionals Behavior
Get Url from Open Dialog of Standard Swift Document-Based Application
Problem with Swiftui and Foreach on Xcode Playground
Swift: Get The Compile Time Name of Variable (Referencing to a Class)