温故知新SEL/MethodSignature/Invocation

  • SEL是方法或者函数指针吗?
  • 方法签名是什么,有什么用处?
  • 为什么方法转发需要先返回一个方法签名?
  • 除了runtime方法外你会如何调用私有方法?
  • 为什么OC没有方法重载的概念?

iOS开发中我们整日跟方法打交道,我们都知道它最后都是发送该消息,它用起来足够简单,但对于方法调用涉及到的一些知识和概念我觉得有必要再次认识一下,接下来的篇幅我将要介绍

  • SEL
  • IMP
  • Method
  • NSMethodSignature
  • NSInvocation

SEL只是方法的名称

在运行时objc.h中可以看到如下定义,SEL是一个指向objc_selector结构体的指针,SEL可以看似是人的名字

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

在运行时源码里面我们并没有看到objc_selector结构体的具体实现,但根据我们的打印数据我们可以认为objc_selector内部至少包含一个c字符串的字段,可能还包含其他用来加快SEL查找的辅助字段。

  • SEL的三种创建方式
SEL s1 = @selector(todo);
SEL s2 = NSSelectorFromString(@"todo");
SEL s3 = sel_registerName("todo");
NSLog(@"s1 %p", s1);
NSLog(@"s2 %p", s2);
NSLog(@"s3 %p", s3);

[3057:414576] s1 0x10b0ee595
[3057:414576] s2 0x10b0ee595
[3057:414576] s3 0x10b0ee595
打印输出三个SEL变量的值,可以看到三个变量的值是一样的,这是因为SEL是存储在静态数据区,像字符串常量一样只要是名称一样的方法他们的sel都会是同一份内存,所以SEL并不依赖于方法而存在,可以创建一个SEL但是整个App可以没这个方法存在。

即使来自不同类的或者不同模块的,只要方法名称相同(比如'setName:'就是一个方法名称),那么这些方法的SEL的值都一样,所有的SEL都会由全局变量来维护;使用@selector宏的方式创建SEL的一个好处是在编码阶段它会在当前上下文环境中寻找对应的名称的方法,所以如果查找不到该方法会则Xcode会给出警告提示;

IMP 是方法的函数指针

IMP定义如下:

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

可以看到IMP是一个拥有多参数的函数指针,OC中的所有的方法调用最后都将转换为IMP指针指向的函数调用

Method是SEL和IMP的一个映射关系的包装

  • Method定义
typedef struct old_method *Method;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
  • 方法的获取
Method class_getInstanceMethod(Class cls, SEL sel)        
  • 从方法列表中查找方法过程
static inline old_method *_findMethodInList(old_method_list * mlist, SEL sel) {
    int i;
    if (!mlist) return nil;
    for (i = 0; i < mlist->method_count; i++) {
        old_method *m = &mlist->method_list[i];
        if (m->method_name == sel) {
            return m;
        }
    }
    return nil;
}

解析:在类的结构中包含有它的实例方法列表(Method列表),而Method的结构体中包含方法选择子SEL和方法实现IMPruntime的方法查找过程,简化起来就是根据SEL在类的方法列表中遍历查找与之匹配的SEL的方法。

这种查找过程也决定了OC语言不支持方法重载这个特性。如果支持重载,包含不止一个同名方法,那么该方法的查找只会找到第一个就直接返回,因为根据SEL不能确定是查找哪个重载方法;
  • 至此我们能回答"为什么OC没有方法重载?"这个问题了

重载方法是【方法名称一样,但是参数个数,参数类型不一样的方法】,在这样的前提下,我们要确定一个方法就得需要方法名称SEL和方法参数类型描述method_types这两个条件了,上面方法查找的过程_findMethodInList我们可以看到运行时查找方法仅仅根据方法名SEL来查找的if (m->method_name == sel),所以OC目前是不支持方法重载这个很多语言都有的特性的;

NSMethodSignature是对方法参数的描述

  • 方法签名的本质
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;

从方法签名的生成方法可以看到方法签名只需要一个类型参数字符串就可以构造,这个类型参数是OC中通用的类型编码,字符串格式为:返回参数类型参数1类型参数2类型,比如setName:(NSString *)name的类型字符串为v@:@

  • 类型编码 type Encodings

runtime中为了表达方便,对各种数据类型的表示进行了编码,所有的类型都可以用一个对应的字符来表示,其中v表示void类型,@表示OC对象类型,:表示SEL类型,具体可参考官方文档Type Encodings

// 类型编码枚举
enum _NSObjCValueType {
    NSObjCNoType = 0,
    NSObjCVoidType = 'v',
    NSObjCCharType = 'c',
    NSObjCShortType = 's',
    NSObjCLongType = 'l',
    NSObjCLonglongType = 'q',
    NSObjCFloatType = 'f',
    NSObjCDoubleType = 'd',
    NSObjCBoolType = 'B',
    NSObjCSelectorType = ':',
    NSObjCObjectType = '@',
    NSObjCStructType = '{',
    NSObjCPointerType = '^',
    NSObjCStringType = '*',
    NSObjCArrayType = '[',
    NSObjCUnionType = '(',
    NSObjCBitfield = 'b'
}

// 练习一下类型编码
// - (void)setName:(NSString *)name; 
// - (NSString *)name; 
// - (void)downloadImage:(NSString *)url completionHandler:^(void(^)(UIImage *image))completionHandler; 

// v@:@
// @@:
// v@:@^
// 测试代码
NSMethodSignature *methodSignature1 = [NSMethodSignature signatureWithObjCTypes:"v@:"];
NSMethodSignature *methodSignature2 = [NSMethodSignature signatureWithObjCTypes:"v@:"];
NSLog(@"methodSignature1 %p", methodSignature1);
NSLog(@"methodSignature2 %p", methodSignature2);

// 输出
// 2018-01-29 22:18:34.622 IANLearn[3057:66145] methodSignature1 0x608000266ec0
// 2018-01-29 22:18:34.622 IANLearn[3057:66145] methodSignature2 0x608000266ec0
方法签名是用来表达一个方法的参数特征,这些特征包含方法的参数个数,参数类型,返回值类型和SEL一样,所以只要是方法的参数特性(参数和返回值)一样,那么方法的签名就一样,所有的方法签名都会由全局变量来维护
  • NSMethodSignature查找
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;

// 测试代码
NSMethodSignature *methodSignature3 = [self methodSignatureForSelector:@selector(application:didFinishLaunchingWithOptions:)];
NSMethodSignature *methodSignature4 = [AppDelegate instanceMethodSignatureForSelector:@selector(application:didFinishLaunchingWithOptions:)];
NSMethodSignature *methodSignature5 = [AppDelegate instanceMethodSignatureForSelector:@selector(unknowMethod)];
NSLog(@"methodSignature3 %p", methodSignature3);
NSLog(@"methodSignature4 %p", methodSignature4);
NSLog(@"methodSignature5 %p", methodSignature5);

// 输出
// 2018-01-29 22:28:11.792 IANLearn[3207:70694] methodSignature3 0x600000263900
// 2018-01-29 22:28:11.792 IANLearn[3207:70694] methodSignature4 0x600000263900
// 2018-01-29 22:28:11.793 IANLearn[3207:70694] methodSignature5 0x0

上面两个方法都是从某个类中查找指定SEL的方法签名,整个过程大概是是去对应的类方法列表中寻找与该SEL匹配的方法Method,然后直接返回该Method的签名,如果没有查找到该方法则返回nil

有一点需要说明的是在其他语言里面方法签名包含方法名称和参数信息,而OC的NSMethodSignature 仅仅包含参数信息

NSInvocation是OC的方法调用器

OC语言中有多种方式去调用方法,抛开c/c++的调用方式可以列举出如下几种:

  1. 对象/类对象直接调用方法[obj method:param1:],这种方式是最面向对象的姿势了;
  2. [peformSelector withObject:],这种方式它可以很方便的去延迟调用,或者丢到后台线程调用,并且调用没有定义的SEL也不会导致编译错误,但它最大的问题是最多传递2个参数,没有返回值,并且参数只能是对象类型的所以不是很灵活;
  3. block调用 block(name, age) ,闭包可以看做是匿名方法,它的调用不用去对象类中去寻找方法,本身block的结构中就包含方法的实现,它其中的方法不属于某个对象只属于闭包本身;
    4.invocation调用,invocation调用是OC中最灵活的方法调用方式,它可以调用对象的私有方法,可以传递任意多个参数,可以兼容各种参数类型,并且可以存储方法调用的返回值。
  • invocation的构建
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;

一个NSInvocation主要由方法签名+参数值来确定, 通过方法签名创建NSInvocation后我们给他的参数值就行,其中调用对象是第一个参数,方法名称SEL是第二个参数

  • invocation的调用
- (void)invoke;
- (void)invokeWithTarget:(id)target;

NSInvocation的调用有2个方法,target参数可以直接设置target属性或者设置为第一个参数,如果不设置则调用第二个方法将target传入

// 测试
// 构建对象 测试UILabel的setText:方法
UILabel *myObj = [UILabel new];
NSLog(@"invocation执行前myObj.text=%@", myObj.text);
// 构建方法签名返回类型void编码为v,对象UILabel类型编码为@,SEL编码为:,参数类型NSString编码为@
NSMethodSignature *myMethodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
// 构建NSInvocation
NSInvocation *myInvocation = [NSInvocation invocationWithMethodSignature:myMethodSignature];
// 设置第1个参数
myInvocation.target = myObj;
// 设置第2个参数
myInvocation.selector = @selector(setText:);
// 设置第3个参数
NSString *newText = @"change new text";
[myInvocation setArgument:&newText atIndex:2];
[myInvocation retainArguments];
// 执行
[myInvocation invoke];
NSLog(@"invocation执行后myObj.text=%@", myObj.text);
  • invocation的使用场景
  1. 调用多参数的私有方法
    私有方法我们不能通过对象直接调用,我们可以使用peformSelector的方式调用,但是对于多余2个参数的情况我们就没办法调用了,这时候就可以构建NSInvocation来调用了。
  2. 方法转发调用
    方法转发的forwardInvocation中需要我们去执行NSInvocation,通常是将NSInvocation通过更改target参数的形式转发给其他对象来执行。

参考资料:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,698评论 0 9
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,134评论 0 9
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 730评论 0 2
  • 继上Runtime梳理(四) 通过前面的学习,我们了解到Objective-C的动态特性:Objective-C不...
    小名一峰阅读 749评论 0 3
  • 一、Runtime简介 Runtime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消...
    林安530阅读 1,063评论 0 2