目录[-]面试
相信不少同窗都听过运行时,可是我相信仍是有不少同窗不了解什么是运行时,到底在项目开发中怎么用?何时适合使用?想一想咱们的项目中,到底在哪里使用过运行时呢?还能想起来吗?另外,在面试的时候,是否常常有笔试中要求运用运行时或者在面试时面试官会问是否使用过运行时,又是如何使用的?数组
回想本身,曾经在面试中被面试官拿运行时刁难过,也在笔试中遇到过。所以,后来就深刻地学习了Runtime
机制,学习里面的API。因此才有了后来的组件封装中使用运行时。网络
相信咱们都遇到过这样一个问题:我想在扩展(category)中添加一个属性,若是iOS是不容许给扩展类扩展属性的,那怎么办呢?答案就是使用运行时机制函数
Runtime
是一套比较底层的纯C语言的API, 属于C语言库, 包含了不少底层的C语言API。 在咱们平时编写的iOS代码中, 最终都是转成了runtime的C语言代码。学习
所谓运行时,也就是在编译时是不存在的,只是在运行过程当中才去肯定对象的类型、方法等。利用Runtime
机制能够在程序运行时动态修改类、对象中的全部属性、方法等。测试
还记得咱们在网络请求数据处理时,调用了-setValuesForKeysWithDictionary:
方法来设置模型的值。这里什么原理呢?为何能这么作?其实就是经过Runtime
机制来完成的,内部会遍历模型类的全部属性名,而后设置与key
对应的属性名的值。atom
咱们在使用运行时的地方,都须要包含头文件:#import
。若是是Swift
就不须要包含头文件,就能够直接使用了。spa
利用运行时获取对象的全部属性名是能够的,可是变量名获取就得用另外的方法了。咱们能够经过class_copyPropertyList
方法获取全部的属性名称。.net
下面咱们经过一个Person
类来学习,这里的方法没有写成扩展,只是为了简化,将获取属性名的方法直接做为类的实例方法:代理
@interface Person : NSObject { NSString *_variableString; } // 默认会是什么呢? @property (nonatomic, copy) NSString *name; // 默认是strong类型 @property (nonatomic, strong) NSMutableArray *array; // 获取全部的属性名 - (NSArray *)allProperties; @end
下面主要是写如何获取类的全部属性名的方法。注意,这里的objc_property_t
是一个结构体指针objc_property *
,所以咱们声明的properties
就是二维指针。在使用完成后,咱们必定要记得释放内存,不然会形成内存泄露。这里是使用的是C语言的API,所以咱们也须要使用C语言的释放内存的方法free
。
/// An opaque type that represents an Objective-C declared property. typedef struct objc_property *objc_property_t;
- (NSArray *)allProperties { unsigned int count; // 获取类的全部属性 // 若是没有属性,则count为0,properties为nil objc_property_t *properties = class_copyPropertyList([self class], &count); NSMutableArray *propertiesArray = [NSMutableArray arrayWithCapacity:count]; for (NSUInteger i = 0; i < count; i++) { // 获取属性名称 const char *propertyName = property_getName(properties[i]); NSString *name = [NSString stringWithUTF8String:propertyName]; [propertiesArray addObject:name]; } // 注意,这里properties是一个数组指针,是C的语法, // 咱们须要使用free函数来释放内存,不然会形成内存泄露 free(properties); return propertiesArray; }
对于获取对象的全部属性名,在上面的-allProperties
方法已经能够拿到了,可是并无处理获取属性值,下面的方法就是能够获取属性名和属性值,将属性名做为key
,属性值做为value
。
- (NSDictionary *)allPropertyNamesAndValues { NSMutableDictionary *resultDict = [NSMutableDictionary dictionary]; unsigned int outCount; objc_property_t *properties = class_copyPropertyList([self class], &outCount); for (int i = 0; i < outCount; i++) { objc_property_t property = properties[i]; const char *name = property_getName(property); // 获得属性名 NSString *propertyName = [NSString stringWithUTF8String:name]; // 获取属性值 id propertyValue = [self valueForKey:propertyName]; if (propertyValue && propertyValue != nil) { [resultDict setObject:propertyValue forKey:propertyName]; } } // 记得释放 free(properties); return resultDict; }
测试一下:
//此方法返回的只有属性值不为空的属性 NSDictionary *dict = p.allPropertyNamesAndValues; for (NSString *propertyName in dict.allKeys){ NSLog(@"propertyName: %@ propertyValue: %@", propertyName, dict[propertyName]); }
看下打印结果,属性值为空的属性并无打印出来,所以字典的key对应的value不能为nil:
propertyName: name propertyValue: Lili
经过class_copyMethodList
方法就能够获取全部的方法。
- (void)allMethods { unsigned int outCount = 0; Method *methods = class_copyMethodList([self class], &outCount); for (int i = 0; i < outCount; ++i) { Method method = methods[i]; // 获取方法名称,可是类型是一个SEL选择器类型 SEL methodSEL = method_getName(method); // 须要获取C字符串 const char *name = sel_getName(methodSEL); // 将方法名转换成OC字符串 NSString *methodName = [NSString stringWithUTF8String:name]; // 获取方法的参数列表 int arguments = method_getNumberOfArguments(method); NSLog(@"方法名:%@, 参数个数:%d", methodName, arguments); } // 记得释放 free(methods); }
测试一下:
[p allMethods];
调用打印结果以下,为何参数个数看起来不匹配呢?好比-allProperties
方法,其参数个数为0才对,可是打印结果为2。根据打印结果可知,无参数时,值就已是2了。:
方法名:allProperties, 参数个数:2 方法名:allPropertyNamesAndValues, 参数个数:2 方法名:allMethods, 参数个数:2 方法名:setArray:, 参数个数:3 方法名:.cxx_destruct, 参数个数:2 方法名:name, 参数个数:2 方法名:array, 参数个数:2 方法名:setName:, 参数个数:3
要获取对象的成员变量,能够经过class_copyIvarList
方法来获取,经过ivar_getName
来获取成员变量的名称。对于属性,会自动生成一个成员变量。
- (NSArray *)allMemberVariables { unsigned int count = 0; Ivar *ivars = class_copyIvarList([self class], &count); NSMutableArray *results = [[NSMutableArray alloc] init]; for (NSUInteger i = 0; i < count; ++i) { Ivar variable = ivars[i]; const char *name = ivar_getName(variable); NSString *varName = [NSString stringWithUTF8String:name]; [results addObject:varName]; } free(ivars); return results; }
测试一下:
for (NSString *varName in p.allMemberVariables) { NSLog(@"%@", varName); }
打印结果说明属性也会自动生成一个成员变量:
2015-10-23 23:54:00.896 PropertiesDemo[46966:3856655] _variableString 2015-10-23 23:54:00.897 PropertiesDemo[46966:3856655] _name 2015-10-23 23:54:00.897 PropertiesDemo[46966:3856655] _array
iOS中,能够在运行时发送消息,让接收消息者执行对应的动做。可使用objc_msgSend
方法,发送消息。
Person *p = [[Person alloc] init]; p.name = @"Lili"; objc_msgSend(p, @selector(allMethods));
iOS的category是不能扩展存储属性的,可是咱们能够经过运行时关联来扩展“属性”。
假设扩展下面的“属性”:
// 因为扩展不能扩展属性,所以咱们这里在实现文件中须要利用运行时实现。 typedef void(^HYBCallBack)(); @property (nonatomic, copy) HYBCallBack callback;
在实现文件中,咱们用一个静态变量做为key:
const void *s_HYBCallbackKey = "s_HYBCallbackKey"; - (void)setCallback:(HYBCallBack)callback { objc_setAssociatedObject(self, s_HYBCallbackKey, callback, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (HYBCallBack)callback { return objc_getAssociatedObject(self, s_HYBCallbackKey); }
其实就是经过objc_getAssociatedObject
取得关联的值,经过objc_setAssociatedObject
设置关联。
在开发中,咱们比较经常使用的是使用关联属性的方式来扩展咱们的“属性”,以便在开发中简单代码。咱们在开发中使用关联属性扩展全部响应事件、将代理转换成block版等。好比,咱们能够将全部继承于UIControl的控件,都拥有block版的点击响应,那么咱们就能够给UIControl扩展一个TouchUp、TouchDown、TouchOut的block等。
对于动态获取属性的名称、属性值使用较多的地方通常是在使用第三方库中,好比MJExtension等。这些三方库都是经过这种方式将Model转换成字典,或者将字典转换成Model。