我有一个循环引用问题。如何在Objective-C中创建弱引用?

20

我正在开发一个iPhone应用程序。我有一个Row类的对象,需要释放许多Block类的对象。每个Block当前都有一个属性来保留Row类的实例变量。

@interface Block : UIImageView {
  Row *yCoord;
}
@property (nonatomic,retain) Row *yCoord;
@end

每个Row都包含这些块的一个NSMutableArray

@interface Row : NSObject {
    NSMutableArray *blocks;
}
-(void)addBlock:(Block*)aBlock;
@end

@implementation Row
-(void)addBlock:(Block*)aBlock {
    [blocks addObject:aBlock];
    aBlock.yCoord = self;
}
@end

我理解这是一个循环引用。苹果的文档说明,为了释放具有循环引用的对象,我需要使用弱引用而不是强引用(保留属性),但它没有进一步解释如何做到这一点。我计划同时发布和释放行中的所有块以及行本身。如何在每个块内部设置一个指向其“父”行的弱引用?


4
顺便说一下,作为一个新的SO用户,你的编辑和适当的标记问题的做法值得称赞。太棒了!我很欣赏那些花时间打磨事物以造福社区的人。 - Quinn Taylor
2
谢谢!这个网站在各个方面都是我一直在寻找的。既然有这么多人愿意立即跳出来回答问题,花时间提出一个清晰的问题是很有意义的。 - Tozar
4个回答

19

编辑: 由于提问者澄清他没有使用垃圾回收(iPhone目前不支持),我的建议是通过只有其中一个对象保留另一个对象来避免循环引用,就像使用代理一样。当使用属性时,使用“assign”而不是“retain”来实现这一点。例如:

@property (nonatomic,assign) Row *yCoord;
我的回答关于Objective-C 2.0和GC中的"弱引用"。
在使用垃圾收集(10.5+)时,通过在变量声明前加上__weak来创建一个弱引用。当你对该变量赋值时,GC(如果启用)会跟踪引用并在所有对被引用对象的强引用都消失时自动将其清零。(如果未启用GC,则忽略__weak属性。)
因此,可以将上面的答案修改为更好地与垃圾回收(目前在10.5+上,也许有一天在iPhone上)配合使用,如下所示:(请参阅相关的Apple文档。)
@property (nonatomic,assign) __weak Row *yCoord;

引用Chris Hanson的话(您可以在该网址找到更详细的信息):

通过在实例变量声明前加上__weak,可以告诉垃圾收集器,如果它是对象的唯一引用,则应将对象视为可回收。

我想澄清的是,“如果对象没有非弱引用”的意思。一旦最后一个强引用被移除,该对象就可以被收集,并且所有弱引用将自动被置零。

注意:这与创建弱引用并不直接相关。但也有__strong属性,但由于Objective-C对象变量默认为强引用,因此通常仅用于对于诸如结构体或基本类型等垃圾收集器不会视为根源并且如果未将其声明为强引用则它们将从下面被回收的原始C指针。(而缺乏__weak可能会导致保留周期和内存泄漏,缺乏__strong则可能导致内存篡改以及非常奇怪和难以跟踪的出现不确定性的错误。)


1
让我澄清一下,这是针对 iPhone 应用程序的。我不认为 iPhone 使用 GC。 - Tozar
1
所以,不是声明 Row *yCoord; 而是使用@property(nonatomic,retain) Row *yCoord; 我应该这样做: __weak Row *yCoord; 但是,如何声明属性呢? - Tozar
1
我很感激这些信息,但我认为我的问题还没有得到解决。我想要实现类似于委托和子视图的结构,但我不知道如何将块类中的行声明为弱引用,以便不增加保留计数。 - Tozar
1
谢谢,我相信这就是我要找的。 - Tozar
@QuinnTaylor,虽然你说的是正确的,但在某些情况下,你可能仍然希望使用成员变量而不是属性。例如:如果你想让来自基类的对象只能被子类访问而不能被外部世界访问,那么你应该将其定义为成员变量而不是属性,因为属性不支持访问修饰符。 - Deepak G M
显示剩余4条评论

8

只需将其更改为赋值而不是保留,就没有循环引用问题了。

@interface Block : UIImageView {
  Row *yCoord;
}
@property (nonatomic,assign) Row *yCoord;
@end

4
一种弱引用就是一个赋值操作(除非你谈论的是垃圾回收,这是一个完全不同的问题,但不会遭受保留循环的影响)。
通常,在Cocoa中,Row会保留Block对象(通过将它们包含在NSMutableArray中),但Block不会保留Row,每个对象只是将其存储在ivar中(具有“assign”属性)。
只要Row小心地在释放每个Block之前进行释放(即,它的dealloc应该释放NSMutableArray,只要没有其他人有指向它们的指针,就会释放Blocks),则一切都将被适当地释放。
您还可以在从数组中删除条目之前将行引用清零,类似于:
- (void) dealloc {
    for (Block* b in _blocks) {
        b.row = nil;
    }
    [_blocks release];
    [super dealloc];
}

_blocks是由blocks属性引用的实例变量。


_b是由self.blocks返回的NSMutableArray实例变量吗?如果是这样,我猜你的意思是[_b release]。此外,在持有引用的类内部使用self.blocks是没有必要的——只需使用for (Block* b in _b)。如果_b被命名为“_blocks”甚至是“blocks”,可能会更清晰易懂。(我知道有些人喜欢下划线,但我从不使用它。) - Quinn Taylor

3
使用assign创建弱引用在多线程系统中可能不安全,特别是当其中一个对象可以被第三个对象保留,然后用于取消引用另一个对象时。幸运的是,这通常是层次结构问题,包含弱引用的对象仅关心所引用对象的生命周期,这是上下级关系的常见情况。我认为OP评论中的情况与此相符,其中Row=Superior,Block=Subordinate。在这种情况下,我将使用一个句柄来从Subordinate引用Superior。
// Superior.h

@class Superior;

@interface SuperiorHandle : NSObject {
    @private
        Superior* superior_;
}

// note the deliberate avoidance of "nonatomic"
@property (readonly) Superior *superior;

@end

@interface Superior : NSObject {
    @private
        SuperiorHandle *handle_;
        // add one or more references to Subordinate instances
}

// note the deliberate avoidance of "nonatomic"
@property (readonly) SuperiorHandle *handle;

@end


// Superior.m

#import "Superior.h"

@implementation SuperiorHandle

@synthesize
    superior = superior_;

- (id)initWithSuperior:(Superior *)superior {
    if ((self = [super init])) {
        superior_ = superior; // weak reference
    }
}

- (void)invalidate {
    @synchronized (self) {
        superior_ = nil;
    }
}

- (Superior *)superior {
    @synchronized (self) {
        // retain and autorelease is required to prevent dealloc before we're ready, thanks to AndroidDev for pointing out this mistake
        return [[superior_ retain] autorelease];
    }
}

@end

@implementation Superior

@synthesize
    handle = handle_;

- (id)init {
    if ((self = [super init])) {
        handle_ = [[SuperiorHandle alloc] initWithSuperior:self];
    }
    return self;
}

- (void)dealloc {
    [handle_ invalidate];
    [handle_ release];

    [super dealloc];
}

@end


// Subordinate.h

@class Superior;
@class SuperiorHandle;

@interface Subordinate : NSObject {
    @private
        SuperiorHandle *superior_handle_;
}

@property (readonly) Superior *superior;

@end


// Subordinate.m

#import "Subordinate.h"

#import "Superior.h"

@implementation Subordinate

// no synthesize this time, superior's implementation is special

- (id)initWithSuperior:(Superior *)superior {
    if ((self = [super init])) {
        superior_handle_ = [superior.handle retain];
    }
    return self;
}

- (void)dealloc {
    [superior_handle_ release];

    [super dealloc];
}

- (Superior *)superior { 
    @synchronized (superior_handle_) {
        return superior_handle_.superior; 
    }
}

@end

一些优点:

  1. 它是线程安全的。您无法使包含在Subordinate中的弱引用成为无效指针。它可能变成nil,但这没关系。
  2. 只有对象本身需要知道嵌入式弱引用。所有其他对象都可以将Subordinate视为对Superior的常规引用。

这个设计是可行的,但是你不能在SuperiorHandle类中使用@synthesize superior。@synthesize将为每个字段创建一个单独的锁(准确来说是自旋锁)。您需要在与“invalidate”中使用的相同锁上进行锁定,该锁在返回“superior_”变量时也要使用。因此,您需要自己实现superior并返回@synchronized(self) {returnValue = _superior} return returnValue; - Android Dev
实际上,也许这个“超级”的设计不会起作用。如果你调用“superior”方法获取“Superior”的值,然后另一个线程使句柄无效,那么你仍然在刚刚获得“superior”值的线程中留下了指向已释放内存的指针。除非,当然,你假设所有这些都不是线程安全的(但如果是这样的话,你就不需要@synchronize调用)。不幸的是,我认为这根本行不通。你真的需要一个GC环境才能让它工作,否则你必须在返回之前保留/自动释放优越值。 - Android Dev
关于自旋锁的问题。我不知道这个行为,你有文档吗?根据Hillegass的书和这里记录的内容,由“synthesize atomic”生成的锁是@synchronized(self),就像我在invalidate()中手动执行的一样。 - Brane
回复:另一个线程的dealloc。很好的观点,我错过了部分内存管理。需要在Subordinate.superior中保留/自动释放。我已经更新了帖子。谢谢! - Brane
实际上,我找到了一种更简洁的方法,也解决了第一个评论。将retain/autorelease放在SuperiorHandle.superior中而不是Subordinate.superior中意味着下属(可能有很多)不必了解内存管理。 - Brane

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