Difference between revisions of "FPC PasCocoa"
(→Syntax (implemented): updated for new "external" syntax) |
|||
Line 68: | Line 68: | ||
<delphi> | <delphi> | ||
type | type | ||
− | ObjCClassName = objcclass [(ObjCSuperClassName|ProtocolName [, ProtocolName, ProtocolName])] | + | ObjCClassName = objcclass [external [name 'ExternalClassName']] [(ObjCSuperClassName|ProtocolName [, ProtocolName, ProtocolName])] |
[private, protected, public] | [private, protected, public] | ||
[variables declarations] | [variables declarations] | ||
[method declarations] | [method declarations] | ||
− | end; | + | end; |
</delphi> | </delphi> | ||
* ''objcclass'': defines an Objective-C class type declaration | * ''objcclass'': defines an Objective-C class type declaration | ||
+ | * ''external'': many Objective-C classes are implemented in external frameworks, such as Cocoa. The ''external'' modifiers enables you to declare such external classes for use in Pascal code. It is possible to specify a different external name for the class than the identifier used in Pascal code, because the duplicate identifier rules in Objective-C are laxer than those of Pascal (e.g., there are both a class and a protocol called ''NSObject'' in Cocoa, so one of the two has to be renamed in Pascal) | ||
* ObjCSuperClassName: defines parent (or super) class for the class. If name is not specified, the class defines a new root class. See http://wiki.freepascal.org/FPC_PasCocoa/Differences#No_unique_root_class for more information. | * ObjCSuperClassName: defines parent (or super) class for the class. If name is not specified, the class defines a new root class. See http://wiki.freepascal.org/FPC_PasCocoa/Differences#No_unique_root_class for more information. | ||
− | |||
For example: | For example: | ||
Line 84: | Line 84: | ||
// External classes from Apple's framework | // External classes from Apple's framework | ||
// have all fields in private sections | // have all fields in private sections | ||
− | NSView = objcclass(NSResponder) | + | NSView = objcclass external (NSResponder) |
private | private | ||
_subview : id; // private field | _subview : id; // private field | ||
Line 93: | Line 93: | ||
procedure setAutoresizesSubviews_(flag: LongBool); message 'setAutoresizesSubviews:'; | procedure setAutoresizesSubviews_(flag: LongBool); message 'setAutoresizesSubviews:'; | ||
procedure drawRect_(dirtyRect: NSRect); message 'drawRect:'; | procedure drawRect_(dirtyRect: NSRect); message 'drawRect:'; | ||
− | end | + | end; |
// MyView is a locally implemented class | // MyView is a locally implemented class | ||
Line 132: | Line 132: | ||
// note: | // note: | ||
// * no inheritance is specified | // * no inheritance is specified | ||
− | // * | + | // * the external name (if any) must match the external name of any real definition |
− | // | + | // to which this formal definition resolves later on |
− | MyExternalClass = objcclass | + | MyExternalClass = objcclass external; |
</delphi> | </delphi> | ||
Line 148: | Line 148: | ||
type | type | ||
// Formal declaration of MyItemClass, which is defined in another unit | // Formal declaration of MyItemClass, which is defined in another unit | ||
− | MyItemClass = objcclass | + | MyItemClass = objcclass external; |
MyContainerClass = objcclass | MyContainerClass = objcclass | ||
Line 238: | Line 238: | ||
<delphi> | <delphi> | ||
type | type | ||
− | ObjCProtocolName = objcprotocol [(ObjCParentProtocol1[, ObjCParentProtocol2, ...])] | + | ObjCProtocolName = objcprotocol [external [name 'ExternalClassName']] [(ObjCParentProtocol1[, ObjCParentProtocol2, ...])] |
[required, optional] | [required, optional] | ||
[method declarations] | [method declarations] | ||
− | end; | + | end; |
</delphi> | </delphi> | ||
* ''objcprotocol'': defines an Objective-C protocol type declaration | * ''objcprotocol'': defines an Objective-C protocol type declaration | ||
+ | * ''external'': same as with objcclass | ||
* ''ObjCParentProtocols'': the protocols this protocol inherits from (so a class conforming to this protocol, will also have to implement all methods from those other protocols) | * ''ObjCParentProtocols'': the protocols this protocol inherits from (so a class conforming to this protocol, will also have to implement all methods from those other protocols) | ||
* ''required, optional'': If nothing is specified, the default is ''required''. ''optional'' methods do not have to be implemented by classes conforming to this protocol. | * ''required, optional'': If nothing is specified, the default is ''required''. ''optional'' methods do not have to be implemented by classes conforming to this protocol. | ||
− | |||
Example: | Example: | ||
Line 280: | Line 280: | ||
<delphi> | <delphi> | ||
type | type | ||
− | ObjCCategoryName = objccategory(ExtendedObjCClass[,ObjCProtocol1, ObjCProtocol2, ...]) | + | ObjCCategoryName = objccategory [external [name 'ExternalCategoryName']] (ExtendedObjCClass[,ObjCProtocol1, ObjCProtocol2, ...]) |
[method declarations] | [method declarations] | ||
− | end; | + | end; |
</delphi> | </delphi> | ||
* ''objccategory'': defines an Objective-C category type declaration | * ''objccategory'': defines an Objective-C category type declaration | ||
+ | * ''external'': same as with objcclass. As of Objective-C 2.0, the name of a category is optional. ''Not yet implemented'': to declare an external category without a name, use an empty string. | ||
* ''method declarations'': the methods that this category adds or replaces in the extended class. These can be both class and instance methods. Note that when replacing a method in the extended class, you must mark this new method as ''reintroduce''. The reason that we do not use ''override'' here, is that the method is really replaced in the original class. This means, e.g., that if you call ''inherited'' from the new method, that you will call this method in the parent class of the extended class, and not the replaced method. The replaced method is "lost". | * ''method declarations'': the methods that this category adds or replaces in the extended class. These can be both class and instance methods. Note that when replacing a method in the extended class, you must mark this new method as ''reintroduce''. The reason that we do not use ''override'' here, is that the method is really replaced in the original class. This means, e.g., that if you call ''inherited'' from the new method, that you will call this method in the parent class of the extended class, and not the replaced method. The replaced method is "lost". | ||
* ''ObjCParentProtocols'': the protocols this category implements. The category will have to implement all required methods from these protocols. | * ''ObjCParentProtocols'': the protocols this category implements. The category will have to implement all required methods from these protocols. | ||
− | |||
Example: | Example: |
Revision as of 16:54, 2 January 2011
Introduction
A large and growing fraction of the Mac OS X system frameworks are written in Objective-C and only offer an Objective-C interface. This makes it unwieldy to use them directly from other programming languages. The Objective-C run time library, which is used for everything from defining classes to sending messages (calling methods), has a public procedural C interface however, which makes it possible to integrate other languages directly with Objective-C code. Two general approaches exist.
The first approach is to create a bridge. In this case calls to the Objective-C run time helpers are wrapped in host language constructs and helpers. The host language itself is generally not modified. A number of examples of Cocoa bridges can be found at http://www.cocoadev.com/index.pl?CocoaBridges.
A second approach is to extend the host language so that it can directly interface with Objective-C code using new language constructs. A list of such language extensions can be found at http://www.cocoadev.com/index.pl?CocoaLanguages. As far as Pascal is concerned, a first proposal for Objective-Pascal was made by MetroWerks in 1997. Based on this preliminary proposal (it was never finalised nor implemented by MetroWerks) and on evolutions of the Object Pascal language since that time, we have designed our own Objective-Pascal dialect.
PasCocoa wrappers
Before the compiler supported Objective-Pascal (as described below), the primary way for Objective-C interfacing was through ObjFPC wrapper classes. This process is described on the PasCocoa page, and falls in the bridge approach mentioned above.
Objective-C FPC Compiler
All Objective-C support is now available in svn trunk. For instructions on how to check it out, see http://www.freepascal.org/develop.var#svn
For an overview of some conceptual differences between Objective-Pascal and Object Pascal on the one hand, and between Objective-Pascal and Objective-C on the other hand, see FPC_PasCocoa/Differences.
Syntax (implemented)
Enabling Objective-C/Objective-Pascal
The compiler has mode switches to enable the use of Objective-C-related constructs. To enabled Objective-C 1.0 language features, either add {$modeswitch objectivec1} to a source file, or using the -Mobjectivec1 command line switch. Similarly, for Objective-C 2.0 language features use {$modeswitch objectivec2} resp. -Mobjectivec2 (this automatically also enables Objective-C 1.0 features). Note that in the latter case, the resulting program may only work on Mac OS X 10.5 and later. Due to these being mode switches rather than actual syntax modes, they can be used in combination with every general syntax mode (fpc, objfpc, tp, delphi, macpas).
Note that mode switches are reset when the general syntax mode is changed using, e.g., {$mode objfpc}. So if you have such a statement in a unit, compiling it with -Mobjectivec1 will not change anything. You will have to add {$modeswitch objectivec1} in the source code after the {$mode objfpc} in that case.
Whether or not the compiler has support for Objective-C-related constructs can be checked using {$ifdef FPC_HAS_FEATURE_OBJECTIVEC1}. There will be no separate switches when new Objective-C-related features are implemented. Since all this works happens in FPC trunk, simply expect that people are using the latest version available.
Selectors
To declare a selector for a method, use the objcselector() statement. It returns a value of the type objc.SEL. It accepts one parameter, which can either be a constant string representing an Objective-C method name, or a method of an Objective-C class.
<delphi> {$modeswitch objectivec1} var
a: SEL;
begin
a:=objcselector('initiWithWidth:andHeight:'); a:=objcselector('myMethod');
end. </delphi>
Method declaration
In general methods are declared in the same way as in Object Pascal, with the exception that each objcclass method must also have a messaging name specified:
<delphi> NSSomeObject = objcclass(NSObject)
procedure method_(params: Integer); message 'method:'; class procedure classmethod_(para: char); override; // "message 'classmethod:'" not required, compiler will get this from the parent class
end; </delphi>
Every objcclass/objcprotocol/objccategorymethod must be associated with an Objective-C message name, a.k.a. selector. This can happen in two different ways:
- an inherited method, an implemented protocol method or a method reintroduced by a category will automatically use the message name of the inherited/implemented/reintroduced method. If an explicit message name is specified, it must match the message name specified for the corresponding method.
- in other cases, the message keyword must be used, followed by a constant string that contains the selector name
Instance and class methods correspond respectively to methods preceded with "-" and "+" in Objective-C. Furthermore, every method is by definition virtual in Objective-C/Pascal.
Note: it's common for Objective-C to start method names with a lower case character.
Class declaration
Regular declaration/definition
To declare an Objective-C class, use the keyword objcclass. Objective-C classes must be declared like regular Object Pascal types in a type block: <delphi> type
ObjCClassName = objcclass [external [name 'ExternalClassName']] [(ObjCSuperClassName|ProtocolName [, ProtocolName, ProtocolName])] [private, protected, public] [variables declarations] [method declarations]
end; </delphi>
- objcclass: defines an Objective-C class type declaration
- external: many Objective-C classes are implemented in external frameworks, such as Cocoa. The external modifiers enables you to declare such external classes for use in Pascal code. It is possible to specify a different external name for the class than the identifier used in Pascal code, because the duplicate identifier rules in Objective-C are laxer than those of Pascal (e.g., there are both a class and a protocol called NSObject in Cocoa, so one of the two has to be renamed in Pascal)
- ObjCSuperClassName: defines parent (or super) class for the class. If name is not specified, the class defines a new root class. See http://wiki.freepascal.org/FPC_PasCocoa/Differences#No_unique_root_class for more information.
For example: <delphi> // NSView is an external class, declared in some external framework // External classes from Apple's framework // have all fields in private sections NSView = objcclass external (NSResponder) private
_subview : id; // private field
public
function initWithFrame_(rect : NSRect): id; message 'initWithFrame:'; procedure addSubview_(aview: NSView); message 'addSubview:'; procedure setAutoresizingMask_(mask: NSUInteger); message 'setAutoresizingMask:'; procedure setAutoresizesSubviews_(flag: LongBool); message 'setAutoresizesSubviews:'; procedure drawRect_(dirtyRect: NSRect); message 'drawRect:';
end;
// MyView is a locally implemented class // that should be declared in the local unit // * drawRect_ method is overriden (the 'message' of the inherited method will be reused) // * customMessage_ is a newly added method // * data is a newly added field MyView = objcclass(MSView) public
data : Integer; procedure customMessage_(dirtyRect: NSRect); message 'customMessage'; procedure drawRect_(dirtyRect: NSRect); override;
end;
... procedure MyView.customMessage_(dirtyRect: NSRect); begin end;
procedure MyView.drawRect_(dirtyRect: NSRect); begin end;
</delphi>
Formal declaration
In Objective-C, a class can also be declared formally using the @class SomeClass syntax. This is called a forward declaration in Objective-C, but because this term has a different meaning in Pascal, we have termed this a formal declaration in Objective-Pascal.
A formal declaration of an objcclass instructs the compiler to assume that somewhere, there is a complete definition of this objcclass (including fields, methods, etc), but that it is not in this unit. The practical upshot is that it is possible to use such a formal objcclass type for parameter types, field types, function result types and variable types, but that it is not possible to send any messages to such instances, nor to access any fields (since the compiler does not know the full definition).
The compiler will automatically resolve any uses of formal objcclasses to the real declaration when this real declaration is also in scope. Hence, as soon as the unit containing the real declaration is added to the uses clause, the aforementioned restrictions no longer apply and the compiler will treat the occurrences of the formal objcclass type the same as the actual type.
To declare a formal objcclass, use the following syntax:
<delphi> type
// note: // * no inheritance is specified // * the external name (if any) must match the external name of any real definition // to which this formal definition resolves later on MyExternalClass = objcclass external;
</delphi>
Example: <delphi> unit ContainerClass;
{$mode objfpc} {$modeswitch objectivec1}
interface
type
// Formal declaration of MyItemClass, which is defined in another unit MyItemClass = objcclass external;
MyContainerClass = objcclass private item: MyItemClass; public function getItem: MyItemClass; message 'getItem'; end;
implementation
function MyContainerClass.getItem: MyItemClass; begin // we cannot send any message to item, but // we can use it in assignments result:=item; end;
end.
// ====================== // ======================
unit ItemClass;
{$mode objfpc} {$modeswitch objectivec1}
interface
type
// the actual definition of MyItemClass MyItemClass = objcclass(NSObject) private content : longint; public function initWithContent(c: longint): MyItemClass; message 'initWithContent:'; function getContent: longint; message 'getContent'; end;
implementation
function MyItemClass.initWithContent(c: longint): MyItemClass; begin content:=c; result:=self; end;
function MyItemClass.getContent: longint; begin result:=content; end;
end.
// ====================== // ======================
Program test;
{$mode objfpc} {$modeswitch objectivec1}
// regardless of the order of the units in the uses-clause, as soon as // ItemClass is used then the full declaration of MyItemClass is in // scope and all uses of the formal declaration are replaced by the // actual declaration uses
ItemClass, ContainerClass;
var
c: MyContainerClass; l: longint;
begin
c:=MyContainerClass.alloc.init; ... // even though MyContainerClass.getItem returns a "formal" MyItemClass, // the fact that the real declaration is in scope means that it can be // used as a regular MyItemClass instance l:=c.getItem.getContent;
end. </delphi>
Protocol declaration
An Objective-C Protocol is pretty much the same as an interface in Object Pascal: it lists a number of methods to be implemented by classes that conform to the protocol. The only difference is that protocol methods can be optional. Moreover, unlike in Object Pascal, a protocol can inherit from multiple other protocols:
<delphi> type
ObjCProtocolName = objcprotocol [external [name 'ExternalClassName']] [(ObjCParentProtocol1[, ObjCParentProtocol2, ...])] [required, optional] [method declarations]
end; </delphi>
- objcprotocol: defines an Objective-C protocol type declaration
- external: same as with objcclass
- ObjCParentProtocols: the protocols this protocol inherits from (so a class conforming to this protocol, will also have to implement all methods from those other protocols)
- required, optional: If nothing is specified, the default is required. optional methods do not have to be implemented by classes conforming to this protocol.
Example: <delphi> type
MyProtocol = objccprotocol // default is required procedure aRequiredMethod; message 'aRequiredMethod'; optional procedure anOptionalMethodWithPara_(para: longint); message 'anOptionalMethodWithPara:'; procedure anotherOptionalMethod; message 'anotherOptionalMethod'; required function aSecondRequiredMethod: longint; message 'aSecondRequiredMethod'; end;
MyClassImplementingProtocol = objcclass(NSObject,MyProtocol) // the "message 'xxx'" modifiers can be repeated, but this is not required procedure aRequiredMethod; procedure anOptionalMethodWithPara_(para: longint); function aSecondRequiredMethod: longint; end;
</delphi>
Similar to objcclasses, objcprotocols can be external in case they are implemented in e.g. the Cocoa framework.
Category declaration
An Objective-C category is similar to a class helper in Object Pascal (class helpers are not yet supported by FPC, but categories are supported): it allows adding methods to an existing class, without inheriting from that class. In Objective-C, this goes even one step further: a category can also replace existing methods in another class. Since all methods are virtual in Objective-C, this also means that this method changes for all classes that inherit from the class in which the method was replaced (unless they override it). An Objective-C category can also implement protocols.
Note that a category cannot extend another category. Furthermore, if two categories add/replace a method with the same selector in the same class, it is undefined which method will actually be called at run time. This would depend on the order in which the categories are registered with the Objective-C run time.
<delphi> type
ObjCCategoryName = objccategory [external [name 'ExternalCategoryName']] (ExtendedObjCClass[,ObjCProtocol1, ObjCProtocol2, ...]) [method declarations]
end; </delphi>
- objccategory: defines an Objective-C category type declaration
- external: same as with objcclass. As of Objective-C 2.0, the name of a category is optional. Not yet implemented: to declare an external category without a name, use an empty string.
- method declarations: the methods that this category adds or replaces in the extended class. These can be both class and instance methods. Note that when replacing a method in the extended class, you must mark this new method as reintroduce. The reason that we do not use override here, is that the method is really replaced in the original class. This means, e.g., that if you call inherited from the new method, that you will call this method in the parent class of the extended class, and not the replaced method. The replaced method is "lost".
- ObjCParentProtocols: the protocols this category implements. The category will have to implement all required methods from these protocols.
Example: <delphi> type
MyProtocol = objcprotocol procedure protocolmethod; message 'protocolmethod'; end;
MyCategory = objccategory(NSObject,MyProtocol) // method replaced in NSObject. This means that every objcclass that does not // override the hash function will now use this method instead. function hash: cuint; reintroduce;
// we have to implement this method, since we declare that we implement the // MyProtocol protocol. This method is added to NSObject afterwards. procedure protocolmethod;
// a random class methods added to NSObject class procedure newmethod; message 'newmethod'; end;
</delphi>
Similar to objcclasses, objccategorys can be external in case they are implemented in e.g. the Cocoa framework.
The id type
The id type is special in Objective-C/Pascal. It is assignment-compatible with instances of every objcclass and objcprotocol type, in two directions. This means that you can assign variables of any objcclass/objcprotocol type to a variable of the type id, but also that you can assign these id variables to variables of any particular objcclass/objcprotocol type. In neither case an explicit typecast is required.
Furthermore, it is possible to call any Objective-C method declared in an objcclass or objccategory that's in scope using an id-typed variable. If, at run time, the objcclass instance stored in the id-typed variable does not respond to the sent message, the program will terminate with a run time error.
When there are multiple methods with the same Pascal identifier, the compiler will use the standard overload resolution logic to pick the most appropriate method. In this process, it will behave as if all objcclass/objccategory methods in scope have been declared as global procedures/functions with the overload specifier. If the compiler cannot determine which overloaded method to call, it will print an error. If you compile with -vh, it will print a list of all methods that could be used to implement the call when it cannot determine the appropriate overloaded method. In this case, you will have to use an explicit type cast to clarify the class type from which you wish to call a method;
Fast enumeration
Fast enumeration is a convention that enables enumerating the elements in a Cocoa container class in a generic yet fast way. The syntax used for this feature is the for-in construct, both in Objective-C and in Objective-Pascal. This feature behaves identically in both languages. It requires the Objective-C 2.0 mode switch to be activated.
Example: <delphi> {$mode delphi} {$modeswitch objectivec2}
uses
CocoaAll;
var
arr: NSMutableArray; element: NSString; pool: NSAutoreleasePool; i: longint;
begin
pool:=NSAutoreleasePool.alloc.init; arr:=NSMutableArray.arrayWithObjects( NSSTR('One'), NSSTR('Two'), NSSTR('Three'), NSSTR('Four'), NSSTR('Five'), NSSTR('Six'), NSSTR('Seven'), nil);
i:=0; for element in arr do begin inc(i); if i=2 then continue; if i=5 then break; if i in [2,5..10] then halt(1); NSLog(NSSTR('element: %@'),element); end; pool.release;
end. </delphi>
Syntax (proposed)
todo:
a) how to declare a constant NSString/CFString in Pascal (the @"somestring" from Objective-C)
-- the dollar sign could work... ex. $theString
ObjectiveP Examples
Please note, for the menu to be created application must be located in the application Bundle. Lazarus IDE may create the bundle for you.
program project1; {$mode objfpc}{$H+} {$modeswitch objectivec1} uses CocoaAll; type { MyCallback } MyCallback = objcclass(NSObject) public procedure haltMenuEvent(sender: id); message 'haltMenuEvent:'; end; var pool : NSAutoreleasePool; filemenu : NSMenu; subitem : NSMenuItem; app : NSApplication; callback : MyCallback; function NSStr(const s: AnsiString): NSString; begin if s= then Result:=NSString.alloc.init else Result:=NSString.alloc.initWithCString(@s[1]); end; { MyCallback } procedure MyCallback.haltMenuEvent(sender:id); begin writelN('HALT!'); app.stop(self); end; procedure initMenus; var root : NSMenu; rootitem : NSMenuItem; begin root:=NSMenu.alloc.init; rootitem:=root.addItemWithTitle_action_keyEquivalent(NSStr(), nil, NSStr()); filemenu:=NSMenu.alloc.initWithTitle(NSStr('File')); rootitem.setSubmenu(filemenu); rootitem.setEnabled(true); subitem:=filemenu.addItemWithTitle_action_keyEquivalent(NSStr('Halt'), nil, NSStr()); subitem.setEnabled(true); app.setMainMenu(root); end; procedure InitCallback; begin callback:=MyCallback.alloc; subitem.setAction( ObjCSelector( callback.haltMenuEvent) ); subitem.setTarget( callback); end; begin pool := NSAutoreleasePool.alloc.init; app:=NSApplication.sharedApplication; initMenus; initCallback; app.run; pool.release; end.