【问题标题】:I have a circular reference. How can I create a weak reference in Objective-C?我有一个循环参考。如何在 Objective-C 中创建弱引用?
【发布时间】:2009-07-02 04:45:28
【问题描述】:

我正在开发一个 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

我知道这是一个循环引用。 Apple 的文档指出,为了释放具有循环引用的对象,我需要一个弱引用而不是强引用(保留属性),但它没有贯彻并解释我是如何做到这一点的。我计划同时释放和释放行内的所有块以及行本身。如何在我的每个块中设置对其“父”行的弱引用?

【问题讨论】:

  • 顺便说一句,您对您的问题的编辑和适当标记是值得称赞的,尤其是作为一个新的 SO 用户。太棒了!我感谢那些花时间为社区的利益做一些改进的人。
  • 为什么,谢谢!这个网站正是我在各个方面一直在寻找的。只有当这么多人愿意跳出来立即回答时,花时间提出一个明确的问题才有意义。

标签: objective-c iphone weak-references circular-reference


【解决方案1】:

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

@property (nonatomic,assign) Row *yCoord;

我的其余答案与 Objective-C 2.0 和 GC 方面的“弱引用”有关。


当您使用垃圾回收 (10.5+) 时,通过在变量声明前加上 __weak 来创建弱引用。当您分配给该变量时,GC(如果启用)会跟踪引用,如果对被引用对象的所有强引用都消失了,它将自动为您清零。 (如果未启用 GC,则忽略 __weak 属性。)

因此,您可以安全地修改上述答案以更好地使用垃圾收集(目前在 10.5+,也许有一天会在 iPhone 上)如下:(参见related Apple docs。)

@property (nonatomic,assign) __weak Row *yCoord;

引用Chris Hanson(您可以在其中找到更详细的信息):

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

我会通过说“如果没有对对象的非弱引用”来澄清这一点。一旦最后一个强引用被删除,对象可能会被收集,所有弱引用都会自动归零。

注意:直接与创建弱引用有关,但也有一个__strong属性,但由于Objective -C 对象变量默认是强引用,它通常仅用于原始 C 指针,指向垃圾收集器不会将其视为根的结构或原语之类的东西,如果你不将它们声明为,它们将从你下面收集强的。 (虽然缺少__weak 可能会导致保留周期和内存泄漏,但缺少__strong 可能会导致内存阻塞以及非确定性发生的非常奇怪和隐蔽的错误,并且很难追踪。)

【讨论】:

  • 让我澄清一下,这是针对 iPhone 应用程序的。我不相信 iPhone 使用 GC。
  • so 而不是声明 Row *yCoord; with @property(nonatomic,retain) Row *yCoord;我做:__weak Row *yCoord;但那我该如何申报财产呢?
  • 您可以混合使用@property 和__weak:@property(nonatomic,retain) __weak Row *yCoord;
  • 我很欣赏这些信息,但我认为我的问题没有得到解决。我想实现与委托和子视图类似的结构,但我不知道如何将 Block 类中的 Row 声明为弱引用,以便保留计数不增加。
  • 谢谢,我相信这就是我想要的。
【解决方案2】:

只需将其更改为分配而不是保留,不再循环引用。

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

【讨论】:

    【解决方案3】:

    弱引用只是一个赋值(除非你说的是垃圾收集,它是一个完全独立的蠕虫罐,但不会受到保留周期的影响)。

    通常,在 Cocoa 中,Row 会保留 Block 对象(通过将它们包含在 NSMutableArray 中),但 Block 不会保留 Row,每个都将简单地将其存储在 ivar 中(使用 "分配”属性)。

    只要Row 在释放每个Block 之前小心地释放它(即,它的dealloc 应该释放NSMutableArray,只要没有其他人有任何指向它们的指针,它就会释放块)然后一切都将酌情释放。

    您还可以采取预防措施,在从数组中删除条目之前将块中的行引用归零,例如:

    - (void) dealloc {
        for (Block* b in _blocks) {
            b.row = nil;
        }
        [_blocks release];
        [super dealloc];
    }
    

    其中 _blocks 是 blocks 属性引用的 ivar。

    【讨论】:

    • _b 是 self.blocks 返回的 NSMutableArray ivar 吗?如果是这样,我假设您的意思是 [_b release]。此外,不需要在包含引用的类中使用 self.blocks — 只需使用 for (Block* b in _b)。如果 _b 被命名为“_blocks”甚至是“blocks”,它可能不会那么混乱。 (我知道有些人更喜欢下划线,但我从不使用它。)
    【解决方案4】:

    在多线程系统中使用 assign 创建弱引用可能是不安全的,尤其是当其中一个对象可以被第三个对象保留,然后用于取消引用另一个对象时。

    幸运的是,这通常是层次结构的问题,包含弱引用的对象只关心被引用对象的生命周期内它所引用的对象。这是上级下级关系的常见情况。

    我认为 OP 评论中的情况与此对应,行 = 上级,块 = 下级。

    在这种情况下,我会使用句柄从下级引用上级:

    // 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. 只有对象本身需要知道嵌入的弱引用。所有其他对象都可以将下属视为具有对上级的常规引用。

    【讨论】:

    • 这个设计可以工作,但是你不能在SuperiorHandle 类中使用@synthesize Superior。 @synthesize 将为每个字段创建一个单独的锁(准确地说是一个自旋锁)。您需要锁定在返回“superior_”变量时在“无效”中使用的相同锁。所以需要自己实现superior,return @synchronized(self) { returnValue = _superior} return returnValue;
    • 其实,这种“高级”的设计或许行不通。如果调用 'superior' 方法来获取 'Superior' 的值,然后另一个线程使句柄无效怎么办?你仍然有一个指向你刚刚获得“优越”值的线程中已释放内存的指针。当然,除非你假设这些都不是线程安全的(但如果是这种情况,你不会' t 需要 @synchronize 调用)。不幸的是,我认为这根本行不通。你真的需要一个 GC 环境才能让它工作,或者你必须在返回之前保留/自动释放优越的价值。
    • 回复:自旋锁。我不知道这种行为,你有这方面的文件吗?我在 Hillegass 的书中读到的内容以及记录在 here 中的内容表明,“synthesize atomic”生成的锁是 @synchronized (self),就像我在 invalidate() 中手动执行的操作一样。
    • Re: 另一个线程dealloc。好点,我错过了部分内存管理。需要的是在 Subordinate.superior 中保留/自动释放。我已经更新了帖子。谢谢!
    • 实际上我找到了一种更简洁的方法,也可以解决第一条评论。将保留/自动释放放在 SuperiorHandle.superior 而不是 Subordinate.superior 意味着下属(可能有很多)不必了解内存管理。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多