NSObject类作为Objective-C中绝大多数类的父类,向其子类提供了基本的Runtime接口与Objective-C Class的一些方法默认实现。
在NSObject中有两个类方法,load与initialize方法,由Runtime动态调用,用于配置Class或Category。(这种配置对于所有的Class实例,均有效)
在Apple文档中,它们被分类为Initializing a Class方法。
+initialize
Initializes the class before it receives its first message.
+load
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
在程序运行时,Runtime会将所有的Class和Category加载到内存中,这时,会调用类的load方法,通知我们Class或Category已经被加载到内存中。
+(void)load;
在Objective-C中,初始化的顺序为
The order of initialization is as follows:
- All initializers in any framework you link to.
- All +load methods in your image.
- All C++ static initializers and C/C++ attribute(constructor)
functions in your image.
- All initializers in frameworks that link to you.
其中对于Objective-C的Class与Category的load方法的调用顺序为:
注意,在load方法中,你可以向其他类发送消息(即方法调用),Objective-C会保证消息发送成功。但是并不保证接受消息的load函数已经被调用。
initialize方法是用来初始化一个Class或Category的另一个方法。它与load方法调用的不同之处在于,它会在该Class或其subclass 接受代码中第一个消息之前时候被调用。如果subclass没有重写initialize方法的话,则super class的initialize函数会被多次调用。
而superclass会在subclass之前被调用initialize方法。
+(void)initialize;
对于initialize方法,需要注意两点:
Runtime对于initialize方法的调用是线程安全的(注意这里并没有包含并自己显示调用的情况),这意味着:
(1)initialize会在class接受第一个消息的线程中被调用。
(2)initialize会阻塞class的message sending,只有当initialize方法执行完毕,其余的message才会被执行。这里有dead lock的危险,因此在initialize中的代码不应过于复杂。
同一个class的initialize方法可能会被多次调用,这种情况发生在subclass没有实现initialize方法的时候或者显示调用[super initialize]时。
为了防止initialize多次调用,可以在initialize方法中加入判断当前调用者是否为当前类本身。
+(void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
load 与 initialize 方法是没有先后关系的。
它们调用的时机不一样:
load 与 initialize的继承性不一样
@interface Car : NSObject
@end
@interface AutoCar : Car
@end
@interface BMWCar : AutoCar
@end
@interface BMWCar(MyCar)
@end
@implementation Car
+(void) initialize
{
NSLog(@"%@ %s", [self class], __FUNCTION__);
}
@end
@implementation AutoCar
+(void) initialize
{
NSLog(@"%@ %s", [self class], __FUNCTION__);
}
@end
@implementation BMWCar
+(void) initialize
{
NSLog(@"%@ %s", [self class], __FUNCTION__);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
BMWCar *myCar = [BMWCar new];
}
输出结果为:
Car +[Car initialize]
AutoCar +[AutoCar initialize]
BMWCar +[BMWCar initialize]
Method Swizzling 和 AOP 实践
Notification Once
突然想到了一个问题,既然load方法与initialize方法都会被Runtime自动调用一次,并且在Runtime情况下,这两个方法都是线程安全的,那么是否可以作为单例类的一个实现呢?
不废话,上代码:
@interface MySpecialObject : NSObject
+(MySpecialObject *) sharedInstance;
@end
// 利用initialize函数实现
static MySpecialObject *singalObject = nil;
@implementation MySpecialObject
+(void) initialize
{
if (self == [MySpecialObject self] && singalObject == nil) {
singalObject = [[MySpecialObject alloc] init];
}
}
+(MySpecialObject *) sharedInstance
{
return singalObject;
}
- (void)viewDidLoad {
[super viewDidLoad];
MySpecialObject *obj = [MySpecialObject sharedInstance];
NSLog(@"%@", obj);
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
MySpecialObject *obj = [MySpecialObject sharedInstance];
NSLog(@"%@", obj);
}
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
MySpecialObject *obj = [MySpecialObject sharedInstance];
NSLog(@"%@", obj);
}
输出:
2016-08-13 16:13:07.951 SpecialSingalObject[1304:110175] <MySpecialObject: 0x7fc5d849f400>
2016-08-13 16:13:08.826 SpecialSingalObject[1304:110175] <MySpecialObject: 0x7fc5d849f400>
2016-08-13 16:13:09.556 SpecialSingalObject[1304:110175] <MySpecialObject: 0x7fc5d849f400>
确实,得到了同一个实例。
对于initialize 函数,的解释:
(1) if (self == [MySpecialObject self] …
是为了保证 initialize函数只有在本类而非subclass时才执行单例初始化函数。
(2)if(…&& singalObject == nil) 是为了防止initialize多次调用而产生多个实例(除了Runtime调用,我们也可以显示调用initialize方法)。经过测试,当我们将initialize方法本身作为class的第一个方法执行时,Runtime的initialize会被先调用(这保证了线程安全),然后我们自己显示调用的initialize函数再被调用。
由于initialize方法的第一次调用一定是Runtime调用,而Runtime又保证了线程安全性,因此这里只简单的检测 singalObject == nil即可。
上面的代码确实是实现了线程安全的单例模式,但是由于load与initialize方法的本意是对于Class做设置,并且考虑到load或initialize方法有可能是在程序运行的早期执行,可能我们的单例所依赖的环境尚未形成,所有我们还是不要用这种方式实现单例吧。
经过测试,当我们的MySpecialObject类中包含其他自定义class的属性时,如Book。
当我们在MySepcialObject 的init函数中初始化Book属性时,此时虽然Book类的load方法没被调用,但是我们的Book属性仍然能被正常初始化。
也许,对于load方法的调用,Runtime是在将所有的Class加载到内存中后,再统一调用的原因吗?
关于自己最后一个关于Book属性的问题,在查阅了Runtime源码之后,确实如推测那样,Runtime会在所有类都加载入内存后,在统一的调用load方法。当初在写这篇博客时,还没有去深入的分析Runtime的源码(还没有能力:)),现在回过头来看一下,这也就是对一个问题不断深入理解的过程吧~
附上相关代码, 当dyld将所有的class image都映射到内存后,会调用load images来加载class,最后,会调用load 方法:
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}