Sign in Join
Wiki copytemporary

Cocoa Internals/Memory Management: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11All Objective-C objects are reference counted objects. Once the count reaches zero - the object is freed.
22
33The modern Objective-C (or Swift) recommends (strongly, fiercely) to use Automatic-Reference-Counting (ARC) support. FPC support for Objective-C was started way before ARC became highly recommended and was part of the language. Right now Objective-C 2.0 syntax provides the language level support for ARC, while FPC doesn't have those (yet?). All Cocoa Widgetset code cannot doesn't rely on ARC and using reference and dereference manually.
44
55## Memory Management
66
77As mentioned earlier - all Cocoa objects are reference counted objects. They should be allocated via a class constructor (typically alloc()) and be released via release() method (rather than calling a "destructor" dealloc).
88
99Consider the following code example:
1010
1111A new sub-class of NSObject is declared, overriding its alloc (constructor) and dealloc (destructor) methods. Overridden methods are providing a logging output.
1212
1313```delphi
1414program project1;
1515
1616{$mode delphi}{$H+}
1717{$modeswitch objectivec1}
1818{$modeswitch objectivec2}
1919
2020uses CocoaAll;
2121
2222type
2323
2424 { NSMyObject }
2525
2626 NSMyObject = objcclass(NSObject)
2727 public
2828 class function alloc: id; override;
2929 procedure dealloc; override;
3030 end;
3131
3232{ NSMyObject }
3333
3434class function NSMyObject.alloc: id;
3535begin
3636 Result:=inherited alloc;
3737 writeln('alloc: ', PtrUInt(Result));
3838end;
3939
4040procedure NSMyObject.dealloc;
4141begin
4242 writeln('dealloc: ', PtrUInt(Self));
4343 inherited dealloc;
4444end;
4545
4646procedure MakeObject;
4747var
4848 obj : NSMyObject;
4949begin
5050 obj:=NSMyObject.alloc.init;
5151 obj.release;
5252end;
5353
5454begin
5555 MakeObject;
5656 MakeObject;
5757 MakeObject;
5858end.
5959```
6060
6161The output of calling such process would be:
6262
63-`alloc:   3206800`\
64-`dealloc: 3206800`\
65-`alloc:   3206800`\
66-`dealloc: 3206800`\
67-`alloc:   3206800`\
68-`dealloc: 3206800`
63+```
64+alloc: 3206800
65+dealloc: 3206800
66+alloc: 3206800
67+dealloc: 3206800
68+alloc: 3206800
69+dealloc: 3206800
70+```
6971
7072This is happening, because whenever an object is allocated, it's reference count is set to 1. Calling release method reduced the reference counter by 1. If reference count becomes zero, objc runtime - is calling objects destructor - dealloc.
7173
7274### Leaking
7375
7476If MakeObject procedure is modified as following:
7577
7678```delphi
7779procedure MakeObject;
7880var
7981 obj : NSMyObject;
8082begin
8183 obj:=NSMyObject.alloc.init;
8284 // removed the line with .release call
8385end;
8486```
8587
8688the output of the application changes to:
8789
88-`alloc:   2146560 `\
89-`alloc:   2146288 `\
90-`alloc:   2146304`
90+```
91+alloc: 2146560
92+alloc: 2146288
93+alloc: 2146304
94+```
9195
9296That looks like a memory leak via ObjC objects, because allocated objects were not released.
9397
9498Preventing a leak requires a proper release of an object.
9599
96100### Autorelease Pools
97101
98102In order to decrease a complexity of tracking of object life-span and releasing, Cocoa provides an autorelease mechanism. Any object registered in with an autorelease pool, would be released at the time autorelease pool is drained and the object's reference count is equal to 1.
99103
100104(Autorelease pools are drained whenever they're released).
101105
102106Let's modify the example above in the following manner:
103107
104108```delphi
105109procedure MakeObject;
106110var
107111 obj : NSMyObject;
108112begin
109113 obj:=NSMyObject.alloc.init.autorelease; // registering the allocated object with autorelease pool
110114end;
111115
112116var
113117 pool : NSAutoreleasePool;
114118begin
115119 pool := NSAutoreleasePool.alloc.init;
116120 MakeObject;
117121 MakeObject;
118122 MakeObject;
119123 writeln('releasing the pool');
120124 pool.release;
121125 writeln('done');
122126end.
123127```
124128
125129The output of the program is as following:
126130
127-`alloc:   3175888`\
128-`alloc:   3172352`\
129-`alloc:   3172368 `\
130-`releasing the pool`\
131-`dealloc: 3172368 `\
132-`dealloc: 3172352`\
133-`dealloc: 3175888`\
134-`done`
131+```
132+alloc: 3175888
133+alloc: 3172352
134+alloc: 3172368
135+releasing the pool
136+dealloc: 3172368
137+dealloc: 3172352
138+dealloc: 3175888
139+done
140+```
135141
136142All objects were released together with release of the pool.
137143
138144### Leaking out of the pool
139145
140146Note that any time a pool is created it's placed on the "stack" of the pools and become a current pool. Any objects allocated before the pool, would not be released with the pool itself. \<source lang="delphi\>var
141147
142148` pool : NSAutoreleasePool;`
143149
144150begin
145151
146152` MakeObject;`\
147153` pool := NSAutoreleasePool.alloc.init;`\
148154` MakeObject;`\
149155` MakeObject;`\
150156` writeln('releasing the pool');`\
151157` pool.release;`\
152158` writeln('done');`
153159
154160end.
155161
156162output:
157163
158-`alloc:   4199616`\
159-`alloc:   4202144 `\
160-`alloc:   4202160`\
161-`releasing the pool`\
162-`dealloc: 4202160 `\
163-`dealloc: 4202144`\
164-`done`
164+```
165+alloc: 4199616
166+alloc: 4202144
167+alloc: 4202160
168+releasing the pool
169+dealloc: 4202160
170+dealloc: 4202144
171+done
172+```
165173
166174As you can see only two later objects were released. The first object remained in the memory.
167175
168176### Multiple pools
169177
170178It's expected that multiple pools would could be used. The typical example of is to have an additional pool for a routine where a great number of temporary objects could be used.
171179
172180In the end the memory would be cleaned up, together with the local pool released:
173181
174182```delphi
175183procedure ExtraProc;
176184var
177185 pool : NSAutoreleasePool;
178186begin
179187 pool := NSAutoreleasePool.alloc.init;
180188 try
181189 MakeObject;
182190 MakeObject;
183191 finally
184192 pool.release;
185193 end;
186194end;
187195
188196var
189197 pool : NSAutoreleasePool;
190198begin
191199 pool := NSAutoreleasePool.alloc.init;
192200 MakeObject;
193201 writeln('calling ExtraProc()');
194202 ExtraProc();
195203 writeln('ExtraProc() done');
196204 pool.release;
197205end.
198206```
199207
200208output:
201209
202-`alloc:   3155200`\
203-`calling ExtraProc()`\
204-`alloc:   3155024`\
205-`alloc:   3155248`\
206-`dealloc: 3155248`\
207-`dealloc: 3155024`\
208-`ExtraProc() done`\
209-`dealloc: 3155200`
210+```
211+alloc: 3155200
212+calling ExtraProc()
213+alloc: 3155024
214+alloc: 3155248
215+dealloc: 3155248
216+dealloc: 3155024
217+ExtraProc() done
218+dealloc: 3155200
219+```
210220
211221Objects allocated within ExtraProc were released within ExtraProc. Object created outside of extra proc was released with its own "global" autorelease pool.
212222
213223### Leaking with Pools because of too many Retains
214224
215225Note, that an object would be released with autorelease pool only, if it's reference count reached 1. (when draining a pool would simply decrease the reference count, and if it drops to 0 the object is released).
216226
217227Modifying ExtraProc from the example above as following:
218228
219229```delphi
220230procedure ExtraProc;
221231var
222232 pool : NSAutoreleasePool;
223233 obj : NSMyObject;
224234begin
225235 pool := NSAutoreleasePool.alloc.init;
226236 try
227237 MakeObject;
228238 obj:=NSMyObject.alloc.init.autorelease; // the same allocation code as in MakeObject proc
229239 writeln('ref count = ', obj.retainCount);
230240 obj.retain;
231241 writeln('ref count = ', obj.retainCount);
232242 finally
233243 pool.release;
234244 writeln('ref count = ', obj.retainCount);
235245 end;
236246end;
237247```
238248
239249the output of the program would be:
240250
241-` alloc:   4202000`\
242-` calling ExtraProc()`\
243-` alloc:   4201824    ; allocating the first object in ExtraProc `\
244-` alloc:   4202048    ; allocating the second object in ExtraProc`\
245-` ref count = 1       ; the reference count after allocation`\
246-` ref count = 2       ; manually increasing the reference count by calling .retain`\
247-` dealloc: 4201824    ; on release of the pool, the first object with reference count was removed. `\
248-` ref count = 1       ; the second object had its reference count decreased to 1 and leaked `\
249-` ExtraProc() done`\
250-` dealloc: 4202000`
251+``` pascal
252+ alloc: 4202000
253+ calling ExtraProc()
254+ alloc: 4201824 ; allocating the first object in ExtraProc
255+ alloc: 4202048 ; allocating the second object in ExtraProc
256+ ref count = 1 ; the reference count after allocation
257+ ref count = 2 ; manually increasing the reference count by calling .retain
258+ dealloc: 4201824 ; on release of the pool, the first object with reference count was removed.
259+ ref count = 1 ; the second object had its reference count decreased to 1 and leaked
260+ ExtraProc() done
261+ dealloc: 4202000
262+```
251263
252264**Note** that "global" pool also has no effect on the leaked object. So after the global pool released, the leaked object remained. The leaked object attached to "local" pool that was in current at that time.
253265
254266### App Event Loop and AutoreleasePools
255267
256268Every event (i.e. mouse press, key press, draw, etc) handling is enclosed in it's own AutoreleasePool. The pool is released once the event has been processed.
257269
258270Cocoa Widgetset implements its own event queue, but the same rule remains enforced.
259271
260272Make sure that an object that should stay alive after the event is not autoreleased.
261273
262274### Object Ownership
263275
264276As [Apple documentation](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-SW1) suggests:
265277
266278The memory management model is based on object ownership. Any object may have one or more owners. As long as an object has at least one owner, it continues to exist. If an object has no owners, the runtime system destroys it automatically. To make sure it is clear when you own an object and when you do not, Cocoa sets the following policy:
267279
268280- **You own any object you create**
269281
270282
271283You create an object using a method whose name begins with “alloc”, “new”, “copy”, or “mutableCopy” (for example, alloc, newObject, or mutableCopy).
272284
273285- **You can take ownership of an object using retain**
274286
275287
276288A received object is normally guaranteed to remain valid within the method it was received in, and that method may also safely return the object to its invoker. You use retain in two situations: (1) In the implementation of an accessor method or an init method, to take ownership of an object you want to store as a property value; and (2) To prevent an object from being invalidated as a side-effect of some other operation (as explained in Avoid Causing Deallocation of Objects You’re Using).
277289
278290- **When you no longer need it, you must relinquish ownership of an object you own**
279291
280292
281293You relinquish ownership of an object by sending it a release message or an autorelease message. In Cocoa terminology, relinquishing ownership of an object is therefore typically referred to as “releasing” an object.
282294
283295- **You must not relinquish ownership of an object you do not own**
284296
285297
286298This is just corollary of the previous policy rules, stated explicitly.
287299
288300In practice, not following the serules, might cause unexpected errors in run-time.
289301
290302#### Ownership Violation
291303
292304Consider this following:
293305
294306```delphi
295307{$mode objfpc}{$H+}
296308{$modeswitch objectivec2}
297309uses CocoaAll;
298310var
299311 p : NSIndexSet;
300312begin
301313 p:=NSIndexSet.indexSetWithIndex(10);
302314 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
303315 p.release; // a potential error
304316end.
305317```
306318
307319If you try to execute the code, it would act as expected and produce no problems.
308320
309321However, there's an error. The object was created using "indexSetWithindex" method.
310322
311323The last line attempts to .release() the object. That violates the ownership rule, where the object is owned only if "alloc"/"new" or "copy" methods were used.
312324
313325The run-time error will show up, if you try to put this code into Autorelease pool:
314326
315327```delphi
316328var
317329 p : NSIndexSet;
318330 pool : NSAutoreleasePool;
319331begin
320332 pool := NSAutoreleasePool.alloc.init;
321333 p:=NSIndexSet.indexSetWithIndex(10);
322334 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
323335 p.release;
324336 pool.release; // error here: *** error for object 0x308a70: pointer being freed was not allocated
325337end.
326338```
327339
328340The reason behind it, is that "indexSetWithIndex" did allocate an object of NSIndexSet, however it also autorelease-d object. (Notably, why Cocoa internally is using AutoreleasePools often).
329341
330342There are two ways to fix the error:
331343
3323441\) either alloc.initXXX pair should be used, that guaranteers the ownership of the object
333345
334346```delphi
335347var
336348 p : NSIndexSet;
337349 pool : NSAutoreleasePool;
338350begin
339351 pool := NSAutoreleasePool.alloc.init;
340352 p:=NSIndexSet.alloc.initWithIndex(10);
341353 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
342354 p.release;
343355 pool.release;
344356end.
345357```
346358
3473592\) or the object should not be released explicitly
348360
349361```delphi
350362var
351363 p : NSIndexSet;
352364 pool : NSAutoreleasePool;
353365begin
354366 pool := NSAutoreleasePool.alloc.init;
355367 p:=NSIndexSet.indexSetWithIndex(10);
356368 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
357369 pool.release;
358370end.
359371```
360372
361373The actual approach depends on how NSIndexSet needs to be used in the code.
362374
363375#### Using NSZombies
364376
365377In the example below, the error typed into stderr is not very informative.
366378
367-`error here: *** error for object 0xNNNNNNNN: pointer being freed was not allocated`
379+``` pascal
380+error here: *** error for object 0xNNNNNNNN: pointer being freed was not allocated
381+```
368382
369383When there's potentially a great number of objects/code that could cause a double-release, it might be handy to use NSZombies.
370384
371385NSZombies are the special debugging mode in Cocoa library. Whenever an object is released, it's not actually being released, but instead is released by NSZombie object. If NSZombie object is released again, then it would produce an error message to stderr, to warn about the double release.
372386
373387In order to enable NSZombies an environmental variable NSZombiesEnable must be set to "YES" (no quotes) declared at the time application starts.
374388
375389Here's an example:
376390
377391```delphi
378392program project1;
379393
380394{$mode objfpc}{$H+}
381395{$modeswitch objectivec2}
382396
383397uses
384398 CocoaAll,SysUtils, Classes;
385399
386400type
387401 NSMyObject = objcclass(NSObject);
388402
389403var
390404 m: NSMyObject;
391405begin
392406 m:=NSMyObject.alloc.init;
393407 m.release;
394408 m.release;
395409end.
396410```
397411
398412In order to have the zombies enabled you might want to run the application from Terminal using the following commands:
399413
400-`export NSZombieEnabled="YES"`\
401-`./project1`
414+```
415+export NSZombieEnabled="YES"
416+./project1
417+```
402418
403419(export command could be ran once per Terminal tab. It doesn't affect other opened terminal tabs. Any new Terminal tab or window would require to run the command again, if you need to enable zombies)
404420
405421when application fails, it would produce the following error message:
406422
407-`*** -[NSMyObject release]: message sent to deallocated instance 0x606d20`
423+```
424+*** -[NSMyObject release]: message sent to deallocated instance 0x606d20
425+```
408426
409427If you try to run the faulty code from ownership violation the error you'd see would be like this:
410428
411-`*** -[NSIndexSet release]: message sent to deallocated instance 0x605140`
429+```
430+*** -[NSIndexSet release]: message sent to deallocated instance 0x605140
431+```
412432
413433indicating that NSIndexSet was improperly released.
414434
415435While NSZombies is very handy way of resolving problems, such as incorrect and double releases, they should be treated carefully. NSZombies in their nature are memory leaks, because the objects are never released, but rather replaced by the zombie's code. Thus NSZombies should never be used with memory leaks checks, as they would produce false positive results. If there are many objects allocation and releases, the application memory consumption will grow, that might also impact performance.
416436
417437## ObjC Object Leaks
418438
419439Free Pascal debugging unit [[heaptrc]] is only able to track leaking of memory allocated via Pascal Run-time memory management. It's not able to track allocations made via C-runtime/Obj-C memory manager.
420440
421441Apple provides external tools in its Xcode Instruments to keep track of ObjC allocations.
422442
423443### Xcode Instruments
424444
425445The Xcode Instruments Allocations profiling template uses the Allocations and VM Tracker instruments to measure general and virtual memory usage in your app. However, to track down abandoned memory that’s been allocated but isn’t needed again, focus strictly on the Allocations instrument. This instrument measures heap memory usage and tracks allocations, including specific object allocations by class.
426446
427447- See further [Find abandoned memory](https://help.apple.com/instruments/mac/current/#/dev7b8f6eb6).
428448
429449The Xcode Instruments Leaks profiling template uses the Allocations and Leaks instruments to measure general memory usage in your app and check for leaks — memory that has been allocated to objects that are no longer referenced and reachable.
430450
431451- See further [Find memory leaks](https://help.apple.com/instruments/mac/current/#/dev022f987b).
432452
433453## LCL Handles
434454
435455Whenever CreatingHandle in Cocoa widgetset at least 1 NSView (or NSControl) is allocated. (Allocation makes a control with 1 reference count)
436456
437457The view is added to parent view by calling addSubView. By Cocoa standards, adding a view to subview adds another reference count, and object can be released right after the call. LCL Widgetset code SHOULD NOT release immediately, instead the explicit .release call would be called during DestroyHandle.
438458
439459Any additional controls should also be released during DestroyHandle. It's not predictable when the actual "dealloc" would be called. It might be called right away, or at the end of the event processing, with NSAutorelease pool, or whenever the hosting NSWindow decides to cleanup. It's safer to release as much memory as possible at DestroyHandle.
440460
441461## See Also
442462
443463- [[Cocoa Internals]]
444464- [Advanced Memory Management](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html) - at Apple Developer
445465
446466---
447467*From the Free Pascal wiki, page "Cocoa Internals/Memory Management", as it stood on 2020-09-19 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
448468Category: Cocoa
449469Category: macOS

Back to history