iOS消息转发学习笔记

如果深入学习ios Runtime,不得不提到消息转发,很多框架的实现都基于这一功能实现(例如JSPatch)

虽然看了很多篇关于消息转发的文章,但是理解的不是很透彻,还是自己实践一些理解能更加透彻一下。

首先我自己定义了一个MyString继承NSString

@interface MyString : NSString

@end

@implementation MyString

@end

然后创建一个MyString,通过performSelector调用MissMethod,MissMethod1,MissMethod2等方法。

- (void)testForward
{
    MyString *str = [[MyString alloc] init];
    [str performSelector:@selector(MissMethod) withObject:nil];
    [str performSelector:@selector(MissMethod2) withObject:nil];
    [str performSelector:@selector(MissMethod3) withObject:nil];
}

如果什么都不写,这样肯定会crash,会出现这个错误[NSObject(NSObject) doesNotRecognizeSelector:],因为MyString没有这三个方法,而且父类也没有。

如果没有找到方法,系统会尝试进行补救,看看有没有能处理的能力,首先会调用resolveInstanceMethod这个方法,这个方法默认是返回NO,走一下步流程,如果返回YES,例如下面方法的实现,如果传入的sel的名称是MissMethod开头,则认为我们的类是可以处理这个方法的。而且如果是MissMethod方法,我们就给这个类添加一个方法dynamicMethodIMP,作为MissMethod的实现。此时当外界调用MissMethod时,其实相当于调用dynamicMethodIMP

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #000000 }
p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #d12f1b }
span.s1 { color: #ba2da2 }
span.s2 { }
span.s3 { color: #000000 }
span.s4 { color: #3e1e81 }

void dynamicMethodIMP(id self, SEL _cmd) {

NSLog(@" >> dynamicMethodIMP");

}

+ (BOOL)resolveInstanceMethod:(SEL)name
{
    NSLog(@" >> Instance resolving %@", NSStringFromSelector(name));

    NSString *selName = NSStringFromSelector(name);

    if ([selName hasPrefix:@"MissMethod"]) {
        if (name == @selector(MissMethod)) {
            class_addMethod([self class], name, (IMP)dynamicMethodIMP, "[email protected]:");
            return YES;
        } else {
            return NO;
        }
    }

    return [super resolveInstanceMethod:name];
}

如果resolveInstanceMethod方法返回NO了,下面怎么办?首先我定义了一个类MyString2作为接盘侠,实现了MissMethod1,MissMethod2方法

@interface MyString2 : NSString

@end

@implementation MyString2

- (void)MissMethod2
{
    NSLog(@"MissMethod2");
}

- (void)MissMethod3
{
    NSLog(@"MissMethod3");
}

@end

当resolveInstanceMethod方法返回NO了,系统会尝试调用下面方法,看看有没有接盘侠来接这个锅,_str2是MyString2的一个示例,而且实现了MissMethod2方法。当MyString的示例调用MissMethod2方法,MissMethod2->resolveInstanceMethod(NO)->forwardingTargetForSelector,返回一个接盘侠去给这个示例发送消息objc_sendmsg(_str2,sel)

- (id)forwardingTargetForSelector:(SEL)sel {
    if(sel == @selector(MissMethod2)){
        return _str2;
    }
    return [super forwardingTargetForSelector:sel];
}

当forwardingTargetForSelector也无法处理,返回nil时,下面会走到这个方法methodSignatureForSelector,判断sig是否为nil,如果不为nil会走forwardInvocation,最后调用forwardInvocation,如果这个方法也没有处理,做了最后尝试之后也就会抛出那个异常doesNotRecognizeSelector

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

    NSMethodSignature *sig;
    sig = [_str2 methodSignatureForSelector:sel];
    if (sig) {
        return sig;
    }
    return [super methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = nil;
    if ([_str2 methodSignatureForSelector:[invocation selector]] ) {
        target = _str2;
        [invocation invokeWithTarget:target];
    }
}

总结一下消息转发的整个流程,大致流程MissMethod->resolveInstanceMethod(NO)->forwardingTargetForSelector(nil)->methodSignatureForSelector(sig)->forwardInvocation。

下面是完整代码实现:

//
//  MyString.m
//  objc
//
//  Created by zilong.li on 2017/8/17.
//
//

#import "MyString.h"

#import <objc/runtime.h>

@interface MyString2 : NSString

@end

@implementation MyString2

- (void)MissMethod2
{
    NSLog(@"MissMethod2");
}

- (void)MissMethod3
{
    NSLog(@"MissMethod3");
}

@end

void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@" >> dynamicMethodIMP");
}

@interface MyString ()
{
    MyString2 *_str2;
}

@end

@implementation MyString

- (instancetype)init
{
    self = [super init];
    if (self) {
        _str2 = [[MyString2 alloc] init];
    }
    return self;
}

+ (BOOL)resolveInstanceMethod:(SEL)name
{
    NSLog(@" >> Instance resolving %@", NSStringFromSelector(name));

    NSString *selName = NSStringFromSelector(name);

    if ([selName hasPrefix:@"MissMethod"]) {
        if (name == @selector(MissMethod)) {
            class_addMethod([self class], name, (IMP)dynamicMethodIMP, "[email protected]:");
            return YES;
        } else {
            return NO;
        }
    }

    return [super resolveInstanceMethod:name];
}

- (id)forwardingTargetForSelector:(SEL)sel {
    if(sel == @selector(MissMethod2)){
        return _str2;
    }
    return [super forwardingTargetForSelector:sel];
}

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

    NSMethodSignature *sig;
    sig = [_str2 methodSignatureForSelector:sel];
    if (sig) {
        return sig;
    }
    return [super methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = nil;
    if ([_str2 methodSignatureForSelector:[invocation selector]] ) {
        target = _str2;
        [invocation invokeWithTarget:target];
    }
}

@end
时间: 2024-10-12 15:39:14

iOS消息转发学习笔记的相关文章

iOS消息转发机制

iOS消息转发机制 “消息派发系统”(message-dispatch system) 若想令类能够理解某条消息,我们必须实现出对应的方法才行.但是,在编译器向类发送其无法解读的消息时并不会报错,因为在运行期可以继续向类中添加方法,所以编译器在编译时还无法确定类中到底会不会有某个方法的实现.当对象接收到无法解读的消息时,就会启动“消息转发”机制,我们可以经由此过程告诉对象应该如何处理未知消息. 消息转发分为两个阶段.第一阶段先征询接收者所属的类,看其是否能动态添加方法,已处理当前这个“未知的选择

IOS图层Layer学习笔记(二)—— CALayer(上)

IOS图层Layer学习笔记(二)-- CALayer(上) 简介 CALayer是所有图层的基类.主要是一些基本显示属性(位置.锚点.颜色.透明度等).层次关系(子图层和父图层).基本动画等. 接下来分别从常用属性.类方法和实例方法来介绍CALayer的使用.顺序是按头文件的排序来. 常用属性 bounds CGRect,Animatable.控制layer的大小,其中x和y无效果,默认是(0,0). position CGPoint,Animatable.控制layer锚点在父图层的位置.

iOS Code Signing 学习笔记&lt;转写&gt;

最近看了objc.io上第17期中的文章 <Inside Code Signing> 对应的中文翻译版 <代码签名探析> ,受益颇深,对iOS代码签名机制有了进一步的认识.想了解详细内容建议大家还是去看原文好了. 下面是对此文章的理解再结合自己之前对该部分的认识写出的学习笔记.本文的前提是已经对非对称加密有了一定的了解. 一.数字签名(digital signature) 对指定信息使用哈希算法,得到一个固定长度的信息摘要,然后再使用 私钥 (注意必须是私钥)对该摘要加密,就得到了

iOS消息转发

消息转发是一种功能强大的技术,可以大大增加Objective-C的表现力.什么是消息转发?简而言之,它允许未知的消息被困住并作出反应.换句话说,无论何时发送未知消息,它??都会以一个很好的包发送到您的代码中,此时您可以随心所欲地执行任何操作. 为什么它被称为 "转发"? 当某个对象没有任何响应某个 消息 的操作就 "转发" 该 消息.原因是这种技术主要是为了让对象让其他对象为他们处理 消息,从而 "转发". 1. 类,对象,方法 在我们开始使用消

IOS 消息转发

最近在看消息转发的资料,发现大部分都是理论知识,很少有完整的代码.现在以代码的形式形象的解释一下: 用Xcode创建一个工程 1.正常方法调用 创建一个类Person 代码如下 Person.h代码如下: #import <Foundation/Foundation.h> @interface Person : NSObject - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithUserName:(NSString *)u

IOS内存管理学习笔记

内存管理作为iOS中非常重要的部分,每一个iOS开发者都应该深入了解iOS内存管理,最近在学习iOS中整理出了一些知识点,先从MRC开始说起. 1.当一个对象在创建之后它的引用计数器为1,当调用这个对象的alloc.retain.new.copy方法之后引用计数器自动在原来的基础上加1(ObjC中调用一个对象的方法就是给这个对象发送一个消息),当调用这个对象的release方法之后它的引用计数器减1,如果一个对象的引用计数器为0,则系统会自动调用这个对象的dealloc方法来销毁这个对象. [e

iOS之动画学习笔记二

今天,我趁着项目空暇之余,把有关CAAnimation以及它的子类的相关属性和方法都罗列一遍.以便将来在忘记的时候能够快速拾起. 一.CAAnimation(The base animation class) 它有两个私有属性: void *_attr; uint32_t _flags; // 暂时不知道它的用途 -.- 以后补上. + (instancetype)animation; // 创建动画实例对象的工厂方法 + (nullable id)defaultValueForKey:(NSS

IOS Vuforia SDK学习笔记

最近可能工作会用到关于AR方面的东西 所以找了一下有名气的AR库 发现2个:Metaio和Vuforia 先说说Metaio 我看了看网上的反应 好像这个库反响不错 用的人蛮多的.但是悲催的是 我7月1号去看的时候已经无法注册新用户了.没办法用 因为它被苹果收购了,也不知道该高兴还是该忧伤  好像目前是没办法用了.只好找了别的,比如:Vuforia Vuforia是个啥? Vuforia™是Qualcomm Connected Experiences, Inc.的产品.它是一款针对移动设备开发增

iOS开发ReactiveCocoa学习笔记(-)

学习 RAC 我们首先要了解 RAC 都有哪些类 RACSignal RACSubject RACSequence RACMulticastConnection RACCommand 在学习的时候写了一个小 demo 来分别介绍每个类的作用,gitHub 地址: https://github.com/SummerHH/ReactiveCocoa.git demo 的目录结构如下 RAC学习起来的特点 学习起来比较难 团队开发的时候需要谨慎使用 团队代码需要不断的评审,保证团队中所有人代码的风格一