阅读 782

OC消息机制和super关键字

博客链接OC消息机制和super关键字

消息发送

在Objective-C里面调用一个方法[object method],运行时会将它翻译成objc_msgSend(id self, SEL op, ...)的形式。

objc_msgSend

objc_msgSend的实现在objc-msg-arm.sobjc-msg-arm64.s等文件中,是通过汇编实现的。这里主要看在arm64objc-msg-arm64.s的实现。由于汇编不熟,里面的实现只能连看带猜。

	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
	MESSENGER_START

	cmp	x0, #0			// nil check and tagged pointer check
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
	ldr	x13, [x0]		// x13 = isa
	and	x16, x13, #ISA_MASK	// x16 = class	
LGetIsaDone:
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

LNilOrTagged:
    /* nil check,如果为空就是调用LReturnZero,LReturnZero里调用MESSENGER_END_NIL*/
	b.eq	LReturnZero		// nil check

	// tagged
	mov	x10, #0xf000000000000000
	cmp	x0, x10
	b.hs	LExtTag
	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]
	b	LGetIsaDone

LExtTag:
	// 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
	
LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	MESSENGER_END_NIL
	ret

	END_ENTRY _objc_msgSend
复制代码

上面的流程可能是这样的:

objc_msgsend

CacheLookup的注释有两处:

  1. calls imp or objc_msgSend_uncached
  2. Locate the implementation for a selector in a class method cache.

即使看不懂汇编代码,但是从上面的注释我们可以猜测,消息机制会先从缓存中去查找。

__objc_msgSend_uncached

通过方法名我们可以知道,没有缓存的时候应该会执行__objc_msgSend_uncached

	STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band x16 is the class to search
	
	MethodTableLookup
	br	x17

	END_ENTRY __objc_msgSend_uncached
复制代码

这里的MethodTableLookup里涉及到objc-runtime-new.mm文件中的_class_lookupMethodAndLoadCache3。该函数会调用lookUpImpOrForward函数。

lookUpImpOrForward

lookUpImpOrForward会返回一个imp,它的函数实现比较长,但是注释写的非常清楚。它的实现主要由以下几步(这里直接从缓存获取开始):

  1. 通过cache_getImp从缓存中获取方法,有则返回,否则进入第2步;
  2. 通过getMethodNoSuper_nolock从类的方法列表中获取,有加入缓存中并返回,否则进入第3步;
  3. 通过父类的缓存和父类的方法列表中寻找是否有对应的imp,此时会进入一个for循环,沿着类的父类一直往上找,直接找到NSObject为止。如果找到返回,否则进入第4步;
  4. 进入方法决议(method resolve)的过程即调用_class_resolveMethod,如果失败,进入第5步;
  5. 在缓存、当前类、父类以及方法决议都没有找到的情况下,Objective-C还为我们提供了最后一次翻身的机会,调用_objc_msgForward_impcache进行方法转发,如果找到便加入缓存;如果没有就crash。

上述过程中有几个比较重要的函数:

_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);
        }
    }
}
复制代码

上述函数会根据当前传入的类的是不是一个元类,在_class_resolveInstanceMethod_class_resolveClassMethod中选择一个进行调用。注释也说明了这两个方法的作用就是判断当前类是否实现了 resolveInstanceMethod:或者resolveClassMethod:方法,然后用objc_msgSend执行上述方法。

_class_resolveClassMethod

_class_resolveClassMethod_class_resolveInstanceMethod实现类似,这里就只看_class_resolveClassMethod的实现。

static void _class_resolveClassMethod(Class cls, SEL sel, id inst) {
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
         //没有找到resolveClassMethod方法,直接返回。
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // 缓存结果
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
    // 以下代码省略不影响阅读                          
}
复制代码

_objc_msgForward_impcache

	STATIC_ENTRY __objc_msgForward_impcache

	MESSENGER_START
	nop
	MESSENGER_END_SLOW

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	x17, [x17, __objc_forward_handler@PAGEOFF]
	br	x17
	
	END_ENTRY __objc_msgForward
复制代码

_objc_msgForward_impcache用来进行消息转发,但是其真正的核心是调用_objc_msgForward

消息转发

关于_objc_msgForwardobjc中并没有其相关实现,只能看到_objc_forward_handler。其实_objc_msgForward的实现是在CFRuntime.c中的,但是开源出来的CFRuntime.c并没有相关实现,但是也不影响我们对真理的追求。

我们做几个实验来验证消息转发。

消息重定向测试

// .h文件
@interface AObject : NSObject

- (void)sendMessage;

@end
// .m文件
@implementation AObject

/** 验证消息重定向 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(sendMessage)) {
         return [BObject new];
    }

    return [super forwardingTargetForSelector:aSelector];
}

@end

// .h文件
@interface BObject : NSObject

- (void)sendMessage;

@end

// .m文件
@implementation BObject

- (void)sendMessage {
    NSLog(@"%@ send message", self.class);
}

@end

// 调用
AObject *a = [AObject new];
[a sendMessage];
复制代码

运行结果:

2019-03-12 10:18:54.252949+0800 iOSCodeLearning[18165:5967575] BObject send message
复制代码

forwardingTargetForSelector:处打个断点,查看一下调用栈:

message_redirection

_CF_forwarding_prep_0___forwarding___这两个方法会先被调用了,之后调用了forwardingTargetForSelector:

方法签名测试

// .h文件
@interface AObject : NSObject

- (void)sendMessage;

@end
// .m文件
@implementation AObject

/** 消息重定向 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
   return nil;
}

/** 方法签名测试 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(sendMessage)) {
        return [BObject instanceMethodSignatureForSelector:@selector(sendMessage)];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL selector = [anInvocation selector];
    if (selector == @selector(sendMessage)) {
        [anInvocation invokeWithTarget:[BObject new]];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

@end

// .h文件
@interface BObject : NSObject

- (void)sendMessage;

@end

// .m文件
@implementation BObject

- (void)sendMessage {
    NSLog(@"%@ send message", self.class);
}

@end

// 调用
AObject *a = [AObject new];
[a sendMessage];
复制代码

method_signature

代码执行结果和消息重定向测试的运行结果一致。_CF_forwarding_prep_0___forwarding___这两个方法又再次被调用了,之后代码会先执行forwardingTargetForSelector:(消息重定向),消息重定向如果失败后调用methodSignatureForSelector:forwardInvocation:方法签名。所以说___forwarding___方法才是消息转发的真正实现。

crash测试

// .h文件
@interface AObject : NSObject

- (void)sendMessage;

@end
// .m文件
@implementation AObject

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
}

/** 验证Crash */
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    if (aSelector == @selector(sendMessage)) {
        NSLog(@"%@ doesNotRecognizeSelector", self.class);
    }
}

@end

// .h文件
@interface BObject : NSObject

- (void)sendMessage;

@end

// .m文件
@implementation BObject

- (void)sendMessage {
    NSLog(@"%@ send message", self.class);
}

@end

// 调用
AObject *a = [AObject new];
[a sendMessage];
复制代码

代码运行结果肯定是crash,结合上面的代码我们知道消息转发会调用___forwarding___这个内部方法。___forwarding___方法调用顺序是forwardingTargetForSelector:->methodSignatureForSelector:->doesNotRecognizeSelector:

我们用一张图表示整个消息发送的过程:

消息机制流程图

super关键字

我们先查看一下执行[super init]的时候,调用了那些方法

super_init

objc_msgSendSuper2的声明在objc-abi.h

// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);
复制代码

objc_super的定义如下:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};
复制代码

从上面的定义我们可以知道receiver即消息的实际接收者, super_class为指向当前类的父类。

所以该函数实际的操作是:从objc_super结构体指向的super_class开始查找,直到会找到NSObject的方法为止。找到后以receiver去调用。当然整个查找的过程还是和消息发送的流程一样。

所以我们能理解为什么下面这段代码执行的结果都是AObject了吧。虽然使用[super class],但是真正执行方法的对象还是AObject

// 代码
@implementation AObject

- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"%@", [super class]);
        NSLog(@"%@", [self class]);
    }
    
    return self;
}

@end

// 执行结果
2019-03-12 19:44:46.003313+0800 iOSCodeLearning[34431:7234182] AObject
2019-03-12 19:44:46.003442+0800 iOSCodeLearning[34431:7234182] AObject
复制代码