7.5 Runtime
runtime参考
ios学习--你会遇到的runtime面试题(详) - CSDN博客
什么是Runtime
- 定义:
- runtime简称运行时,OC就是运行时机制。也就是在运行时候的一些机制,其中最主要的是消息机制。
- Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制。而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库。它是 Objective-C 面向对象和动态机制的基石。
- Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。理解 Objective-C 的 Runtime 机制可以帮我们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。了解 Runtime ,要先了解它的核心 - 消息传递 (Messaging)。
应用场景
- 1.发送消息
- 方法调用的本质,就是让对象发送消息。
- objc_msgSend,只有对象才能发送消息,因此以objc开头.
- 使用消息机制前提,必须导入#import <objc/message.h>
- 消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现
- 消息机制简单使用
// 创建person对象 Person *p = [[Person alloc] init]; // 调用对象方法 [p eat]; // SEL:方法编号,根据方法编号就可以找到对应方法实现 [p performSelector:@selector(eat)]; // 本质:让对象发送消息 objc_msgSend(p, @selector(eat)); // 调用类方法的方式:两种 // 第一种通过类名调用本质类名转换成类对象 [Person eat]; // 第二种通过类对象调用 [[Person class] eat]; [personClass performSelector:@selector(eat)]; // 用类名调用类方法,底层会自动把类名转换成类对象调用 // 本质:让类对象发送消息 objc_msgSend([Person class], @selector(eat));
- 2.交换方法
- 开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. // 需求:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。 // 步骤一:先搞个分类,定义一个能加载图片并且能打印的方法+ (UIImage *)xmg_imageNamed:(NSString *)imageName; // 步骤二:交换imageNamed和xmg_imageNamed的实现,就能调用xmg_imageNamed,间接调用xmg_imageNamed的实现。 UIImage *image = [UIImage imageNamed:@"123"]; imageNamed: 实现方法:底层调用PH_imageNamed 本质:交换两个方法的实现imageNamed和PH_imageNamed方法 调用imageNamed其实就是调用PH_imageNamed imageNamed加载图片,并不知道图片是否加载成功 以后调用imageNamed的时候,就知道图片是否加载 } @end @implementation UIImage (Image) // 加载分类到内存的时候调用 + (void)load { // 交换方法实现,方法都是定义在类里面 // class_getMethodImplementation:获取方法实现 // class_getInstanceMethod:获取对象 // class_getClassMethod:获取类方法 // IMP:方法实现 // imageNamed // Class:获取哪个类方法 // SEL:获取方法编号,根据SEL就能去对应的类找方法 Method imageNameMethod = class_getClassMethod([UIImage class], @selector(imageNamed:)); Method PH_imageNameMethod = class_getClassMethod([UIImage class], @selector(PH_imageNamed:)); // 交换方法实现 method_exchangeImplementations(imageNameMethod, PH_imageNameMethod); } } // 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super. // 既能加载图片又能打印 + (UIImage *)PH_imageNamed:(NSString *)imageName { // 加载图片 UIImage *image = [UIImage PH_imageNamed:imageName]; // 2.判断功能 if (image == nil) { NSLog(@"加载为空"); } return image; } @end
- 开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并且保持原有的功能
- 3.动态添加方法
- 开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. Person *p = [[Person alloc] init]; // 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。 // 动态添加方法就不会报错 [p performSelector:@selector(eat)]; } @end @implementation Person // void(*)() // 默认方法都有两个隐式参数, 默认一个方法都有两个参数,self,_cmd,隐式参数 self:方法调用者 _cmd:调用方法的编号 void eat(id self,SEL sel) { NSLog(@"%@ %@",self,NSStringFromSelector(sel)); } // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来. // 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法 <!--动态添加方法,首先实现这个resolveInstanceMethod--> <!-- resolveInstanceMethod调用:当调用了没有实现的方法没有实现就会调用resolveInstanceMethod--> <!-- resolveInstanceMethod作用:就知道哪些方法没有实现,从而动态添加方法--> <!-- sel:没有实现方法--> + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(eat)) { // 动态添加eat方法 // 第一个参数:给哪个类添加方法 // 第二个参数:添加方法的方法编号 // 第三个参数:添加方法的函数实现(函数地址) // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd class_addMethod(self, @selector(eat), eat, "v@:"); } return [super resolveInstanceMethod:sel]; } @end
- 开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
- 4.给分类添加属性
- 原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. // 给系统NSObject类动态添加属性name NSObject *objc = [[NSObject alloc] init]; objc.name = @"abc"; NSLog(@"%@",objc.name); } @end // 定义关联的key static const char *key = "name"; - (void)setName:(NSString *)name { // 添加属性,跟对象 // 给某个对象产生关联,添加属性 // object:给哪个对象添加属性 // key:属性名,根据key去获取关联的对象 ,void * == id // value:关联的值 // policy:策略 objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)name { return objc_getAssociatedObject(self, @"name"); }
- 原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间
- 5.获取属性列表
Person *p = [[Person alloc] init]; [p setValue:@"Kobe" forKey:@"name"]; [p setValue:@18 forKey:@"age"]; // p.address = @"广州大学城"; p.weight = 110.0f; // 1.打印所有ivars unsigned int ivarCount = 0; // 用一个字典装ivarName和value NSMutableDictionary *ivarDict = [NSMutableDictionary dictionary]; Ivar *ivarList = class_copyIvarList([p class], &ivarCount); for(int i = 0; i < ivarCount; i++){ NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivarList[i])]; id value = [p valueForKey:ivarName]; if (value) { ivarDict[ivarName] = value; } else { ivarDict[ivarName] = @"值为nil"; } } // 打印ivar for (NSString *ivarName in ivarDict.allKeys) { NSLog(@"ivarName:%@, ivarValue:%@",ivarName, ivarDict[ivarName]); } // 2.打印所有properties unsigned int propertyCount = 0; // 用一个字典装propertyName和value NSMutableDictionary *propertyDict = [NSMutableDictionary dictionary]; objc_property_t *propertyList = class_copyPropertyList([p class], &propertyCount); for(int j = 0; j < propertyCount; j++){ NSString *propertyName = [NSString stringWithUTF8String:property_getName(propertyList[j])]; id value = [p valueForKey:propertyName]; if (value) { propertyDict[propertyName] = value; } else { propertyDict[propertyName] = @"值为nil"; } } // 打印property for (NSString *propertyName in propertyDict.allKeys) { NSLog(@"propertyName:%@, propertyValue:%@",propertyName, propertyDict[propertyName]); } // 3.打印所有methods unsigned int methodCount = 0; // 用一个字典装methodName和arguments NSMutableDictionary *methodDict = [NSMutableDictionary dictionary]; Method *methodList = class_copyMethodList([p class], &methodCount); for(int k = 0; k < methodCount; k++){ SEL methodSel = method_getName(methodList[k]); NSString *methodName = [NSString stringWithUTF8String:sel_getName(methodSel)]; unsigned int argumentNums = method_getNumberOfArguments(methodList[k]); methodDict[methodName] = @(argumentNums - 2); // -2的原因是每个方法内部都有self 和 selector 两个参数 } // 打印method for (NSString *methodName in methodDict.allKeys) { NSLog(@"methodName:%@, argumentsCount:%@", methodName, methodDict[methodName]); } /* 2016-09-04 17:06:49.070 Runtime-实践篇[13723:1044813] ivarName:_name, ivarValue:Kobe 2016-09-04 17:06:49.071 Runtime-实践篇[13723:1044813] ivarName:_age, ivarValue:18 2016-09-04 17:06:49.071 Runtime-实践篇[13723:1044813] ivarName:_weight, ivarValue:110 2016-09-04 17:06:49.072 Runtime-实践篇[13723:1044813] ivarName:_address, ivarValue:值为nil 2016-09-04 17:06:49.072 Runtime-实践篇[13723:1044813] propertyName:address, propertyValue:值为nil 2016-09-04 17:06:49.072 Runtime-实践篇[13723:1044813] propertyName:weight, propertyValue:110 2016-09-04 17:06:49.073 Runtime-实践篇[13723:1044813] methodName:setWeight:, argumentsCount:1 2016-09-04 17:06:49.073 Runtime-实践篇[13723:1044813] methodName:weight, argumentsCount:0 2016-09-04 17:06:49.074 Runtime-实践篇[13723:1044813] methodName:setAddress:, argumentsCount:1 2016-09-04 17:06:49.074 Runtime-实践篇[13723:1044813] methodName:address, argumentsCount:0 2016-09-04 17:06:49.074 Runtime-实践篇[13723:1044813] methodName:.cxx_destruct, argumentsCount */