面试题
1.讲一下 OC 的消息机制
- OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
- objc_msgSend底层有3大阶段
- 消息发送(当前类、父类中查找)
- 动态方法解析
- 消息转发
2.什么是Runtime?平时项目中有用过么?
- OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
- OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
- 平时编写的OC代码,底层都是转换成了Runtime API进行调用
3.具体应用
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
- 交换方法实现(交换系统的方法)
- 利用消息转发机制解决方法找不到的异常问题
- ......
前言
OC的方法调用:消息机制,给方法调用者发送消息
Person *person = [[Person alloc] init];
[person personTest];
// objc_msgSend(person, @selector(personTest));
// 消息接收者(receiver):person
// 消息名称:personTest
[Person initialize];
// objc_msgSend([MJPerson class], @selector(initialize));
// 消息接收者(receiver):[MJPerson class]
// 消息名称:initialize
objc_msgSend如果找不到合适的方法进行调用,会报错unrecognized selector sent to instance
objc_msgSend执行流程
objc_msgSend的执行流程可以分为3大阶段
- 消息发送
- 动态方法解析
- 消息转发
1 objc_msgSend执行流程01-消息发送
2 objc_msgSend执行流程02-动态方法解析
实例代码如下
#import <Foundation/Foundation.h>
@interface CSPersion : NSObject
- (void)test;
- (void)missMethod;
+ (void)eat;
@end
#import "CSPersion.h"
#import <objc/runtime.h>
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@" >> dynamicMethodIMP");
}
@implementation CSPersion
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@" >> Instance resolving %@", NSStringFromSelector(sel));
if (sel == @selector(missMethod)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@" >> Class resolving %@", NSStringFromSelector(sel));
if (sel == @selector(eat)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveClassMethod:sel];
}
- (void)test {
NSLog(@"test ...");
}
@end
运行结果
3 objc_msgSend的执行流程03-消息转发
生成NSMethodSignature
代码例子佐证
- Cat类
@interface Cat : NSObject
- (int)test:(int)age;
@end
@implementation Cat
- (int)test:(int)age {
NSLog(@"%s",__func__);
return age * age;
}
@end
- Person类
/** 消息发送 */
@interface Student : NSObject
- (void)test:(int)age;
@end
@implementation Student
//+ (BOOL)resolveInstanceMethod:(SEL)sel
//{
// class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
//}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test:)) {
// 测试一
// return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];
// 测试二
// return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
// 测试三
// return [[[Cat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 参数顺序:receiver、selector、other arguments
// 测试一
// int age;
// [anInvocation getArgument:&age atIndex:2];
// NSLog(@"%d", age + 10);
// 测试二
// anInvocation.target == [[MJCat alloc] init]
// anInvocation.selector == test:
// anInvocation的参数:15
// [[[Cat alloc] init] test:15];
// 测试三
[anInvocation invokeWithTarget:[[Cat alloc] init]];
int ret;
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 2.消息转发
Student *stu = [[Student alloc] init];
[stu test:10];
}
return 0;
}
运行结果如下
4 objc_msgSend-类方法消息转发
- CSCat类
@interface CSCat : NSObject
+ (void)test;
- (void)test;
@end
@implementation CSCat
+ (void)test {
NSLog(@"%s", __func__);
}
- (void)test {
NSLog(@"%s", __func__);
}
@end
- CSPerson类
/** 类方法的转发过程 */
@interface CSPerson : NSObject
+ (void)test;
@end
@implementation CSPerson
+ (id)forwardingTargetForSelector:(SEL)aSelector {
// objc_msgSend([[MJCat alloc] init], @selector(test))
// [[[MJCat alloc] init] test]
// 该方法显示与注释后有不同的结果
// if (aSelector == @selector(test)) {
// return [[CSCat alloc] init];
// }
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"1123");
}
@end
- main.mm
int main(int argc, const char * argv[]) {
@autoreleasepool {
[CSPerson test];
}
return 0;
}
运行结果
1.如果方法
methodSignatureForSelector:
返回nil
,则会崩溃
2.方法methodSignatureForSelector:
返回的是方法的签名,即
[NSMethodSignature signatureWithObjCTypes:"v@:"
,其中参数v@:"
决定了方法forwardInvocation:
中参数NSInvocation
的类型。
synthesize
关键字
synthesize
会默认帮我们生成带下划线的变量和set方法get方法
@implementation Person
@synthesize age = _age;
- (void)setAge:(int)age {
_age = age;
}
-(int)age {
return _age;
}
@end
这个是很久之前Xcode版本需要做的事情,现在都不需要了,Xcode都已经帮我们做好了。
dynamic
关键字
dynamic
提醒编译器不要自动生成setter和getter的实现、不要自动生成成员变量
@dynamic weight;
运行结果
- 动态添加方法
@implementation Person
@dynamic weight;
void setWeight(id self, SEL _cmd, int weight) {
NSLog(@"weight is %d", weight);
}
int weight(id self, SEL _cmd) {
return 120;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(setWeight:)) {
class_addMethod(self, sel, (IMP)setWeight, "v@:i");
return YES;
} else if (sel == @selector(weight)) {
class_addMethod(self, sel, (IMP)weight, "i@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *per = [[Person alloc] init];
per.age = 100;
NSLog(@"age = %d",per.age);
per.weight = 200;
NSLog(@"weight = %f",per.weight);
}
return 0;
}
运行结果
本文主要参考MJ老师的教案,非常感谢MJ老师。
项目连接地址 - runtime-object_msgSend
项目连接地址 - runtim-dynamic关键字
项目连接地址 - runtime-object_msgSend_defendCrash
关于runtime更多文章请看如下链接
iOS-runtime-API详解+使用
iOS Runtime原理及使用
iOS - runtime如何通过selector找到对应的 IMP地址(分别考虑类方法和实例方法)
iOS - Runtime之面试题详解一
iOS-runtime之面试题详解二
iOS runtime的使用场景-实战篇