用+(void)load对全局变量初始化,实际上并不是一个非常好应用,因为大多情况下我们并不需要使用全局变量。但是另一方面,我们倒是可以通过+(void)load
来实现对象关系映射(Object-relational mapping, ORM)的结构。
首先我们要定义一个ORM
类,它含有Dictionary成员。这个Dictionary用来保存我们要用来映射对象所属类的名字(作为key)和类本身(作为value)。
[codesyntax lang=”objc” lines=”normal”]
@interface ORM : NSObject { NSMutableDictionary *mapper; } + (ORM*)instance; + (void)registerClass:(Class) class withName:(NSString*) className; - (id)getObjectWithData:(NSDictionary *)dataDict; @end @implementation ORM + (ORM *)instance { static ORM *orm = nil; if (!orm) { orm = [[ORM alloc] init]; } return orm; } + (void)registerClass:(Class)class withName:(NSString *)className { ORM * orm = [self instance]; [orm->mapper setObject:class forKey:className]; } - (id)init { if ((self = [super init])) { mapper = [[NSMutableDictionary alloc] init]; } return self; } - (id)getObjectWithData:(NSDictionary *)dataDict { NSString * classNames = [dataDict objectForKey:@"className"]; Class class = [mapper objectForKey:classNames]; if (!class) { return nil; } id obj = [[class alloc] init]; for (NSString *key in dataDict) { if ([key isEqualToString:@"className"]) { continue; } [obj setValue:[dataDict objectForKey:key] forKey:key]; } return [obj autorelease]; } @end
[/codesyntax]
然后我们可以定义任何数据类,但关键在于我们要对该用实现+(void)load
方法,并在该方法内调用ORM的+(void)registerClass:withName:
方法来把类注册在ORM的mapper
中。
[codesyntax lang=”objc” lines=”normal”]
@interface UserContact : NSObject @property (nonatomic) int userId; @property (nonatomic, retain) NSString *name; @property (nonatomic, retain) NSString *phone; @property (nonatomic, retain) NSString *address; + (NSString *)shortName; @end @implementation UserContact @synthesize userId, name, phone, address; + (NSString *)shortName { return @"uc"; } + (void)load { [ORM registerClass:[self class] withName:[self shortName]]; } - (NSString *)description { return [NSString stringWithFormat:@"UserId: %d\nName: %@\nPhone: %@\nAdress: %@\n", userId, name, phone, address]; } @end
[/codesyntax]
在这个UserContact类里,我们用了一个shortName作为类名而不是其全名。这也是为何我们需要一个mapper来关联他们。
我们再回过投去看ORM类的-(id)getObjectWithData:
方法,我们约定它接收一个NSDictionary的对象作为参数,该对象包含一个className的key,该key下存的数据就该data所要映射上的Objective-C对象类型的类名关键字。然后我们再通过这个值在ORM的mapper中找到真正的类,再用这个类构构造出对象。
传入参数的其他数据,则以Objective-C类的属性名(@property)作为key而存的数据。我们将值一一取出并用NSObject神奇的-(void)setValue:forKey:
方法付给对象。
对于-(void)setValue:forKey:方法,它来自于NSKeyValueCoding Protocol,并通过类别(Category)对NSObject实现扩展。Apple官方文档对它的解释如下:
The default implementation of this method does the following:
- Searches the class of the receiver for an accessor method whose name matches the pattern -set<Key>:. If such a method is found the type of its parameter is checked. If the parameter type is not an object pointer type but the value is nil -setNilValueForKey: is invoked. The default implementation of -setNilValueForKey: raises an NSInvalidArgumentException, but you can override it in your application. Otherwise, if the type of the method’s parameter is an object pointer type the method is simply invoked with the value as the argument. If the type of the method’s parameter is some other type the inverse of the NSNumber/NSValue conversion done by -valueForKey: is performed before the method is invoked.
- Otherwise (no accessor method is found), if the receiver’s class’ +accessInstanceVariablesDirectly method returns YES, searches the class of the receiver for an instance variable whose name matches the pattern _<key>, _is<Key>, <key>, or is<Key>, in that order. If such an instance variable is found and its type is an object pointer type the value is retained and the result is set in the instance variable, after the instance variable’s old value is first released. If the instance variable’s type is some other type its value is set after the same sort of conversion from NSNumber or NSValue as in step 1.
- Otherwise (no accessor method or instance variable is found), invokes -setValue:forUndefinedKey:. The default implementation of -setValue:forUndefinedKey: raises an NSUndefinedKeyException, but you can override it in your application.
看了-(void)setValue:forKey:方法的具体实现过程,就知道我们可以不用知道具体的对象所属的类是什么而直接调用该方法来对对象进行赋值操作。这也是这个ORM的关键点之一。
下边我们通过一个JSON对象数据来测试一下我们是否真的可以通过这个ORM来获得一个所需的UserContact
类。这个JSON数据也很简单:
{ "className": "uc", "userId": 10, "name": "Ider Zheng", "phone": "(555)123-4567", "address": "Earth" }
根据约定,数据中也包含了className这一项,其值是UserContact
的shortName。
我们先通过Objective-C的NSJSONSerialization
类把我们的字符串数据转换成得JSON对象,该对象实际上是一个NSDictionary。接着我们把结果传给ORM的-(void)getObjectWithData:
方法。之后打印该对象。
[codesyntax lang=”objc” lines=”normal”]
int main(int argc, const char * argv[]) { @autoreleasepool { NSString * jsonData = @"{\"className\":\"uc\",\"userId\":10,\"name\":\"Ider Zheng\",\"phone\":\"(555)123-4567\",\"address\":\"Earth\"}"; NSError *error = nil; id jsonObject = [NSJSONSerialization JSONObjectWithData:[jsonData dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&error]; if (!jsonObject || error) { NSCAssert(NO, @"NSJSONSerialization / JSONValue failed"); } id obj = [[ORM instance] getObjectWithData:jsonObject]; NSLog(@"%@", obj); } return 0; }
[/codesyntax]
程序的输出结果是:
UserId: 10 Name: Ider Zheng Phone: (555)123-4567 Adress: Earth
它也正式我们在UserContact 的-(void)description
中定义的格式,而其数据值,则来自我们最初的字符串。
现在我们可以不用改变这个ORM的结构,定义任意类,只要在+(void)load中把我们的类注册到ORM中就好了,不过也要注意不能有相同的字符串类名作为key。
我们可以从网上获取数据,可以从数据库中得到原始数据,可以从文件中读取出数据,只要按约定有className我们可以该数据对应类,然后根据key/value正确映射成我们程序中的Objective-C的对象。
除了获得单一对象,我们还可以更进一步进行递归分析,获得嵌套的对象以及对象集合。
在这个ORM结果中,我们必须使用+(void)load方法预先来注册每个对象的映射关系到ORM的mapper中,因为这些数据应该在程序运行前就获得。而通过+(void)load我们也很方面的完成这个过程,我们不用找一个方法集中注册这些类。如果要改名要用作key的类名,要删除一个类的使用,我们都只需要在该类的定义中进行,而不用担心其它地方。
当然如果这ORM的className和实际定义的类名总是一致的话,我们其实并不需要定义mapper也无需使用+(void)load
来注册到mapper
中。因为可以直接使用Objective-C的NSClassFromString方法来得到程序中的类。如果NSString指定的类没有被载入到程序中,该方法会返回nil值。而如果类没有被载入进来,我们的+(void)load
方法也不会被调用。所以实际效果是一样的。
Curso de Capacitación de Acompañante Terapéutico.
whoah thіѕ blog is fantastic i likе studying үouг posts.
Stay սp the good work! You understand, many persons
are hunting around fоr this informatіon, үοu could
help them greatly.