PasCocoaKit Guide

Memory management

Like with Cocoa objects are constantly being created and disposed of, in PasCocoa wrappers are being generated, often behind the scenes from inside the headers. To deal with this flood of extra memory that normally would not exist in Cocoa, additional memory management needs to be introduced in order to spare the user from manually manage it. The first step to using PasCocoa safely is understanding how memory works.
Also important reading is how Cocoa manages memory, as the two are directly linked.

http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html

Deferred releases
Wrappers that are created internally within the headers from constructor methods are added to an auto release table for deferred release (using NSRunLoop). This means you can guarantee the wrapper will exist in the current scope but will be released upon the next event loop cycle. If you wish to transfer ownership you must call retain on the object, which will remove it from the table and retain the Objective-C object. When you are finished using the object you must call release, which will free both the wrapper and internal Objective-C object.

The rules for which constructors return objects that are owned by the user are ambiguous and rely on you being familiar with the rules of memory management in Cocoa. Here is a good guide:

http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html

These type of constructors do not allocate and will be auto released. When the object is disposed of by calling release it will only dispose of the wrapper, not the Objective-C object (unless retain was called prior, see section b).

constructor NSColor.redColor;
constructor NSNotificationCenter.defaultCenter;
constructor NSApplication.sharedApplication;

These type of constructors do allocate and they will not be auto released. When the object is disposed of by calling release it will release both the wrapper and the Objective-C object.

constructor NSTextView.initWithFrame(frameRect: NSRect);
constructor NSDocument.initWithType_error(typeName: CFStringRef; var outError: NSErrorRef);

If you are allocating wrappers in performance sensitive code, or loops within the current scope you should retain the object to prevent generating large (and pointless) auto release tables. For example:

{ This is wrong, the auto release table now has 1000 entries to dispose of }
for i := 1 to 1000 do
  CFShow(NSColor.redColor.description);

{ This is correct,  the object is retained (thus removed from the table) and released once }
color := NSColor.redColor.retain;
for i := 1 to 1000 do
  CFShow(color.description);
color.release;

Retain/Release
Balancing retain and release calls is the same as in Cocoa except one difference: wrappers begin with their retain count at zero (unless they are allocated by Cocoa, they begin at 1. see section a above). When they are released the retain count is decremented 1, if the count reaches -1 the wrapper is released but not the Objective-C object. If the count reaches 0 both objects are released. For example:

color := NSColor.redColor   // retain count = 0
color.release;              // retain count = -1, release the wrapper

color := NSColor.redColor   // retain count = 0
color.retain;               // retain count = 1, you own the object
color.release;              // retain count = 0, release both

textField := TTextField.initWithFrame(textFieldRect); // retain count = 1 because initWithFrame allocates an object
textField.release;          // retain count = 0, release both

Wrapper Tables
All NSObjects maintain a wrapper table which releases all of it’s entries when the object is destroyed. You can append items to the table by delegating to an “owner” (see section d) or directly to an object. Read section d for more information.

{ Add value to the wrapper table of "self" }
value := NSValue.CreateWithHandle(someObject).manageObject(self);

{ Use AppendWrapperTable for a 2-line approach }
color := NSColor.blueColor;
myWindow.AppendWrapperTable(color);  // color belongs to myWindow
myWindow.release;                    // wrapper table is released, including color

{ Retrieve wrappers using a generic untyped object (id) }
color := myWindow.GetWrapper(someColor);  // someColor is a Cocoa object without a Pascal wrapper

{ Remove the object for efficiency }
color.RemoveFromTable;

Delegating Management
To make managing wrappers more convenient you can “delegate” the object to another which you are certain will be disposed of. This process adds the wrapper to the owning objects wrapper table (see section c). For example:

color.redColor.manageObject(myWindow);

When the object myWindow is disposed it will release the wrapper table which contains color.

Caching Objects
It is common that a Cocoa object may maintain any number of internal/static objects that you can retrieve and manipulate using accessor methods. When the accessor methods are called they will return deferred wrappers, which can be inefficient if you access the object often. To solve this you can “cache” objects which essentially removes the object from the auto release table and delegates (see section d) ownership to another object (which are certain will be released). Note, the object is not retained, the original retain count still stands. For example:

textView.textStorage.cacheObject(textView);
This code will return an NSTextStorage wrapper from textStorage and cache the object to textView which a NSTextView. When textView is disposed it will release it’s wrapper table, including the NSTextStorage object. Please note, only cache objects you are certain are static, i.e. they will not be released before the object who owns them is.

If you wish to un-cache the object, for example if you want to change the Cocoa reference, use RemoveFromTable.

textView.textContainer.cacheObject(owner);  // the object is cached to "owner"
textView.textContainer.RemoveFromTable;     // remove it from the cache
textView.setTextContainer(newStorage);      // we can change the Cocoa reference now safely

Or, you can simply retain the wrapper to your own instance variable and release it at any time:

myTextContainer := textView.textContainer;   // retain count = 0
size := myTextContainer.containerSize;       // use our wrapper
myTextContainer.release;                     // retain count = -1, release the wrapper, but not the Cocoa object.

Instantiating Objects from InterfaceBuilder

If you use InterfaceBuilder you will need to intervene when objects are instantiated and provide your wrapper method. To do this you must override one of a few methods depending on the type of object.

  • InstantiateWithInit (init). Custom classes, generally sub classed from NSObject.
  • InstantiateWithView (initWithFrame:). Classes descendent of NSView
  • InstantiateWithWindow (initWithContentRect:styleMask:backing:defer:). Classes descendent of NSWindow
  • InstantiateWithCoder (initWithCoder:). Any other object in InterfaceBuilder which was not included above.

To learn about why this is important read this article: http://developer.apple.com/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW19. These methods implement initialization methods that are called when the NIB is loaded and allow you to assign the wrapper to the instance allocated from Cocoa.

This is ugly boiler plate code needed for every class appearing in InterfaceBuilder until FPC has better runtime support. The example below instantiates a custom NSView and allocates our wrapper TCustomView. Basically we call Instantiate with our newly allocated wrapper (TCustomView) and the results from inherited, which invokes the Objective-C super class. From here you can override AddMethods to add methods to the class. The object will be released automatically from Cocoa, do not call release on these objects unless you made a prior call to retain. For example:

class function TCustomView.InstantiateWithView (_self: objc.id; cmd: SEL; frameRect: CGRect): Pointer; cdecl; static;
begin
  result := NSObject.Instantiate(TCustomView.CreateFromNIB, inherited InstantiateWithView(_self, cmd, frameRect));
end;

 
Finally you must register the class before the NIB is loaded, probably in the main program block. The method RegisterFromNIB will override the proper initialization methods in the class using a constant:

  • kRegisterCustomObject. Custom classes, generally sub classed from NSObject.
  • kRegisterStandardObject. Classes descendent of NSView
  • kRegisterViewObject. Classes descendent of NSWindow
  • kRegisterWindowObject. Any other object in InterfaceBuilder which was not included above.

So, if you want to register a class named TCustomView as a view object do the following:

TCustomView.RegisterFromNIB(kRegisterViewObject);

dealloc
The NSObject method dealloc has been overridden automatically behind the scenes and it will be invoked by Cocoa. If you override the destroyer method Destroy you can dispose of instance variables when the class is released by Cocoa.

awakeFromNib
This is normally a method defined in Cocoa (awakeFromNib:) but we implement a special version that is called when a new object has been fully initialized and all it’s outlets and actions connected. Override this method to initialize private instances variables.

IBActions

PasCocoa supports IBActions but they require manually registering methods to the Objective-C runtime. This step can be removed when FPC has better runtime support. Here is an example:
type
  TMyDelegate = class (NSObject)
    procedure doSomething (sender: id);
    procedure AddMethods; override;
  end;

procedure TMyDelegate.AddMethods;
begin
  AddIBAction('doSomething:', Pointer(doSomething));
end;

In AddMethods you call AddIBAction with the name of the selector (always suffixed with : ) and a pointer to the method. It’s good convention to name the method and the selector the same (including case) but this is not strictly enforced. Please note that when adding methods to the class they must follow that same format, a procedure with one parameter of type id.  You can create a wrapper from the sender if needed. For example if the sender was a button (NSButton)

button := NSButton.CreateWithHandle(sender);

This manual will not go into the details of how to create and assign targets for these actions in InterfaceBuilder.

IBOutlets

Once again, IBOutlets are supported but require ugly and annoying boiler plate code until FPC supports better runtime access. The example below overrides ConnectIBOutlet and by checking the name parameter allocates a new wrapper and sets it to an instance variable with the same name (textView). The last parameter theObject is the Objective-C object allocated by the NIB. Also note we call manageObject on self which means the wrapper will be disposed with TMyDelegate.
function TMyDelegate.ConnectIBOutlet (name, theType: String; theObject: objc.id): Pointer;
begin
  if name = 'textView' then
    textView := NSTextView.CreateWithHandle(theObject).manageObject(self);
end;

Additionally you need to register these outlets with the Objective-C runtime so the NIB knows how to send messages to them for initialization. To do this call AddIBOutlet with the name of the outlet as defined in InterfaceBuilder. For example:
 
AddIBOutlet('textView');

This manual will not go into the details of how to create outlets and connect them with user interface elements in InterfaceBuilder.

Using Delegate Methods and Controllers

PasCocoa offers a big convenience by adding all delegate methods in both AppKit and Foundation framework in NSObject and auto-wrapping parameters that contains NSObject’s. All you must do is simply override the method you require and add it to the runtime. For example to respond to applicationDidFinishLaunching events from an application delegate controller:

type
  TAppController = class (NSObject)
    procedure applicationDidFinishLaunching(notification: NSNotification); override;
    procedure AddMethods; override;
  end;

procedure TAppController.applicationDidFinishLaunching(notification: NSNotification);
begin
  CFShow(notification.name);
end;

procedure TAppController.AddMethods;
begin
  add_applicationDidFinishLaunching;
end;

To add the method use the name of the method prefixed with “add_”, which makes: add_applicationDidFinishLaunching in our example. Then override the method using plain Pascal. Please note that the parameter notification is an allocated wrapper to NSNotification which will be disposed as soon as the method ends. Do not attempt to retain these wrappers.

Subclassing Cocoa Classes & Overriding Methods

If you want to override methods defined in existing Cocoa classes you must utilize the wrapper methods built into each Cocoa class. Each method in a class has a corresponding wrapper method: implemented, super and override. To call any particular method you simply prefix the method followed by an underscore. For example if you want to call the super classes implementation of drawRect you call super_drawRect.

Each constructor method in the headers will automatically register and allocate an instance of the sub class being created. In the example below initWithFrame registers TTextField in the Objective-C runtime (if it did not exist), assigns a reference to the object (self) to the Objective-C object then call AddMethods to allow the user to add, or override methods in the class.

In the example below we created a new instance of TTextField (a subclass of NSTextField), override drawRect and then override the implemented method implemented_drawRect, where drawing is performed. Also note that we can call the super classes implementation with super_drawRect. The steps in order are:

  1. Override AddMethods.
  2. Override methods using override_theMethod.
  3. Override the implemented method (in the Pascal class) using implemented_theMethod.
  4. Optionally call the super class using super_theMethod.

type
  TTextField = class (NSTextField)
    procedure implemented_drawRect (rect: NSRect); override;
    procedure AddMethods; override;
  end;

procedure TTextField.implemented_drawRect (rect: NSRect);
begin
  super_drawRect(rect);
  NSRectFill(bounds);
end;

procedure TTextField.AddMethods;
begin
  override_drawRect;
end;

textField := TTextField.initWithFrame(textFieldRect);

Type safety

PasCocoa defines some additional types for each Cocoa class that help you protect against confusing wrappers with the actual Objective-C object. For example NSTextView declares these 2 types:

NSTextViewRef = id;
NSTextViewPointer = Pointer;

It is important in functions that return Cocoa objects you use the actual Cocoa object, not the wrapper. In the headers implemented methods that you can override, they are careful to request the “Ref” of the object as returning the wrapper will produce an error. For example if you override NSTextView.backgroundColor you are expected to an NSColor object, but not the wrapper.

function TTextView.implemented_backgroundColor: NSColorRef;
var
  color: NSColor;
begin
  color := NSColor.redColor;
  result := color.Handle; // return the Handle, not color.
  color.release; // retain count = -1, release the wrapper but not the Handle
end; 

Note in this example you use Handle to reference the Objcetive-C object. Similarly, in some places a pointer to an object is requested, in those cases use NS***Pointer.

Selectors

Selectors in Objective-C are defined as SEL = Pointer but in the headers a type SELString is accepted where a selector (SEL) would normally be used. This is because the methods in the headers automatically call sel_registerName for you.

Adding Observers to the Notification Center

Adding observer methods to a notification center is made possible using the same method as IBActions. For example if you want to add the selector processEditing: follow the example below. Note, when adding methods to the class they must follow that same format, a procedure with one parameter of type id.

type
  TTextDelegate = class (NSObject)
    procedure processEditing (sender: id);
    procedure AddMethods; override;
 end;

procedure TTextDelegate.processEditing (sender: id);
var
  notification: NSNotification;
begin
  notification := NSNotification.CreateWithHandle(sender);
  CFShow(notification.name);
end;

procedure TTextDelegate.AddMethods;
begin
  NSNotificationCenter.defaultCenter.addObserver_selector_name_object(self, 'processEditing:', NSTextStorageDidProcessEditingNotification, nil);

  AddObserver('processEditing:', Pointer(processEditing));
end;

This code adds the selector processEditing: to the default notification center and then calls AddObserver to add the method to the Objective-C runtime.

Toll-Free Bridges

Currently CoreFoundation types are preferred over their Cocoa counterparts because it’s safer to have all methods expecting an opaque type rather than an object. This means that methods that returns objects and implemented methods that generate wrappers will return the CoreFoundation type.  Ideally the methods would be able to get the type of the parameter at runtime and choose Cocoa or CoreFoundation. If you want to access the Cocoa version you can use alloc to gain quick inline access:

function TController.tableView_writeRows_toPasteboard(tableView: NSTableView; rows: CFArrayRef; pboard: NSPasteboard): LongBool;
begin
 if NSArray.alloc(rows).objectAtIndex(0) <> nil then
  result := true;
end;

A deferred wrapper will be created and auto-disposed allowing you to use an accessor method. This is the preferred method for “inline” access and Objective-C like syntax.

Methods that use toll-free bridge types will accept the CoreFoundation version, which means you must not pass wrappers to Cocoa objects even though they are “toll-free” bridges. In order to use a Cocoa object you must pass Handle which is the Objective-C object. For example:

someString := NSString.initWithString(CFSTR('some string value'));    // creates a NSString from CFString
newString := someString.stringWithString(someString.Handle);          // use the Handle instead of the wrapper

Because stringWithString requests CFStringRef you must pass Handle which is the Objective-C object, and a toll-free bridge.

 Dynamically Typed Objects (id or NSObjectRef)

Types of id (or NSObjectRef) are untyped Objective-C objects who’s type can dynamically change during runtime. In PasCocoa however we must manually create wrappers depending on what class the object should be. To gain quick inline access to accessor methods for any class use alloc. For example:

writeln(NSObject.alloc(anObject).description);

In that example we create a temporary wrapper to NSObject will be auto-released and invoke a method, all in a single line, thus making it “inline” access.

It is very important you remember to use Handle (the Objective-C object the class wraps) when passing arguments to Cocoa code. For example:

class function loadNibNamed_owner(nibName: NSString; owner: NSObjectRef): LongBool;
NSBundle.loadNibNamed_owner(CFSTR('MainMenu'), NSApp.Handle);

Notice “owner” is NSObjectRef so we pass NSApp.Handle (the Objective-C object), not NSApp (which is the Pascal wrapper). Confusing the 2 types will throw compiler errors but in some cases it may be tempting to type cast and be done with it. Don’t.

Protocols

Protocols in PasCocoa are plain procedural calls which accept the first argument as id (or NSObjectRef) and are prefixed by the name of the protocol (in the Cocoa headers) followed by an underscore. For example:


function NSObject_respondsToSelector(sourceObject: NSObject; aSelector: SELString): LongBool;

This function refers to the method respondsToSelector: in the protocol @protocol NSObject.

To do

  • NSObject is not fully implemented, including all it’s various protocols and categories, which are numerous.
  • Reading IBOutlets from NIBs. I think this is possible but you will still have to connect them manually in code. 🙁
  • @protocols are not yet integrated into classes which conform to them. This is complicated and may run into troubles.
  • WebKit API headers

Bugs

  • Calling a super class is not tested and likely has problems
  • Functions that return structures are still not well tested
  • Function pointers from callback structures in the headers are incomplete
  • Types are still not 100% correct in all places
  • There is no concrete way to know which methods are constructors in Objective-C. Furthermore, certain methods with alloc, init, copy included in the name are owned by YOU the user, not the class. If there are methods parsed wrong based on that sloppy logic that only humans can decode, the memory management may cause crashes.
  • I’m not going to guarantee all the memory management is perfect as it has not been tested for very long. Consider version one to be buggy.
Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Google
  • BarraPunto
  • blinkbits
  • BlinkList
  • blogmarks
  • Facebook
  • StumbleUpon
  • TwitThis