专栏名称: 3033
iOS开发
目录
相关文章推荐
天津市文化和旅游局  ·  早安·天津 ·  昨天  
天津市文化和旅游局  ·  早安·天津 ·  昨天  
生态梦网  ·  霸气登顶!天津介孩子可真行 ·  昨天  
生态梦网  ·  紧急提醒!滨海人明后天冷到哭! ·  2 天前  
生态梦网  ·  太火爆!生态城勇夺全市热门景区前两名 ·  2 天前  
51好读  ›  专栏  ›  3033

Runtime 从NullSafe源码看消息转发 机制

3033  · 掘金  ·  · 2017-12-14 00:35

正文

#####开篇 马上就要年底了再码一波,自己总结一下Runtime,打算总结一下Runtime的各种用法,结合一些常见的源码来分析一下,有错误在所难免,希望尽量少一点。。。 #####NullSafe与消息转发 在处理后台返回的数据时会碰到返回的空的情况,大家有自己的处理方式来增加代码的稳健性,这里就借常见的NullSafe的源码来举例。 NullSafe源码 内容并不算很多,主要的实现代码如下

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    //look up method signature
    NSMethodSignature *signature = [super methodSignatureForSelector:selector];
    if (!signature)
    {
        //check implementation cache first
        NSString *selectorString = NSStringFromSelector(selector);
        signature = signatureCache[selectorString];
        if (!signature)
        {
            @synchronized([NSNull class])
            {
                //check again, in case it was resolved while we were waitimg
                signature = signatureCache[selectorString];
                if (!signature)
                {
                    //创建一个缓存 获取到所有的类名
                    //not supported by NSNull, search other classes
                    if (signatureCache == nil)
                    {
                        if ([NSThread isMainThread])
                        {
                            cacheSignatures();
                        }
                        else
                        {
                            dispatch_sync(dispatch_get_main_queue(), ^{
                                cacheSignatures();
                            });
                        }
                    }
                    //遍历缓存,寻找是否已经有可以执行此方法的类
                    //find implementation
                    for (Class someClass in classList)
                    {
                        if ([someClass instancesRespondToSelector:selector])
                        {
                            //其次如果有方法签名返回,runtime则根据方法签名创建描述该消息的NSInvocation,向当前对象发送forwardInvocation:消息,以创建的NSInvocation对象作为参数;
                            signature = [someClass instanceMethodSignatureForSelector:selector];
                            break;
                        }
                    }

                    //cache for next time
                    signatureCache[selectorString] = signature ?: [NSNull null];
                }
                else if ([signature isKindOfClass:[NSNull class]])
                {
                    signature = nil;
                }
            }
        }
    }
    return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
    invocation.target = nil;
    [invocation invoke];
}

主要方法 int objc_getClassList(Class *buffer, int bufferLen) //获取class列表 获取到,项目中类的所有类名。

static void cacheSignatures()
{
    classList = [[NSMutableSet alloc] init];
    signatureCache = [[NSMutableDictionary alloc] init];

    //get class list
    int numClasses = objc_getClassList(NULL, 0);
    Class *classes = (Class *)malloc(sizeof(Class) * (unsigned long)numClasses);
    numClasses = objc_getClassList(classes, numClasses);

    //add to list for checking
    for (int i = 0; i < numClasses; i++)
    {
        //determine if class has a superclass
        Class someClass = classes[i];
        Class superclass = class_getSuperclass(someClass);
        while (superclass)
        {
            if (superclass == [NSObject class])
            {
                [classList addObject:someClass];
                [classList removeObject:[someClass superclass]];
                break;
            }
            superclass = class_getSuperclass(superclass);
        }
    }

    //free class list
    free(classes);
}

######NullSafe的实现原理 把发送给NSNull的而NSNull又无法处理的消息经过如下几步处理:

  • 创建一个方法缓存,这个缓存会缓存项目中类的所有类名( cacheSignatures()方法),并且对缓存查找,看是否有可以执行的类方法。
  • 其次如果有方法签名返回,runtime则根据方法签名创建描述该消息的NSInvocation,向当前对象发送forwardInvocation:消息,以创建的NSInvocation对象作为参数。(想要走 forwardInvocation方法, 必须 先实现 methodSignatureForSelector 而且 返回 NSMethodSignature 必须不能为空。)
  • 如果没有的话,返回nil,接下来会走forwardInvocation:方法。
  • [invocation invokeWithTarget:nil];将消息转发给nil。

下面我们看一下Runtime的消息转发原理以及实现。 ####Runtime中的消息转发 原理我就不一个个敲了。如下: 消息的转发分为两大阶段。第一阶段先征询接收者,所属的类,看其是否能动态添加方法,以处理当前这个“未知的选择子”(unknown selector),这叫做“动态方法解析”(dynamic method resolution)。第二阶段涉及“完整的消息转发机制”。如果运行期系统已经把第一阶段执行完了,那么接收者自己就无法再以动态新增方法的手段来响应包含该选择子的消息了。此时,运行期系统会请求接受者以其他手段来处理与消息相关的方法调用。这又细分为两小步。首先,请接受者看看有没有其他对象处理这条消息。若有,则运行期系统会把消息转给那个对象,于是消息转发过程结束,一起如常。若没有“备援的接收者”,则启动完整的消息转发机制,运行期系统会把于消息有关的全部细节都封装到NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。( 深入理解Objective-C消息转发机制 )

举个栗子
我们新建一个项目,定义一个goHome方法,但是不去实现,直接调用,可以见到如下错误
goHome
常见的错误

unrecognized selector sent to instance

因为我们的方法并没有实现,去调用,在消息的发送过程中并没有找到接受的对象去处理这个消息,导致了项目抛错。在Runtime中我们可以通过其他的方式去进行项目Crash的补救。

#####消息转发的三种处理方式

  • 动态方法解析 接受者1
+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)name 

在调用方法时,对象在收到无法解读的消息后,首先将调用其所属类的上述类方法,然后在方法中判断方法名,利用class_addMethod动态添加新的方法名,IMP的功能是实现的新方法 必须有两个参数。这时我们再运行项目,调用 [self goHome];虽然没有实现,但是会走我们动态添加的方法 newGoHOme并打印数据。

- (void)viewDidLoad {
    [super viewDidLoad];
    [self goHome];
}

//1 防止crash
void newGoHOme(id self, SEL _cmd){
    // implementation
    NSLog(@"这里执行goHOme的打印");
}
+ (BOOL)resolveInstanceMethod:(SEL)name {
    NSLog(@"resolveInstanceMethod: %@", NSStringFromSelector(name));
    if (name == @selector(goHome)) {
        class_addMethod([self class], name, (IMP)newGoHOme, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:name];
}
+ (BOOL)resolveClassMethod:(SEL)name {
    NSLog(@"resolveClassMethod %@", NSStringFromSelector(name));
    return [super resolveClassMethod:name];
}
/****
 class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
 const char * _Nullable types)
 IMP的功能是实现的新方法  必须有两个参数
 ****/
  • 备援接收者2
- (id)forwardingTargetForSelector:(SEL)aSelector

如果接受者1并未执行,则消息传递到被接受者2处理。我们新建一个CompanyVC,设置并实现goHome方法,

CompanyVC.png

- (void)viewDidLoad {
    [super viewDidLoad];
    [self goHome];
}
//执行其他已实现的方法
-(id)forwardingTargetForSelector:(SEL)aSelector{
    return [[CompanyVC alloc]init];
}

我们在ViewController中引入CompanyVC头文件,然后在forwardingTargetForSelector返回CompanyVC,然后运行项目,我们可以看到虽然我们仍然没有实现ViewController中的goHome方法,但是没有crash并且打印出了CompanyVC中方法的数据。 其实他实现的原理就是接收到转发信息时看当前是否能找到援助对象,如果有则将其返回,若找不到就返回nil。在一个对象内部,可能还有一系列其他对象,该对象可经由此方法将能够处理某选择子的相关内部对象返回。这里找到了CompanyVC,所以不会崩溃。







请到「今天看啥」查看全文


推荐文章
天津市文化和旅游局  ·  早安·天津
昨天
天津市文化和旅游局  ·  早安·天津
昨天
生态梦网  ·  霸气登顶!天津介孩子可真行
昨天
生态梦网  ·  紧急提醒!滨海人明后天冷到哭!
2 天前
凤凰财经  ·  世界工厂东莞已死?
8 年前