惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
L
LINUX DO - 热门话题
月光博客
月光博客
B
Blog
博客园 - 叶小钗
美团技术团队
D
Docker
A
About on SuperTechFans
Stack Overflow Blog
Stack Overflow Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
P
Proofpoint News Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Y
Y Combinator Blog
V
V2EX
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 三生石上(FineUI控件)
The Register - Security
The Register - Security
博客园_首页
The Cloudflare Blog
I
InfoQ
T
Tailwind CSS Blog
MongoDB | Blog
MongoDB | Blog
Engineering at Meta
Engineering at Meta
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Microsoft Azure Blog
Microsoft Azure Blog
有赞技术团队
有赞技术团队
C
CERT Recently Published Vulnerability Notes
AWS News Blog
AWS News Blog
Spread Privacy
Spread Privacy
V
Visual Studio Blog
博客园 - Franky
Cloudbric
Cloudbric
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News and Events Feed by Topic
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Webroot Blog
Webroot Blog
博客园 - 【当耐特】
TaoSecurity Blog
TaoSecurity Blog
B
Blog RSS Feed
N
News | PayPal Newsroom
人人都是产品经理
人人都是产品经理
H
Heimdal Security Blog
L
LangChain Blog
PCI Perspectives
PCI Perspectives
Jina AI
Jina AI
Google DeepMind News
Google DeepMind News
Schneier on Security
Schneier on Security

土土哥的Blog

好久没有更新博客=。= 阿里巴巴国际无线技术部 - 招人啦~求iOS、Android、Java 转-阿里巴巴国际无线技术部 - 在这里遇见最好的自己 反编译分析Xcode8的Bug, release下连续两次调用有二级指针参数的空方法会Crash 有趣的Autolayout示例5-Masonry实现 在对象dealloc的后期执行Task-开源库TTGDeallocTaskHelper 用QuartzCode快速实现一个收藏动画 开源项目-拼图验证控件TTGPuzzleVerify的实现 Swift开源Mac App - BingWallPaper 有趣的Autolayout示例4-Masonry实现 翻译-为什么objc_msgSend必须用汇编实现 API返回结果设计经验与总结 结合访问Out Parameters出现EXC_BAD_ACCESS的例子,反编译汇编解读__autoreleasing 总结一些iOS项目中组织代码的方法 对组件化与模块化的思考与总结 开源项目-TTGTagCollectionView 有趣的Autolayout示例3-Masonry实现 Swift开源项目: TTGEmojiRate的实现 Swift写的库-TTGSnackbar 有趣的Autolayout示例2-Masonry实现 解决iOS项目的版本兼容问题-结合宏、Category和Runtime 用Runtime的手段填充任意NSObject对象的nil属性 有趣的Autolayout示例-Masonry实现 UITextView编辑时插入自定义表情-续-自定义表情图片的大小 RPC框架Thrift例子-PHP调用C++后端程序 GCD使用经验与技巧浅谈 为GCD队列绑定NSObject类型上下文数据-利用__bridge_retained(transfer)转移内存管理权 Enum-枚举的正确使用-Effective-Objective-C-读书笔记-Item-5 @autoreleasepool-内存的分配与释放 有关宏定义的经验与技巧-简化代码-增强Log Effective-Objective-C-读书笔记-Item-4-如何正确定义常量 UITextView编辑时插入自定义表情-简单的图文混编 关于评论不见了=。= Entity和Model的不同-关于代码的数据层 一次审核被拒的经历-关于iCloud到底应该备份什么数据 Block类型变量-缓存Http请求与回调 提升UITableView性能-复杂页面的优化 NSString的Copy与内存分配 利用NSProxy实现消息转发-模块化的网络接口层设计-原创 Effective-Objective-C-读书笔记-Item-3 Effective-Objective-C-读书笔记-Item-2 Effective-Objective-C-读书笔记-Item-1 iOS项目的目录结构-原创 Android开源库-LinkTextView-原创 第一篇Blog
反编译分析并模拟实现methodSignatureForSelector方法
土土哥 · 2017-04-08 · via 土土哥的Blog

前言

最近跟同事讨论了有关methodSignatureForSelector:的问题,大概如下:

  1. 一个ProtocolA声明了一个实例方法funcA,然后一个类ClassA声明实现这个ProtocolA,但是并没有实现方法funcA,对ClassA的实例调用methodSignatureForSelector:@selector(funcA)能否返回正确的signature?
  2. 一个类ClassB,在@interface声明了实例方法funcB,但是没有实现funcB,对ClassB的实例调用methodSignatureForSelector:@selector(funcB),能否返回正确的signature?

写个Demo验证了下,结果非常有意思,不由得好奇起来,所以深入研究了下methodSignatureForSelector:的实现,然后自己模拟实现出来。

Github地址:TTGRemakeMethodSignatureForSelector

详细的研究过程如下:

写Demo验证问题

测试类TestClass和Protocol如下:

1
2
3
4
5
6
7
8
9
@protocol TestProtocol <NSObject>
- (void)protocolTestFunc1;
@end

@interface TestClass : NSObject <TestProtocol>
- (void)testFunc1;
@end
@implementation TestClass
@end

调用methodSignatureForSelector:获取protocolTestFunc1testFunc1的methodSignature,protocolTestFunc1可以获取到,但是testFunc1就不行。

扩展到所有情况

分析整理下可能的情况:

对于一个类:

  1. 方法没有声明,也没有实现
  2. 方法有声明,但是没有实现
  3. 方法没有声明,但是有实现
  4. 实例方法和类方法都有上面1、2、3的情况

对于一个Protocol:

  1. 方法有声明,但是没有实现
  2. 实例方法和类方法
  3. required和optional方法

经过验证,发现:

  1. 对于类,只要@implementation里实现了方法,不管@interface是否有声明,都可以返回methodSignature
  2. 对于Protocol声明的方法,不管实例方法还是类方法,不管required还是optional的,都可以返回methodSignature

所以,有必要进一步研究methodSignatureForSelector:的实现。

从Runtime源码入手

methodSignatureForSelector:NSObject的方法,所以想知道原理,第一步当然就是翻看Runtime的源码,看看有没有实现,最新的objc4-709源码,实现如下:

1
2
3
4
5
6


- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}

可见,Runtime源码是没有实现的,要到CoreFoundation里面找。

找CoreFoundation源码

接着Google CoreFoundation源码,找到https://opensource.apple.com/source/CF/CF-1153.18/,怎么都找不到有关methodSignatureForSelector:的实现,难道要就此打住?

反编译大法

既然找不到源码,那就只好祭出反编译大法,直接上汇编=。=

找到CoreFoundation动态库

CoreFoundation是在程序运行时链接上去的动态库,属于系统动态库的一部分,所以首先要找到CoreFoundation动态库的二进制代码文件,命令行:

1
2
sudo -i
find / -name CoreFoundation.framework

搜到的结果很多,以iPhone模拟器的为例:

1
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/CoreFoundation.framework

打开CoreFoundation.framework文件夹,CoreFoundation文件就是要反编译的文件。

Hopper Disassembler反编译

用Hopper Disassembler打开CoreFoundation文件,在Labels搜索methodSignatureForSelector:,就能找到对应的汇编实现代码,如下:

Hopper翻译的代码如下:

1
2
3
4
5
6
7
8
9
10
11
void * +[NSObject methodSignatureForSelector:](void * self, void * _cmd, void * arg2) {
rdi = self;
rbx = arg2;
if ((rbx != 0x0) && (___methodDescriptionForSelector(object_getClass(rdi), rbx) != 0x0)) {
rax = [NSMethodSignature signatureWithObjCTypes:rdx];
}
else {
rax = 0x0;
}
return rax;
}

可发现,关键在于___methodDescriptionForSelector:方法。

___methodDescriptionForSelector:方法的实现

进一步看___methodDescriptionForSelector:的实现。

汇编代码、Hopper翻译的代码都很长,循环也不见了,全是goto:

经过反复校对整理(排除Hopper的错误=。=),可以整理出主要的流程如下图所示:

根据流程,可以得出,___methodDescriptionForSelector:方法:

  1. class_copyProtocolListprotocol_getMethodDescription方法,检查是否有对应的selector,不管是否实现
  2. class_getInstanceMethod检查selector是否有implementation,注意,是implementation,跟是否声明了没有关系

所以,符合前面的结论。

模拟实现

流程都有了,不如更进一步,自己实现一个methodSignatureForSelector:方法出来~

模拟实现___methodDescriptionForSelector:

按照流程,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

struct objc_method_description ttg_MethodDescription(Class class, SEL sel) {

struct objc_method_description description = (struct objc_method_description){NULL, NULL};
Class currentClass = class;

while (currentClass && description.name == NULL) {

unsigned int count = 0;
__unsafe_unretained Protocol **protocols = class_copyProtocolList(currentClass, &count);


for (unsigned int i = 0; i < count; i++) {

description = protocol_getMethodDescription(protocols[i], sel, YES, class_isMetaClass(currentClass) ^ 1);
if (description.name != NULL) {
break;
}


description = protocol_getMethodDescription(protocols[i], sel, NO, class_isMetaClass(currentClass) ^ 1);
if (description.name != NULL) {
break;
}
}


free(protocols);


if (description.name != NULL) {
return description;
}


currentClass = class_getSuperclass(currentClass);
}


Method method = class_getInstanceMethod(class, sel);
if (method) {

return *method_getDescription(method);
} else {

return (struct objc_method_description){NULL, NULL};
}
}
  1. 用description记录迭代中的结果,只要找到就返回
  2. 用currentClass记录每次迭代的类class
  3. Protocol的@required和@optional方法都要检查
  4. 注意释放Protocol数组
  5. 最后才用class_getInstanceMethod获取类本身的实现

模拟实现methodSignatureForSelector:

用Category给NSObject加两个方法:

1
2
3
4
@interface NSObject (TTGRemakeMethodSignatureForSelector)
- (NSMethodSignature *)ttg_methodSignatureForSelector:(SEL)sel;
+ (NSMethodSignature *)ttg_methodSignatureForSelector:(SEL)sel;
@end

实现基本一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@implementation NSObject (TTGRemakeMethodSignatureForSelector)

- (NSMethodSignature *)ttg_methodSignatureForSelector:(SEL)sel {
struct objc_method_description description = ttg_MethodDescription([self class], sel);
if (sel && description.types != NULL) {
return [NSMethodSignature signatureWithObjCTypes:description.types];
} else {
return nil;
}
}

+ (NSMethodSignature *)ttg_methodSignatureForSelector:(SEL)sel {
struct objc_method_description description = ttg_MethodDescription(object_getClass(self), sel);
if (sel && description.types != NULL) {
return [NSMethodSignature signatureWithObjCTypes:description.types];
} else {
return nil;
}
}

@end

注意要获取正确的”class”:

  1. 对于对象实例,从[obj class]object_getClass(obj)结果一致,都是对象的类
  2. 对于类,要获取元类,也就是metaClass,只能用object_getClass(class)

验证模拟实现

验证自己模拟实现的methodSignatureForSelector:和系统的是否一致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

NSMethodSignature *signature1 = [test methodSignatureForSelector:@selector(testFunc1)];
NSMethodSignature *signature2 = [test ttg_methodSignatureForSelector:@selector(testFunc1)];
NSLog(@"signature1 == signature2: %d", [signature1 isEqual:signature2]);


NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[test ttg_methodSignatureForSelector:@selector(testFunc1)]];
invocation.target = test;
invocation.selector = @selector(testFunc1);
[invocation invoke];


invocation = [NSInvocation invocationWithMethodSignature:[TestClass ttg_methodSignatureForSelector:@selector(staticTestFunc1)]];
invocation.target = [TestClass class];
invocation.selector = @selector(staticTestFunc1);
[invocation invoke];

结果跟系统实现的行为一致,更多Case见Github的Demo。

总结

从两个小问题出发,到最后通过反编译、模拟实现,终于搞清楚了methodSignatureForSelector:的原理、流程、各种隐藏特性!哈哈~~过瘾~~