load方法說明
在Objective-C中,絕大多數類都繼承自NSObject這個根類,而該類有load方法,可以用來實現初始化操作。其原型如下:
+ (void)load
對於加入運行期系統中的每個類(class)及分類(category)來說,必定會調用此方法,而且僅調用一次。當包含類或分類的程序庫載入系統時,就會執行此方法(通常指應用程序啟動)。如程序是iOS平台設計的,則肯定會在此時執行。Mac OS X應用程序更自由一些,它們可以使用“動態加載”(dynamic loading)之類的特性,等應用程序啟動好之後再去加載程序庫。如果分類和其所屬的類都定義了load方法,則先調用類裡的,再調用分類裡的。
執行load方法時,運行期系統處於“脆弱狀態”。在執行子類的load方法之前,必定會先執行所有超類的load方法,而如果代碼還依賴了其他程序庫,那麼程序庫裡相關類的load方法也必定會先執行。
load方法的妙用
簡化AppDelegate類
隨著項目功能的不斷增加,我們有很多功能或者第三庫需要啟動項目時就加載,AppDelegate類就會越來越龐大。這樣結構既不夠清晰,而且耦合性比較強。
改進前:
//設置NUI配置 [self setNUIConfig]; //開啟統計 [[KSStatisticalMgr sharedInstance] start]; //初始化數據庫 [KSDBUtils startInitDB]; //注冊統計平台 if (!TARGET_IPHONE_SIMULATOR) { [[SocialService sharedInstance] registerPlatforms]; } //檢測服務器狀態 [[KSServerMgr sharedInstance] doGetServerStatus]; //獲取用戶數據 [USER_MGR updateUserAssets]; //啟動圖界面 KSLaunchVC *splashVC = [[KSLaunchVC alloc] initWithNibName:@"KSLaunchVC" bundle:nil]; UIWindow *keywindow = [UIApplication sharedApplication].keyWindow; [keywindow addSubview:splashVC.view]; [keywindow bringSubviewToFront:splashVC.view]; [self.window makeKeyAndVisible]; //自適應屏幕鍵盤控件 IQKeyboardManager * manager = [IQKeyboardManager sharedManager]; manager.enable = YES; manager.shouldResignOnTouchOutside = YES; manager.shouldToolbarUsesTextFieldTintColor = YES; manager.enableAutoToolbar = YES; //設置首頁 BYCircleListViewController *homePageVC = [[BYCircleListViewController alloc] init]; BYNavigationViewController *navVC = [[BYNavigationViewController alloc] initWithRootViewController:homePageVC]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; self.window.rootViewController = navVC; [self.window makeKeyAndVisible];
改進後
目錄結構如下:
改進後AppDelegate目錄
初始化第三方庫BYThirdPartService.m的代碼如下:
#import "BYThirdPartService.h" @implementation BYThirdPartService + (void)load{ static dispatch_once_t onceToken; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //設置NUI配置 [self setNUIConfig]; //開啟統計 [self startStatistics]; //鍵盤初始化 [self initKeyboard]; }); } //設置NUI配置 - (void)setNUIConfig{ //判斷屏幕尺寸 CGFloat scale = [UIScreen mainScreen].scale; int scaleInt = (int)scale; NSString *nuiStyleStartName = @"KSDefault"; NSString *nuiStyleName = @"KSDefault.NUI"; [NUISettings initWithStylesheet:nuiStyleName]; if([NUISettings hasProperty:@"translucent" withClass:@"NavigationBar"]) { [[UINavigationBar appearance] setTranslucent:[NUISettings getBoolean:@"translucent" withClass:@"NavigationBar"]]; } if ([NUISettings hasProperty:@"tint-color" withClass:@"NavigationBar"]) { [[UINavigationBar appearance] setTintColor:[NUISettings getColor:@"tint-color" withClass:@"NavigationBar"]]; } } //鍵盤初始化 - (void)initKeyboard{ IQKeyboardManager * manager = [IQKeyboardManager sharedManager]; manager.enable = YES; manager.shouldResignOnTouchOutside = YES; manager.shouldToolbarUsesTextFieldTintColor = YES; manager.enableAutoToolbar = YES; } //開始統計 - (void)startStatistics{ [MobClick startWithConfigure:UMConfigInstance]; } 初始化數據 BYInitData.m的代碼(思路,具體代碼根據自身項目的實際情況進行修改) #import "BYInitData.h" @implementation BYInitData + (void)load{ static dispatch_once_t onceToken; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //初始化數據庫 [self initDB]; //檢測網絡狀態 [self GetServerStatus]; //獲取用戶信息 [self GetUserinfo]; }); } //初始化數據庫 - (void)initDB{ [[KSDBHelper sharedInstance] startInitOrUpdate]; } - (void)GetServerStatus{ //檢測網絡狀態 ........... } - (void)GetServerStatus{ //獲取用戶信息 ........... } @end
簡化後AppDelegate如下:
#import "AppDelegate.h" #import "BYCircleListViewController.h" #import "BYNavigationViewController.h" //只需增加相應的兩個頭文件 #import "BYThirdPartService.h" #import "BYInitData.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. BYCircleListViewController *homePageVC = [[BYCircleListViewController alloc] init]; BYNavigationViewController *navVC = [[BYNavigationViewController alloc] initWithRootViewController:homePageVC]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; self.window.rootViewController = navVC; [self.window makeKeyAndVisible]; return YES; }
當類被引入項目時, runtime 會向每一個類對象發送 load 消息. 神奇的load 方法, 會在每一個類甚至分類被引入時僅調用一次, 調用的順序是父類優先於子類, 子類優先於分類. 而且 load 方法不會被類自動繼承, 每一個類中的 load 方法都不需要像 viewDidLoad 方法一樣調用父類的方法。
埋點統計
在iOS中,在運行時替換兩個方法的實現,達到“勾住”某個方法並注入代碼的目的。具體方法如下:
重載類的“+(void)load”方法,在程序加載到內存時利用Runtime的method_exchangeImplementations等接口將方法的實現互相交換。當方法M被調用時就會被勾住(Hook),執行我們的方法。
該技術稱為Method Swizzling,屬於面向切面編程(Aspect-Oriented Programming)的一種實現。
替換兩個方法的實現,代碼如下:
#import "BYStatistics.h" #import @implementation BYStatistics + (void)swizzlingClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{ Class class = cls; Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL isExistMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (isExistMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else { method_exchangeImplementations(originalMethod, swizzledMethod); } } @end
BYStatistics統計類下文會用到。利用神奇的load方法統計兩個頁面的展示與離開次數
#import "UIViewController+Stastistics.h" #import "BYStatistics.h" @implementation UIViewController (Stastistics) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(swizzling_viewWillAppear:); [BYStatistics swizzlingClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector]; SEL originalSelector2 = @selector(viewWillDisappear:); SEL swizzledSelector2 = @selector(swizzling_viewWillDisappear:); [BYStatistics swizzlingClass:[self class] originalSelector:originalSelector2 swizzledSelector:swizzledSelector2]; }); } #pragma mark - Method Swizzling - (void)swizzling_viewWillAppear:(BOOL)animated{ //插入需要執行的代碼 [self inject_viewWillAppear]; [self swizzling_viewWillAppear:animated]; } //利用hook,統計頁面的停留時間 - (void)inject_viewWillAppear{ NSString *pageName = [self pageEventName:YES]; if (pageName) { //統計代碼 } } - (void)swizzling_viewWillDisappear:(BOOL)animated{ [self inject_viewWillDisappear]; [self swizzling_viewWillDisappear:animated]; } - (void)inject_viewWillDisappear { NSString *pageName = [self pageEventName:YES]; if (pageName) { //統計代碼 } } @end
load方法與initialize方法
NSObject的load方法和initialize方法都是用來實現初始化操作。
load方法
對於加入運行期系統中的每個類及分類來說,必定會調用此方法,而且近調用一次。當包含類或分類的程序庫載入系統時,就會執行此方法,而這通常就是指應用程序啟動的時候,若程序是為iOS平台設計的,則肯定會在此時執行。
如果分類和其所屬的類都定義了load方法,則先調用類裡的,在調用分類裡的。在執行子類的load方法之前,必定會先執行所有超類的load方法,而如果代碼還依賴了其他程序庫,那麼程序庫裡相關類的load方法也必定會先執行。
在整個應用程序執行load方法時都會阻塞
initialize方法
它是“惰性”調用的,也就是說,只有當程序用到了相關的類時,才會調用。因此,如果某個類一直都沒有使用,那麼其initialize方法就一直不會運行。這也就等於說,應用程序無須先把每個類的initialize都執行一遍
注意事項
與其他方法不同,load方法不參與覆寫機制
load方法實現得精簡一些,有助於保持應用程序的響應能力,也能
如有寫的不對地方,請在評論區指出,謝謝!