Prototypes in Objective-C
Updated 1 year, 8 months ago
| David Chisnall | Reviewers | ||
| EtoileCore | |||
| None | Etoile trunk (etoile/trunk/Etoile) | ||
Implemented Lieberman prototypes using hidden class transforms, removing the need for a modified runtime. Hidden classes are reference counted, and are free'd when all of the objects that have them in their class hierarchy are dealloc'd. I propose removing ETPrototype (which no one is using, because it depends on a libobjc that only I have installed) in favour of this, which allows any arbitrary object to be used as a prototype. The test program shows two methods being added to a simple NSObject subclass (NSObject could be used, but the subclass implements copyWithZone: allowing cloning to work). These methods simply set and return a specific key on self, showing that both slots and methods can work. I intend to also add a trampoline allowing blocks to be used as methods, so Smalltalk users can enjoy fun with prototypes too.
Running the following program gives this output:
$ ./obj/test
2008-12-16 16:51:27.762 test[18935] Test value: A string
2008-12-16 16:51:27.778 test[18935] Extra ivars destroyed
Test program:
#import <Foundation/Foundation.h>
#import "ETPrototype.h"
// Two free-standing methods which will be added to an object for testing.
id setValueIMP(id self, SEL _cmd, id aValue)
{
[self setValue:aValue forKey:@"TestKey"];
return self;
}
id getValueIMP(id self, SEL _cmd)
{
return [self valueForKey:@"TestKey"];
}
// Protocol added to remove warnings when invoking the methods, and to set type
// info for them.
@interface MyPrototype
- (id) testValue;
- (id) setTestValue:(id)aValue;
@end
@interface MyObject : NSObject {}
@end
@implementation MyObject
// Implement copying, to ensure make sure that the class is not freed
// prematurely
- (id) copyWithZone:(NSZone*)z
{
return [isa allocWithZone:z];
}
@end
@interface MyObject2 : NSObject {}
@end
@implementation MyObject2
- (void) dealloc
{
NSLog(@"Extra ivars destroyed");
[super dealloc];
}
@end
int main(void)
{
id pool = [NSAutoreleasePool new];
id proto = [[MyObject new] autorelease];
[proto setMethod:(IMP)setValueIMP forSelector:@selector(setTestValue:)];
[proto setMethod:(IMP)getValueIMP forSelector:@selector(testValue)];
[proto setTestValue:@"A string"];
proto = [proto copy];
[pool release];
pool = [NSAutoreleasePool new];
NSLog(@"Test value: %@", [proto testValue]);
[proto setTestValue:[[MyObject2 new] autorelease]];
[proto release];
[pool release];
return 0;
}
Posted 1 year, 8 months ago (December 16th, 2008, 12:48 p.m.)
-
Frameworks/EtoileFoundation/Source/NSObject+Prototypes.m (Diff revision 1) -
Should be #import "NSObject+Prototypes.h"
-
Frameworks/EtoileFoundation/Source/NSObject+Prototypes.m (Diff revision 1) -
Redefining the structure could be avoided by making it a pointer and allocate it from load(). I don't know if you think that's worth it (probably not since you chose to redefine the struct).
-
Frameworks/EtoileFoundation/Source/NSObject+Prototypes.m (Diff revision 1) -
It looks an awful lot like this is going to free when refCount hits 1 and not 0. What am I missing here?
-
Frameworks/EtoileFoundation/Source/NSObject+Prototypes.m (Diff revision 1) -
Should there be a way to remove the NULL_OBJECT_PLACEHOLDER? Or is it a good idea to make it a one way street like this? Once you store nil in a slot, you cannot go back to inherit the value.
-
Frameworks/EtoileFoundation/Source/NSObject+Prototypes.m (Diff revision 1) -
Could also be written as; for (Class cls = class ; cls != Nil ; cls = cls->super_class) { if (CLS_ISHIDDEN(cls)) -
Frameworks/EtoileFoundation/Source/NSObject+Prototypes.m (Diff revision 1) -
Any reason not to use HiddenClass as return type here?
-
Frameworks/EtoileFoundation/Source/NSObject+Prototypes.m (Diff revision 1) -
I think you can avoid the need for a cloneWithZone: method. In the objc_hidden_class struct you could add; id owner; Then in hiddenClassTransform() extend the criteria and let the function that makes hidden classes know the owner; if (CLS_ISHIDDEN(obj->class_pointer) && ((HiddenClass)obj->class_pointer)->owner == obj) { return; } obj->class_pointer = (Class)hiddenClassForObject(obj); This way, setMethod:forSelector: will subclass whenever you add methods to an object that does not already have it's own hidden class. No need to distinguish copy and clone. Also, you could inline either hiddenClassForObject() in hiddenClassTransform() or hiddenClassTransform() in setMethod:forSelector:.