美文网首页
为OC类添加属性的若干方法

为OC类添加属性的若干方法

作者: Randy1993 | 来源:发表于2017-07-20 11:06 被阅读669次

    前言

    为了解决为某些类添加属性而在不破坏封装性、影响原有代码并进行解耦优化的前提下,一般可以使用分类、扩展进行属性的添加,还有一种就是使用协议进行添加,这也是这篇文章想要讲的。

    分类添加属性

    一般来说分类是为主类添加方法,而不会选择添加属性。在分类头文件中添加的属性,只会生成setter、getter方法的声明而不会在.m文件当中添加它们的实现,所以需要我们手动的添加setter、getter,一般采用runtime关联对象的方法进行解决:

    - (BasePropertyManager *)propertyManager {
        return objc_getAssociatedObject(self, _cmd);
    }
    
    - (void)setPropertyManager:(BasePropertyManager *)propertyManager {
        objc_setAssociatedObject(self, @selector(propertyManager), propertyManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    

    这能够解决我们大多的需求,但是也会存在某些问题。

    问题

    • 如果想要添加的属性很多,那么就会出现大量上面类似的代码。
    • 对于weak对象的引用的不支持,虽然runtime提供了OBJC_ASSOCIATION_ASSIGN这样的内存策略,其解释也是表示是对对象的弱引用,但是网上各种说法是这种内存策略和unsafe_unretained的作用一样,即指针指向的内存释放了,指针不会被自动置为nil, 再使用该指针的时候相当于调用了野指针,程序会崩溃。 而weak指针则会在指向的内存释放时自动清为nil。但是也有解决方案,解决方案如下。(ps:虽然网上各种这样的说法,但是自己试过是没有什么问题,也可能是恰好那块内存还没有被真正的释放仅仅被标记为可用,有人说过OC所说的内存被释放了,只是说这块内存被标记为可以被其他地方使用而已。)
    /// 解决关联对象过程当中weak对象引用的问题
    
    /// 1.创建一个容器类来保存想要weak的对象,然后关联这个容器,避免循环引用:
    @interface WeakObjectContainer : NSObject
    
    @property (nonatomic, readonly, weak) id weakObject;
    
    - (instancetype)initWithWeakObject:(id)object;
    
    @end
     // 使用如下,注意这里使用的是OBJC_ASSOCIATION_RETAIN_NONATOMIC
     objc_setAssociatedObject(self, kEmptyDataSetSource, [[WeakObjectContainer alloc] initWithWeakObject:datasource], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
     
     /// 2.使用block弱引用这个对象,然后关联block
     - (void)setContext:(CDDContext*)object {    
        id __weak weakObject = object;    
        id (^block)() = ^{ return weakObject; };
        objc_setAssociatedObject(self, @selector(context), block, OBJC_ASSOCIATION_COPY);
    }
    
    - (CDDContext*)context {    
        id (^block)() = objc_getAssociatedObject(self, @selector(context));    
        id curContext = (block ? block() : nil);    
        return curContext;
    }
    
    

    扩展添加属性

    我们现在在Xcode里面创建一个类,会自动的在.m文件当中为我们创建一个扩展。当然也可以单独创建一个扩展文件独立出来,使用的时候导入即可。
    可以在扩展中声明属性和方法,声明的属性的setter、getter、实例变量会自动在类的.m文件中生成,但是扩展中声明的方法必须在.m文件当中实现。

    @interface ViewController () 
    /// <#description#>
    @property (nonatomic, strong) UITableView *tableView;
    @end
    
    @implementation ViewController
    
    

    协议添加属性

    协议中添加的方法需要我们去实现,那么协议中添加的属性的setter、getter方法也是需要我们实现的,只是对于属性使用@synthesize关键字即可,该关键字会在对应的实现文件中动态的添加实例变量、setter、getter方法。

    // 协议 注意下面用了@optional修饰
    @protocol BaseScrollViewPropertyProtocol <NSObject>
    
    @optional
    /// 翻页
    @property (nonatomic, assign) NSInteger pageIndex;
    /// pageSize
    @property (nonatomic, assign) NSInteger pageSize;
    
    @end
    
    // BasePropertyManager采纳协议并在.m文件中使用@synthesize进行同步
    @implementation BasePropertyManager
    @synthesize pageSize, pageIndex = _pageIndex;
    @end
    // 创建完成后,生成的实例变量分别为pageSize、_pageIndex,上面的 pageIndex = _pageIndex只是为了我们手动去规定实例变量名字,默认生成的实例变量名和属性名一样,不会为我们自动添加下划线。
    
    

    可以通过runtime进行检测:

    - (void)getIvarName {
        unsigned int count = 0;
        //拷贝出所有的成员变量的列表
        Ivar *ivars = class_copyIvarList(object_getClass(self), &count);
        for (int i =0; i<count; i++) {
            //取出成员变量
            Ivar var = *(ivars + i);
            
            //打印成员变量名字
            NSLog(@"BasePropertyManager的实例变量:%s",ivar_getName(var));
        }
        
        //释放
        free(ivars);
    }
    
    /**
     *  获取对象的所有方法
     */
    -(NSArray *)getAllMethods
    {
        unsigned int count_f =0;
        //获取方法链表
        Method* methodList_f = class_copyMethodList([self class],&count_f);
        
        NSMutableArray *methodsArray = [NSMutableArray arrayWithCapacity:count_f];
        
        for(int i=0;i<count_f;i++)
        {
            Method temp_f = methodList_f[i];
            //方法
            SEL name_f = method_getName(temp_f);
            //方法名字符串
            NSLog(@"BasePropertyManager的方法:%@",NSStringFromSelector(name_f));
        }
        free(methodList_f);
        
        return methodsArray;
    }
    
    

    有一个好处是,这样的一个写满属性的协议,就可以被所有类采纳了,那就相当于所有的类都有了这样的属性!

    但是到这里并没有完!

    如果说你想要为苹果自己的类、或者第三方类添加属性,但是又想自定义setter、getter的实现呢? 是的,最前面说到的分类直接添加属性可以解决问题, 但是所有的地方都说并不建议分类中添加属性。

    于是我为了解决这一问题,想到了结合消息转发机制.

    上面说到定义一个属性的协议,所有的类可以采纳,那么你可以写一个基类(控制器为例),让基类采纳这个协议,并自定义setter、getter,于是每一个不同的基类你都写了一遍同样的setter、getter。 这个时候利用消息转发机制可以将相同的setter、getter方法提取出来。

    1、新建一个BasePropertyManager采纳这个协议,并同步所有的属性。

    2、让这些基类本身持有一个BasePropertyManager。

    /// 属性管理器,会接收所有的setter、getter调用信息
    @property (nonatomic, strong) BasePropertyManager *propertyManager;
    

    3、在每个基类的内部实现消息转发的方法:

    // 因为基类并没有setter、getter的实现,所以你调用基类的属性时,理论上是会崩溃的,但是下面的方法会返回一个可以处理消息的对象,去处理setter、getter方法。
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if (!self.propertyManager) {
            self.propertyManager = [[BasePropertyManager alloc] init];
        }
        
        return self.propertyManager;
    }
    

    于是每一个不同的基类你都写了一个propertyManager属性并实现了消息转发的方法。

    这个时候就可以结合分类了,写一个专门进行消息转发的UIViewController的分类,分类里面有一个propertyManager属性,当然也可以让这个分类采纳协议,并不用所有的基类控制器都去采纳一遍协议了。.m实现如下:

    #pragma mark - 属性管理
    - (BasePropertyManager *)propertyManager {
        return objc_getAssociatedObject(self, _cmd);
    }
    
    - (void)setPropertyManager:(BasePropertyManager *)propertyManager {
        objc_setAssociatedObject(self, @selector(propertyManager), propertyManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if (!self.propertyManager) {
            self.propertyManager = [[BasePropertyManager alloc] init];
        }
        
        return self.propertyManager;
    }
    

    扩展

    这样的一个属性管理器BasePropertyManager,可以专门用于整个项目的属性扩充处理。 比如说为UISCrollView添加属性,那么可以建立一个BaseScrollViewPropertyProtocol协议,然后在UISCrollView分类中实现消息的转发,实现如下:

    #pragma mark - 转发消息
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        UIViewController *vc = self.viewController;
        NSAssert(vc, @"拿不到Scrollview所在的控制器,请先将Scrollview加入到控制器中再执行协议中属性的赋值");
        vc.propertyManager.managerOwner = self;
        return self.viewController;
    }
    

    注意这里UISCrollView并没有添加一个propertyManager属性,而是这里选择将消息先转发到UISCrollView所在的控制器,再由控制器将消息转发到propertyManager。 managerOwner里面有一个managerOwner指向当前属性真正的拥有者。

    问题

    其实前面铺垫了那么多只是为了引出后面使用协议、属性管理器进行属性的添加。 但是这种做法会存在一个问题,因为我在分类里面覆写了UIViewController的方法,如果说UIViewController的其他基类或者子类也覆写了这个方法,那么就会出现意向不到的问题。
    关于性能上面的事情,个人觉得这归根结底也是方法的调用,当系统找到这个方法的时候并会将其缓存起来,下次再调用就会省下很多步骤了。

    Stay hungry,Stay foolish!

    相关文章

      网友评论

          本文标题:为OC类添加属性的若干方法

          本文链接:https://www.haomeiwen.com/subject/awedkxtx.html