欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

弱引用的使用

程序员文章站 2024-03-19 15:02:04
...

弱引用的使用

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

通常情况下,我们可以直接使用 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 -> <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

所以,这三个输出中都包含了 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 -> <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 对象,返回封装的实例。

相关标签: 弱引用