從JSONModel看Objective-C的反射機制


移動互聯時代,JSON作為一種數據傳輸格式幾乎隨處可見。作為iOS開發者,收到一串JSON字符串要怎么處理?我想多數情況下是需要將它轉成自定義的NSObject對象再使用,對於這個轉換的過程,大部分人是這么做的:

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
···

//inspects the class, get's a list of the class properties
-(void)__inspectProperties
{
//JMLog(@"Inspect class: %@", [self class]);

NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];

//temp variables for the loops
Class class = [self class];
NSScanner* scanner = nil;
NSString* propertyType = nil;

// inspect inherited properties up to the JSONModel class
while (class != [JSONModel class]) {
//JMLog(@"inspecting: %@", NSStringFromClass(class));

unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList(class, &propertyCount);

//loop over the class properties
for (unsigned int i = 0; i < propertyCount; i++) {

JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];

//get property name
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
p.name = [NSString stringWithUTF8String:propertyName];

//JMLog(@"property: %@", p.name);

//get property attributes
const char *attrs = property_getAttributes(property);
NSString* propertyAttributes = [NSString stringWithUTF8String:attrs];

if ([propertyAttributes hasPrefix:@"Tc,"]) {
//mask BOOLs as structs so they can have custom convertors
p.structName = @"BOOL";
}

scanner = [NSScanner scannerWithString: propertyAttributes];

//JMLog(@"attr: %@", [NSString stringWithCString:attrs encoding:NSUTF8StringEncoding]);
[scanner scanUpToString:@"T" intoString: nil];
[scanner scanString:@"T" intoString:nil];

···

//finally store the property index in the static property index
objc_setAssociatedObject(self.class,
&kClassPropertiesKey,
[propertyIndex copy],
OBJC_ASSOCIATION_RETAIN // This is atomic
);

在這邊可以看到基本步驟如下

  1. 通過調用自身的class方法獲取當前類的元數據信息
  2. 通過runtime的 class_copyPropertyList 方法取得當前類的屬性列表,以指針數組的形式返回
  3. 遍歷指針數組,通過property_getName獲取屬性名,property_getAttributes獲取屬性類型
  4. 使用NSScanner來掃描屬性類型字符串,將類似如下的形式"T@"NSNumber",&,N,V_id",處理成NSNumber,逐個屬性循環處理
  5. 將所有處理好的數據放入propertyIndex這個字典中
  6. 通過objc_setAssociatedObject將這些數據關聯到kClassPropertiesKey

使用時在properties方法中這樣取出屬性數據:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//returns a list of the model's properties
-(NSArray*)__properties__
{
//fetch the associated object
NSDictionary* classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
if (classProperties) return [classProperties allValues];

//if here, the class needs to inspect itself
[self __setup__];

//return the property list
classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
return [classProperties allValues];
}

以上就是JSONModel中使用反射機制實現的類屬性獲取過程,相比常見的逐個取值賦值的方式,這種方法在代碼上的確簡潔優雅了很多,特別是對於使用的類屬性比較復雜的情況,免除了很多不必要的代碼。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com