Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added dynamic properties

  • Loading branch information...
commit 8fdb2a4478c012949ab8eafcaaf510c1d44e5af7 1 parent f7b2092
Kevin Malakoff authored
View
58 SubjectiveScript/JavaScript/NSMutableDictionary+JavaScript.m
@@ -28,6 +28,10 @@
//
#import "NSMutableDictionary+JavaScript.h"
+#import "NSString+SS.h"
+#import <objc/runtime.h>
+
+static const char* SSNamedPropertiesKey = "SSNP";
@implementation NSMutableDictionary (JavaScript)
@@ -39,4 +43,58 @@ @implementation NSMutableDictionary (JavaScript)
};
}
+// dynamic property lookup
++ (BOOL)resolveInstanceMethod:(SEL)selector
+{
+ NSS* name = [NSS stringWithUTF8String:sel_getName(selector)];
+ BOOL isSetter = name.startsWith(@"set");
+
+ // setter
+ if (isSetter)
+ {
+ unichar firstLetter = [name characterAtIndex:3];
+ name = [NSS stringWithFormat:@"%@%@",[[NSS stringWithCharacters:&firstLetter length:1] lowercaseString], [name substringWithRange:NSMakeRange(4, name.length-5)]];
+ }
+
+ objc_property_t propertyInfo = class_getProperty(self.class, name.UTF8String);
+ if (!propertyInfo) return [super resolveInstanceMethod:selector]; // not a property
+
+ // signature checker for getters and setters
+ NSRange colonsRange = [name rangeOfString:@":"];
+ if (
+ name.startsWith(@"_") || // internal
+ (isSetter && name.endsWith(@":") && (colonsRange.location!=name.length-1)) || // not quite a setter
+ (!isSetter && (colonsRange.length!=0)) // not a getter
+ ) {
+ return [super resolveInstanceMethod:selector];
+ }
+
+ // find the class that declared the property
+ Class owningClass = self.class;
+ Class superClass = class_getSuperclass(owningClass);
+ while(superClass && class_getProperty(superClass, name.UTF8String)==propertyInfo) {
+ owningClass = superClass; superClass = class_getSuperclass(owningClass);
+ }
+
+ O* dynamicProperties = objc_getAssociatedObject(self, SSNamedPropertiesKey);
+ if (!dynamicProperties) {
+ dynamicProperties = O.new;
+ objc_setAssociatedObject(self, SSNamedPropertiesKey, dynamicProperties, OBJC_ASSOCIATION_RETAIN);
+ }
+
+ // collection information about the property
+ IMP implementation;
+ if (isSetter)
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj, id value) {
+ [dynamicProperties setValue:value forKey:name];
+ }));
+ else
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj) {
+ return [dynamicProperties valueForKey:name];
+ }));
+
+ class_addMethod(owningClass, selector, implementation, property_getAttributes(propertyInfo));
+ return YES;
+}
+
@end
View
1  SubjectiveScript/Types/NSString+SS.h
@@ -36,6 +36,7 @@
+ (NSS*(^)(NSS* format, ...))newFormatted;
- (S*(^)())toMutable;
+- (UI)count;
- (NSS*(^)(UI index))getAt;
View
1  SubjectiveScript/Types/NSString+SS.m
@@ -63,6 +63,7 @@ @implementation NSString (SS)
}
- (S*(^)())toMutable { return ^{ return self.mutableCopy; }; }
+- (UI)count { return self.length; }
- (NSS*(^)(UI index))getAt
{
View
22 SubjectiveScriptTests/Types/Object+SSTests.m
@@ -7,7 +7,29 @@
//
#import "Object+SSTests.h"
+#import "SubjectiveScript.h"
+#import "QUnit.h"
+
+// TODO: document this
+@interface Test : O
+ @property (copy) NSS* name;
+ @property (copy) Date* createdAt;
+@end
+@implementation Test : O
+ @dynamic name, createdAt;
+@end
@implementation Object_SSTests
+- (void)test_OBJECT
+{
+ Test* test = Test.new;
+
+ test.name = @"bob";
+ test.createdAt = Date.new;
+
+ equal(test.name, @"bob", @"bob's my name and don't wear it out");
+ ok(test.createdAt.instanceof(Date.class), @"it's a date!");
+}
+
@end

0 comments on commit 8fdb2a4

Please sign in to comment.
Something went wrong with that request. Please try again.