Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Updated named properties.

  • Loading branch information...
commit 076b88c98f3b9959828758d870ba41d31e011d0d 1 parent 8fdb2a4
Kevin Malakoff authored
View
18 README.md
@@ -263,6 +263,24 @@ SS.setTimeout(^{ called2 = true; }, NSEC_PER_SEC*2);
// called
```
+Just One More Thing
+---------------
+
+You can also easily add named properties on your own objects:
+
+```
+@interface MyObject : O
+ @property (strong) NSS* name;
+@end
+@implementation MyObject : O
+ @dynamic name;
+@end
+
+MyObject* obj = MyObject.new;
+obj.name = @"Steve";
+// they call me Steve
+```
+
Want to Add Something?
---------------
View
58 SubjectiveScript/JavaScript/NSMutableDictionary+JavaScript.m
@@ -28,10 +28,6 @@
//
#import "NSMutableDictionary+JavaScript.h"
-#import "NSString+SS.h"
-#import <objc/runtime.h>
-
-static const char* SSNamedPropertiesKey = "SSNP";
@implementation NSMutableDictionary (JavaScript)
@@ -43,58 +39,4 @@ @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
123 SubjectiveScript/Types/NSMutableDictionary+SS.m
@@ -28,6 +28,11 @@
//
#import "NSMutableDictionary+SS.h"
+#import "NSString+SS.h"
+#import "NSNumber+SS.h"
+#import <objc/runtime.h>
+
+static const char* SSNamedPropertiesKey = "SSNP";
@implementation NSMutableDictionary (Object)
@@ -73,4 +78,122 @@ @implementation NSMutableDictionary (Object)
};
}
+// Thank you to Jens Alfke for his CouchDynamicObject to show me that dynamic properties were possible: https://github.com/couchbaselabs/CouchCocoa/blob/master/Model/CouchDynamicObject.m
++ (BOOL)resolveInstanceMethod:(SEL)selector
+{
+ NSS* name = [NSS stringWithUTF8String:sel_getName(selector)];
+ BOOL isSetter = name.startsWith(@"set");
+
+ // 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];
+ }
+
+ // 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
+
+ // 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);
+ }
+
+ // look for a mismatch or invalid type
+ const char* attrs = property_getAttributes(propertyInfo);
+ NSA* typeComponents = [[S stringWithUTF8String:attrs] componentsSeparatedByString:@","];
+ for (NSS* component in typeComponents) {
+ // read only mismatch
+ if (([component characterAtIndex:0] == 'R') && isSetter) {
+#ifdef DEBUG
+ @throw [NSException exceptionWithName:@"TypeError" reason:@"SubjectiveScript: Setter is read-only for property '%@' on '%@'." userInfo:nil];
+#else
+ NSLog(@"SubjectiveScript: Setter is read-only for property '%@' on '%@'. Skipping.", name, NSStringFromClass(owningClass));
+ return NO;
+#endif
+ }
+ }
+
+ // create an NSMutableDictionary to hold the properties
+ O* dynamicProperties = objc_getAssociatedObject(self, SSNamedPropertiesKey);
+ if (!dynamicProperties) {
+ dynamicProperties = O.new;
+ objc_setAssociatedObject(self, SSNamedPropertiesKey, dynamicProperties, OBJC_ASSOCIATION_RETAIN);
+ }
+
+ // create a custom setter or getter based on the type of property
+ IMP implementation;
+ if (isSetter) {
+ if (attrs[1]==_C_ID)
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj, id value) {
+ [dynamicProperties setValue:value forKey:name];
+ }));
+ else if ((attrs[1]==_C_CHR) || (attrs[1]==_C_UCHR) || (attrs[1]==_C_USHT) || (attrs[1]==_C_INT) || (attrs[1]==_C_UINT) || (attrs[1]==_C_LNG) || (attrs[1]==_C_ULNG))
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj, I value) {
+ [dynamicProperties setValue:N.I(value) forKey:name];
+ }));
+ else if(attrs[1]==_C_FLT)
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj, F value) {
+ [dynamicProperties setValue:N.F(value) forKey:name];
+ }));
+ else if(attrs[1]==_C_DBL)
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj, D value) {
+ [dynamicProperties setValue:N.D(value) forKey:name];
+ }));
+ else if(attrs[1]==_C_BOOL)
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj, bool value) {
+ [dynamicProperties setValue:N.B(value) forKey:name];
+ }));
+ else {
+ NSLog(@"SubjectiveScript: Unsupported type encountered for property '%@' on '%@'. Skipping.", name, NSStringFromClass(owningClass));
+ return NO;
+ }
+ }
+ else {
+ if (attrs[1]==_C_ID)
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj) {
+ return [dynamicProperties valueForKey:name];
+ }));
+ else if ((attrs[1]==_C_CHR) || (attrs[1]==_C_UCHR) || (attrs[1]==_C_USHT) || (attrs[1]==_C_INT) || (attrs[1]==_C_UINT) || (attrs[1]==_C_LNG) || (attrs[1]==_C_ULNG))
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj) {
+ N* value = [dynamicProperties valueForKey:name];
+ return value ? value.I : 0;
+ }));
+ else if(attrs[1]==_C_FLT)
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj) {
+ N* value = [dynamicProperties valueForKey:name];
+ return value ? value.F : 0;
+ }));
+ else if(attrs[1]==_C_DBL)
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj) {
+ N* value = [dynamicProperties valueForKey:name];
+ return value ? value.D : 0;
+ }));
+ else if(attrs[1]==_C_BOOL)
+ implementation = imp_implementationWithBlock((void*)CFBridgingRetain(^(O* obj) {
+ N* value = [dynamicProperties valueForKey:name];
+ return value ? value.B : false;
+ }));
+ else {
+ NSLog(@"SubjectiveScript: Unsupported type encountered for property '%@' on '%@'. Skipping.", name, NSStringFromClass(owningClass));
+ return NO;
+ }
+ }
+
+ class_addMethod(owningClass, selector, implementation, attrs);
+ return YES;
+}
+
@end
View
75 SubjectiveScriptTests/Types/Object+SSTests.m
@@ -10,26 +10,83 @@
#import "SubjectiveScript.h"
#import "QUnit.h"
-// TODO: document this
@interface Test : O
- @property (copy) NSS* name;
- @property (copy) Date* createdAt;
+ @property (assign) I valueI;
+ @property (assign) UI valueUI;
+ @property (assign) F valueF;
+ @property (assign) D valueD;
+ @property (assign) B valueB;
+ @property (assign) bool valueBool;
+ @property (strong) NSS* valueNSS;
+ @property (strong) S* valueS;
+ @property (strong) NSA* valueNSA;
+ @property (strong) A* valueA;
+ @property (strong) NSD* valueNSD;
+ @property (strong) O* valueO;
+ @property (strong) Date* valueDate;
@end
@implementation Test : O
- @dynamic name, createdAt;
+ @dynamic valueI, valueUI, valueF, valueD, valueB, valueBool;
+ @dynamic valueNSS, valueS, valueNSA, valueA, valueNSD, valueO, valueDate;
@end
@implementation Object_SSTests
-- (void)test_OBJECT
+- (void)test_namedProperties
{
Test* test = Test.new;
- test.name = @"bob";
- test.createdAt = Date.new;
+ equal(test.valueI, 0, @"valueI: no value set");
+ test.valueI = 1;
+ equal(test.valueI, 1, @"valueI: value was set");
+
+ equal(test.valueUI, 0, @"valueUI: no value set");
+ test.valueUI = 1;
+ equal(test.valueUI, 1, @"valueUI: value was set");
+
+ equal(test.valueF, 0, @"valueF: no value set");
+ test.valueF = 1;
+ equal(test.valueF, 1, @"valueF: value was set");
+
+ equal(test.valueD, 0, @"valueD: no value set");
+ test.valueD = 1;
+ equal(test.valueD, 1, @"valueD: value was set");
+
+ equal(test.valueB, NO, @"valueB: no value set");
+ test.valueB = YES;
+ equal(test.valueB, YES, @"valueB: value was set");
- equal(test.name, @"bob", @"bob's my name and don't wear it out");
- ok(test.createdAt.instanceof(Date.class), @"it's a date!");
+ equal(test.valueBool, false, @"valueBool: no value set");
+ test.valueBool = true;
+ equal(test.valueBool, true, @"valueBool: value was set");
+
+ equal(test.valueNSS, nil, @"valueNSS: no value set");
+ test.valueNSS = @"bob";
+ equal(test.valueNSS, @"bob", @"valueNSS: value was set");
+
+ equal(test.valueS, nil, @"valueS: no value set");
+ test.valueS = S.newS(@"bob");
+ equal(test.valueS, @"bob", @"valueS: value was set");
+
+ equal(test.valueNSA, nil, @"valueNSA: no value set");
+ test.valueNSA = AI(1,2,3).copy;
+ equal(test.valueNSA, AI(1,2,3), @"valueNSA: value was set");
+
+ equal(test.valueA, nil, @"valueA: no value set");
+ test.valueA = AI(1,2,3);
+ equal(test.valueA, AI(1,2,3), @"valueA: value was set");
+
+ equal(test.valueNSD, nil, @"valueNSD: no value set");
+ test.valueNSD = OKV({@"one", N.I(1)}).copy;
+ equal(test.valueNSD, OKV({@"one", N.I(1)}), @"valueNSD: value was set");
+
+ equal(test.valueO, nil, @"valueO: no value set");
+ test.valueO = OKV({@"one", N.I(1)});
+ equal(test.valueO, OKV({@"one", N.I(1)}), @"valueO: value was set");
+
+ equal(test.valueDate, nil, @"valueDate: no value set");
+ test.valueDate = Date.new;
+ ok(test.valueDate.instanceof(Date.class), @"valueDate: value was set");
}
@end
View
5 SubjectiveScriptTests/Vendor/QUnitTestCase.m/QUWrap.h
@@ -11,9 +11,8 @@
@interface QUWrap : NSObject
-+ (id)wrap:(void*)value type:(char[2])type;
++ (id)wrap:(unsigned long long)value type:(char[2])type;
@end
-
-#define QUWrapValue(_value) [QUWrap wrap:(void*)_value type:@encode(__typeof__(_value))]
+#define QUWrapValue(_value) [QUWrap wrap:(unsigned long long)_value type:@encode(__typeof__(_value))]
View
27 SubjectiveScriptTests/Vendor/QUnitTestCase.m/QUWrap.m
@@ -11,34 +11,35 @@
@implementation QUWrap
-+ (id)wrap:(void*)value type:(char[2])type
++ (id)wrap:(unsigned long long)value type:(char[2])type
{
- NSAssert(type[0]!=_C_FLT && type[0]!=_C_DBL, @"QUnit current doesn't support floats and doubles");
-
switch(type[0])
{
case _C_ID:
+ case _C_PTR:
#if __has_feature(objc_arc)
- return (__bridge id)value;
+ return (__bridge id)(void*)value;
#else
return (id)value;
#endif
+ case _C_UCHR:
+ case _C_SHT:
+ case _C_USHT:
case _C_INT:
- return [NSNumber numberWithInt:(int)value];
case _C_UINT:
- return [NSNumber numberWithUnsignedInt:(unsigned int)value];
case _C_LNG:
- return [NSNumber numberWithInteger:(NSInteger)value];
case _C_ULNG:
- return [NSNumber numberWithUnsignedInteger:(NSUInteger)value];
+ return [NSNumber numberWithInt:(NSInteger)value];
case _C_CHR:
+ case _C_BOOL:
return [NSNumber numberWithBool:(BOOL)value];
+ case _C_FLT:
+ return [NSNumber numberWithFloat:(float)value];
+ case _C_DBL:
+ return [NSNumber numberWithDouble:(double)value];
default:
-#if __has_feature(objc_arc)
- return (__bridge id)value;
-#else
- return (id)value;
-#endif
+ @throw [NSException exceptionWithName:@"TypeError" reason:@"QUnit: unsupported comparison type" userInfo:nil];
+ return (id)nil;
}
}
Please sign in to comment.
Something went wrong with that request. Please try again.