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];
//開啓統計
[MobClick startWithConfigure:UMConfigInstance];
//初始化數據庫
[BYDBUtils startInitDB];
//註冊統計平臺
if (!TARGET_IPHONE_SIMULATOR)
{
[[SocialService sharedInstance] registerPlatforms];
}
//檢測服務器狀態
[BYServerMgr sharedInstance] doGetServerStatus];
//獲取用戶數據
[USER_MGR updateUserAssets];
//啓動圖界面
BYLaunchVC *splashVC = [[KSLaunchVC alloc] initWithNibName:@"BYLaunchVC" 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];
改進後
目錄結構如下:
初始化第三方庫BYThirdPartService.m的代碼如下:
#import "BYThirdPartService.h"
@implementation BYThirdPartService
+ (void)load{
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 = @"BYDefault";
NSString *nuiStyleName = @"BYDefault.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;
dispatch_once(&onceToken, ^{
//初始化數據庫
[self initDB];
//檢測網絡狀態
[self GetServerStatus];
//獲取用戶信息
[self GetUserinfo];
});
}
//初始化數據庫
- (void)initDB{
[[BYDBHelper 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 <objc/runtime.h>
@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);
//class_addMethod的返回BOOL代表的是isNotExist,即當前類未實現該方法時才能添加成功
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod ) {
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方法實現得精簡一些,有助於保持應用程序的響應能力,也能減少引入”依賴環”的機率。
如有寫的不對地方,請在評論區指出,謝謝!
請指明出處:
https://jingwanli6666.github.io/2016/11/08/%E7%A5%9E%E5%A5%87%E7%9A%84load%E6%96%B9%E6%B3%95/