当前位置:   article > 正文

Runtime系列(二)--Runtime的使用场景

那些地方用到了runtime

Runtime 理解介绍的文章非常多,我只想讲讲Runtime 可以用在哪里,而我在项目里哪些地方用到了runtime。多以实际使用过程为主,来介绍runtime的使用。

* 那么runtime 怎么使用?可以用在哪些场景下呢?*
首先,使用runtime 相关API,要#import <objc/runtime.h>

运行时获取某个类的属性或函数

运行时动态获取某个类的属性或者函数等,可以用来做很多事情,如json 解析、数据库结果解析、判断某个类的子类等。

解析、转化为Model
  1. // 获取属性列表
  2. objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount)
  3. // 获取属性名
  4. const char *property_getName(objc_property_t property)
  5. // 获取属性类型
  6. const char *property_getAttributes(objc_property_t property)

以上方法可以用来:
* 解析json数据,转化为Model对象。
* 解析数据库查询结果,转化为Model 对象。

这里有动态获取类的属性的示例代码片段:

  1. unsigned int outCount, i;
  2. objc_property_t *properties = class_copyPropertyList([self class], &outCount);
  3. for (i = 0; i < outCount; i++) {
  4. objc_property_t property = properties[i];
  5. //获取属性名
  6. NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
  7. //获取属性类型等参数
  8. NSString *propertyType = [NSString stringWithCString: property_getAttributes(property) encoding:NSUTF8StringEncoding];
  9. /*
  10. 各种符号对应类型,部分类型在新版SDK中有所变化,如long 和long long
  11. c char C unsigned char
  12. i int I unsigned int
  13. l long L unsigned long
  14. s short S unsigned short
  15. d double D unsigned double
  16. f float F unsigned float
  17. q long long Q unsigned long long
  18. B BOOL
  19. @ 对象类型 //指针 对象类型 如NSString 是@“NSString”
  20. propertyType,你可以打印出来,看看它是什么。
  21. 要判断某个属性的类型,只需要[propertyType hasPrefix:@"Ti"]
  22. 这代表它是int 类型。
  23. */
  24. }
  25. free(properties);
判断某个类的子类

有时候我们在程序中需要判断某个类是否是另一个类的子类。这个功能也可以利用runtime类实现,这里有示例代码:

  1. int numClasses;
  2. Class *classes = NULL;
  3. numClasses = objc_getClassList(NULL,0);
  4. if (numClasses >0 )
  5. {
  6. classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
  7. numClasses = objc_getClassList(classes, numClasses);
  8. for (int i = 0; i < numClasses; i++) {
  9. if (class_getSuperclass(classes[i]) == [xxxxClass class]){
  10. id class = classes[i];
  11. // 执行某个方法 或者 做其他事情
  12. [class performSelector:@selector(xxxxMethod) withObject:nil];
  13. }
  14. }
  15. free(classes);
  16. }

以上两段示例代码摘自我之前写的FMDB Model 封装:JKDBModel,你可以去看更详尽的解析和使用过程。

获取某个类的实例变量

如果你还需要获取某个类的实例变量做什么操作的话,可以使用如下这几个API:

  1. // 获取实例变量数组
  2. Ivar * class_copyIvarList(Class cls, unsigned int *outCount)
  3. // 获取实例变量名称
  4. const char * ivar_getName( Ivar ivar)
  5. // 获取实例变量类型
  6. const char * ivar_getTypeEncoding( Ivar ivar)

这面有获取实例变量的示例代码片段:

  1. unsigned int outCount, i;
  2. Ivar *ivaries = class_copyIvarList([Son class], &outCount);
  3. for (i = 0; i < outCount; i++) {
  4. Ivar ivar = ivaries[i];
  5. NSString *ivarName = [NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding];
  6. NSString *ivarType = [NSString stringWithCString:ivar_getTypeEncoding(ivar) encoding:NSUTF8StringEncoding];
  7. NSLog(@"名称:%@---类型:%@",ivarName,ivarType);
  8. /*
  9. 各种符号对应类型,部分类型在新版SDK中有所变化,如long 和long long
  10. c char C unsigned char
  11. i int I unsigned int
  12. l long L unsigned long
  13. s short S unsigned short
  14. d double D unsigned double
  15. f float F unsigned float
  16. q long long Q unsigned long long
  17. B BOOL
  18. @ 对象类型 //指针 对象类型 如NSString 是@“NSString”
  19. */
  20. }
  21. free(ivaries);
获取某个类的方法

获取某个类的方法,会包含这个类的property 的set 和get 方法,但是不包括父类的property set 和get 方法,不包括父类的方法(如果在当前类覆写,就包括)。
主要API:

  1. // 获取方法数组
  2. Method * class_copyMethodList(Class cls, unsigned int *outCount)
  3. // 获取方法的 SEL
  4. SEL method_getName( Method method)
  5. // 获取方法名
  6. const char* sel_getName(SEL aSelector)

获取方法数组的示例代码片段:

  1. unsigned int outMethodCount, j;
  2. Method *methods = class_copyMethodList([Son class], &outMethodCount);
  3. for (j = 0; j < outMethodCount; j++) {
  4. Method method = methods[j];
  5. SEL selector = method_getName(method);
  6. if (selector) {
  7. NSString *methodName = [NSString stringWithCString:sel_getName(selector) encoding:NSUTF8StringEncoding];
  8. NSLog(@"方法:%@",methodName);
  9. }
  10. }
  11. free(methods);

运行时替换方法(Method Swizzling)

Method Swizzling 的使用需要谨慎,因为一不小心可能就会导致无法排查的Bug,毕竟它替换的是官方的API,有些API内部做了什么事情,很难完全把握。

使用场景,需要监控用户经常打开的界面,以及在某界面停留的时长。
我们可以怎么做呢?写一个UIViewController 的Category,然后在类别中,添加自定义的方法:如-xxxviewDidAppear:和-xxxviewDidDisappear:方法,然后在-load 方法中,用自定义的方法替换原来的方法。

  1. + (void)load {
  2. static dispatch_once_t onceToken;
  3. dispatch_once(&onceToken, ^{
  4. Class class = [self class];
  5. // When swizzling a class method, use the following:
  6. // Class class = object_getClass((id)self);
  7. SEL originalSelector = @selector(viewWillAppear:);
  8. SEL swizzledSelector = @selector(xxx_viewWillAppear:);
  9. Method originalMethod = class_getInstanceMethod(class, originalSelector);
  10. Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
  11. BOOL didAddMethod =
  12. class_addMethod(class,
  13. originalSelector,
  14. method_getImplementation(swizzledMethod),
  15. method_getTypeEncoding(swizzledMethod));
  16. if (didAddMethod) {
  17. class_replaceMethod(class,
  18. swizzledSelector,
  19. method_getImplementation(originalMethod),
  20. method_getTypeEncoding(originalMethod));
  21. } else {
  22. method_exchangeImplementations(originalMethod, swizzledMethod);
  23. }
  24. });
  25. }
  26. #pragma mark - Method Swizzling
  27. - (void)xxx_viewWillAppear:(BOOL)animated {
  28. [self xxx_viewWillAppear:animated];
  29. NSLog(@"xxx_viewWillAppear: %@", self);
  30. // 在这里,我们可以发送一个消息到服务器,或者做其他事情等。
  31. }

以上示例代码摘自:Objective-C Runtime 运行时之四:Method Swizzling
关于Method Swizzling,他是把两个方法的实现部分互换了。
比如上面我们调用-xxx_viewWillAppear:,因为-xxx_viewWillAppear: 和-viewWillAppear:的实现部分互换后,其实执行的时候,并不会执行上面的这个实现,而是调用-viewWillAppear:的内部实现。所以上面的代码,完全不会产生循环调用。
还是写段代码说明吧:

  1. - (void)viewWillAppear:(BOOL)animated {
  2. NSLog(@"这是原来的方法");
  3. }
  4. - (void)xxx_viewWillAppear:(BOOL)animated {
  5. NSLog(@"xxx_viewWillAppear: %@", self);
  6. // 在这里,我们可以发送一个消息到服务器,或者做其他事情等。
  7. }

假如上面这俩方法用method swizzling 替换后,我们调用-xxx_viewWillAppear:会打印这是原来的方法;而调用-viewWillAppear:会打印xxx_viewWillAppear:。这里需要细细体会一下。
关于Method Swizzling更多的注意点请看原文Method Swizzling

对象关联(Associated Objects)

对象关联(或称为关联引用)本来是Objective-C 运行时的一个重要特性,它能让开发者对已经存在的类在扩展中添加自定义的属性
需要用的以下三个函数:

  1. void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
  2. id objc_getAssociatedObject(id object, void *key)
  3. void objc_removeAssociatedObjects(id object)

众所周知,OC 中的Category 中不能添加新的属性,但是我们通过Associated Objects可以间接的实现往类上添加自定义的属性。
不能添加属性的根本原因是不会帮我们自动添加对象的实例变量,也不会帮我们生成set 和get方法,虽然set /get 方法可以自己实现,但是没有实例变量来存储数据。

很容易看懂官方文档对参数的描述,但是key 需要注意一下:
通常推荐的做法是添加的属性最好是 static char类型的,当然更推荐是指针型的。通常来说该属性应该是常量、唯一的、在适用范围内用getter和setter访问到,所以通常我们这样写:

  1. static char kAssociatedObjectKey;
  2. objc_setAssociatedObject(self, &kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  3. objc_getAssociatedObject(self, &kAssociatedObjectKey);

当然,对于key 还有更好的做法,那就是selector。用selector 的示例在下面。

下面用代码演示如何在Category中添加一个新的属性。
这是Son+AssociatedObject.h

  1. #import "Son.h"
  2. @interface Son (AssociatedObject)
  3. /** 家庭住址 */
  4. @property (copy, nonatomic) NSString *address;
  5. /** 身高 */
  6. @property (assign, nonatomic) int height;
  7. @end

这是Son+AssociatedObject.m

  1. #import "Son+AssociatedObject.h"
  2. #import <objc/runtime.h>
  3. @implementation Son (AssociatedObject)
  4. - (void)setAddress:(NSString *)address
  5. {
  6. objc_setAssociatedObject(self, @selector(address), address, OBJC_ASSOCIATION_COPY_NONATOMIC);
  7. }
  8. - (NSString *)address
  9. {
  10. return objc_getAssociatedObject(self, @selector(addObject:));
  11. }
  12. - (void)setHeight:(int)height
  13. {
  14. NSNumber *heighNum = [NSNumber numberWithInt:height];
  15. objc_setAssociatedObject(self, @selector(height), heighNum, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  16. }
  17. - (int)height
  18. {
  19. NSNumber *heightNum = objc_getAssociatedObject(self, @selector(addObject:));
  20. return heightNum.intValue;
  21. }
  22. @end

虽然上面有提到void objc_removeAssociatedObjects(id object),但是不要轻易使用这个函数,因为它会移除所有的关联对象。我们一般要移除某个关联对象,只需要用objc_setAssociatedObject传入nil即可。

补充一个关联对象的使用场景:
你在使用AlertView 或者ActionSheet的时候,有没有很苦恼不能在点击的代理方法中方便的获取到Model对象呢?
除了在控制器中添加一个property 这种方式外;
我们也可以为AlertView 或者ActionSheet 添加一个关联对象,这样就可以在代理方法中方便的获取到Model 对象啦。
这里如果我们为AlertView 或者ActionSheet 添加Category来实现的话,代码跟上面为Son 添加类别基本一样,对象类型改为id 类型即可。
或者我们在控制器中调用的时候,添加关联对象也可以。这时候就用这种方式:

  1. static char kAssociatedObjectKey;
  2. objc_setAssociatedObject(self, &kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  3. objc_getAssociatedObject(self, &kAssociatedObjectKey);

UIAlertController 也跟上面一样。

关于Associated Objects的使用,有连个为Category扩展功能,使得Category中也能方便的添加属性以及相应的getter 和setter 的例子。
OC 自动生成分类属性方法
一个库–DProperty

运行时动态创建一个类

我再某控制器中测试写了这么一个方法,来创建一个MyClass 类。项目中并不存在叫MyClass 的类文件。

  1. - (void)createClass
  2. {
  3. Class MyClass = objc_allocateClassPair([NSObject class], "MyClass", 0);
  4. // 1.添加一个叫name 类型为NSString的实例变量,第四个参数是对其方式,第五个参数是参数类型
  5. if (class_addIvar(MyClass, "name", sizeof(NSString *), 0, "@")) {
  6. NSLog(@"add name ivar success");
  7. }
  8. // 2.添加一个property
  9. // 这里需要注意,添加property之前需要先添加一个与之对应的实例变量
  10. if (class_addIvar(MyClass, "_address", sizeof(NSString *), 0, "@")) {
  11. NSLog(@"add _address ivar success");
  12. }
  13. objc_property_attribute_t type = {"T", "@\"NSString\""};
  14. objc_property_attribute_t ownership = { "C", "" };
  15. objc_property_attribute_t backingivar = { "V", "_address"};
  16. objc_property_attribute_t attrs[] = {type, ownership, backingivar};
  17. class_addProperty(MyClass, "address", attrs,2);
  18. // 3.添加函数, myclasstest是已经实现的函数,"v@:"这种写法见参数类型连接
  19. class_addMethod(MyClass, @selector(myclasstest:), (IMP)myclasstest, "v@:");
  20. // 4.注册这个类到runtime系统中就可以使用他了
  21. objc_registerClassPair(MyClass);
  22. // 5.生成了一个实例化对象
  23. id myobj = [[MyClass alloc] init];
  24. NSString *str = @"名字";
  25. // 6.给刚刚添加的变量赋值
  26. // object_setInstanceVariable(myobj, "itest", (void *)&str);在ARC下不允许使用
  27. [myobj setValue:str forKey:@"name"];
  28. [myobj setValue:@"这是地址" forKey:@"address"];
  29. // 7.调用myclasstest方法,也就是给myobj这个接受者发送myclasstest这个消息
  30. [myobj myclasstest:10];
  31. }
  32. //这个方法实际上没有被调用,但是必须实现否则不会调用下面的方法
  33. - (void)myclasstest:(int)a
  34. {
  35. NSLog(@"啊啊啊啊啊");
  36. }
  37. //调用的是这个方法
  38. static void myclasstest(id self, SEL _cmd, int a) //self和_cmd是必须的,在之后可以添加其他参数
  39. {
  40. Ivar v = class_getInstanceVariable([self class], "name");
  41. //返回名为name的ivar的变量的值
  42. id o = object_getIvar(self, v);
  43. //成功打印出结果
  44. NSLog(@"name is %@", o);
  45. NSLog(@"参数 a is %d", a);
  46. objc_property_t property = class_getProperty([self class], "address");
  47. NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
  48. id value = [self valueForKey:propertyName];
  49. NSLog(@"address is %@", value);
  50. }

关于运行时创建一个新类,上面的注释已经写的很详细了。
Have Fun!

转载于:https://www.cnblogs.com/wanghang/p/6298848.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/1022503
推荐阅读
相关标签
  

闽ICP备14008679号