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

前言

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

分类添加属性

一般来说分类是为主类添加方法,而不会选择添加属性。在分类头文件中添加的属性,只会生成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!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,112评论 18 139
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,644评论 0 9
  • [书] 1 围城 围城展现给你的只是一个故事而已,但当你在不经意间思考人生的时候,或许发现你的过去现在未来都是一部...
    彭彭想遇到丁满阅读 212评论 0 2
  • 今天说说加入007写作群的感受。 要点速读 007写作群的玩法是脑洞大开的赚钱方式 它的第一个价值是在为你的写作行...
    笑眼朋朋阅读 588评论 6 2
  • 从很小很小的时候开始就是这样,每时每刻都能迸发出一些新的想法,感觉这是一件能够改变我的命运发自我心底真正的热爱一定...
    joadare阅读 140评论 0 0