講一下 OC 的消息機(jī)制
- OC中的方法調(diào)用其實(shí)是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用 給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
- objc_msgSend底層三大階段
a.消息發(fā)送
b.動(dòng)態(tài)方法解析
c.消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)機(jī)制流程
什么是Runtime期揪?平時(shí)項(xiàng)目中有用過么税娜?
- OC是一門動(dòng)態(tài)性比較強(qiáng)的編程語言 允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行
- OC的動(dòng)態(tài)性就是由Runtime來支撐和實(shí)現(xiàn)的 Runtime是一套C語言的API,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù)
- 平時(shí)編寫的OC代碼兑凿,底層都是轉(zhuǎn)換成了Runtime API進(jìn)行調(diào)用
- 利用關(guān)聯(lián)對象Associated 給分類添加屬性
- 遍歷類的所有成員變量 (訪問私有成員變量 字典轉(zhuǎn)模型 自動(dòng)歸檔解檔)
- 交換方法實(shí)現(xiàn)(交換系統(tǒng)自帶的方法)
- 利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問題
OC的動(dòng)態(tài)性是由Runtime來支持的
union 共用體 大家共用一個(gè)內(nèi)存 struct是每個(gè)成員都是獨(dú)立的個(gè)體 都有獨(dú)立的內(nèi)存
按位與能取出特定的值 這個(gè)位置的置為1 其他位置置為0就行
union Data{
int year;
char month;
};
isa
1.在arm64架構(gòu)之前剩蟀,isa就是一個(gè)普通的指針猖辫,存儲(chǔ)著Class、Meta-Class對象的內(nèi)存地址
2.從arm64架構(gòu)開始伍宦,對isa進(jìn)行了優(yōu)化芽死,變成了一個(gè)共用體(union)結(jié)構(gòu)乏梁,還使用位域來存儲(chǔ)更多的信息
nonpointer
0,代表普通的指針关贵,存儲(chǔ)著Class遇骑、Meta-Class對象的內(nèi)存地址
1,代表優(yōu)化過揖曾,使用位域存儲(chǔ)更多的信息
has_assoc
是否有設(shè)置過關(guān)聯(lián)對象落萎,如果沒有,釋放時(shí)會(huì)更快(只要設(shè)置過 不管當(dāng)前是否有)
has_cxx_dtor
是否有C++的析構(gòu)函數(shù)(.cxx_destruct)翩肌,如果沒有模暗,釋放時(shí)會(huì)更快
shiftcls
存儲(chǔ)著Class、Meta-Class對象的內(nèi)存地址信息
magic
用于在調(diào)試時(shí)分辨對象是否未完成初始化
weakly_referenced
是否有被弱引用指向過念祭,如果沒有兑宇,釋放時(shí)會(huì)更快(只要被指向過 不管當(dāng)前是否弱引用)
deallocating
對象是否正在釋放
extra_rc
里面存儲(chǔ)的值是引用計(jì)數(shù)器減1
has_sidetable_rc
引用計(jì)數(shù)器是否過大無法存儲(chǔ)在isa中
如果為1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫SideTable的類的屬性中
類對象 元類對象的地址值 最后3位一定是0
&ISA_MASK 取出來shiftcls 的值 直接取出來不是的 因?yàn)閕sa里面存儲(chǔ)著很多信息(用位域的方式存儲(chǔ))
Class的結(jié)構(gòu)
class_rw_t里面的methods粱坤、properties隶糕、protocols是二維數(shù)組,是可讀可寫的站玄,包含了類的初始內(nèi)容枚驻、分類的內(nèi)容
class_ro_t里面的baseMethodList、baseProtocols株旷、ivars再登、baseProperties是一維數(shù)組,是只讀的晾剖,包含了類的
初始內(nèi)容
method_t
method_t是對方法\函數(shù)的封裝
- SEL代表方法\函數(shù)名锉矢,一般叫做選擇器,底層結(jié)構(gòu)跟char *類似
- 通過@selector 或者sel_registerName() 獲得
- 可以通過sel_getName()和NSStringFromSelector()轉(zhuǎn)成字符串
不同類中相同名字的方法齿尽,所對應(yīng)的方法選擇器是相同的
Type Encoding
iOS中提供了一個(gè)叫做@encode的指令沽损,可以將具體的類型表示成字符串編碼
方法緩存
Class內(nèi)部結(jié)構(gòu)中有個(gè)方法緩存(cache_t),用散列表(哈希表)來緩存曾經(jīng)調(diào)用過的方法循头,可以提高方法的查找速度
如果在緩存中快速查找到方法
拿到方法名SEL & (緩存的長度 - 1) = 索引值 也就找到了這個(gè)方法的bucket_t
這種方法是以空間換時(shí)間 也就是犧牲到內(nèi)存空間來換取快速查到
散列表是有個(gè)固定的容量 當(dāng)放不下的時(shí)候 就會(huì)擴(kuò)容
一旦散列表放不下 就會(huì)擴(kuò)容(擴(kuò)大為原來的2倍) 這個(gè)時(shí)候會(huì)清掉緩存 因?yàn)閙ask改變了 通過&也無法找到原先緩存的方法的實(shí)現(xiàn)
當(dāng)緩存方法绵估,準(zhǔn)備插入散列表的時(shí)候,發(fā)現(xiàn)所在位置已經(jīng)有了方法的存在 那么就會(huì)執(zhí)行索引值-1 繼續(xù)進(jìn)行插入 直到找到?jīng)]有方
法存在的索引 把這個(gè)方法緩存進(jìn)散列表(當(dāng)索引值為0還是沒有找到的時(shí)候 索引值賦值為mask 再繼續(xù)進(jìn)行插入)
當(dāng)根據(jù)方法名SEL & (緩存的長度 - 1) = 索引 得到的索引值去相應(yīng)位置找方法實(shí)現(xiàn) 發(fā)現(xiàn)跟方法名不符時(shí) 按照
索引值 - 1 繼續(xù)查找 直到找到具體的實(shí)現(xiàn)(當(dāng)索引值為0還是沒有找到的時(shí)候 索引值賦值為mask 從后往前 繼續(xù)查找方法
實(shí)現(xiàn))
objc_msgSend
OC中的方法調(diào)用,其實(shí)都是轉(zhuǎn)換為objc_msgSend函數(shù)的調(diào)用
OC中的方法調(diào)用 消息機(jī)制 給方法調(diào)用者發(fā)送消息
objc_msgSend的執(zhí)行流程可以分為3大階段
1.消息發(fā)送
2.動(dòng)態(tài)方法解析
3.消息轉(zhuǎn)發(fā)
消息發(fā)送
當(dāng)尋找方法 發(fā)現(xiàn)是父類的或者父類的父類的方法的時(shí)候卡骂,找到之后,會(huì)把方法緩存在消息接受者的緩存中去
當(dāng)尋找方法的時(shí)候 如果方法是有順序的 就會(huì)按照折半查找的方式去找方法 如果沒有順序 就按照for循環(huán)的方法查找方法
動(dòng)態(tài)方法解析
如果已經(jīng)動(dòng)態(tài)添加方法實(shí)現(xiàn) 那么流程會(huì)再回到消息發(fā)送的步驟
- (void)other{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s",__func__);
if(sel == @selector(test)){
//獲取其他方法
Method method = class_getInstanceMethod(self, @selector(other));
//動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
//返回YES代表有動(dòng)態(tài)添加方法實(shí)現(xiàn)
return YES;
}
return [super resolveInstanceMethod:sel];
}
動(dòng)態(tài)添加C語言函數(shù)方法
void c_other(id self, SEL _cmd){
NSLog(@"c_other - %@ - %@", self,NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%s",__func__);
if(sel == @selector(test)){
//動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
//返回YES代表有動(dòng)態(tài)添加方法實(shí)現(xiàn)
return YES;
}
return [super resolveInstanceMethod:sel];
}
可以通過實(shí)現(xiàn)+resolveInstanceMethod: 或者+resolveClassMethod: 來動(dòng)態(tài)添加方法解析
動(dòng)態(tài)解析過后国裳,會(huì)重新走“消息發(fā)送”的流程 “從receiverClass的cache中查找方法”這一步開始執(zhí)行(如果找到了方法 就去執(zhí)行 如果沒有找到方法 就不會(huì)再進(jìn)行動(dòng)態(tài)方法解析 而是去執(zhí)行消息轉(zhuǎn)發(fā))
消息轉(zhuǎn)發(fā)
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [[MJStudent alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
//方法簽名 返回值類型 參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
//NSInvocation 封裝了一個(gè)方法調(diào)用 包括方法調(diào)用者 方法 方法參數(shù)
//anInvocation.target 方法調(diào)用者
//anInvocation.selector 方法名
//[anInvocation getArgument:NULL atIndex:0];
- (void)forwardInvocation:(NSInvocation *)anInvocation{
anInvocation.target = [[MJStudent alloc] init];
[anInvocation invoke];
}
參數(shù)順序 receiver selector 其他參數(shù)
獲取參數(shù)
int age;
[anInvocation getArgument:&age atIndex:2];
獲取返回值
int ret;
[anInvocation getReturnValue:&ret];
如果同時(shí)存在對象方法 類方法 直接上objc_msgSend方法是走的對象方法
@dynamic是告訴編譯器不用自動(dòng)生成getter和setter的實(shí)現(xiàn),等到運(yùn)行時(shí)再添加方法實(shí)現(xiàn)
super
super調(diào)用全跨,底層會(huì)轉(zhuǎn)換為objc_msgSendSuper2函數(shù)的調(diào)用躏救,接收2個(gè)參數(shù) struct objc_super2 SEL
receiver是消息接收者
current_class是receiver的Class對象
self調(diào)用方法是根據(jù)isa指針找到類對象 找相應(yīng)的對象方法 沒有的話 根據(jù)superClass指針往上找 直到找到為之
super調(diào)用方法是直接從父類那里開始查找方法 但是,消息接受者仍然是子類對象
(super message)底層實(shí)現(xiàn)
1.消息接受者仍然是子類對象
2.從父類開始查找方法
isKindOfClass 和 isMemberOfClass 區(qū)別
對象方法
isMemberOfClass 判斷當(dāng)前對象的類對象是否是傳進(jìn)來的這個(gè)類對象
isKindOfClass 判斷當(dāng)前對象的類對象是否是傳進(jìn)來的這個(gè)類對象或者他的子類的類對象
類方法
isMemberOfClass 判斷當(dāng)前對象的元類對象是否是傳進(jìn)來的這個(gè)元類對象
isKindOfClass 判斷當(dāng)前對象的元類對象是否是傳進(jìn)來的這個(gè)元類或者他的子類的元類對象
- 局部變量分配在棧空間
棧空間分配 從高地址到低地址
LLVM的中間代碼(IR)
Objective-C在變?yōu)闄C(jī)器代碼之前盒使,會(huì)被LLVM編譯器轉(zhuǎn)換為中間代碼(Intermediate Representation)
可以使用以下命令行指令生成中間代碼
clang -emit-llvm -S main.m
LLVM的語法簡介
@ - 全局變量
% - 局部變量
alloca - 在當(dāng)前執(zhí)行的函數(shù)的堆棧幀中分配內(nèi)存崩掘,當(dāng)該函數(shù)返回到其調(diào)用者時(shí),將自動(dòng)釋放內(nèi)存
i32 - 32位4字節(jié)的整數(shù)
align - 對齊
load - 讀出少办,store 寫入
icmp - 兩個(gè)整數(shù)值比較苞慢,返回布爾值
br - 選擇分支,根據(jù)條件來轉(zhuǎn)向label英妓,不根據(jù)條件跳轉(zhuǎn)的話類似 goto
label - 代碼標(biāo)簽
call - 調(diào)用函數(shù)
Runtime的類
- 動(dòng)態(tài)創(chuàng)建一個(gè)類(參數(shù):父類挽放,類名,額外的內(nèi)存空間)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
- 注冊一個(gè)類
void objc_registerClassPair(Class cls)
- 銷毀一個(gè)類
void objc_disposeClassPair(Class cls)
- 獲取isa指向的Class
Class object_getClass(id obj)
- 設(shè)置isa指向的Class
Class object_setClass(id obj, Class cls)
- 判斷一個(gè)OC對象是否為Class
BOOL object_isClass(id obj)
- 判斷一個(gè)Class是否為元類
BOOL class_isMetaClass(Class cls)
- 獲取父類
Class class_getSuperclass(Class cls)
要在類注冊之前添加成員變量
//創(chuàng)建類
//Class _Nullable __unsafe_unretained superclass 父類
//const char * _Nonnull name 類名
//size_t extraBytes 額外的大小 是否需要擴(kuò)充空間
Class newClass = objc_allocateClassPair([NSObject class], "MJDgo", 0);
//添加成員變量
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
//添加方法
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
//注冊類
objc_registerClassPair(newClass);
id dog = [[newClass alloc] init];
Runtime的成員變量
- 獲取一個(gè)實(shí)例變量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
- 拷貝實(shí)例變量列表(最后需要調(diào)用free釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
- 設(shè)置和獲取成員變量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
- 動(dòng)態(tài)添加成員變量(已經(jīng)注冊的類是不能動(dòng)態(tài)添加成員變量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
- 獲取成員變量的相關(guān)信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
//獲取成員變量信息
Ivar ageIvar = class_getInstanceVariable([MJPerson class], "_age");
//成員變量名稱
NSLog(@"%s %s",ivar_getName(ageIvar),ivar_getTypeEncoding(ageIvar));
//設(shè)置和獲取成員變量的值
object_setIvar(person, ageIvar, @10);
object_getIvar(person, ageIvar);
int類型的無法這么賦值 可以使用kvc賦值 或者 object_setIvar(person, ageIvar, (__bridge id)(void *)10);這樣設(shè)置
runtime里面使用到copy 或者 create的 最后都要釋放掉
//成員變量的數(shù)量
unsigned int count;
Ivar *ivars = class_copyIvarList([MJPerson class], &count);
for (int i = 0; i < count; i++) {
//取出i位置的成員變量
Ivar ivar = ivars[i];
NSLog(@"%s %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
}
free(ivars);
//在不需要這個(gè)類的時(shí)候釋放掉 objc_disposeClassPair(newClass);
簡單的字典轉(zhuǎn)模型
+(instancetype)mj_objectWithJson:(NSDictionary *)json{
id obj = [[self alloc] init];
//成員變量的數(shù)量
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
//取出i位置的成員變量
Ivar ivar = ivars[i];
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
//刪掉下劃線
[name deleteCharactersInRange:NSMakeRange(0, 1)];
//設(shè)置
[obj setValue:json[name] forKey:name];
}
return obj;
}
Runtime的方法
- 獲得一個(gè)實(shí)例方法蔓纠、類方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
- 方法實(shí)現(xiàn)相關(guān)操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
- 拷貝方法列表(最后需要調(diào)用free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
- 動(dòng)態(tài)添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
- 動(dòng)態(tài)替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
- 獲取方法的相關(guān)信息(帶有copy的需要調(diào)用free去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
- 選擇器相關(guān)
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
- 用block作為方法實(shí)現(xiàn)
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
方法交換
Method runMethod = class_getInstanceMethod([MJPerson class], @selector(run));
Method testthod = class_getInstanceMethod([MJPerson class], @selector(test));
method_exchangeImplementations(runMethod, testthod);
exchangeImplementations交換的是類對象的class_rw_t里面的methods的method_t的IMP 當(dāng)交換的時(shí)候 就會(huì)清空緩存
表面是一種類型 實(shí)際上是另外一種類型 這種是類簇