1 2 3 4 5 6 7
|
NSDictionary* json = (fetch from Internet) ... User* user=[[User alloc] init]; user.userId =[json objectForKey:@"userId"]; user.nick= [json objectForKey:@"nick"]; user.image = [json objectForKey:@"image"]; user.age = [json objectForKey:@"age"]; ...
|
這樣的代碼沒錯,但也絕對說不上優雅,model里面的屬性越多,冗余的代碼量也相應越多。對於這個問題,自然是有更好的解決方案,比如說這樣:
1 2
|
NSError* err = nil; User* user = [[User alloc] initWithDictionary:json error:&err];
|
兩行代碼足矣,當然,實現這個功能,實際上是把很多背后的工作交給JSONModel這個開源包去做了。至於其實現原理,則主要是基於Objective-C Runtime的反射機制。
關於反射
《Thinking in Java》中將反射稱解釋為運行時的類信息,說白了就是這個類信息在編譯的時候是未知的,需要在程序運行期間動態獲取,而這正是我們之前試圖去解決的問題。對於從網絡上獲取到的一段JSON字符串,在代碼編譯期間當然是無法知曉的。雖然這里說的是Java語言,但是對於Objective-C,這種反射機制也是同樣支持的。
JSONModel中的實現
打斷點記錄了下JSONModel這個類中的方法調用順序如下:
調用順序
對象屬性的獲取則主要在最后一個inspectProperties方法
以下是inspectProperties方法的代碼片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
···
-(void)__inspectProperties { NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary]; Class class = [self class]; NSScanner* scanner = nil; NSString* propertyType = nil; while (class != [JSONModel class]) { unsigned int propertyCount; objc_property_t *properties = class_copyPropertyList(class, &propertyCount); for (unsigned int i = 0; i < propertyCount; i++) {
JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];
objc_property_t property = properties[i]; const char *propertyName = property_getName(property); p.name = [NSString stringWithUTF8String:propertyName]; const char *attrs = property_getAttributes(property); NSString* propertyAttributes = [NSString stringWithUTF8String:attrs]; if ([propertyAttributes hasPrefix:@"Tc,"]) { p.structName = @"BOOL"; } scanner = [NSScanner scannerWithString: propertyAttributes]; [scanner scanUpToString:@"T" intoString: nil]; [scanner scanString:@"T" intoString:nil];
···
objc_setAssociatedObject(self.class, &kClassPropertiesKey, [propertyIndex copy], OBJC_ASSOCIATION_RETAIN );
|
在這邊可以看到基本步驟如下
- 通過調用自身的class方法獲取當前類的元數據信息
- 通過runtime的 class_copyPropertyList 方法取得當前類的屬性列表,以指針數組的形式返回
- 遍歷指針數組,通過property_getName獲取屬性名,property_getAttributes獲取屬性類型
- 使用NSScanner來掃描屬性類型字符串,將類似如下的形式"T@"NSNumber",&,N,V_id",處理成NSNumber,逐個屬性循環處理
- 將所有處理好的數據放入propertyIndex這個字典中
- 通過objc_setAssociatedObject將這些數據關聯到kClassPropertiesKey
使用時在properties方法中這樣取出屬性數據:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
-(NSArray*)__properties__ { NSDictionary* classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey); if (classProperties) return [classProperties allValues];
[self __setup__]; classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey); return [classProperties allValues]; }
|
以上就是JSONModel中使用反射機制實現的類屬性獲取過程,相比常見的逐個取值賦值的方式,這種方法在代碼上的確簡潔優雅了很多,特別是對於使用的類屬性比較復雜的情況,免除了很多不必要的代碼。