弱引用的使用

弱引用的使用

在一些情况下,可能需要使用某个实例,但是又不想对其进行强引用而导致其不能被释放。

通常情况下,我们可以直接使用 NSPointerArrayNSHashTableNSMapTable 类,创建一个持有弱引用实例的集合,那么当添加到集合中的实例被释放时,相应的其也会被移出该集合。

另外,在将基本类型添加到集合中时,我们需要借助 NSValue 类对其进行包装,而该类中的 NSValueExtensionMethods 分类还提供了对实例对象和指针的封装方法。

@interface NSValue (NSValueExtensionMethods)

+ (NSValue *)valueWithNonretainedObject:(nullable id)anObject;
@property (nullable, readonly) id nonretainedObjectValue;

+ (NSValue *)valueWithPointer:(nullable const void *)pointer;
@property (nullable, readonly) void *pointerValue;

@end

测试代码:

@interface Info : NSObject
@end

@implementation Info
@end

@interface ViewController ()

@property (nonatomic, strong) NSMapTable *dic;
@property (nonatomic, strong) NSPointerArray *array;

@property (nonatomic, strong) Info *info;

@property (nonatomic, weak) NSString *str;
@property (nonatomic, weak) NSMutableString *str1;
@end

ViewController 中声明用来持有实例的两个集合,一个 Info 类实例,以及两个字符串。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.array = [NSPointerArray weakObjectsPointerArray];
    self.dic = [NSMapTable strongToWeakObjectsMapTable];
    
    Info *info = [Info new];
    
    NSString *str = @"this is a test ";
    NSMutableString *str1 = [NSMutableString stringWithString:str];
    
    self.str1 = str1;
    self.info = info;
    self.str = str;

    NSValue *value = [NSValue valueWithNonretainedObject:str];
    NSValue *value1 = [NSValue valueWithNonretainedObject:str1];
    NSValue *value2 = [NSValue valueWithPointer:(void *)self.info];
    
    [self.dic setObject:value forKey:@"key"];
    [self.dic setObject:value1 forKey:@"key1"];
    [self.dic setObject:value2 forKey:@"key2"];
    [self.dic setObject:str forKey:@"key3"];
    [self.dic setObject:str1 forKey:@"key4"];
    [self.dic setObject:self.info forKey:@"key5"];
    [self.dic setObject:self.str forKey:@"key6"];
    [self.dic setObject:self.str1 forKey:@"key7"];
    
    [self.array addPointer:(void *)value];
    [self.array addPointer:(void *)value1];
    [self.array addPointer:(void *)value2];
    [self.array addPointer:(void *)str];
    [self.array addPointer:(void *)str1];
    [self.array addPointer:(void *)self.info];
    [self.array addPointer:(void *)self.str];
    [self.array addPointer:(void *)self.str1];
    
    NSLog(@"%@",self.dic);
    
    NSLog(@"%@",self.array.allObjects);
    
    NSLog(@"str : %p , &str : %p , str1 : %p , &str1 : %p , self.str : %p , self.str1 : %p",str,&str,str1,&str1,self.str,self.str1);
}

输出结果如下:

NSMapTable {
[3] key1 -> <10cefd00 00600000>
[4] key3 -> this is a test 
[5] key -> <8880d10b 01000000>
[6] key4 -> this is a test 
[7] key7 -> this is a test 
[8] key5 -> 
[9] key6 -> this is a test 
[12] key2 -> <802f3900 00600000>
}

(
    "<8880d10b 01000000>",
    "<10cefd00 00600000>",
    "<802f3900 00600000>",
    "this is a test ",
    "this is a test ",
    "",
    "this is a test ",
    "this is a test "
)

str : 0x10bd18088 , &str : 0x7ffee3ee7a20 , str1 : 0x600000fdce10 , &str1 : 0x7ffee3ee7a18 , self.str : 0x10bd18088 , self.str1 : 0x600000fdce10

之后,再执行下面的方法:

- (void)btnClick {
    self.info = nil;
    NSLog(@"%@",self.dic);
    NSLog(@"%@",self.array.allObjects);   
}

输出结果如下:

NSMapTable {
[4] key3 -> this is a test 
[9] key6 -> this is a test 
}
(
    "this is a test ",
    "this is a test "
)

如果将 self.info = nil 注释,那么输出结果如下:

NSMapTable {
[4] key3 -> this is a test 
[8] key5 -> 
[9] key6 -> this is a test 
}
(
    "this is a test ",
    "",
    "this is a test "
)

通过上面三个输出结果的比较可知,当实例销毁时,集合中的实例会被移除。而需要注意的是,字符串比较特殊,对于 NSString 而言,其实际的值是保存在内存的数据段区域的,而不像 NSMutableString 保存在堆中。从下面的输出地址也可推断出各个变量大致存储在栈中还是堆中:

str : 0x10bd18088 , &str : 0x7ffee3ee7a20 ,

str1 : 0x600000fdce10 , &str1 : 0x7ffee3ee7a18 ,

self.str : 0x10bd18088 , self.str1 : 0x600000fdce10

所以,这三个输出中都包含了 strself.str 的值。

还有一个问题值得注意,当使用 NSValue 封装指针时,如果将封装后的 NSValue 对象放在普通的集合中,那么如果指针所指向的实例对象销毁了,NSValue 的实例并不会被移出集合,并且访问 nonretainedObjectValuepointerValue 属性得到的也并不是 nil

现在,添加一个集合属性,并将 info 属性封装后放入该集合。

@property (nonatomic, strong) NSMutableDictionary *dic1;

- (void)viewDidLoad {
	[super viewDidLoad];
	self.dic1 = [NSMutableDictionary dictionary];
	[self.dic1 setObject:value2 forKey:@"key2"];
}
- (void)btnClick {
    
    NSLog(@"%@",self.dic);
    
    NSLog(@"%@",self.array.allObjects);

    NSValue *value = [self.dic1 objectForKey:@"key2"];
    
    if (value.nonretainedObjectValue){
        NSLog(@"%@",value.nonretainedObjectValue);
    }
}

得到输出结果如下:

NSMapTable {
[4] key3 -> this is a test 
[8] key5 -> 
[9] key6 -> this is a test 
[12] key2 -> <80ae8e03 00600000>
}
(
    "<80ae8e03 00600000>",
    "this is a test ",
    "",
    "this is a test "
)

此时,临时变量 value2 并没有被释放,而其封装的 Info 实例对象也是可以访问的。

@property (nonatomic, weak) Info *info;

但是如果将属性 info 的修饰字段 strong 修改为 weak 如上,那么会发现程序输出下面的结果后,便报错了。

NSMapTable {
[4] key3 -> this is a test 
[9] key6 -> this is a test 
[12] key2 -> 
}
(
    "",
    "this is a test ",
    "this is a test "
)

对比上面的结果可知,info 实例对象已经被释放了,而此时的 value2 所封装的地址 却并未置为空,再去访问value.nonretainedObjectValue 具体封装的实例对象,便造成了指针越界。

所以除非确定封装的对象在访问时不会被销毁,否则不要使用普通的集合来保存封装的指针。

这里的地址 是按字节从低到高打印的,实际地址为 0x00 00 60 00 00 ae c7 a0

当然,除此之外,还可以利用 block 的特性来实现弱引用的使用。将实例作为一个参数来创建一个 block 对象,然后持有该对象,需要获取实例时,便执行持有的 block 对象,返回封装的实例。

你可能感兴趣的:(iOS)