弱引用的使用
弱引用的使用
在一些情况下,可能需要使用某个实例,但是又不想对其进行强引用而导致其不能被释放。
通常情况下,我们可以直接使用 NSPointerArray
、 NSHashTable
或 NSMapTable
类,创建一个持有弱引用实例的集合,那么当添加到集合中的实例被释放时,相应的其也会被移出该集合。
另外,在将基本类型添加到集合中时,我们需要借助 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 -> <Info: 0x600000392f80>
[9] key6 -> this is a test
[12] key2 -> <802f3900 00600000>
}
(
"<8880d10b 01000000>",
"<10cefd00 00600000>",
"<802f3900 00600000>",
"this is a test ",
"this is a test ",
"<Info: 0x600000392f80>",
"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 -> <Info: 0x6000006210e0>
[9] key6 -> this is a test
}
(
"this is a test ",
"<Info: 0x6000006210e0>",
"this is a test "
)
通过上面三个输出结果的比较可知,当实例销毁时,集合中的实例会被移除。而需要注意的是,字符串比较特殊,对于 NSString
而言,其实际的值是保存在内存的数据段区域的,而不像 NSMutableString
保存在堆中。从下面的输出地址也可推断出各个变量大致存储在栈中还是堆中:
str : 0x10bd18088 , &str : 0x7ffee3ee7a20 ,
str1 : 0x600000fdce10 , &str1 : 0x7ffee3ee7a18 ,
self.str : 0x10bd18088 , self.str1 : 0x600000fdce10
所以,这三个输出中都包含了 str
或 self.str
的值。
还有一个问题值得注意,当使用 NSValue
封装指针时,如果将封装后的 NSValue
对象放在普通的集合中,那么如果指针所指向的实例对象销毁了,NSValue
的实例并不会被移出集合,并且访问 nonretainedObjectValue
或 pointerValue
属性得到的也并不是 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 -> <Info: 0x6000038eae80>
[9] key6 -> this is a test
[12] key2 -> <80ae8e03 00600000>
}
(
"<80ae8e03 00600000>",
"this is a test ",
"<Info: 0x6000038eae80>",
"this is a test "
)
<Info: 0x6000038eae80>
此时,临时变量 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 -> <a0c7ae00 00600000>
}
(
"<a0c7ae00 00600000>",
"this is a test ",
"this is a test "
)
对比上面的结果可知,info
实例对象已经被释放了,而此时的 value2
所封装的地址 <a0c7ae00 00600000>
却并未置为空,再去访问value.nonretainedObjectValue
具体封装的实例对象,便造成了指针越界。
所以除非确定封装的对象在访问时不会被销毁,否则不要使用普通的集合来保存封装的指针。
这里的地址
<a0c7ae00 00600000>
是按字节从低到高打印的,实际地址为0x00 00 60 00 00 ae c7 a0
当然,除此之外,还可以利用 block 的特性来实现弱引用的使用。将实例作为一个参数来创建一个 block 对象,然后持有该对象,需要获取实例时,便执行持有的 block 对象,返回封装的实例。