Sign in Join
Wiki copytemporary

Cocoa Internals/Memory Management: what changed

Revision 1 to 2. 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
6363`alloc:   3206800`\
6464`dealloc: 3206800`\
6565`alloc:   3206800`\
6666`dealloc: 3206800`\
6767`alloc:   3206800`\
6868`dealloc: 3206800`
6969
7070This 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.
7171
7272### Leaking
7373
7474If MakeObject procedure is modified as following:
7575
7676```delphi
7777procedure MakeObject;
7878var
7979 obj : NSMyObject;
8080begin
8181 obj:=NSMyObject.alloc.init;
8282 // removed the line with .release call
8383end;
8484```
8585
8686the output of the application changes to:
8787
8888`alloc:   2146560 `\
8989`alloc:   2146288 `\
9090`alloc:   2146304`
9191
9292That looks like a memory leak via ObjC objects, because allocated objects were not released.
9393
9494Preventing a leak requires a proper release of an object.
9595
9696### Autorelease Pools
9797
9898In 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.
9999
100100(Autorelease pools are drained whenever they're released).
101101
102102Let's modify the example above in the following manner:
103103
104104```delphi
105105procedure MakeObject;
106106var
107107 obj : NSMyObject;
108108begin
109109 obj:=NSMyObject.alloc.init.autorelease; // registering the allocated object with autorelease pool
110110end;
111111
112112var
113113 pool : NSAutoreleasePool;
114114begin
115115 pool := NSAutoreleasePool.alloc.init;
116116 MakeObject;
117117 MakeObject;
118118 MakeObject;
119119 writeln('releasing the pool');
120120 pool.release;
121121 writeln('done');
122122end.
123123```
124124
125125The output of the program is as following:
126126
127127`alloc:   3175888`\
128128`alloc:   3172352`\
129129`alloc:   3172368 `\
130130`releasing the pool`\
131131`dealloc: 3172368 `\
132132`dealloc: 3172352`\
133133`dealloc: 3175888`\
134134`done`
135135
136136All objects were released together with release of the pool.
137137
138138### Leaking out of the pool
139139
140140Note 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
141141
142142` pool : NSAutoreleasePool;`
143143
144144begin
145145
146146` MakeObject;`\
147147` pool := NSAutoreleasePool.alloc.init;`\
148148` MakeObject;`\
149149` MakeObject;`\
150150` writeln('releasing the pool');`\
151151` pool.release;`\
152152` writeln('done');`
153153
154154end.
155155
156-</source>
157-
158156output:
159157
160158`alloc:   4199616`\
161159`alloc:   4202144 `\
162160`alloc:   4202160`\
163161`releasing the pool`\
164162`dealloc: 4202160 `\
165163`dealloc: 4202144`\
166164`done`
167165
168166As you can see only two later objects were released. The first object remained in the memory.
169167
170168### Multiple pools
171169
172170It'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.
173171
174172In the end the memory would be cleaned up, together with the local pool released:
175173
176174```delphi
177175procedure ExtraProc;
178176var
179177 pool : NSAutoreleasePool;
180178begin
181179 pool := NSAutoreleasePool.alloc.init;
182180 try
183181 MakeObject;
184182 MakeObject;
185183 finally
186184 pool.release;
187185 end;
188186end;
189187
190188var
191189 pool : NSAutoreleasePool;
192190begin
193191 pool := NSAutoreleasePool.alloc.init;
194192 MakeObject;
195193 writeln('calling ExtraProc()');
196194 ExtraProc();
197195 writeln('ExtraProc() done');
198196 pool.release;
199197end.
200198```
201199
202200output:
203201
204202`alloc:   3155200`\
205203`calling ExtraProc()`\
206204`alloc:   3155024`\
207205`alloc:   3155248`\
208206`dealloc: 3155248`\
209207`dealloc: 3155024`\
210208`ExtraProc() done`\
211209`dealloc: 3155200`
212210
213211Objects allocated within ExtraProc were released within ExtraProc. Object created outside of extra proc was released with its own "global" autorelease pool.
214212
215213### Leaking with Pools because of too many Retains
216214
217215Note, 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).
218216
219217Modifying ExtraProc from the example above as following:
220218
221219```delphi
222220procedure ExtraProc;
223221var
224222 pool : NSAutoreleasePool;
225223 obj : NSMyObject;
226224begin
227225 pool := NSAutoreleasePool.alloc.init;
228226 try
229227 MakeObject;
230228 obj:=NSMyObject.alloc.init.autorelease; // the same allocation code as in MakeObject proc
231229 writeln('ref count = ', obj.retainCount);
232230 obj.retain;
233231 writeln('ref count = ', obj.retainCount);
234232 finally
235233 pool.release;
236234 writeln('ref count = ', obj.retainCount);
237235 end;
238236end;
239237```
240238
241239the output of the program would be:
242240
243241` alloc:   4202000`\
244242` calling ExtraProc()`\
245243` alloc:   4201824    ; allocating the first object in ExtraProc `\
246244` alloc:   4202048    ; allocating the second object in ExtraProc`\
247245` ref count = 1       ; the reference count after allocation`\
248246` ref count = 2       ; manually increasing the reference count by calling .retain`\
249247` dealloc: 4201824    ; on release of the pool, the first object with reference count was removed. `\
250248` ref count = 1       ; the second object had its reference count decreased to 1 and leaked `\
251249` ExtraProc() done`\
252250` dealloc: 4202000`
253251
254252**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.
255253
256254### App Event Loop and AutoreleasePools
257255
258256Every 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.
259257
260258Cocoa Widgetset implements its own event queue, but the same rule remains enforced.
261259
262260Make sure that an object that should stay alive after the event is not autoreleased.
263261
264262### Object Ownership
265263
266264As [Apple documentation](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-SW1) suggests:
267265
268266The 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:
269267
270268- **You own any object you create**
271269
272270
273271You create an object using a method whose name begins with “alloc”, “new”, “copy”, or “mutableCopy” (for example, alloc, newObject, or mutableCopy).
274272
275273- **You can take ownership of an object using retain**
276274
277275
278276A 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).
279277
280278- **When you no longer need it, you must relinquish ownership of an object you own**
281279
282280
283281You 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.
284282
285283- **You must not relinquish ownership of an object you do not own**
286284
287285
288286This is just corollary of the previous policy rules, stated explicitly.
289287
290288In practice, not following the serules, might cause unexpected errors in run-time.
291289
292290#### Ownership Violation
293291
294292Consider this following:
295293
296294```delphi
297295{$mode objfpc}{$H+}
298296{$modeswitch objectivec2}
299297uses CocoaAll;
300298var
301299 p : NSIndexSet;
302300begin
303301 p:=NSIndexSet.indexSetWithIndex(10);
304302 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
305303 p.release; // a potential error
306304end.
307305```
308306
309307If you try to execute the code, it would act as expected and produce no problems.
310308
311309However, there's an error. The object was created using "indexSetWithindex" method.
312310
313311The 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.
314312
315313The run-time error will show up, if you try to put this code into Autorelease pool:
316314
317315```delphi
318316var
319317 p : NSIndexSet;
320318 pool : NSAutoreleasePool;
321319begin
322320 pool := NSAutoreleasePool.alloc.init;
323321 p:=NSIndexSet.indexSetWithIndex(10);
324322 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
325323 p.release;
326324 pool.release; // error here: *** error for object 0x308a70: pointer being freed was not allocated
327325end.
328326```
329327
330328The 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).
331329
332330There are two ways to fix the error:
333331
3343321\) either alloc.initXXX pair should be used, that guaranteers the ownership of the object
335333
336334```delphi
337335var
338336 p : NSIndexSet;
339337 pool : NSAutoreleasePool;
340338begin
341339 pool := NSAutoreleasePool.alloc.init;
342340 p:=NSIndexSet.alloc.initWithIndex(10);
343341 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
344342 p.release;
345343 pool.release;
346344end.
347345```
348346
3493472\) or the object should not be released explicitly
350348
351349```delphi
352350var
353351 p : NSIndexSet;
354352 pool : NSAutoreleasePool;
355353begin
356354 pool := NSAutoreleasePool.alloc.init;
357355 p:=NSIndexSet.indexSetWithIndex(10);
358356 writeln('idx=',p.firstIndex,' refCount=',p.retainCount);
359357 pool.release;
360358end.
361359```
362360
363361The actual approach depends on how NSIndexSet needs to be used in the code.
364362
365363#### Using NSZombies
366364
367365In the example below, the error typed into stderr is not very informative.
368366
369367`error here: *** error for object 0xNNNNNNNN: pointer being freed was not allocated`
370368
371369When there's potentially a great number of objects/code that could cause a double-release, it might be handy to use NSZombies.
372370
373371NSZombies 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.
374372
375373In order to enable NSZombies an environmental variable NSZombiesEnable must be set to "YES" (no quotes) declared at the time application starts.
376374
377375Here's an example:
378376
379377```delphi
380378program project1;
381379
382380{$mode objfpc}{$H+}
383381{$modeswitch objectivec2}
384382
385383uses
386384 CocoaAll,SysUtils, Classes;
387385
388386type
389387 NSMyObject = objcclass(NSObject);
390388
391389var
392390 m: NSMyObject;
393391begin
394392 m:=NSMyObject.alloc.init;
395393 m.release;
396394 m.release;
397395end.
398396```
399397
400398In order to have the zombies enabled you might want to run the application from Terminal using the following commands:
401399
402400`export NSZombieEnabled="YES"`\
403401`./project1`
404402
405403(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)
406404
407405when application fails, it would produce the following error message:
408406
409407`*** -[NSMyObject release]: message sent to deallocated instance 0x606d20`
410408
411409If you try to run the faulty code from ownership violation the error you'd see would be like this:
412410
413411`*** -[NSIndexSet release]: message sent to deallocated instance 0x605140`
414412
415413indicating that NSIndexSet was improperly released.
416414
417415While 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.
418416
419417## ObjC Object Leaks
420418
421419Free 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.
422420
423421Apple provides external tools in its Xcode Instruments to keep track of ObjC allocations.
424422
425423### Xcode Instruments
426424
427425The 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.
428426
429427- See further [Find abandoned memory](https://help.apple.com/instruments/mac/current/#/dev7b8f6eb6).
430428
431429The 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.
432430
433431- See further [Find memory leaks](https://help.apple.com/instruments/mac/current/#/dev022f987b).
434432
435433## LCL Handles
436434
437435Whenever CreatingHandle in Cocoa widgetset at least 1 NSView (or NSControl) is allocated. (Allocation makes a control with 1 reference count)
438436
439437The 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.
440438
441439Any 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.
442440
443441## See Also
444442
445443- [[Cocoa Internals]]
446444- [Advanced Memory Management](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html) - at Apple Developer
447445
448446---
449447*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).*
450448Category: Cocoa
451449Category: macOS

Back to history