Category 在平常代码中经常使用,在使用第三方工具时,遇到工具不能直接解决当前问题的情况,我就会使用到Category,通过给特定的类加一个方法来处理这种情况。
本文仅仅介绍给(Category)加 weak 属性。
weak:修饰OBJC对象,不会持有指向修饰对象,同样指向的对象引用计数就不会增加,当指向的对象被释放释放的时候,weak修饰的对象会被置为nil。
因为堆内存是动态的,所以当某个地址的对象被释放的时候,所有指向他的指针都应该被置为空。weak就是为了满足避免循环引用,同时在对象被释放的时候可以被置为空的情况而存在的。
assign是为了修饰栈内存中的数值对象,当使用assign修饰了一个OBJC对象的时候,可能造成野指针。原因在上面刚刚提到。
进入主题:给 Category 添加 weak 属性。
首先,给 Category 属性是需要使用runtime中的关联来实现set 和 get 方法。但runtime只提供如下几种修饰实现,并没有weak。
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. */
};
看了别人的思路之后,加以修改。runtime没有开放weak解决方案,所以只能模仿 weak 的思路来实现. 思路是这样的,让需要被 weak 修饰的对象去持有一个 strong 的对象,然后当被修饰的对象被释放的时候,持有的对象也会被释放,那么我们就可以捕捉到释放的事件,进而使用OBJC_ASSOCIATION_ASSIGN 来实现弱引用,在释放事件里面再将其释放掉,进而实现weak功能。
// 定义一个对象,使用block来回调析构函数。
typedef void (^DeallocBlock)();
@interface OriginalObject : NSObject
@property (nonatomic, copy) DeallocBlock block;
- (instancetype)initWithBlock:(DeallocBlock)block;
@end
@implementation OriginalObject
- (instancetype)initWithBlock:(DeallocBlock)block
{
self = [super init];
if (self) {
self.block = block;
}
return self;
}
- (void)dealloc {
self.block ? self.block() : nil;
}
@end
Category添加属性
// Category
// NSObject+property.h
@interface NSObject (property)
@property (nonatomic, weak) id objc_weak_id;
@end
// NSObject+property.m
@implementation NSObject (property)
- (id)objc_weak_id {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setObjc_weak_id:(id)objc_weak_id {
OriginalObject *ob = [[OriginalObject alloc] initWithBlock:^{
objc_setAssociatedObject(self, @selector(objc_weak_id), nil, OBJC_ASSOCIATION_ASSIGN);
}];
// 这里关联的key必须唯一,如果使用_cmd,对一个对象多次关联的时候,前面的对象关联会失效。
// 给需要被 assign 修饰的对象添加一个 strong 对象.
objc_setAssociatedObject(objc_weak_id, (__bridge const void *)(ob.block), ob, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, @selector(objc_weak_id), objc_weak_id, OBJC_ASSOCIATION_ASSIGN);
}
tips: 一个不被持有的对象是不能被 weak 指向. 即,
{
A *a = [[A alloc] init];
self.weakObj = a;
}
这样写是不成立的, a 会在{}执行之后便释放掉, self.weakObj 指向 nil. 所以上面 objc_weak_id 要关联一个 retain 的对象.
具体体验weak 和 assgin的区别:
@property (nonatomic,weak) id weakPoint;
@property (nonatomic,assign) id assignPoint;
@property (nonatomic,strong) id strongPoint;
self.strongPoint = [NSDate date];
self.weakPoint = self.strongPoint;
self.assignPoint = self.strongPoint;
self.strongPoint = nil;
NSLog(@"%p", self.weakPoint); // print 0x0 指针置为空。
NSLog(@"%p", self.assignPoint); // crash 因为self.assignPoint指针指向的对象已经被释放。
测试weak是否正确:
self.strongPoint = [NSDate date];
self.objc_weak_id = self.strongPoint;
self.weakPoint = self.strongPoint;
NSLog(@"%p", self.weakPoint); // print 指针。
NSLog(@"%p", self.objc_weak_id); // print 相同的指针。
self.strongPoint = nil;
NSLog(@"%p", self.weakPoint); // print 0x0 指针置为空。
NSLog(@"%p", self.objc_weak_id); // print 0x0 指针置为空。