unsafe_unretained属性有什么用途?

16

我知道unsafe_unretained的定义。

所以我不希望有人写它的定义。

我想知道它的用法和示例,以及它如何与内存管理一起使用。


你可以查看以下链接:https://dev59.com/4moy5IYBdhLWcg3wYc8l - Nishant Tyagi
4个回答

40

unsafe_unretained 只在 ARC(Automatic Reference Counting)中存在。它的作用类似于 MRC(Manual Reference Counting)中的 assign。这些属性不会被保留。通常你会想要将这样的属性用于代理,因为它们不需要一个拥有者来保留它们。

weak 属性与 unsafe_unretained 类似,但是它们的工作更加智能一些。当分配给该属性的对象释放时,弱引用自动变成 nil,以避免向该对象(内存地址)发送消息时导致崩溃。

unsafe_unretained 属性则不会做到这一点。它们将始终保留指定给它的内存地址(除非您手动更改)。在这种情况下,弱引用可以防止崩溃,但结果仍然不如预期。如果您的代码组织良好并且写得很好,则不应该出现这种情况。

那么为什么要使用 unsafe_unretained 而不是 weak 呢?弱引用仅适用于 iOS 5 及更高版本,因此如果您正在构建针对 iOS 4 的 ARC 应用程序,则需要使用 unsafe_unretained 属性。再次强调一下,向已释放的属性发送消息不是任何代码中想要发生的事情。如果您的代码组织良好,则不应该出现任何问题。


2

在ARC之前,为了防止保留循环,可以将委托或其他父引用属性指定为assign。随着ARC和新编译器的引入,您现在应该使用unsafe_unretained

因此,在不需要对引用拥有所有权时,以及不需要或不想使用新的weak引用类型(在引用被释放时将其设置为null)时,可以使用它。


0

这里有一个关于unsafe_unretained的具体用例。假设两个类相互引用,其中一个方向是强引用,另一个方向是弱引用。在第一个类的dealloc期间,来自第二个类的弱引用将已经为nil,从而防止进行适当的清理。将弱引用替换为unsafe_unretained引用将解决此问题。请参见下面的代码示例:

@class Foo;

@interface Bar: NSObject

//Replacing weak with unsafe_unretained prevents this property from becoming nil during Foo.dealloc
@property (nonatomic, weak) Foo *foo;

- (id)initWithFoo:(Foo *)foo;

@end

@interface Foo : NSObject

@property (nonatomic, strong) Bar *bar;

- (void)startObserving;
- (void)endObserving;

@end

@implementation Bar

- (id)initWithFoo:(Foo *)foo {
    if ((self = [super init])) {
        self.foo = foo;

        //Start observing
        [self.foo startObserving];
    }
    return self;
}

- (void)dealloc {
    //Since foo is a weak property, self.foo may actually be nil at this point! See dealloc of class Foo.
    [self.foo endObserving];
}

@end

@implementation Foo

- (id)init {
    if ((self = [super init])) {
        self.bar = [[Bar alloc] initWithFoo:self];
    }
    return self;
}

- (void)dealloc {
    //This will trigger the deallocation of bar. However, at this point all weak references to self will return nil already!
    self.bar = nil;

    //endObserving is never called, because Bar.foo reference was already nil.
}

- (void)startObserving {
    NSLog(@"Start observing");
}

- (void)endObserving {
    NSLog(@"End observing");
}

@end

0

unsafe_unretained 完全不涉及内存管理。在 ARC 中,以下代码

{
    __strong id obj = someObject;
    obj = someOtherObject;
}

等同于

{
    id obj = [someObject retain];
    [obj release];
    obj = [someOtherObject retain];
    [obj release];
}

在非ARC环境中,强引用会使对象保持存活状态。如果您将它们分配给另一个对象的引用,则旧引用首先被释放。如果这样的引用超出范围,同样会发生这种情况。请注意,默认情况下引用是强引用,因此通常您不必使用__strong,除非您想强调变量是强引用。
弱引用不会保持对象保持存活状态,并且如果所分配对象的保留计数变为零,则变为nil
__weak id wobj = nil;
{
    __strong id obj = [Object new];
    wobj = obj;
    // wobj points to the same object as obj
}
// wobj is nil again

在作用域内,obj 保持对象的生命周期,因此 wobjc 指向与 obj 相同的对象。但是当 obj 超出作用域时,该对象会被释放,因为这是唯一的强引用,所以它将被释放。因此,wobj 变成了 nil

unsafe_unretained 是指没有关于 Obj-C 内存管理的任何知识的对象指针。代码如下:

_unsafe_unretained id wobj = nil;
{
    __strong id obj = [Object new];
    wobj = obj;
    // wobj points to the same object as obj
}
// wobj points to the memory address where an object used to be.
// This object has been deallocated! wobj is a dangling pointer!
// Trying to access the object via wobj may crash the app or cause
// other undefined behavior.

等同于

id wobj = nil;
{
    id obj = [Object new];
    wobj = obj;
    [obj release];
}

在非ARC环境下,wobj不会是nil,但它曾经指向的对象已经被释放了,内存可能不再属于该进程,或者已经被回收用于存储另一个对象或任何类型的分配数据(例如使用malloc())。

通常情况下,您希望对不保持对象活动状态的引用使用弱引用。一个典型的问题是,如果对象A对对象B有强引用A->B,反之亦然B->A,那么就会出现保留循环,现在无论是A还是B都不会再次被释放,因为A使B保持活动状态,而B使A保持活动状态,即使没有其他人引用A或B,两个对象始终都将具有至少一个保留计数。要解决这个问题,其中一个必须对另一个使用弱引用。

例如,如果您编写一个XML解析器,那么XML节点将对其所有子节点具有强引用,以保持这些子节点的存活和可访问性,但是子节点通常会引用其父节点,那么父节点就是一个弱节点。这样,您可以从任何子节点返回到父节点,但它们不会保持父节点的存活状态,因此如果没有其他人(既不是其父节点也不是任何外部引用)保持父节点存活,则父节点将死亡,并且其中的子节点也随之而亡。
通常使用unsafe_unretained的唯一原因是性能。首先,每个对象都有一个链接列表,其中包含指向该对象的弱变量的所有地址指针,因此当对象死亡时,所有这些变量都可以设置为nil。管理该链接列表需要消耗时间,而列表本身需要消耗内存。然后,每个弱引用必须暂时变为强引用,以确保在调用其方法时不会释放对象。最后,所有这些操作都必须是线程安全的,因此即使您的进程是单线程的,它也始终涉及锁定。
__weak id obj = someObject;
[obj performMethod];
obj = nil;

实际上,在幕后有复杂的代码,大致相当于

lock(memlockOf(someObject));
id obj = someObject;
addWeakRefToLinkList(someObject, &obj);
unlock(memlockOf(someObject));

lock(memlockOf(obj));
id temp = [obj retain];
unlock(memlockOf(obj));
[temp performMethod];
[temp release];

lock(memlockOf(obj));
removeWeakRefFromLinkList(obj, &obj);
obj = nil;
unlock(memlockOf(obj));

请记住,C指针不能是弱引用,因此它们始终是unsafe_unretained。如果您这样做
id obj = [Object new];
void * cptr = (__bridge void *)obj;

那么cptr实际上是对该对象的一个unsafe_unretained引用。如果增加保留计数,您可以使C引用“准强引用”。

id obj = [Object new];
void * cptr = (__bridge_retained void *)obj;

这相当于

id obj = [Object new];
void * cptr = (void *)[obj retain];

在非ARC环境下,保留计数器会增加一个,因此ARC永远不会释放该对象,即使所有ARC引用都消失了,该对象仍然至少有一个保留计数器。

但现在这个对象根本不会被释放,除非你使用转换桥接来平衡它:

{
    id obj = (__bridge_transfer id)cptr;
    // Use obj all you want but don't touch cptr after that!
}

这相当于

{
    id obj = (id)cptr;
    // Use obj all you want but don't touch cptr after that!
    [obj release];
}

在非 ARC 环境下,"转移" 的意思是将所有权转移回 ARC,因此在这样做后,您不得再触摸 cptr 变量,因为现在 ARC 决定对象何时死亡,而当发生这种情况时,它不会将 cptr 设置为 nil。转移意味着 "从中创建一个 strong ARC 引用,但不对 retain 计数器进行任何操作",因此 retain 计数器不会增加,但一旦丢失 strong 引用,retain 计数器就像往常一样减少,这样就能平衡保留桥接的额外 retain,在那里 retain 计数器只是增加而已,却没有其他事情发生。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接