一、objc_msgSend
- OC中的方法調(diào)用,其實都是轉(zhuǎn)換為objc_msgSend函數(shù)的調(diào)用
- objc_msgSend的執(zhí)行流程可以分為3大階段
- a)消息發(fā)送
- b)動態(tài)方法解析
- c)消息轉(zhuǎn)發(fā)
二、消息發(fā)送
- 在Objective-C對象的分類中詳細(xì)的說了instance對象、class對象、meta-class對象。也詳細(xì)的說消息發(fā)送和方法尋找的過程
objc_msgSend消息發(fā)送圖盜圖.png
- 在Runtime(一)中又進一步說明了Class的結(jié)構(gòu),更加詳細(xì)的說明了Class中方法存儲的過程和尋找過程,先要從緩存中尋找是否有方法,然后再從方法列表中尋找方法,這個整個過程可以理解為消息發(fā)送,也就是整個OC執(zhí)行方法過程中的第一步
1、通過源碼來窺探消息發(fā)送原理
在終端輸入下面的命令把OC代碼的.m文件轉(zhuǎn)換成.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc [OC文件] -o [.cpp文件]
#import <Foundation/Foundation.h>
@interface RevanPerson : NSObject
- (void)personInstanceMethod;
@end
#import "RevanPerson.h"
@implementation RevanPerson
- (void)personInstanceMethod {
NSLog(@"%s", __func__);
}
@end
#import "ViewController.h"
#import "RevanPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
RevanPerson *person = [[RevanPerson alloc] init];
[person personInstanceMethod];
}
@end
打印輸出:
2018-07-12 14:18:31.450980+0800 01-Runtime_objc_msgSend[2057:54187] -[RevanPerson personInstanceMethod]
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
RevanPerson *person = ((RevanPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((RevanPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("RevanPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personInstanceMethod"));
}
//簡化后源碼
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
//實例化person對象
RevanPerson *person =
objc_msgSend(
objc_msgSend(objc_getClass("RevanPerson"), sel_registerName("alloc")),
sel_registerName("init")
);
//person對象調(diào)用personInstanceMethod方法
objc_msgSend(
person, sel_registerName("personInstanceMethod")
);
}
- 源碼解析
- 1、在實例化person對象時使用了2次objc_msgSend發(fā)送消息函數(shù);里面的一次是給RevanPerson類發(fā)送一個alloc消息;外面的一次是給RevanPerson的instance對象發(fā)送一個init消息
- 2、最后給person對象發(fā)送一個personInstanceMethod消息
2、當(dāng)找不到personInstanceMethod方法會怎么樣
#import <Foundation/Foundation.h>
@interface RevanPerson : NSObject
- (void)personInstanceMethod;
@end
#import "RevanPerson.h"
@implementation RevanPerson
@end
#import "ViewController.h"
#import "RevanPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
RevanPerson *person = [[RevanPerson alloc] init];
[person personInstanceMethod];
}
@end
崩潰輸出:
2018-07-12 14:46:51.714853+0800 01-Runtime_objc_msgSend[2616:75078] -[RevanPerson personInstanceMethod]: unrecognized selector sent to instance 0x6000000151d0
2018-07-12 14:46:51.727333+0800 01-Runtime_objc_msgSend[2616:75078] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RevanPerson personInstanceMethod]: unrecognized selector sent to instance 0x6000000151d0'
- 這是找不到方法最常見的崩潰信息。經(jīng)過上面圖中的分析來看,我們了解了在發(fā)送一個消息并且尋找這個消息的過程,在我們看來尋遍上圖的過程后如果依然沒有找到就會崩潰,輸出上面的錯誤信息。在尋找消息的過程中如果經(jīng)歷了上面的過程依然沒有找到就會查看有無方法動態(tài)解析,這就是下面討論的
-
消息發(fā)送流程
三、動態(tài)方法解析
- 在動態(tài)方法解析中可以讓代碼執(zhí)行其他的方法,好比上面的問題,因為沒有實現(xiàn)personInstanceMethod方法,所以程序會因為找不到方法而崩潰。為了不讓程序崩潰,我們可以重寫resolveInstanceMethod:或者resolveClassMethod:方法來給personInstanceMethod方法添加實現(xiàn)
1、instance方法的動態(tài)方法解析
#import <Foundation/Foundation.h>
@interface RevanPerson : NSObject
- (void)personInstanceMethod;
@end
#import "RevanPerson.h"
#import <objc/runtime.h>
@implementation RevanPerson
/**
動態(tài)解析instanceMethod方法
@param sel 發(fā)送的具體消息
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(personInstanceMethod)) {
// 把一個方法構(gòu)造成一個 Method類型
Method method = class_getInstanceMethod(self, @selector(resolveTest));
// 獲取Method類型中方法的地址
IMP imp = method_getImplementation(method);
// 獲取Method類型參數(shù)
const char *types = method_getTypeEncoding(method);
// 給類添加對象方法,給元類添加類方法
class_addMethod(self, sel, imp, types);
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)resolveTest {
NSLog(@"%s", __func__);
}
@end
#import "ViewController.h"
#import "RevanPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
RevanPerson *person = [[RevanPerson alloc] init];
[person personInstanceMethod];
}
@end
打印輸出:
2018-07-12 15:31:48.004090+0800 01-Runtime_objc_msgSend[4588:111337] -[RevanPerson resolveTest]
- 會發(fā)現(xiàn)我們外面調(diào)用的person對象的personInstanceMethod方法,但是真正調(diào)用的是resolveTest方法,這就是動態(tài)方法解析的魅力所在,這也是動態(tài)語言的魅力所在
- 這次也更好的讓我們認(rèn)識到,OC在調(diào)用方法的本質(zhì)是給instance對象、類對象發(fā)送消息,當(dāng)你在使用類對象調(diào)用類方法的時候,發(fā)現(xiàn)執(zhí)行的是instance方法時,不要奇怪。
- class_getInstanceMethod函數(shù)是用來把一個方法構(gòu)造成一個 Method類型結(jié)構(gòu)體
- method_getImplementation函數(shù)是用來獲取Method類型結(jié)構(gòu)體中方法的地址
- method_getTypeEncoding函數(shù)是用來獲取Method類型結(jié)構(gòu)體中參數(shù)
- class_addMethod函數(shù)用來給class對象和meta-class對象添加方法
/**
添加方法
@param cls 給類添加instance方法;給元類添加類方法
@param name 消息名稱
@param imp 方法地址
@param types 方法參數(shù)和返回值信息
*/
class_addMethod(cls, name, imp, types)
2、instance方法的動態(tài)方法解析調(diào)用函數(shù)
#import <Foundation/Foundation.h>
@interface RevanPerson : NSObject
- (void)personInstanceMethod;
@end
#import "RevanPerson.h"
#import <objc/runtime.h>
@implementation RevanPerson
void resolveFunc(id self, SEL _cmd) {
NSLog(@"%@, %s", self, _cmd);
}
/**
動態(tài)解析instanceMethod方法
@param sel 發(fā)送的具體消息
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(personInstanceMethod)) {
// 給類添加對象方法,給元類添加類方法
class_addMethod(self, sel, resolveFunc, "V16@0:8");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
#import "ViewController.h"
#import "RevanPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
RevanPerson *person = [[RevanPerson alloc] init];
[person personInstanceMethod];
}
@end
打印輸出:
2018-07-12 15:53:25.849709+0800 01-Runtime_objc_msgSend[4801:126769] <RevanPerson: 0x600000014310>, personInstanceMethod
3、class方法的動態(tài)方法解析
#import <Foundation/Foundation.h>
@interface RevanPerson : NSObject
+ (void)personClassMethod;
@end
#import "RevanPerson.h"
#import <objc/runtime.h>
@implementation RevanPerson
/**
動態(tài)解析instanceMethod方法
@param sel 發(fā)送的具體消息
*/
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(personClassMethod)) {
// 把一個方法構(gòu)造成一個 Method類型數(shù)據(jù)結(jié)構(gòu)
Method method = class_getClassMethod(self, @selector(resolveTest));
// 獲取Method類型中方法的地址
IMP imp = method_getImplementation(method);
// 獲取Method類型參數(shù)
const char *types = method_getTypeEncoding(method);
// 給類添加對象方法,給元類添加類方法
class_addMethod(object_getClass(self), sel, imp, types);
return YES;
}
return [super resolveClassMethod:sel];
}
+ (void)resolveTest {
NSLog(@"%s", __func__);
}
@end
#import "ViewController.h"
#import "RevanPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[RevanPerson personClassMethod];
}
@end
打印輸出:
2018-07-12 16:08:05.664423+0800 01-Runtime_objc_msgSend[5018:140031] +[RevanPerson resolveTest]
- 小結(jié)
- 在構(gòu)造Method類型的結(jié)構(gòu)體時要使用class_getClassMethod函數(shù)
- 在使用class_addMethod添加類方法時,第一個參數(shù)要傳入元類對象
4、Class方法的動態(tài)方法解析調(diào)用函數(shù)
#import <Foundation/Foundation.h>
@interface RevanPerson : NSObject
+ (void)personClassMethod;
@end
#import "RevanPerson.h"
#import <objc/runtime.h>
@implementation RevanPerson
void resolveFunc(id self, SEL _cmd) {
NSLog(@"%@, %s", self, _cmd);
}
/**
動態(tài)解析instanceMethod方法
@param sel 發(fā)送的具體消息
*/
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(personClassMethod)) {
// 給類添加對象方法,給元類添加類方法
class_addMethod(object_getClass(self), sel, resolveFunc, "V16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
@end
#import "ViewController.h"
#import "RevanPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[RevanPerson personClassMethod];
}
@end
打印輸出:
2018-07-12 16:13:53.628104+0800 01-Runtime_objc_msgSend[5077:144122] RevanPerson, personClassMethod
四、消息轉(zhuǎn)發(fā)
- 當(dāng)在動態(tài)方法解析過程中程序員沒有重寫resolveClassMethod方法和resolveInstanceMethod方法,就會進入消息轉(zhuǎn)發(fā)階段
- 消息轉(zhuǎn)發(fā)顧名思義,因為自己沒有辦法處理這個方法,所以要讓其他對象來處理
1、轉(zhuǎn)給其他對象處理
#import <Foundation/Foundation.h>
@interface RevanTest : NSObject
- (void)personInstanceMethod;
@end
#import "RevanTest.h"
@implementation RevanTest
- (void)personInstanceMethod {
NSLog(@"%s", __func__);
}
@end
#import <Foundation/Foundation.h>
@interface RevanPerson : NSObject
- (void)personInstanceMethod;
@end
#import "RevanPerson.h"
#import <objc/runtime.h>
#import "RevanTest.h"
@implementation RevanPerson
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(personInstanceMethod)) {
return [[RevanTest alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
#import "ViewController.h"
#import "RevanPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
RevanPerson *person = [[RevanPerson alloc] init];
[person personInstanceMethod];
}
@end
打印輸出:
2018-07-12 16:33:34.707800+0800 01-Runtime_objc_msgSend[5414:161031] -[RevanTest personInstanceMethod]
- 從輸出可以看出是輸出RevanTest對象中同名的personInstanceMethod方法
- 處理的對象中的方法必須要和當(dāng)前發(fā)送的消息名稱、參數(shù)類型保持一致,否則會崩潰
- 4、只要是方法名SEL相同,至于是調(diào)用了RevanTest對象中的personInstanceMethod對象方法還是personInstanceMethod類方法都有可以能
- 4.1:RevanTest
#import <Foundation/Foundation.h>
@interface RevanTest : NSObject
+ (void)personInstanceMethod;
- (void)personInstanceMethod;
@end
#import "RevanTest.h"
@implementation RevanTest
+ (void)personInstanceMethod {
NSLog(@"%s", __func__);
}
- (void)personInstanceMethod {
NSLog(@"%s", __func__);
}
@end
#import <Foundation/Foundation.h>
@interface RevanPerson : NSObject
- (void)personInstanceMethod;
@end
#import "RevanPerson.h"
#import "RevanTest.h"
@implementation RevanPerson
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(personInstanceMethod)) {
//返回的是class對象
return [RevanTest class];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
#import "ViewController.h"
#import "RevanPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
RevanPerson *person = [[RevanPerson alloc] init];
//調(diào)用的是實例方法
[person personInstanceMethod];
}
@end
打印輸出:
2018-07-12 22:59:31.363051+0800 02-runtime[1452:65484] +[RevanTest personInstanceMethod]
- 調(diào)用的是類方法中的實現(xiàn)
- forwardingTargetForSelector:方法的返回值為nil或者返回值中不包含aSelector方法時都會崩潰
- 當(dāng)forwardingTargetForSelector:沒有返回處理轉(zhuǎn)發(fā)消息的對象時,系統(tǒng)調(diào)用方法簽名方法中,可以在方法簽名的執(zhí)行代碼中做任何事
2、方法簽名
#import <Foundation/Foundation.h>
@interface RevanPerson : NSObject
- (void)personInstanceMethod;
@end
#import "RevanPerson.h"
#import "RevanTest.h"
@implementation RevanPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(personInstanceMethod)) {
//返回方法簽名:也就是aSelector的參數(shù)
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
// 滿足方法簽名后就會調(diào)用forwardInvocation:方法
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s", __func__);
}
@end
#import "ViewController.h"
#import "RevanPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
RevanPerson *person = [[RevanPerson alloc] init];
//調(diào)用的是實例方法
[person personInstanceMethod];
}
@end
打印輸出:
2018-07-12 23:24:51.887746+0800 02-runtime[1817:84543] -[RevanPerson forwardInvocation:]
- 所以可以在forwardInvocation:中做任何事
- 類方法也是同樣的道理,只需要把方法前面的"-"改為"+"就可以了
-
消息轉(zhuǎn)發(fā)流程
五、源碼
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {//是否有緩存,來查找緩存
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.read();
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
//沒有初始化
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertReading();
#pragma mark - 一:消息發(fā)送
// Try this class's cache.
//MARK:1.1:從類的緩存中尋找
imp = cache_getImp(cls, sel);
if (imp) goto done;
//MARK:1.2:如果緩存中沒有,就從類對象的方法列表中尋找(class_rw_t中的 methods中尋找)
// Try this class's method lists.
{
//獲取meth
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
//如果找在類對象的方法列表中找到方法,將找到的方法緩存到當(dāng)前cls中
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
//MARK:1.3:從父類的緩存和方法列表中尋找
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
//MARK:1.3.1、從父類的緩存中尋找
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
// 在某一個父類的緩存中找到了方法,并且緩存到cls類的緩存中
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
//MARK:1.3.2、從父類的方法列表中尋找
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 在某一個父類的方法列表中找到了方法,并且緩存到cls類的緩存中
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
#pragma mark - 二:動態(tài)方法解析
// No implementation found. Try method resolver once.
// triedResolver初始值為 NO
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
#pragma mark - 三:消息轉(zhuǎn)發(fā)
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}