极客算法

ObjC - 消息和转发机制

2016-04-05

介绍

这是之前《死磕Objective-C runtime运行》的重写。还是延续问答的方式,日期复用当时的日期,并加入测试代码

本文涉及测试代码在这里

问题1: 说一说objc_msgSend方法

Message: 消息, 即objc_msgSendobjc_msgSendSuper

These functions must be cast to an appropriate function pointer type before being called

Sends a message with a simple return value to an instance of a class. When it encounters a method call, the compiler generates a call to one of the functions objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, or objc_msgSendSuper_stret. Messages sent to an object’s superclass (using the super keyword) are sent using objc_msgSendSuper; other messages are sent using objc_msgSend. Methods that have data structures as return values are sent using objc_msgSendSuper_stret and objc_msgSend_stret.

这里复用上一遍文章代码

其中主要摘抄如下:

- (instancetype)init{
    if(self = [super init]){
        //因为PrisonCat没有覆盖class方法,所以调用self和super结果是一样的,如果把下面class注释去掉,cls = super_cls就不一样了
        Class cls = [self class];
        Class super_cls = [super class];
        printf("place holder");
    }
    return self;
}

//- (Class)class{
//    return objc_getClass("NSObject");
//}

Clang重写之后:

static instancetype _I_PrisonCat_init(PrisonCat * self, SEL _cmd) {
    if(self = ((PrisonCat *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("PrisonCat"))}, sel_registerName("init"))){
        Class cls = ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"));
        Class super_cls = ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("PrisonCat"))}, sel_registerName("class"));
        printf("place holder");
    }
    return self;
}
}

从文档和转意代码来看,objective-c消息发送(方括号语法)主要靠objc_msgSend和objc_msgSendSuper来实现。

其中[self class]通过objc_msgSend

其中[super class]通过和class_getSuperclass objc_msgSendSuper

对于不同构架和返回函数,struct返回值用到objc_msgSend_stret, float返回值用到objc_msgSend_fpretobjc_msgSend_fp2ret

相应的,对于super,struct返回值用到objc_msgSendSuper

问题2: objc_msgSend失败了会怎么样

  1. Dynamic Method ResolutionresolveClassMethod:resolveInstanceMethod, 若返回YES同时运行时状态有新函数加入,则直接调用实现,完成消息发送
  2. Message Forwarding: 若不然, forwardingTargetForSelector: 若返回不是nil和self,则完成消息发送
  3. Message Forwarding: 若不然, methodSignatureForSelector: 若返回不为空,则发送消息给forwardInvocation:由Invocation完成
  4. NSInvalidArgumentException : 若不然, 调用doesNotRecognizeSelector:抛出异常

class_addMethod测试

class_addMethod最后的参数参考文档

@interface TestClassAddMethod: NSObject
- (NSString *)hello:(NSString *)content;
@end

NSString * hello(id self, SEL selector, NSString *content){
    return [NSString stringWithFormat:@"%@", content];
}

@implementation TestClassAddMethod
- (instancetype)init{
    if(self = [super init]){
        class_addMethod([self class], @selector(hello:), (IMP)hello, "@@:@"); //@=id :=sel
    }
    return self;
}
@end

假的resolveMethod测试

即使resolveInstanceMethod返回YES, 若没有该方法,直接回调用doesNotRecognizeSelector,抛出异常

@interface TestClassFakeResolve: NSObject
- (NSString *)hello:(NSString *)content;
@end

@implementation TestClassFakeResolve
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(hello:)) {
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end

ios_messaging_fake_resolve

ios_messaging_fake_resolve_crash

正常resolveMethod测试

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(hello:)) {
        class_addMethod([self class], sel, (IMP)hello, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

forwardingTargetForSelector测试

@interface TestClassForwardTarget: NSObject{
    TestClassAddMethod *_surrogate;
}
- (NSString *)hello:(NSString *)content;
@end

@implementation TestClassForwardTarget

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"resolveInstanceMethod called");
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (!_surrogate) {
        _surrogate = [[TestClassAddMethod alloc] init];
    }
    return _surrogate;
}

@end

forwardInvocation测试

@interface TestClassForwardInvocation: NSObject{
    TestClassAddMethod *_surrogate;
}
- (NSString *)hello:(NSString *)content;
@end

@implementation TestClassForwardInvocation

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"forwardingTargetForSelector called");
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{

    NSMethodSignature* signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if (!_surrogate) {
            _surrogate = [[TestClassAddMethod alloc] init];
        }

        signature = [_surrogate methodSignatureForSelector:aSelector];
    }
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    if ([_surrogate respondsToSelector: [anInvocation selector]]){
        [anInvocation invokeWithTarget:_surrogate];
    }
    else{
        [super forwardInvocation:anInvocation];
    }
}
@end

PS: 至于调用顺序,就只能用断点来查看了。

问题3: 说一说doesNotRecognizeSelector方法

doesNotRecognizeSelector是运行时找不到对应的SEL(方法)最后调用的函数,在NSObject里实现, 虽然注释写着已经改为CF实现,但是不影响我们学习,其内部实现主要是抛出NSInvalidArgumentException异常。这也是iOS常见的崩溃原因之一。

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
                object_getClassName(self), sel_getName(sel), self);
}

更多

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105-SW1


评论

内容:
其他: