Sign in Join
Wiki copytemporary

Cocoa Internals/Memory Management: what changed

Revision 3 to 4. 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-```
63+```text
6464alloc: 3206800
6565dealloc: 3206800
6666alloc: 3206800
6767dealloc: 3206800
6868alloc: 3206800
6969dealloc: 3206800
7070```
7171
7272This 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.
7373
7474### Leaking
7575
7676If MakeObject procedure is modified as following:
7777
7878```delphi
7979procedure MakeObject;
8080var
8181 obj : NSMyObject;
8282begin
8383 obj:=NSMyObject.alloc.init;
8484 // removed the line with .release call
8585end;
8686```
8787
8888the output of the application changes to:
8989
90-```
90+```text
9191alloc: 2146560
9292alloc: 2146288
9393alloc: 2146304
9494```
9595
9696That looks like a memory leak via ObjC objects, because allocated objects were not released.
9797
9898Preventing a leak requires a proper release of an object.
9999
100100### Autorelease Pools
101101
102102In 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.
103103
104104(Autorelease pools are drained whenever they're released).
105105
106106Let's modify the example above in the following manner:
107107
108108```delphi
109109procedure MakeObject;
110110var
111111 obj : NSMyObject;
112112begin
113113 obj:=NSMyObject.alloc.init.autorelease; // registering the allocated object with autorelease pool
114114end;
115115
116116var
117117 pool : NSAutoreleasePool;
118118begin
119119 pool := NSAutoreleasePool.alloc.init;
120120 MakeObject;
121121 MakeObject;
122122 MakeObject;
123123 writeln('releasing the pool');
124124 pool.release;
125125 writeln('done');
126126end.
127127```
128128
129129The output of the program is as following:
130130
131-```
131+```text
132132alloc: 3175888
133133alloc: 3172352
134134alloc: 3172368
135135releasing the pool
136136dealloc: 3172368
137137dealloc: 3172352
138138dealloc: 3175888
139139done
140140```
141141
142142All objects were released together with release of the pool.
143143
144144### Leaking out of the pool
145145
146146Note 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
147147
148148` pool : NSAutoreleasePool;`
149149
150150begin
151151
152152` MakeObject;`\
153153` pool := NSAutoreleasePool.alloc.init;`\
154154` MakeObject;`\
155155` MakeObject;`\
156156` writeln('releasing the pool');`\
157157` pool.release;`\
158158` writeln('done');`
159159
160160end.
161161
162162output:
163163
164-```
164+```text
165165alloc: 4199616
166166alloc: 4202144
167167alloc: 4202160
168168releasing the pool
169169dealloc: 4202160
170170dealloc: 4202144
171171done
172172```
173173
174174As you can see only two later objects were released. The first object remained in the memory.
175175
176176### Multiple pools
177177
178178It'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.
179179
180180In the end the memory would be cleaned up, together with the local pool released:
181181
182182```delphi
183183procedure ExtraProc;
184184var
185185 pool : NSAutoreleasePool;
186186begin
187187 pool := NSAutoreleasePool.alloc.init;
188188 try
189189 MakeObject;
190190 MakeObject;
191191 finally
192192 pool.release;
193193 end;
194194end;
195195
196196var
197197 pool : NSAutoreleasePool;
198198begin
199199 pool := NSAutoreleasePool.alloc.init;
200200 MakeObject;
201201 writeln('calling ExtraProc()');
202202 ExtraProc();
203203 writeln('ExtraProc() done');
204204 pool.release;
205205end.
206206```
207207
208208output:
209209
210-```
210+```text
211211alloc: 3155200
212212calling ExtraProc()
213213alloc: 3155024
214214alloc: 3155248
215215dealloc: 3155248
216216dealloc: 3155024
217217ExtraProc() done
218218dealloc: 3155200
219219```
220220
221221Objects allocated within ExtraProc were released within ExtraProc. Object created outside of extra proc was released with its own "global" autorelease pool.
222222
223223### Leaking with Pools because of too many Retains
224224
225225Note, 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).
226226
227227Modifying ExtraProc from the example above as following:
228228
229229```delphi
230230procedure ExtraProc;
231231var
232232 pool : NSAutoreleasePool;
233233 obj : NSMyObject;
234234begin
235235 pool := NSAutoreleasePool.alloc.init;
236236 try
237237 MakeObject;
238238 obj:=NSMyObject.alloc.init.autorelease; // the same allocation code as in MakeObject proc
239239 writeln('ref count = ', obj.retainCount);
240240 obj.retain;
241241 writeln('ref count = ', obj.retainCount);
242242 finally
243243 pool.release;
244244 writeln('ref count = ', obj.retainCount);
245245 end;
246246end;
247247```
248248
249249the output of the program would be:
250250
251251``` pascal
252252 alloc: 4202000
253253 calling ExtraProc()
254254 alloc: 4201824 ; allocating the first object in ExtraProc
255255 alloc: 4202048 ; allocating the second object in ExtraProc
256256 ref count = 1 ; the reference count after allocation
257257 ref count = 2 ; manually increasing the reference count by calling .retain
258258 dealloc: 4201824 ; on release of the pool, the first object with reference count was removed.
259259 ref count = 1 ; the second object had its reference count decreased to 1 and leaked
260260 ExtraProc() done
261261 dealloc: 4202000
262262```
263263
264264**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.
265265
266266### App Event Loop and AutoreleasePools
267267
268268Every 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.
269269
270270Cocoa Widgetset implements its own event queue, but the same rule remains enforced.
271271
272272Make sure that an object that should stay alive after the event is not autoreleased.
273273
274274### Object Ownership
275275
276276As [Apple documentation](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-SW1) suggests:
277277
278278The 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:
279279
280280- **You own any object you create**
281281
282282
283283You create an object using a method whose name begins with “alloc”, “new”, “copy”, or “mutableCopy” (for example, alloc, newObject, or mutableCopy).
284284
285285- **You can take ownership of an object using retain**
286286
287287
288288A 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).
289289
290290- **When you no longer need it, you must relinquish ownership of an object you own**
291291
292292
293293You 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.
294294
295295- **You must not relinquish ownership of an object you do not own**
296296
297297
298298This is just corollary of the previous policy rules, stated explicitly.
299299
300300In practice, not following the serules, might cause unexpected errors in run-time.
301301
302302#### Ownership Violation
303303
304304Consider this following:
305305
306306```delphi
307307{$mode objfpc}{$H+}
308308{$modeswitch objectivec2}
309309uses CocoaAll;
310310var
311311 p : NSIndexSet;
312312begin
313313 p:=NSIndexSet.indexSetWithIndex(10);
314314 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
315315 p.release; // a potential error
316316end.
317317```
318318
319319If you try to execute the code, it would act as expected and produce no problems.
320320
321321However, there's an error. The object was created using "indexSetWithindex" method.
322322
323323The 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.
324324
325325The run-time error will show up, if you try to put this code into Autorelease pool:
326326
327327```delphi
328328var
329329 p : NSIndexSet;
330330 pool : NSAutoreleasePool;
331331begin
332332 pool := NSAutoreleasePool.alloc.init;
333333 p:=NSIndexSet.indexSetWithIndex(10);
334334 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
335335 p.release;
336336 pool.release; // error here: *** error for object 0x308a70: pointer being freed was not allocated
337337end.
338338```
339339
340340The 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).
341341
342342There are two ways to fix the error:
343343
3443441\) either alloc.initXXX pair should be used, that guaranteers the ownership of the object
345345
346346```delphi
347347var
348348 p : NSIndexSet;
349349 pool : NSAutoreleasePool;
350350begin
351351 pool := NSAutoreleasePool.alloc.init;
352352 p:=NSIndexSet.alloc.initWithIndex(10);
353353 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
354354 p.release;
355355 pool.release;
356356end.
357357```
358358
3593592\) or the object should not be released explicitly
360360
361361```delphi
362362var
363363 p : NSIndexSet;
364364 pool : NSAutoreleasePool;
365365begin
366366 pool := NSAutoreleasePool.alloc.init;
367367 p:=NSIndexSet.indexSetWithIndex(10);
368368 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
369369 pool.release;
370370end.
371371```
372372
373373The actual approach depends on how NSIndexSet needs to be used in the code.
374374
375375#### Using NSZombies
376376
377377In the example below, the error typed into stderr is not very informative.
378378
379379``` pascal
380380error here: *** error for object 0xNNNNNNNN: pointer being freed was not allocated
381381```
382382
383383When there's potentially a great number of objects/code that could cause a double-release, it might be handy to use NSZombies.
384384
385385NSZombies 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.
386386
387387In order to enable NSZombies an environmental variable NSZombiesEnable must be set to "YES" (no quotes) declared at the time application starts.
388388
389389Here's an example:
390390
391391```delphi
392392program project1;
393393
394394{$mode objfpc}{$H+}
395395{$modeswitch objectivec2}
396396
397397uses
398398 CocoaAll,SysUtils, Classes;
399399
400400type
401401 NSMyObject = objcclass(NSObject);
402402
403403var
404404 m: NSMyObject;
405405begin
406406 m:=NSMyObject.alloc.init;
407407 m.release;
408408 m.release;
409409end.
410410```
411411
412412In order to have the zombies enabled you might want to run the application from Terminal using the following commands:
413413
414-```
414+```text
415415export NSZombieEnabled="YES"
416416./project1
417417```
418418
419419(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)
420420
421421when application fails, it would produce the following error message:
422422
423-```
423+```text
424424*** -[NSMyObject release]: message sent to deallocated instance 0x606d20
425425```
426426
427427If you try to run the faulty code from ownership violation the error you'd see would be like this:
428428
429-```
429+```text
430430*** -[NSIndexSet release]: message sent to deallocated instance 0x605140
431431```
432432
433433indicating that NSIndexSet was improperly released.
434434
435435While 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.
436436
437437## ObjC Object Leaks
438438
439439Free 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.
440440
441441Apple provides external tools in its Xcode Instruments to keep track of ObjC allocations.
442442
443443### Xcode Instruments
444444
445445The 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.
446446
447447- See further [Find abandoned memory](https://help.apple.com/instruments/mac/current/#/dev7b8f6eb6).
448448
449449The 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.
450450
451451- See further [Find memory leaks](https://help.apple.com/instruments/mac/current/#/dev022f987b).
452452
453453## LCL Handles
454454
455455Whenever CreatingHandle in Cocoa widgetset at least 1 NSView (or NSControl) is allocated. (Allocation makes a control with 1 reference count)
456456
457457The 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.
458458
459459Any 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.
460460
461461## See Also
462462
463463- [[Cocoa Internals]]
464464- [Advanced Memory Management](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html) - at Apple Developer
465465
466466---
467467*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).*
468468Category: Cocoa
469469Category: macOS

Back to history