首先先看幾個面試問題
- Cateogry里面有l(wèi)oad方法么? load方法什么時候調(diào)用?load方法有繼承么拂檩?
1. 新建一個項目嘲碧,并添加TCPerson類,并給TCPerson添加兩個分類
2.新建一個TCStudent類繼承自TCPerson钉迷,并且給TCStudent也添加兩個分類
Cateogry里面有l(wèi)oad方法么糠聪?
-
答:分類里面肯定有l(wèi)oad
#import "TCPerson.h"
@implementation TCPerson
+ (void)load{
}
@end
#import "TCPerson+TCtest1.h"
@implementation TCPerson (TCtest1)
+ (void)load{
}
@end
#import "TCPerson+TCTest2.h"
@implementation TCPerson (TCTest2)
+ (void)load{
}
@end
load方法什么時候調(diào)用舰蟆?
-
load方法在runtime加載類和分類的時候調(diào)用load
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
@implementation TCPerson
+ (void)load{
NSLog(@"TCPerson +load");
}
@end
@implementation TCPerson (TCtest1)
+ (void)load{
NSLog(@"TCPerson (TCtest1) +load");
}
@end
@implementation TCPerson (TCTest2)
+ (void)load{
NSLog(@"TCPerson (TCtest2) +load");
}
@end
可以看到我們在main里面不導入任何的頭文件身害,也不引用任何的類,直接運行侍瑟,控制臺輸出結(jié)果:
從輸出結(jié)果我們可以看出丙猬,三個load方法都被調(diào)用
問題:分類重寫方法,真的是覆蓋原有類的方法么庭瑰?如果不是弹灭,到底分類的方法是怎么調(diào)用的揪垄?
-
首先我們在TCPerson申明一個方法+ (void)test并且在它的兩個分類都重寫+ (void)test
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TCPerson : NSObject
+ (void)test;
@end
NS_ASSUME_NONNULL_END
#import "TCPerson.h"
@implementation TCPerson
+ (void)load{
NSLog(@"TCPerson +load");
}
+ (void)test{
NSLog(@"TCPerson +test");
}
@end
分類重寫test
#import "TCPerson+TCtest1.h"
@implementation TCPerson (TCtest1)
+ (void)load{
NSLog(@"TCPerson (TCtest1) +load");
}
+ (void)test{
NSLog(@"TCPerson (TCtest1) +test1");
}
@end
#import "TCPerson+TCTest2.h"
@implementation TCPerson (TCTest2)
+ (void)load{
NSLog(@"TCPerson (TCtest2) +load");
}
+ (void)test{
NSLog(@"TCPerson (TCtest2) +test2");
}
@end
在main里面我們調(diào)用test
#import <Foundation/Foundation.h>
#import "TCPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[TCPerson test];
}
return 0;
}
輸出結(jié)果:
從輸出結(jié)果中我們可以看到饥努,只有分類2中的test被調(diào)用肪凛,為什么只調(diào)用分類2中的test了?
因為編譯順序是分類2在后翘鸭,1在前戳葵,這個時候我們改變編譯順序(拖動文件就行了)
其輸出結(jié)果為:
細心的老鐵會看到拱烁,為什么load方法一直都在調(diào)用戏自,這是為什么了?它和test方法到底有什么不同了擅笔?真的是我們理解中的load不覆蓋,test覆蓋了念脯,所以才出現(xiàn)這種情況么绿店?
我們打印TCPerson的類方法
void printMethodNamesOfClass(Class cls)
{
unsigned int count;
// 獲得方法數(shù)組
Method *methodList = class_copyMethodList(cls, &count);
// 存儲方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍歷所有的方法
for (int i = 0; i < count; i++) {
// 獲得方法
Method method = methodList[I];
// 獲得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 釋放
free(methodList);
// 打印方法名
NSLog(@"%@ %@", cls, methodNames);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
[TCPerson test];
printMethodNamesOfClass(object_getClass([TCPerson class]));
}
return 0;
}
輸出結(jié)果:可以看到假勿,TCPerson的所有類方法名,并不是覆蓋,三個load堡距,三個test兆蕉,方法都在
load源碼分析:查看objc底層源碼我們可以看到:
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
load方法它是先調(diào)用 while (loadable_classes_used > 0) {call_class_loads(); }類的load虎韵,再調(diào)用more_categories = call_category_loads()分類的load,和編譯順序無關(guān)驶社,都會調(diào)用
我們查看call_class_loads()方法
static void call_class_loads(void)
{
int I;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
其通過的是load_method_t函數(shù)指針直接調(diào)用
函數(shù)指針直接調(diào)用
typedef void(*load_method_t)(id, SEL);
其分類load方法調(diào)用也是一樣
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
為什么test不一樣了
因為test是因為消息機制調(diào)用的亡电,objc_msgSend([TCPerson class], @selector(test));消息機制就牽扯到了isa方法查找份乒,test在元類方法里面順序查找的(關(guān)于isa腕唧,可以查看我的實例對象,類對象颂暇,元類對象的關(guān)聯(lián)---isa/superclass指針(2))里面有詳細的關(guān)于test的方法調(diào)用原理
load只在加載類的時候調(diào)用一次但惶,且先調(diào)用類的load瓣赂,再調(diào)用分類的
load的繼承關(guān)系調(diào)用
首先我們先看TCStudent
#import "TCStudent.h"
@implementation TCStudent
@end
不寫load方法調(diào)用
TCStudent寫上load