技术源于生活,服务生活 Terry 随笔

在扩展类中添加弱引用类型的属性

2017-03-08
Terry
iOS

扩展类中添加属性

  一般需要有个成员变量来保存数据,此时就需要用到运行时库了。只要用到的运行时函数有objc_getAssociatedObjectobjc_setAssociatedObject。这两个函数有个参数是 key 是一个 const void * 的类型。你可以直接定义,或者是使用class_getClassMethod, class_getInstanceMethod 来当成 key 使用   运行时库没有提供弱引用的方式,只有如下定义

/* Associative References */

/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

扩展类中添加弱引用属性

  首先要知道,为对象A关联一个动态属性B,那么当A被释放时,此时B若没有被其它对象引用,那么B也会被释放。这是我们解决弱引用的关键。简单描述下要实现的过程,后面会放上代码

  假设有以下等式和类C,其中A和B为对象,weakObject是A的弱引用属性,key是关联的关键词, 类C非常简单,只需要在释放时去取消指定的关联

A.weakObject = B


//类C的伪实现
@interface C : NSObject

@property (nonatomic, weak) id object;
@property (nonatomic, assign) const void * key;
@end

@implementation C

- (void)dealloc
{
    if (self.object && self.key)
    {
        objc_setAssociatedObject(self.object, self.key, nil, OBJC_ASSOCIATION_ASSIGN);
    }
}

@end

关键的实现步骤

为实现此操作,需要有以下步骤

  1. A关联B,对应操作:objc_setAssociatedObject(A, key, B, OBJC_ASSOCIATION_ASSIGN);
  2. B关联自释放对象C: objc_setAssociatedObject(B, key, c1, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  3. A关联自释放对象C: objc_setAssociatedObject(A, key, c2, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

步骤1:为了用户可以存储变量 步骤2:当B被释放时,自动把与A关联的数据清除,此时要清空两处,一个为对应属性,一个辅助数据(步骤3创建的) 步骤3:当A本身被释放,但是B还存在时,可以清除B中被A关联的信息,此时只需要清空B的数据(步骤2创建)

可能会有些绕口,简单来说就是A挂了,就把B里有关A的信息清除;若是B挂了,就把A里的信息清除

当然还有其它事情要处理,其它的具体见以下Demo例子:

/******************************************************************************************
 类名: JxAutoReleaseAssociated
 说明: 用于辅助释放指定的关联
 ******************************************************************************************/
#pragma mark - ✍️JxAutoReleaseAssociated
@interface JxAutoReleaseAssociated : NSObject

@property (nonatomic, weak) id object1;
@property (nonatomic, assign) const void * key1;

@property (nonatomic, weak) id object2;
@property (nonatomic, assign) const void * key2;

@end

@implementation JxAutoReleaseAssociated

- (instancetype)init
{
    self = [super init];
    logoutObject(self);
    return self;
}

- (void)dealloc
{
    if (self.object1 && self.key1)
    {
        objc_setAssociatedObject(self.object1, self.key1, nil, OBJC_ASSOCIATION_ASSIGN);
    }
    if (self.object2 && self.key2)
    {
        objc_setAssociatedObject(self.object2, self.key2, nil, OBJC_ASSOCIATION_ASSIGN);
    }
    logoutObject(self);
}

@end



/******************************************************************************************
 类名: MyObject
 说明: 测试类
 ******************************************************************************************/
#pragma mark - ✍️MyObject
@interface MyObject : NSObject

@end
@implementation MyObject

- (instancetype)init
{
    self = [super init];
    logoutObject(self);
    return self;
}

- (void)dealloc
{
    logoutObject(self);
}

@end

/******************************************************************************************
 类名: MyObject(Jx)
 说明: 测试类的扩展类
 ******************************************************************************************/
#pragma mark - ✍️MyObject(Jx)
@interface MyObject(Jx)
@property (nonatomic, weak) id weakObject;
@end
@implementation MyObject(Jx)

- (id)weakObject
{
    const void *key = class_getInstanceMethod([self class], @selector(weakObject));
    return objc_getAssociatedObject(self, key);
}

- (void)setWeakObject:(id)aWeakObject
{
    id previousWeakObject = self.weakObject;
    if (previousWeakObject != aWeakObject)
    {
        const void *key = class_getInstanceMethod([self class], @selector(weakObject));
        const void *clearAssObjKey = class_getInstanceMethod([self class], @selector(setWeakObject:));
        
        if (previousWeakObject)
        {
            //清除之前关联的信息
            JxAutoReleaseAssociated *assObj = objc_getAssociatedObject(previousWeakObject, key);
            if (assObj && [assObj isKindOfClass:[JxAutoReleaseAssociated class]])
            {
                objc_setAssociatedObject(previousWeakObject, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            }
        }
        
        if (aWeakObject)
        {
            //关联弱引用对象 self->key->aWeakObject
            objc_setAssociatedObject(self, key, aWeakObject, OBJC_ASSOCIATION_ASSIGN);
            
            //当aWeakObject释放时:清空self对象的属性,相关数据
            JxAutoReleaseAssociated *obj = [[JxAutoReleaseAssociated alloc] init];
            obj.object1 = self;
            obj.key1 = key;
            obj.object2 = self;
            obj.key2 = clearAssObjKey;
            objc_setAssociatedObject(aWeakObject, key, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            
            //为self关联自释放对象:当本身释放时,清除已关联的对象的数据 self->clearAssObjKey->pWeakObject
            obj = [[JxAutoReleaseAssociated alloc] init];
            obj.object1 = aWeakObject;
            obj.key1 = key;
            objc_setAssociatedObject(self, clearAssObjKey, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
    }
}

@end

以宏实现,方便使用

typedef void(^JxEmptyBlock)();

/***********************************************************************
 File       : JxAutoRunBlockWhenFree
 Author     : Terry
 Email      : jxd524@163.com
 CreateTime : 2017-03-09 14:34
 LastModify :
 Description: 当对象被释放时,自动执行相应的回调
 ************************************************************************/
#import <Foundation/Foundation.h>

@interface JxAutoRunBlockWhenFree : NSObject

+ (JxAutoRunBlockWhenFree *)Create:(JxEmptyBlock)aBlock;

@end
@implementation JxAutoRunBlockWhenFree
{
    JxEmptyBlock mDeallocBlock;
}

- (instancetype)initWithDeallocBlock:(JxEmptyBlock)aDeallocBlock
{
    self = [super init];
    mDeallocBlock = aDeallocBlock;
    return self;
}

- (void)dealloc
{
    if (mDeallocBlock) mDeallocBlock();
}

+ (JxAutoRunBlockWhenFree *)Create:(JxEmptyBlock)aBlock
{
    return [[JxAutoRunBlockWhenFree alloc]  initWithDeallocBlock:aBlock];
}

@end

/**
 定义弱引用的动态时属性

 @param aName 获取
 @param aSetter 设置
 @param aType 类型
 
 使用例子
 RuntimeWeakImpl(weakObject, setWeakObject, id);
 
 使用
 A.weakObject = B;
 
 当A或B被释放时,会清除相应的数据
 */
#define RuntimeWeakImpl(aName, aSetter, aType)                                  \
- (aType)aName{                                                                 \
    const void *key = class_getInstanceMethod([self class], @selector(aName));  \
    return objc_getAssociatedObject(self, key);                                 \
}                                                                               \
- (void)aSetter:(id)aWeakObject {                                                                           \
    id previousWeakObject = [self aName];                                                                   \
    if (previousWeakObject != aWeakObject) {                                                                \
        const void *key = class_getInstanceMethod([self class], @selector(aName));                          \
        const void *clearAssObjKey = class_getInstanceMethod([self class], @selector(aSetter:));            \
        if (previousWeakObject) {                                                                           \
            JxAutoRunBlockWhenFree *assObj = objc_getAssociatedObject(previousWeakObject, key);             \
            if (assObj && [assObj isKindOfClass:[JxAutoRunBlockWhenFree class]]) {                          \
                objc_setAssociatedObject(previousWeakObject, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);  \
            }                                                                                               \
        }                                                                                                   \
        if (aWeakObject) {                                                                                  \
            __weak typeof(self) pWeakSelf = self;                                                           \
            __weak typeof(aWeakObject) pWeakObject = aWeakObject;                                           \
            objc_setAssociatedObject(self, key, aWeakObject, OBJC_ASSOCIATION_ASSIGN);                      \
            JxAutoRunBlockWhenFree *obj = [JxAutoRunBlockWhenFree Create:^{                                 \
                __strong typeof(pWeakSelf) pSelf = pWeakSelf;                                               \
                if (pSelf){                                                                                 \
                    objc_setAssociatedObject(pSelf, key, nil, 0);                                           \
                    objc_setAssociatedObject(pSelf, clearAssObjKey, nil, 0);                                \
                }                                                                                           \
            }];                                                                                             \
            objc_setAssociatedObject(aWeakObject, key, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);             \
            obj = [JxAutoRunBlockWhenFree Create:^{                                                         \
                __strong typeof(pWeakObject) pObject = pWeakObject;                                         \
                if (pObject){                                                                               \
                    objc_setAssociatedObject(pObject, key, nil, 0);                                         \
                }                                                                                           \
            }];                                                                                             \
            objc_setAssociatedObject(self, clearAssObjKey, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);         \
        }                                                                                                   \
    }                                                                                                       \
}

实现上使用例子

/******************************************************************************************
 类名: MyObject(Jx)
 说明: 测试类的扩展类
 ******************************************************************************************/
#pragma mark - ✍️MyObject(Jx)
@interface MyObject(Jx)
@property (nonatomic, weak) id weakObject;
@end
@implementation MyObject(Jx)

RuntimeWeakImpl(weakObject, setWeakObject, id);

@end


mTestObject2 = [[MyObject alloc] init];
mTestObject2.weakObject = mTestObject;

结束


下一篇 Photos框架翻译

Content