当前位置:   article > 正文

OC 运行时机制的一些应用_oc运行时机制应用

oc运行时机制应用

OC 运行时机制的一些应用


应用场景

动态添加属性

我们都知道,分类(Category)是 OC 的一个重要的语法,它可以帮助开发者在类的外部扩充方法,保持原类的规模,方法在需要的时候分块声明和实现,然而 Category 并不能帮助我们方便的给原来的类扩充属性,如果我们在分类中如下添加属性:

  1. @interface NSObject (DZTest)
  2. @property (nonatomic, strong) NSString* test;
  3. @end

会出现如下错误:


错误截图

而利用 objc/runtime.h 库中的一些函数可以帮助我们在分类添加属性,具体如下:

  1. #import "NSObject+DZTest.h"
  2. #import <objc/runtime.h>
  3. #define DZTestDemoKey "test"
  4. @implementation NSObject (DZTest)
  5. - (void)setTest:(NSString*)test
  6. {
  7. //将属性同对象关联
  8. objc_setAssociatedObject(self, DZTestDemoKey, test, OBJC_ASSOCIATION_COPY_NONATOMIC);
  9. }
  10. - (NSString*)test
  11. {
  12. //对出 key 关联的对象属性
  13. return objc_getAssociatedObject(self, DZTestDemoKey);
  14. }
  15. @end

这种应用场景在 SDWebImage 框架中多次出现,SDWebImage 框架大量使用了分类来给 UIKit 框架的种类扩充方法,而在有些情况下又不得不通过分类给原类扩充属性来达到框架的设计目的,比如:我们可以在UIImageView+WebCache.m中找到这样的使用场景:

  1. - (void)setShowActivityIndicatorView:(BOOL)show{
  2. objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, [NSNumber numberWithBool:show], OBJC_ASSOCIATION_RETAIN);
  3. }
  4. - (BOOL)showActivityIndicatorView{
  5. return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
  6. }
  7. - (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
  8. objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
  9. }
  10. - (int)getIndicatorStyle{
  11. return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
  12. }

动态检测模型属性

使用 KVC 进行字典转模型的时候,我们一般都会这样写:

  1. - (instancetype)initWithDict:(NSDictionary*)dict
  2. {
  3. if (self = [super init]) {
  4. [self setValuesForKeysWithDictionary:dict];
  5. }
  6. return self;
  7. }

这样写会有一个缺陷,就是当 server 返回的 json 数据比转成字典后属性比我们定义的模型属性多的时候,KVC 转换的时候会报undefined key 错误,我们可以重写

  1. - (void)setValue:(id)value forUndefinedKey:(NSString *)key
  2. {}

改写原来方法默认的报异常的实现方式,这样就不会报错误。
其实,除这个方法进行字典转模型以外,我们可以使用运行时的一些方法动态的获取模型中的属性列表:

  1. - (NSArray *)getProperties
  2. {
  3. unsigned int count;
  4. objc_property_t *properties = class_copyPropertyList(self.class, &count);
  5. NSMutableArray *array = [NSMutableArray array];
  6. for (int i =0; i< count ; i++) {
  7. objc_property_t pro = properties[i];
  8. const char *name = property_getName(pro);
  9. NSString *property = [[NSString alloc] initWithUTF8String:name];
  10. [array addObject:property];
  11. }
  12. return array;
  13. }

然后根据获得属性列表进行有针对性的赋值,达到字典转模型的目的:

  1. +(instancetype)channelWithDict:(NSDictionary *)dict
  2. {
  3. HMChannel *channel = [[HMChannel alloc] init];
  4. NSArray *array = [channel getProperties];
  5. [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  6. //
  7. NSString *key = obj;
  8. if (dict[key]) {
  9. [channel setValue:dict[key] forKey:key];
  10. }
  11. }];
  12. return channel;
  13. }

这个方法也在 MJExtension 框架有所涉及,NSObject+MJProperty.m中的+ (NSMutableArray *)properties方法使用了这种方式获取了属性列表。

方法的交换(拦截)

因为 OC 是动态的语言,方法的调用并不是在编译阶段完全定死的,而是在运行的时候以发送消息的形式进行调用,所以这里就引出了一个比较受争议的做法,就是通过运行时的 api 对方法的调用进行交换,也就是所谓的 method swizzling。我们通过下面的例子演示一下,将系统的[UIImage imageNamed:]的方法与我们自己实现的imageWithName:方法进行交换:

  1. #import "UIImage+Extension.h"
  2. #import <objc/runtime.h>
  3. @implementation UIImage (Extension)
  4. + (void)load{
  5. //当我们的类加载到内存时候调用,在整个程序运行期间,只会调用一次
  6. //1.系统的方法
  7. Method orginalMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
  8. //2.得到自己写的方法
  9. Method myMethod = class_getClassMethod([UIImage class], @selector(imageWithName:));
  10. //将系统方法和我们的方法交换
  11. method_exchangeImplementations(orginalMethod, myMethod);
  12. }
  13. + (UIImage *)imageWithName:(NSString *)name{
  14. name=@"2.png";
  15. //注意调用的方法
  16. return [self imageWithName:name];
  17. }
  18. @end

这个地方需要注意的是:在自己写的需要交换的方法中,由于方法已经被交换了,不能在自己写的方法中使用imageNamed方法,否则会造成死循环。

修改对象中的私有属性

工作中可能会遇到一种情况,你的项目是一个维护有些时日的老项目,当有的时候实现一个功能需要修改基类中的一些属性,然而基类在定义的时候并未考虑到这个属性需要修改,所以在类扩展中进行的定义,没有暴露出来,由于考虑到基类在多个地方被使用,无法评估修改基类对于项目其他地方的影响,这个时候需要用运行时的机制对私有属性进行获取和修改:

  1. DZOldNavigationController* nav = (DZOldNavigationController*)self.navigationController;
  2. unsigned int count = 0;
  3. // 得到类中的 Ivar 数组
  4. Ivar* members = class_copyIvarList([DZOldNavigationController class], &count);
  5. // 遍历数组,获得需要的 Ivar
  6. for (int i = 0; i < count; i++) {
  7. Ivar var = members[i];
  8. // 获取 Ivar 对应的属性名称
  9. const char *memberName = ivar_getName(var);
  10. // 获取对应的 Ivar
  11. if (strcmp(memberName, "_screenShotsList") == 0) {
  12. NSMutableArray* arr = [NSMutableArray array];
  13. // 获取 Ivar 对应的值
  14. arr = object_getIvar(nav, var);
  15. [arr removeObjectAtIndex:arr.count - 1];
  16. // 修改 Ivar 的值
  17. object_setIvar(nav, var, arr);
  18. break;
  19. }
  20. // const char *memberType = ivar_getTypeEncoding(var);
  21. // NSLog(@"%s----%s", memberName, memberType);
  22. }

这个例子中就在没有破坏类结构的前提下修改了DZOldNavigationController中的私有属性_screenShotsList的值。

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

闽ICP备14008679号