FSClass is a 'meta-class' that allows new classes to be created directly in F-Script programs, using F-Script code to implement methods, without adding any new keywords or syntax to the language. Classes created in this manner are full-fledged Cocoa classes, and instances of them can be used like any other Cocoa object. The 3.0 release of FSClass features numerous new bug fixes, a completely redesigned internal architecture, better handling of Objective-C categories, and compatibility with OS X 10.5 Leopard and F-Script 2.0.
Download FSClass 2.0 (for F-Script 1.x and Mac OS X 10.4 only)
Sections:
FSClass is a "meta-class" that allows programmers to create new classes directly in F-Script, instead of having to write them in Objective-C. Methods are implemented with F-Script Blocks, and data is stored as Cocoa-compliant Key-Value properties.
FSClass creates 100% real Cocoa classes; if you use FSClass to create a class 'Foobar', then Foobar will be a fully legitimate class like NSObject or NSString. Method calling is very efficient: in most cases, the FSClass framework inserts only a handful of instructions between the invocation of the method in F-Script and the execution of the Block that implements the method.
To create a class in F-Script, we first create a new "class object", then add properties and methods directly to it. Individual objects are created by asking the class object for a new instance. No additional syntax or keywords are necessary, so the core F-Script language is unaffected and classes can be created programmatically. This approach will be familiar to anyone who has created classes in JavaScript using prototyping:
| JavaScript | F-Script |
// Declare the class and create a constructor
thisradius = r;
;
|
"Create the class"
Circle := FSClass newClass:'Circle'.
|
// accessor methods for radius -
// not strictly necessary
thisradius = r;
;
return thisradius;
;
|
"add radius property - automatically generates
accessor methods"
Circle addProperty:'radius'.
|
|
"Create a class factory method"
Circle onClassMessage:#circleWithRadius:
do:.
|
|
"Declare a constant"
Circle addClassProperty:'pi' withValue:3.14159.
|
// Geometry methods
return MathPI * thisradius * thisradius;
;
return 2 * Mathpi * thisradius;
;
|
"Geometry methods"
Circle onMessage:#area do:.
Circle onMessage:#circumference do:.
|
// class method - compare circles based on size
return circleAcompareTocircleB;
;
// instance comparison method
if thisradius==circleradius
return 0;
else if thisradius>circleradius
return -1;
else
return 1;
|
"Comparison Method"
Circle onMessage:#compare: do:.
|
// create and use a Circle
circle1 = 5.0;
circle1setRadius4.0;
alert"Circle area is : " + circle1area;
circle2 = 5.0;
if Circlecomparecircle1circle2 > 0
alert"circle2 is bigger than circle1";
else
alert"circle2 is bigger than circle1";
|
"create and use a Circle"
circle1 := Circle circleWithRadius:5.0.
circle1 setRadius:4.0.
sys log:'Circle area is: ' ++ (circle1 area description).
circle2 := Circle circleWithRadius:5.0.
((circle1 compare:circle2) = NSOrderedAscending) ifTrue:
ifFalse:.
|
Note the following differences:
circle = new Circle(); circle.radius = 5;. But in F-Script, we have to use methods, which addProperty: creates for us.this is a special keyword in JavaScript, but in F-Script, self is explicitly declared as the first parameter, even though it does not have a slot (i.e. a semicolon) in the message name. Explicit "self" parameters are used the same way in Perl and Python.instance.pi as a class property of Circle, which lets us work around Objective-C's lack of namespace support.#doMethod:withArg:) to specify the method's selector.Here is an example of how to add methods to a class with FSClass:
Greeter := FSClass newClass:'Greeter'.
Greeter addProperty:'defaultGreeting'.
"factory creation method. sets a default greeting"
Greeter onClassMessage:#greeter do:.
Greeter onMessage:#sayHello do:.
Greeter onMessage:#sayDefaultGreeting do:.
Greeter onMessage:#sayGreeting: do:.
"Create a Greeter by using the class factory method"
myGreeter := Greeter greeter.
"Print 'Hello!'"
myGreeter sayHello.
"Print 'Hi there!'"
myGreeter sayDefaultGreeting.
"Print 'Bonjour!'"
myGreeter sayGreeting:'Bonjour!'.
"Save default greeting and print 'Good Morning!'"
defaultGreeting := myGreeter defaultGreeting.
myGreeter setDefaultGreeting:'Good Morning!'.
myGreeter sayDefaultGreeting.
"Print 'Hi there!'"
myGreeter sayGreeting:defaultGreeting.
Blocks supplied as method implementations must take :self as an explicit first parameter, in the same way that methods are written in Perl and Python. In the case of class methods, :self will be the class object, just as in Objective-C. All following parameters should correspond to the slots for arguments in the selector. In the above example, the sayGreeting: message only has a slot for one argument (as it has only one colon), but the block supplied for it must take two parameters. self is only the customary name for the receiver parameter; you may use any name you like.
The method onMessageName:do: can be used when you have a string that represents a selector's name. This method lets you defer not only the implementation, but the actual name of the method until runtime. The section Class Names and Anonymous Classes shows how this method can be used to create custom classes at runtime.
Instance data for F-Script objects are always hidden behind accessor/mutator methods. You create a property by adding it to an existing class object. Instances can then use the Cocoa-style methods propertyName and setPropertyName: to access it:
NewClass := FSClass newClass.
NewClass addProperty:'foobar'.
"Use array messaging to add multiple properties at once"
NewClass addProperty: @ { 'propA', 'propB', 'propC' }.
inst := NewClass alloc init.
inst setFoobar:5.
"Logs '5' to the console"
sys log:(inst foobar).
Objective-C 2.0 uses the term "property" to refer to an instance variable declared in a specific way, with special attributes and a new access syntax. Properties created in F-Script are different; creating a property for an F-Script class only allocates space in the object and creates the accessor/mutator method pair. There is no special syntax, and none of the Objective-C 2.0 attributes (such as copy or readonly) can be applied.
The methods addProperty:withDefault:, addPropertiesWithDefaults:, and addPropertiesFromDictionary: allow you to specify default values that will be automatically filled in by alloc. These default values must be immutable, as they will initially be shared between all instances of a class (and subclasses) until they are replaced. For more information, see the FSClass reference.
As with most scripting languages, there is no support for access control: all properties are public, which may be a problem when you want to have greater control over the behavior of method accessors and mutators, such as the readonly and copy attributes available on Objective-C 2.0 properties. In this case, you can create a 'private' instance property (typically prefixed with an underscore), and then create a pair of publicProperty / setPublicProperty: methods that access the 'private' property indirectly. The following example shows how to create a property that performs filtering in the set method. This design pattern has the added advantage that publicProp will work properly with Key-Value Coding:
PrivatePropertyClass := FSClass newClass:'PrivatePropertyClass'.
"Add a 'private' property that isn't part of the published interface"
PrivatePropertyClass addProperty:'_privateProperty'.
PrivatePropertyClass addProperty:'_copiedProperty'.
PrivatePropertyClass addProperty:'_readOnlyProperty'.
"Add a pair of accessor/mutator methods that perform validation"
"Acts like the @dynamic directive in Objective-C 2.0"
PrivatePropertyClass onMessage:#publicProperty do:.
PrivatePropertyClass onMessage:#setPublicProperty: do:.
"This accessor-mutator pair will copy the value passed to the mutator"
"Acts like the (copy) attribute in Objective-C 2.0"
PrivatePropertyClass onMessage:#otherProperty do:.
PrivatePropertyClass onMessage:#setOtherProperty: do:.
"This logical property does not have a mutator"
"Acts like the (readonly) attribute in Objective-C 2.0)"
PrivatePropertyClass onMessage:#readOnlyProperty do:.
When you create a class in Objective-C, the instance variables are packed into a C-like structure that makes property access very fast. In 64-bit applications, the property access is slightly slower, but has the advantage that adding ivars to parent classes will not break child classes.
Objects of FSClass-created classes are different, in that they store all of their properties in a dictionary, similar to objects in other scripting languages like Perl and Ruby. This makes them much more flexible, as you can add properties to a class after it has been created. However, it introduces significant overhead to accessing the properties: every call to myObj foo requires the FSClass runtime to convert the message name to a string, look up that key in an NSDictionary, and possibly convert the value before returning it.
FSClass 2.1 introduced an optional solution to this problem with 'fast-ivar' classes. If you know the names of all instance properties at the time of class creation, you can specify them in the call to newClass:
FastIvarClass := FSClass newClass:'FastIvarClass' properties:{'propA', 'propB', 'propC'}.
myFastInstance := FastIvarClass alloc init.
myFastInstance setPropA:5.
Instances of FastIvarClass will have their member variables stored directly inside the object, just like Objective-C objects. The accessor methods for these properties will look directly in the object for the values; this reduces the access and mutation overhead to a fraction of their normal costs. Otherwise, instances of FastIvarClass will work exactly as instances of regular FSClasses.
Default values can be added after class creation using the method setDefaultValue:forProperty:. Alternately, property names and defaults can be specified at class creation by passing a dictionary to the method FSClass newClass:name propertiesWithDefaults:propsDictionary. Attempting to add properties to a fast-ivar class with the methods -addProperty: or -addProperty:withDefault: will throw exceptions.
Using fast-ivar classes has an additional debugging benefit: fast ivars will show up in tools that list member variables, like object browsers or XCode's gdb interface.
Regular and fast-ivar classes can be mixed through inheritance: if a fast-ivar class inherits from a regular class, the parent class's properties will stay in the dictionary, but the child's properties will be accessed directly.
FSClass properties are Key-Value Coding compliant, so they can also be accessed with the indirect KVC methods:
KVTestClassA := FSClass newClass:'KVTestClassA'.
KVTestClassA addProperty:'propA'.
KVTestClassB := FSClass newClass:'KVTestClassB'.
KVTestClassB addProperty:'propB'.
kvInstance := KVTestClassA alloc init.
"These two lines are equivalent:"
kvInstance setPropA:5.
kvInstance setValue:5 forKey:'propA'.
"These two lines are equivalent:"
sys log:(kvInstance propA).
sys log:(kvInstance valueForKey:'propA').
"We can also do key paths"
subValue := KVTestClassB alloc init.
kvInstance setPropA:subValue.
"These two lines are equivalent:"
kvInstance propA setPropB:10.
kvInstance setValue:10 forKeyPath:'propA.propB'.
"These two lines are equivalent:"
sys log:(kvInstance propA propB).
sys log:(kvInstance valueForKeyPath:'propA.propB').
"This line will result in a runtime violation because the method is not defined:"
onException:.
"This line will result in a call to valueForUndefinedKey:,
which will then throw an NSUnknownKeyException"
onException:.
To maintain Key-Value Coding compliance, property names should follow the KVC guidelines. Other forms of key-value codings - such as one-to many relationships - can be implemented by defining the proper methods, as in Objective-C. See Apple's Key-Value Coding Programming Guide for more details.
As shown above, once a new class has been created, instances should be created by calling factory methods:
ExampleClass := FSClass newClass:'MyClass'.
"add properties and methods to ExampleClass"
"..."
ExampleClass onClassMessage:#newExample do:.
"Create a new instance"
myInstance := ExampleClass newExample.
To create a new object, use the standard Cocoa methods alloc init. FSClass 3.0 uses the garbage collection system introduced in Leopard and supported by F-Script 2.0, so autorelease is no longer necessary.
If you are implementing a subclass, instantiation becomes a little bit more complicated; see the sections Inheritance and Calling Superclass Methods for more information.
Classes can have multiple factory methods that use some default and some specified property values. Here are example default and specific factories written in F-Script:
ExampleClass := FSClass newClass:'ExampleClass'.
"Set the default name and property as class data"
ExampleClass addClassProperty:'defaultName' withValue:'default name'.
ExampleClass addClassProperty:'defaultPropertyOne' withValue:5.
"Instance variables"
ExampleClass addProperty:'propertyOne'.
ExampleClass addProperty:'name'.
"Default factory method