Difference between revisions of "Cocoa Internals/Memory Management"
m (→App Event Loop) |
|||
Line 216: | Line 216: | ||
The leaked object attached to "local" pool that was in current at that time. | The leaked object attached to "local" pool that was in current at that time. | ||
===App Event Loop=== | ===App Event Loop=== | ||
− | Every event handling is enclosed in it's own | + | Every 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. |
Cocoa Widgetset implements its own event queue, but the same rule remains enforced. | Cocoa Widgetset implements its own event queue, but the same rule remains enforced. |
Revision as of 04:29, 25 December 2017
All Objective-C objects are reference counted objects. Once the count reaches zero - the object is freed.
The 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.
Memory Management
As 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).
Consider the following code example:
A new sub-class of NSObject is declared, overriding its alloc (constructor) and dealloc (destructor) methods. Overridden methods are providing a logging output.
program project1;
{$mode delphi}{$H+}
{$modeswitch objectivec1}
{$modeswitch objectivec2}
uses CocoaAll;
type
{ NSMyObject }
NSMyObject = objcclass(NSObject)
public
class function alloc: id; override;
procedure dealloc; override;
end;
{ NSMyObject }
class function NSMyObject.alloc: id;
begin
Result:=inherited alloc;
writeln('alloc: ', PtrUInt(Result));
end;
procedure NSMyObject.dealloc;
begin
writeln('dealloc: ', PtrUInt(Self));
inherited dealloc;
end;
procedure MakeObject;
var
obj : NSMyObject;
begin
obj:=NSMyObject.alloc.init;
obj.release;
end;
begin
MakeObject;
MakeObject;
MakeObject;
end.
The output of calling such process would be:
alloc: 3206800 dealloc: 3206800 alloc: 3206800 dealloc: 3206800 alloc: 3206800 dealloc: 3206800
This 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.
Leaking
If MakeObject procedure is modified as following:
procedure MakeObject;
var
obj : NSMyObject;
begin
obj:=NSMyObject.alloc.init;
// removed the line with .release call
end;
the output of the application changes to:
alloc: 2146560 alloc: 2146288 alloc: 2146304
That looks like a memory leak via ObjC objects, because allocated objects were not released.
Preventing a leak requires a proper release of an object.
Autorelease Pools
In 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.
(Autorelease pools are drained whenever they're released).
Let's modify the example above in the following manner:
procedure MakeObject;
var
obj : NSMyObject;
begin
obj:=NSMyObject.alloc.init.autorelease; // registering the allocated object with autorelease pool
end;
var
pool : NSAutoreleasePool;
begin
pool := NSAutoreleasePool.alloc.init;
MakeObject;
MakeObject;
MakeObject;
writeln('releasing the pool');
pool.release;
writeln('done');
end.
The output of the program is as following:
alloc: 3175888 alloc: 3172352 alloc: 3172368 releasing the pool dealloc: 3172368 dealloc: 3172352 dealloc: 3175888 done
All objects were released together with release of the pool.
Leaking out of the pool
Note 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.
var
pool : NSAutoreleasePool;
begin
MakeObject;
pool := NSAutoreleasePool.alloc.init;
MakeObject;
MakeObject;
writeln('releasing the pool');
pool.release;
writeln('done');
end.
output:
alloc: 4199616 alloc: 4202144 alloc: 4202160 releasing the pool dealloc: 4202160 dealloc: 4202144 done
As you can see only two later objects were released. The first object remained in the memory.
Multiple pools
It'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.
In the end the memory would be cleaned up, together with the local pool released:
procedure ExtraProc;
var
pool : NSAutoreleasePool;
begin
pool := NSAutoreleasePool.alloc.init;
try
MakeObject;
MakeObject;
finally
pool.release;
end;
end;
var
pool : NSAutoreleasePool;
begin
pool := NSAutoreleasePool.alloc.init;
MakeObject;
writeln('calling ExtraProc()');
ExtraProc();
writeln('ExtraProc() done');
pool.release;
end.
output:
alloc: 3155200 calling ExtraProc() alloc: 3155024 alloc: 3155248 dealloc: 3155248 dealloc: 3155024 ExtraProc() done dealloc: 3155200
Objects allocated within ExtraProc were released within ExtraProc. Object created outside of extra proc was released with its own "global" autorelease pool.
Leaking with Pools because of too many Retains
Note, 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).
Modifying ExtraProc from the example above as following:
procedure ExtraProc;
var
pool : NSAutoreleasePool;
obj : NSMyObject;
begin
pool := NSAutoreleasePool.alloc.init;
try
MakeObject;
obj:=NSMyObject.alloc.init.autorelease; // the same allocation code as in MakeObject proc
writeln('ref count = ', obj.retainCount);
obj.retain;
writeln('ref count = ', obj.retainCount);
finally
pool.release;
writeln('ref count = ', obj.retainCount);
end;
end;
the output of the program would be:
alloc: 4202000 calling ExtraProc() alloc: 4201824 ; allocating the first object in ExtraProc alloc: 4202048 ; allocating the second object in ExtraProc ref count = 1 ; the reference count after allocation ref count = 2 ; manually increasing the reference count by calling .retain dealloc: 4201824 ; on release of the pool, the first object with reference count was removed. ref count = 1 ; the second object had its reference count decreased to 1 and leaked ExtraProc() done dealloc: 4202000
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.
App Event Loop
Every 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.
Cocoa Widgetset implements its own event queue, but the same rule remains enforced.
ObjC Object Leaks
Free 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.
Apple provides an external tools to keep tracking of ObjC allocations.
Xcode Instruments
One of the
See Also
- Cocoa Internals
- Advanced Memory Management - at Apple Developer