IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> [iOS开发]消息传递和消息转发机制 -> 正文阅读

[移动开发][iOS开发]消息传递和消息转发机制

消息传递机制的学习

之前有写过这个机制的学习,第十一条
effectiveOC2.0阅读笔记(二对象\消息\运行期)

在对象上调用方法,术语就叫做传递消息,消息有名称和选择器(方法),可以接受参数,还可能有返回值。

在很多语言,比如 C ,调用一个方法其实就是跳到内存中的某一点并开始执行一段代码。没有任何动态的特性,因为这在编译时就决定好了。

而在 Objective-C 中,[object foo] 语法并不会立即执行 foo这个方法的代码。它是在运行时给object 发送一条叫 foo 的消息。这个消息,也许会由 object 来处理,也许会被转发给另一个对象,或者不予理睬假装没收到这个消息。多条不同的消息也可以对应同一个方法实现。这些都是在程序运行的时候决定的。

消息传递机制的学习其实就是理解OC是怎么样进行调用方法的。

id returnValue = [someObject messageName:parameter];

这样一条代码编译器会将其处理成

 id returnValue = objc_msgSend(someObject, @selectro(messageName:), parameter);

选择子SEL

OC在编译时会根据方法的名字(包括参数序列),生成一个用来区分这个办法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么他们的ID就是相同的。所以不管是父类还是子类,名字相同那么ID就是一样的。

	SEL sell1 = @selector(eat:);
    NSLog(@"sell1:%p", sell1);
    SEL sell2 = @selector(eat);
    NSLog(@"sell2:%p", sell2);
    //sell1:0x100000f63
//sell2:0x100000f68

我们需要注意,@selector等同于是把方法名翻译成SEL方法名,其仅仅关心方法名和参数个数,并不关心返回值与参数类型

生成SEL的过程是固定的,因为它只是一个表明方法的ID,不管是在哪个类写这个dayin方法,SEL值都是固定一个

在Runtime中维护了一个SEL的表,这个表存储SEL不按照类来存储,只要相同的SEL就会被看做一个,并存储到表中。在项目加载时,会将所有方法都加载到这个表中,而动态生成的方法也会被加载到表中。

那么不同的类可以拥有相同的方法,不同类的实例对象执行相同的selector时会在各自的方法列表中去根据SEL去寻找自己类对应的IMP。

IMP本质就是一个函数指针,这个被指向的函数包含一个接收消息的对象id,调用方法的SEL,以及一些方法参数,并返回一个id。因此我们可以通过SEL获得它所对应的IMP,在取得了函数指针之后,也就意味着我们取得了需要执行方法的代码入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。

objc_msgSend()的执行流程

  1. 消息发送阶段:负责从类及父类的缓存列表及方法列表查找方法
  2. 动态解析阶段:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现
  3. 消息转发阶段:如果也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给可以处理消息的接受者来处理

消息发送阶段

	ENTRY _objc_msgSend //进入消息转发
	UNWIND _objc_msgSend, NoFrame
//p0寄存器,消息接收者
🐴	cmp	p0, #0			// nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
🐴	b.le	LNilOrTagged	//b是跳转,le是小于等于,也就是p0小于等于0时,跳转到LNilOrTagged
#else
	b.eq	LReturnZero
#endif
	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13		// p16 = class
LGetIsaDone:
🐴	CacheLookup NORMAL	//缓存查找

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged://如果接收者为nil,跳转至此
🐴	b.eq	LReturnZero		如果消息接受者为空,直接退出这个函数

	// tagged
	adrp	x10, _objc_debug_taggedpointer_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
	ubfx	x11, x0, #60, #4
	ldr	x16, [x10, x11, LSL #3]
	adrp	x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
	add	x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
	cmp	x10, x16
	b.ne	LGetIsaDone

	// ext tagged
	adrp	x10, _objc_debug_taggedpointer_ext_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
	ubfx	x11, x0, #52, #8
	ldr	x16, [x10, x11, LSL #3]
	b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	ret

	END_ENTRY _objc_msgSend
	//结束
  1. 首先从cmp p0,#0开始,这里p0是寄存器,存放的是消息接受者。b.le LNilOrTagged,b是跳转到的意思。le是如果p0小于等于0,总体意思是若p0小于等于0,则跳转到LNilOrTagged,执行b.eq LReturnZero直接退出这个函数
  2. 如果消息接受者不为nil,汇编继续跑,到CacheLookup NORMAL,来看一下具体的实现
//开始
.macro CacheLookup //这是一个宏定义
	// p1 = SEL, p16 = isa
	ldp	p10, p11, [x16, #CACHE]	// p10 = buckets, p11 = occupied|mask
#if !__LP64__
	and	w11, w11, 0xffff	// p11 = mask
#endif
	and	w12, w1, w11		// x12 = _cmd & mask
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
		             // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: p12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f
	ldp	p17, p9, [x12, #-BUCKET_SIZE]!	// {imp, sel} = *--bucket
	b	1b			// loop

3:	// wrap: p12 = first bucket, w11 = mask
	add	p12, p12, w11, UXTW #(1+PTRSHIFT)
		                        // p12 = buckets + (mask << 1+PTRSHIFT)

	// Clone scanning loop to miss instead of hang when cache is corrupt.
	// The slow path may detect any corruption and halt later.

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
🐴	CacheHit $0			//缓存命中,在缓存中找到了对应的方法及其实现-----------------
	
	
2:	// not hit: p12 = not-hit bucket
🐴	CheckMiss $0			//在缓存中没有找到对应的办法
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f
	ldp	p17, p9, [x12, #-BUCKET_SIZE]!	// {imp, sel} = *--bucket
	b	1b			// loop

3:	// double wrap
	JumpMiss $0
	
.endmacro

在缓存中找到了方法那就直接调用,下面看一下从缓存中没有找到方法怎么办
没有找到会执行CheckMiss
,我们对其进行查看

.macro CheckMiss
	// miss if bucket->sel == 0
.if $0 == GETIMP
	cbz	p9, LGetImpMiss
.elseif $0 == NORMAL
🐴	cbz	p9, __objc_msgSend_uncached //缓存中没有找到方法时,主要执行这个方法
.elseif $0 == LOOKUP
	cbz	p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

.macro JumpMiss
.if $0 == GETIMP
	b	LGetImpMiss
.elseif $0 == NORMAL
	b	__objc_msgSend_uncached
.elseif $0 == LOOKUP
	b	__objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

那我们就来看一下__objc_msgSend_uncached

	STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band p16 is the class to search
	
🐴	MethodTableLookup //去方法列表中去查找方法
	TailCallFunctionPointer x17

	END_ENTRY __objc_msgSend_uncached

通过MethodTableLookup这个字面名称我们就大概知道这是从方法列表中去查找方法。我们再查看一下它的结构

.macro MethodTableLookup
	
	// push frame
	SignLR
	stp	fp, lr, [sp, #-16]!
	mov	fp, sp

	// save parameter registers: x0..x8, q0..q7
	sub	sp, sp, #(10*8 + 8*16)
	stp	q0, q1, [sp, #(0*16)]
	stp	q2, q3, [sp, #(2*16)]
	stp	q4, q5, [sp, #(4*16)]
	stp	q6, q7, [sp, #(6*16)]
	stp	x0, x1, [sp, #(8*16+0*8)]
	stp	x2, x3, [sp, #(8*16+2*8)]
	stp	x4, x5, [sp, #(8*16+4*8)]
	stp	x6, x7, [sp, #(8*16+6*8)]
	str	x8,     [sp, #(8*16+8*8)]

	// receiver and selector already in x0 and x1
	mov	x2, x16
🐴	bl	__class_lookupMethodAndLoadCache3//通过这个方法来查找缓存和方法列表

	// IMP in x0
	mov	x17, x0
	
	// restore registers and return
	ldp	q0, q1, [sp, #(0*16)]
	ldp	q2, q3, [sp, #(2*16)]
	ldp	q4, q5, [sp, #(4*16)]
	ldp	q6, q7, [sp, #(6*16)]
	ldp	x0, x1, [sp, #(8*16+0*8)]
	ldp	x2, x3, [sp, #(8*16+2*8)]
	ldp	x4, x5, [sp, #(8*16+4*8)]
	ldp	x6, x7, [sp, #(8*16+6*8)]
	ldr	x8,     [sp, #(8*16+8*8)]

	mov	sp, fp
	ldp	fp, lr, [sp], #16
	AuthenticateLR

.endmacro

因为汇编的函数比C++的多一个下划线
看一下_class_lookupMethodAndLoadCache3的实现

/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher 
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

主要就是实现了lookUpImpOrForward()这个方法,然后我们再查找一下这个方法

学下面这个代码的时候我们需要明白什么是方法缓存

苹果认为如果一个方法被调用了,那个这个方法有更大的几率被再此调用,既然如此直接维护一个缓存列表,把调用过的方法加载到缓存列表中,再次调用该方法时,先去缓存列表中去查找,如果找不到再去方法列表查询。这样避免了每次调用方法都要去方法列表去查询,大大的提高了速率

继续开干

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.lock();
    checkIsKnownClass(cls);

    if (!cls->isRealized()) {
        realizeClass(cls);
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.lock();
    }

    
 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache.
//再查找一次缓存中有没有,因为担心代码在运行中动态添加了方法
🐴    imp = cache_getImp(cls, sel);
    if (imp) goto done;
   
   // Try this class's method lists.
🐴   // 如果是类对象 下面的代码块从类的方法列表中去查找
    {
🐴🐴🐴   Method meth = getMethodNoSuper_nolock(cls, sel);//查找方法
        if (meth) {
        //把方法缓存到类对象的缓存列表中,并返回方法的IMP
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // 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.");
            }
            
            // 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.
                    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;
                }
            }
            
            // Superclass method list.
    
            //查找父类的方法列表
🐴            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }
。。。。。。。。。
省略部分涉及到动态方法解析和消息转发

怎么从类对象中查找方法,主要是在getMethodNoSuper_nolock()这个方法中

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
🐴        method_t *m = search_method_list(*mlists, sel);//通过这个方法具体来查找方法
        if (m) return m;
    }
    return nil;
}

动态解析阶段

动态解析阶段流程

  • 在自己类对象的缓存和方法列表中都没有找到方法,并且在父类的类对象的缓存和方法列表中都没有找到方法时,这时候就会启动动态方法解析
  • 省略部分的动态解析我们再看一下
// No implementation found. Try method resolver once.
	//如果上述在类对象和父类对象中没有查到方法
	//我们进入动态方法解析
    if (resolver  &&  !triedResolver) {//triedResolver用来判断是否曾经进行过动态方法解析,如果没有那就进入动态方法解析,如果进行过,就跳过
        runtimeLock.unlock();
🐴        _class_resolveMethod(cls, sel, inst); //动态方法解析函数
        runtimeLock.lock();
        // 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;//进行过动态方法解析就把这个标志位设置为YES
        goto retry;//前面发消息的整个过程,也就是说进行了方法解析后还要回到前面,从类对象的缓存和方法列表中查找。如果动态方法解析添加了方法实现,那么自然能找到,如果没有,那么还是找不到方法实现,此时也不会进入动态方法解析,而是直接进行下一步,消息转发
    }

大体流程我们知道了
来看一下_class_resolveMethod是怎么实现动态方法解析函数的

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
	//判断是否 不是元类对象
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
 🐴       _class_resolveInstanceMethod(cls, sel, inst);
    } 
    //不是类对象就肯定是元类对象
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
  🐴      _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
  🐴          _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

其实实现很简单,就是判断是类对象还是元类对象,如果是类对象则说明调用的实例方法,则调用类的resolveInstanceMethod:方法,
如果是元类对象,则说明是调用的类方法,则调用类的resolveClassMethod:方法。

resolveClassMethod:默认返回值是NO,如果你想在这个函数里添加方法实现,你需要借助class_addMethod

class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 

@cls : 给哪个类对象添加方法
@name : SEL类型,给哪个方法名添加方法实现
@imp : IMP类型的,要把哪个方法实现添加给给定的方法名
@types : 就是表示返回值和参数类型的字符串

动态解析测试

实现一个类,类在.h文件中声明一个方法,但在.m文件中并没有实现这个方法

我们在外部调用这个方法就会导致程序崩溃

其实很容易理解是为啥


  • 第一步查找方法中,在自己的类对象以及父类的类对象中都没有找到这个方法的实现
  • 所以转向动态方法解析,动态方法解析我们什么也没做,
  • 所以进行第三步,转向消息转发,消息转发我们也什么都没做,最后产生崩溃

  • 动态方法解析
    • 当第一步中方法查找失败时会进行的,当调用的是对象方法时,动态方法解析是在resolveInstanceMethod方法中实现的
    • 当调用的是类方法时,动态方法解析是在resolveClassMethod中实现的
    • 利用动态方法解析和runtime,我们可以给一个没有实现的方法添加方法实现。
#import "Person.h"
#import <objc/message.h>
@implementation Person

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(eat)) {
        Method method  = class_getInstanceMethod(self, @selector(test2));
        class_addMethod(self, sel, method_getImplementation(method), "123");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)test2 {
    NSLog(@"动态方法解析");
}

@end

消息转发阶段

消息转发流程

	//如果上述在类对象和父类对象中没有查到方法
	//我们就进入动态方法解析
 if (resolver  &&  !triedResolver) {//triedResolver用来判断是否曾经进行过动态方法解析,如果没有那就进入动态方法解析,如果进行过,就跳过
        runtimeLock.unlock();
  🐴      _class_resolveMethod(cls, sel, inst); //动态方法解析函数
        runtimeLock.lock();
        // 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; //进行过动态方法解析就把这个标识为设置为YES
  🐴     goto retry;//retry是前面的发送消息的过程
    }
    	
  🐴    //如果动态方法解析失败,就进入消息转发

  🐴  imp = (IMP)_objc_msgForward_impcache; //由这一步进入消息转发
    cache_fill(cls, sel, imp, inst);
//如果消息转发失败,程序崩溃
 done:
  🐴  runtimeLock.unlock();

所以如果本类没有能力去处理这个消息,那么就转发给其他的类,让其他类去处理。

看一下进行消息转发的函数__objc_msgForward_impcache的具体实现

STATIC_ENTRY __objc_msgForward_impcache
	// Method cache version

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band condition register is NE for stret, EQ otherwise.

	jne	__objc_msgForward_stret
	jmp	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache
	
	
	ENTRY __objc_msgForward
	// Non-stret version

	movq	__objc_forward_handler(%rip), %r11
	jmp	*%r11

	END_ENTRY __objc_msgForward

可惜__objc_msgForward_handler并没有开源
看个这个函数实现的伪代码拿来学习一下

int __forwarding__(void *frameStackPointer, int isStret) { 
    id receiver = *(id *)frameStackPointer; 
    SEL sel = *(SEL *)(frameStackPointer + 8); 
    const char *selName = sel_getName(sel); 
    Class receiverClass = object_getClass(receiver); 

    // 调用 forwardingTargetForSelector: 
    if (class_respondsToSelector(receiverClass,@selector(forwardingTargetForSelector:))) { 
      //首先调用消息接收者的forwardingTargetForSelector方法来获取消息转发对象
 🐴    id forwardingTarget = [receiver forwardingTargetForSelector:sel]; 
        if (forwardingTarget && forwarding != receiver) { 
            if (isStret == 1) { 
                    int ret; 
                    objc_msgSend_stret(&ret,forwardingTarget, sel, ...); 
                    return ret; 
            } 
          	//然后直接给这个消息转发对象发送消息
            return objc_msgSend(forwardingTarget, sel, ...); 
       } 
  } 

// 僵尸对象 
const char *className = class_getName(receiverClass); 
const char *zombiePrefix = "_NSZombie_"; 
size_t prefixLen = strlen(zombiePrefix); // 0xa 
if (strncmp(className, zombiePrefix, prefixLen) == 0) { 
    CFLog(kCFLogLevelError, 
            @"*** -[%s %s]: message sent to deallocated instance %p", 
            className + prefixLen, 
            selName, 
            receiver); 
    <breakpoint-interrupt> 
} 

//如果forwardingTargetForSelector没有实现或者返回值为0都会继续往下执行
  
// 调用 methodSignatureForSelector 获取方法签名后再调用forwardInvocation 
🐴if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) { 
  //如果methodSignatureForSelector返回值不为nil
  🐴NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel]; 
    if (methodSignature) { 
       BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct; 
       if (signatureIsStret != isStret) { 
         CFLog(kCFLogLevelWarning , 
                    @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.", 
            selName, 
            signatureIsStret ? "" : not, 
            isStret ? "" : not); 
} 

   //并且实现了forwardInvocation方法
🐴if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) { 
    NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer]; 

    [receiver forwardInvocation:invocation]; 

    void *returnValue = NULL;
    [invocation getReturnValue:&value]; 
    return returnValue; 
    } 
    else { 
        CFLog(kCFLogLevelWarning , 
                @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
             receiver, 
             className); 
        return 0; 
        } 
    } 
} 

SEL *registeredSel = sel_getUid(selName); 

// selector 是否已经在 Runtime 注册过 
if (sel != registeredSel) { 
    CFLog(kCFLogLevelWarning , 
            @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", 
            sel, 
            selName, 
            registeredSel); 
}
  
🐴//如果上面两个方法都未实现,那么就会崩溃
// doesNotRecognizeSelector 
else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) { 
    [receiver doesNotRecognizeSelector:sel]; 
}  else { 
    CFLog(kCFLogLevelWarning , 
            @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort", 
            receiver, 
            className); 
} 

// The point of no return. 
kill(getpid(), 9);
}

消息转发测试

如果没有在发消息阶段找到该方法的实现
动态方法解析和消息转发阶段也是什么都没有做,那么就会崩溃。

第一阶段消息发送结束后会进行第二阶段动态消息解析,没有就会进入第三阶段-消息转发。

消息转发

  • 首先依赖于- (id)forwardingTargetForSelector:(SEL)aSelector这个方法,若是这个方法直接返回一个消息转发对象,则直接通过objc_msgSend()把这个消息转发给消息转发对象。
  • 若是没实现或为nil,则会执行-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector这个函数以及-(void)forwardInvocation:(NSInvocation *)anInvocation这个函数
//在第二阶段动态方法解析阶段没有进行任何处理
//在- (id)forwardingTargetForSelector:(SEL)aSelector这个函数中也不做处理
//代码继续执行到- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector这个函数
//在这个函数中我们需要返回一个方法签名:
//方法签名:返回值类型,参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if(aSelector == @selector(testAge:)){
        return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
    }
    return [super methodSignatureForSelector:aSelector];
}

"v@:I"中对应关系为
v : void
@ : eat
: : sel
I : NSInteger
为了协助运行时系统,编译器用字符串为每个方法的返回值和参数类型和方法选择器编码。使用的编码方案在其他情况下也很有用,所以它是public 的,可用于@encode() 编译器指令。当给定一个类型参数,返回一个编码类型字符串。类型可以是一个基本类型如int,指针,结构或联合标记,或任何类型的类名,事实上,都可以作为C sizeof() 运算符的参数。这个机制也是为了提高Runtime的效率.

编码翻译表:
请添加图片描述

这里为什么只需要返回 返回值类型和参数类型?

  • person方法调用- (void)testAge:(int)age 这个过程,我们就需要知道方法调用者,方法名,方法参数。
  • 而在Person.m中我们肯定知道方法调用者是person对象,方法名也知道是"testAge:",那么现在不知道的就是方法参数了
  • 那么这个方法签名就是表示这个方法参数的,包括返回值和参数,这样方法调用者,方法名和方法参数就都知道了。

再看最后一个函数- (void)forwardInvocation:(NSInvocation *)anInvocation的实现

///NSInvocation封装了一个方法调用,包括:方法调用者,方法名,方法参数
//   anInvocation.target 方法调用者
//   anInvocation.selector 方法名
//   [anInvocation getArgument:NULL atIndex:0];
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%@ %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
    int age;
    [anInvocation getArgument:&age atIndex:2];
    NSLog(@"%d", age);
    //这行代码是把方法的调用者改变为student对象
    [anInvocation invokeWithTarget:[[Student alloc] init]];
}

在这个方法中有一个NSInvocation类型的anInvocation参数,我们可以通过这个参数获取person对象调用- (void)testAge:(int)age方法这个过程中的方法调用者,方法名,方法参数。
然后我们可以通过修改方法调用者来达到消息转发的效果,这里是把方法调用者修改为了student对象。这样就完成了转发消息给student对象。

总结

我们要先明白OC方法调用的本质就是消息发送
消息发送是SEL-IMP的查找过程

我们先进行正常的消息发送
在一个函数找不到时,OC提供了三种方式去补救

  1. 调用resolveInstanceMethod或者resolveClassMethod给机会给一个没有实现的方法添加方法函数
  2. 调用forwardingTargetForSelector让别的对象去执行这个函数
  3. forwardInvocation灵活的将目标函数以其他形式执行(比如触发消息前,先以某种方式改变消息内容,比如追加另一个参数、或者改变选择子等等)

如果失效,抛出异常

再以一个流程图来体会一下这其中消息传递和消息转发的过程

objc_msgSend流程

同样 消息转发也涉及到了一个知识点-------多继承
多继承可以允许子类从多个父类派生,而OC并不支持多继承,不过我们可以通过协议、分类、消息转发来间接实现
iOS多继承的实现及区别
这个作者用demo很好的示范了这三种情况来实现多继承
嫖一张总截图
在这里插入图片描述

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-26 12:12:28  更:2021-07-26 12:12:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/11 3:43:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码