有时,我们需要在应用启动阶段,或者类初始化之前处理一些指定的需求,这时你可能需要这俩方法:
1 2 + (void )load ; + (void )initialize;
1.load
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
1.1.触发时机
dyld 加载镜像文件;
初始化runtime;
注册OC类与分类;
+load
。
所以,+load
是在应用启动阶段,即加载应用可执行文件时、进入程序主入口前触发的。
1.2.调用顺序 父类
->子类
->分类
。
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 @implementation Initializer + (void )load{ NSLog (@"++++ super loaded~" ); }@end @interface SubInitializer : Initializer @end @implementation SubInitializer +(void )load{ NSLog (@"+++ SubInitializer loaded~" ); }@end @interface SubInitializer2 : Initializer @end @implementation SubInitializer2 +(void )load{ NSLog (@"++++ SubInitializer2 loaded~" ); }@end @interface Initializer (Category )@end @implementation Initializer (Category ) +(void )load{ NSLog (@"++++ category loaded~" ); }@end
输出日志:
结论:
父类和子类都重写此方法时,都会触发,且子类晚于父类触发;
原类和分类都重写此方法时,都会触发,且分类晚于原类触发;
父类子类和它们的分类都重写此方法时,都会触发,顺序为:父类>子类>子类分类>父类分类;
不在同一继承树上的类之间的触发顺序不确定;
1.3.示例 检测添加到数组中的对象是否为 nil:
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 #import <objc/runtime.h> @implementation NSMutableArray (Extension ) +(void )load{ [self swizzle]; } + (void )swizzle { Method method1 = class_getInstanceMethod(objc_getClass("__NSArrayM" ), @selector (addObject:)); Method method2 = class_getInstanceMethod(objc_getClass("__NSArrayM" ), @selector (s_ddObject:)); method_exchangeImplementations(method1, method2); } -(void )s_ddObject:(id )anObject { if (nil == anObject){ @try { [self s_ddObject:anObject]; } @catch (NSException *exception) { NSLog (@"Crash reason:\n %@" , [exception callStackSymbols]); } @finally {} } else { [self s_ddObject:anObject]; } }@end
交换方法之后,调用addObject:
实际上会去调用s_ddObject:
;而s_ddObject:
内调用s_ddObject:
实际上是调用数组的addObject:
方法;所以s_ddObject
实际上起到了中间层的作用,用来做异常的检测和其他任务。
随后通过+load
函数在程序启动时执行该替换过程。下面是调用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 #import "NSMutableArray+Extension.h" @implementation AppDelegate - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSMutableArray *mutArr = [NSMutableArray array]; [mutArr addObject:nil ]; return YES ; }@end
这样再往数组中添加 nil 对象时,就不再会崩溃,并且一旦发现 nil 对象就会将信息打印出来。
需要注意,不宜在+load
内处理复杂任务,这会减慢应用的启动速度。优化应用启动时间时,也可从这里入手。
2.initialize
The runtime sends initialize to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program. Superclasses receive this message before their subclasses.
2.1.触发时机 在类或其子类收到第一个消息之前调用;如果类一直没被调用,则+initialize
一直不会触发;
2.2.调用顺序 父类
->子类
;分类覆盖原类。
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 #import "Initializer.h" @implementation Initializer +(void )initialize { NSLog (@"+++ super Initialized~" ); }@end @interface SubInitializer : Initializer @end @implementation SubInitializer +(void )initialize{ NSLog (@"+++ SubInitializer Initialized~" ); }@end @interface Initializer (Category )@end @implementation Initializer (Category ) +(void )initialize{ NSLog (@"++++ category Initialized~" ); }@end
调用及日志:
1 2 3 4 5 // 调用 SubInitializer *subInitial = [[SubInitializer alloc] init];// 日志 ++++ category Initialized~ +++ SubInitializer Initialized~
原类和分类都重写此方法时,只有分类中的会触发,因为查询方法列表时先查到的是分类中的 initialize;
父类重写而子类未重写此方法时,对于每个子类,第一次向其发送消息时,都会触发父类中的 initialize,所以父类的 initialize 可能会触发多次;
父类和子类都重写此方法,首次向父类发送消息,则只会触发父类中的方法;首次向子类发送消息,则两个都会触发且子类晚于父类;先后向子和类父类发送消息,则调用到父类时只触发父类的 initialize;调用到子类时只触发子类的 initialize(因为父类中的 initialize 已经触发过了)。
注意:父类中的+initialize
可能被多次调用,若在父类的+initialize
中单独处理某些需求,要做好判断:
1 2 3 4 5 6 7 8 9 10 @implementation Initializer +(void )initialize { NSLog (@"++++class \"%@\" initialized~" ,[self class ]); if (self == [Initializer self ]) { } }@end
另外,+initialize
是线程安全的,也就是说当 runtime 在第一个线程中调用此方法后,其他线程中需要向此类发送消息时都会被阻塞,直到+initialize
执行完毕。所以,此方法内不适合用来处理复杂的任务,可用来初始化全局变量。
2.3.示例 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 #import <UIKit/UIKit.h> UIKIT_EXTERN int aGlobalInt;UIKIT_EXTERN NSString *className;UIKIT_EXTERN NSMutableDictionary *paramDic;@interface Initializer : NSObject @end #import "Initializer.h" int aGlobalInt = 0 ;NSString *className = @"defaultName" ;NSMutableDictionary *paramDic = nil ;@implementation Initializer +(void )initialize { paramDic = [NSMutableDictionary dictionary]; if (self == [Initializer self ]) { aGlobalInt = 1 ; className = @"Initializer" ; }else { aGlobalInt = 2 ; className = NSStringFromClass ([self class ]); } paramDic[@"name" ] = className; }@end
调用及日志:
1 2 3 4 5 6 7 Initializer *initial = [[Initializer alloc ] init];NSLog(@"++++Int:%d, String:%@, Dic:%@" ,aGlobalInt ,className ,paramDic ) ; ++++Int:1 , String:Initializer, Dic:{ name = Initializer; }
3.比较 从触发的时间序列上来看:
+load
发生在二进制文件加载阶段,在 UIApplicationMain 这个程序主入口之前;
+initialize
发生在程序完全启动之后,也就是在 +load
之后。
从各类中方法的加载顺序来看:
load
发生在加载镜像期间,各类的 load
方法加载顺序不确定,所以不建议在一个类中调用另一个类;
initialize
在程序启动之后,类均已在运行时环境中创建和加载,所以可以正常调用。
从触发次数上来看:
load
在启动阶段会全部触发,且每个load
只会触发一次;
initialize
只在调用当前类时才会触发,不调用则永远不触发;父类中的initialize
可能会触发多次;
调用顺序上:
两个方法在父类、子类和分类等情况下的调用顺序有所不同,注意区分~
相关参考:
#©AppleDev-load
#©AppleDev-initialize