ios runtime基础应用

原创
2016/05/27 00:19
阅读数 804

非盈利无广告开发者专用网址导航:www.dev666.com

1、概述

相信很多同学都听过运行时,但是我相信还是有很多同学不了解什么是运行时,到底在项目开发中怎么用?什么时候适合使用?想想我们的项目中,到底在哪里使用过运行时呢?还能想起来吗?另外,在面试的时候,是否经常有笔试中要求运用运行时或者在面试时面试官会问是否使用过运行时,又是如何使用的?

回想自己,曾经在面试中被面试官拿运行时刁难过,也在笔试中遇到过。因此,后来就深入地学习了Runtime机制,学习里面的API。所以才有了后来的组件封装中使用运行时。

相信我们都遇到过这样一个问题:我想在扩展(category)中添加一个属性,如果iOS是不允许给扩展类扩展属性的,那怎么办呢?答案就是使用运行时机制

2、运行时机制

Runtime是一套比较底层的纯C语言的API, 属于C语言库, 包含了很多底层的C语言API。 在我们平时编写的iOS代码中, 最终都是转成了runtime的C语言代码。

所谓运行时,也就是在编译时是不存在的,只是在运行过程中才去确定对象的类型、方法等。利用Runtime机制可以在程序运行时动态修改类、对象中的所有属性、方法等。

还记得我们在网络请求数据处理时,调用了-setValuesForKeysWithDictionary:方法来设置模型的值。这里什么原理呢?为什么能这么做?其实就是通过Runtime机制来完成的,内部会遍历模型类的所有属性名,然后设置与key对应的属性名的值。

我们在使用运行时的地方,都需要包含头文件:#import 。如果是Swift就不需要包含头文件,就可以直接使用了。

3、获取对象所有属性名

利用运行时获取对象的所有属性名是可以的,但是变量名获取就得用另外的方法了。我们可以通过class_copyPropertyList方法获取所有的属性名称。

下面我们通过一个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;
}

4、获取对象的所有属性名和属性值

对于获取对象的所有属性名,在上面的-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

5、获取对象的所有方法名

通过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

6、获取对象的成员变量名称

要获取对象的成员变量,可以通过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
 

7、运行时发消息

iOS中,可以在运行时发送消息,让接收消息者执行对应的动作。可以使用objc_msgSend方法,发送消息。

Person *p = [[Person alloc] init];

p.name = @"Lili";

objc_msgSend(p, @selector(allMethods));

8、Category扩展属性

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。

更多内容与学习交流请关注个人微信公众账号:极客峰

展开阅读全文
打赏
0
17 收藏
分享
加载中
更多评论
打赏
0 评论
17 收藏
0
分享
返回顶部
顶部