Runtime(二)-objc_msgSend

一、objc_msgSend

  • OC中的方法调用,其实都是转换为objc_msgSend函数的调用
  • objc_msgSend的执行流程可以分为3大阶段
    • a)消息发送
    • b)动态方法解析
    • c)消息转发

二、消息发送

  • 在Objective-C对象的分类中详细的说了instance对象、class对象、meta-class对象。也详细的说消息发送和方法寻找的过程
    objc_msgSend消息发送图盗图.png
  • 在Runtime(一)中又进一步说明了Class的结构,更加详细的说明了Class中方法存储的过程和寻找过程,先要从缓存中寻找是否有方法,然后再从方法列表中寻找方法,这个整个过程可以理解为消息发送,也就是整个OC执行方法过程中的第一步

1、通过源码来窥探消息发送原理

  • 1、查看源码方式
在终端输入下面的命令把OC代码的.m文件转换成.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc [OC文件] -o [.cpp文件]
  • 2、测试代码
  • 2.1:RevanPerson
#import 

@interface RevanPerson : NSObject

- (void)personInstanceMethod;

@end

#import "RevanPerson.h"

@implementation RevanPerson

- (void)personInstanceMethod {
    NSLog(@"%s", __func__);
}

@end
  • 2.2:测试代码
#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]
  • 3、测试代码源码
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对象调用personInstanceMethod方法
    objc_msgSend(
                 person, sel_registerName("personInstanceMethod")
                 );
}
  • 源码解析
    • 1、在实例化person对象时使用了2次objc_msgSend发送消息函数;里面的一次是给RevanPerson类发送一个alloc消息;外面的一次是给RevanPerson的instance对象发送一个init消息
    • 2、最后给person对象发送一个personInstanceMethod消息

2、当找不到personInstanceMethod方法会怎么样

  • 1:RevanPerson
#import 

@interface RevanPerson : NSObject

- (void)personInstanceMethod;

@end


#import "RevanPerson.h"

@implementation RevanPerson

@end

  • 2:测试代码
#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'
  • 这是找不到方法最常见的崩溃信息。经过上面图中的分析来看,我们了解了在发送一个消息并且寻找这个消息的过程,在我们看来寻遍上图的过程后如果依然没有找到就会崩溃,输出上面的错误信息。在寻找消息的过程中如果经历了上面的过程依然没有找到就会查看有无方法动态解析,这就是下面讨论的
  • 消息发送流程
    objc_msgSend消息发送.png

三、动态方法解析

  • 在动态方法解析中可以让代码执行其他的方法,好比上面的问题,因为没有实现personInstanceMethod方法,所以程序会因为找不到方法而崩溃。为了不让程序崩溃,我们可以重写resolveInstanceMethod:或者resolveClassMethod:方法来给personInstanceMethod方法添加实现

1、instance方法的动态方法解析

  • 1.1:RevanPerson
#import 

@interface RevanPerson : NSObject

- (void)personInstanceMethod;

@end

#import "RevanPerson.h"
#import 

@implementation RevanPerson

/**
 动态解析instanceMethod方法

 @param sel 发送的具体消息
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(personInstanceMethod)) {
        // 把一个方法构造成一个 Method类型
        Method method = class_getInstanceMethod(self, @selector(resolveTest));
        // 获取Method类型中方法的地址
        IMP imp = method_getImplementation(method);
        // 获取Method类型参数
        const char *types = method_getTypeEncoding(method);
        // 给类添加对象方法,给元类添加类方法
        class_addMethod(self, sel, imp, types);
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)resolveTest {
    NSLog(@"%s", __func__);
}

@end
  • 1.2:测试代码
#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]
  • 会发现我们外面调用的person对象的personInstanceMethod方法,但是真正调用的是resolveTest方法,这就是动态方法解析的魅力所在,这也是动态语言的魅力所在
  • 这次也更好的让我们认识到,OC在调用方法的本质是给instance对象、类对象发送消息,当你在使用类对象调用类方法的时候,发现执行的是instance方法时,不要奇怪。
  • class_getInstanceMethod函数是用来把一个方法构造成一个 Method类型结构体
  • method_getImplementation函数是用来获取Method类型结构体中方法的地址
  • method_getTypeEncoding函数是用来获取Method类型结构体中参数
  • class_addMethod函数用来给class对象和meta-class对象添加方法
/**
 添加方法
 
 @param cls 给类添加instance方法;给元类添加类方法
 @param name 消息名称
 @param imp 方法地址
 @param types 方法参数和返回值信息
 */
 class_addMethod(cls, name, imp, types)

2、instance方法的动态方法解析调用函数

  • RevanPerson
#import 

@interface RevanPerson : NSObject

- (void)personInstanceMethod;

@end

#import "RevanPerson.h"
#import 

@implementation RevanPerson

void resolveFunc(id self, SEL _cmd) {
    NSLog(@"%@, %s", self, _cmd);
}

/**
 动态解析instanceMethod方法
 
 @param sel 发送的具体消息
 */
+ (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] , personInstanceMethod

3、class方法的动态方法解析

  • 1:RevanPerson
#import 

@interface RevanPerson : NSObject

+ (void)personClassMethod;

@end

#import "RevanPerson.h"
#import 

@implementation RevanPerson

/**
 动态解析instanceMethod方法
 
 @param sel 发送的具体消息
 */
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(personClassMethod)) {
        // 把一个方法构造成一个 Method类型数据结构
        Method method = class_getClassMethod(self, @selector(resolveTest));
        // 获取Method类型中方法的地址
        IMP imp = method_getImplementation(method);
        // 获取Method类型参数
        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

  • 2、测试代码
#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]
  • 小结
    • 在构造Method类型的结构体时要使用class_getClassMethod函数
    • 在使用class_addMethod添加类方法时,第一个参数要传入元类对象

4、Class方法的动态方法解析调用函数

  • 1:RevanPerson
#import 

@interface RevanPerson : NSObject

+ (void)personClassMethod;

@end

#import "RevanPerson.h"
#import 

@implementation RevanPerson

void resolveFunc(id self, SEL _cmd) {
    NSLog(@"%@, %s", self, _cmd);
}

/**
 动态解析instanceMethod方法

 @param sel 发送的具体消息
 */
+ (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
  • 2:测试代码
#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
  • 动态方法解析流程
    动态方法解析.png

四、消息转发

  • 当在动态方法解析过程中程序员没有重写resolveClassMethod方法和resolveInstanceMethod方法,就会进入消息转发阶段
  • 消息转发顾名思义,因为自己没有办法处理这个方法,所以要让其他对象来处理

1、转给其他对象处理

  • 1:RevanTest
#import 

@interface RevanTest : NSObject

- (void)personInstanceMethod;
@end

#import "RevanTest.h"

@implementation RevanTest

- (void)personInstanceMethod {
    NSLog(@"%s", __func__);
}
@end
  • 2:RevanPerson
#import 

@interface RevanPerson : NSObject

- (void)personInstanceMethod;

@end

#import "RevanPerson.h"
#import 
#import "RevanTest.h"

@implementation RevanPerson

- (id)forwardingTargetForSelector:(SEL)aSelector {
    
    if (aSelector == @selector(personInstanceMethod)) {
        return [[RevanTest alloc] init];
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

@end
  • 3:测试代码
#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方法
  • 处理的对象中的方法必须要和当前发送的消息名称、参数类型保持一致,否则会崩溃
  • 4、只要是方法名SEL相同,至于是调用了RevanTest对象中的personInstanceMethod对象方法还是personInstanceMethod类方法都有可以能
  • 4.1:RevanTest
#import 

@interface RevanTest : NSObject

+ (void)personInstanceMethod;
- (void)personInstanceMethod;

@end

#import "RevanTest.h"

@implementation RevanTest

+ (void)personInstanceMethod {
    NSLog(@"%s", __func__);
}

- (void)personInstanceMethod {
    NSLog(@"%s", __func__);
}

@end
  • 4.2:RevanPerson
#import 

@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
  • 4.3:测试代码
#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 22:59:31.363051+0800 02-runtime[1452:65484] +[RevanTest personInstanceMethod]
  • 调用的是类方法中的实现
  • forwardingTargetForSelector:方法的返回值为nil或者返回值中不包含aSelector方法时都会崩溃
  • 当forwardingTargetForSelector:没有返回处理转发消息的对象时,系统调用方法签名方法中,可以在方法签名的执行代码中做任何事

2、方法签名

  • 1:RevanPerson
#import 

@interface RevanPerson : NSObject

- (void)personInstanceMethod;
@end

#import "RevanPerson.h"
#import "RevanTest.h"

@implementation RevanPerson

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(personInstanceMethod)) {
        //返回方法签名:也就是aSelector的参数
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// 满足方法签名后就会调用forwardInvocation:方法
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%s", __func__);
}

@end
  • 2:测试代码
#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 23:24:51.887746+0800 02-runtime[1817:84543] -[RevanPerson forwardInvocation:]
  • 所以可以在forwardInvocation:中做任何事
  • 类方法也是同样的道理,只需要把方法前面的"-"改为"+"就可以了
  • 消息转发流程
    消息转发流程.png

五、源码

/***********************************************************************
* 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 - 一:消息发送
    // 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) {
            //如果找在类对象的方法列表中找到方法,将找到的方法缓存到当前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 - 二:动态方法解析
    // 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 - 三:消息转发
    // 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;
}

你可能感兴趣的:(Runtime(二)-objc_msgSend)