如何在Objective-C中创建弱引用?

10
我有这样的情况:
NSMutableArray * A = [[NSMutableArray alloc]initwithObjects:@"one",nil];
NSMutableArray * B = [[NSMutableArray alloc]initwithObjects:@"two",nil];

[A addObject:B];
[B addObject:A];

这里有一个保留循环,我该如何打破它?(使用弱引用)……在上面的例子中。 谢谢


8
不要这样做,这是简单的答案。为什么你需要把B放入A中,把A放入B中呢? - Jasarien
http://ofcodeandmen.poltras.com/2009/08/26/nsmutablearray-with-weak-references/ - tidwall
5个回答

12

我会质疑你需要首先做这件事的原因 - 但如果你决定它是最好的数据架构,那么我会使用NSValue。要在Foundation集合类中存储一个对象的引用(不保留它),你可以使用+[NSValue valueWithNonretainedObject:]

NSMutableArray *a = [[NSMutableArray alloc] initwithObjects:@"one", nil];
NSMutableArray *b = [[NSMutableArray alloc] initwithObjects:@"two", nil];

[a addObject:b];
[b addObject:[NSValue valueWithNonretainedObject:a]];

然后,要从NSValue实例中稍后获取您的对象,您需要执行以下操作:

NSMutableArray *c = (NSMutableArray *) [[b objectAtIndex:index] nonretainedObjectValue];

补充说明

我已将答案从使用+[NSValue valueWithPointer:]更改为+[NSValue valueWithNonretainedObject:],因为在ARC下,编译器会发出警告(由于从对象类型到const void *的强制转换)。虽然功能上相同,但类型转换和方法名称更有意义(并且编译器实际上会让您编译而不必求助于hackery)。


我建议使用NSValue来创建弱引用。它很容易理解和使用,也许是弱引用的官方实现。CFArrayCreateMutable函数的使用并不坏,但它会带来一些NSMutableArray的使用不一致性,特别是在公开接口中。 - Robin
我完全同意 - 与非标准的CFArray打交道似乎是一种制造内存相关错误的方法,如果/当您忘记了您的NSMutableArray行为与99.9%的NSMutableArray实例不同,那么这些错误可能会在未来出现。然而,使用NSValue技术进行调试不会有任何混淆或歧义。 - Nick Forge

8

我过去解决这个问题的方法是使用NSProxy的子类来打破循环引用。我将创建一个对象,它存储对其中一个数组的弱引用,并将除了内存管理之外的所有消息都传递给它。

     ┌──── NSArray A <────┐
     │                    │
     │                    │
     v        weak        │
ACWeakProxy ┈ ┈ ┈ ┈ ┈ > NSArray B

@interface ACWeakProxy : NSProxy {
    id _object;
}

@property(assign) id object;

- (id)initWithObject:(id)object;

@end

@implementation ACWeakProxy

@synthesize object = _object;

- (id)initWithObject:(id)object {
    // no init method in superclass
    _object = object;
    return self;
}

- (BOOL)isKindOfClass:(Class)aClass {
    return [super isKindOfClass:aClass] || [_object isKindOfClass:aClass];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation setTarget:_object];
    [invocation invoke];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [_object methodSignatureForSelector:sel];
}

@end

然后你的代码变成了:
NSMutableArray * A = [[NSMutableArray alloc] initwithObjects:@"one", nil];
NSMutableArray * B = [[NSMutableArray alloc] initwithObjects:@"two", nil];

[A addObject:B];
ACWeakProxy * proxy = [[ACWeakProxy alloc] initWithObject:A];
[B addObject:proxy];
[proxy release];

// will print "two"
NSLog(@"%@", [[[B objectAtIndex:1] objectAtIndex:1] objectAtIndex:0]);

然而,在你仍在使用弱引用时,你需要确保它不会消失。


这个解决方案与ARC的弱引用很好地配合。如果您将属性(和ivar)声明为weak,则在引用丢失时该值将变为nil。@property(weak) id object; - David V
哇,这真是个好发现,谢谢。我正在使用AVFoundation来录制电影,不幸的是,似乎VideoFileOutput startRecordingToOutputFileURL:url recordingDelegate:没有保持弱引用,在一些视频源和汇流之后出现了循环,但是这个代理最终解决了问题,现在对象被释放了(它是一个OSX的C++包装器)。 - dashesy

7

您可以:

  • manually break the cycle by removing the objects
  • use CFArrayCreateMutable() and set the retain and release callback to NULL:

    CFArrayCallBacks acb = { 0, NULL, NULL, CFCopyDescription, CFEqual };
    NSMutableArray *noRetain = (NSMutableArray *)CFArrayCreateMutable(NULL, 0, &acb);
    
更好的做法是,退一步思考如何以不同方式实现您想要的目标。通过适当的设计,这个问题可能根本不会发生。

0
在iPhone中,没有弱引用的概念。在您的情况下,我相信这会导致其中一个对象被过度释放的问题。
通常,对于两个类,我会这样做:
让一个类通过保留来拥有另一个类,而另一个类只使用分配(assign)。一些示例代码可能会更清楚地说明它。
类A
@interface A {

}

@property (nonatomic, retain) B *b;
@end

类 B

@interface B {

}
@property (nonatomic, assign) A *a;
@end

0
在没有垃圾回收的iPhone环境中,弱引用或多或少被定义为对象未被保留的引用。即使不考虑这一点,您也无法控制NSMutableArray是否创建弱引用。在GC环境中,您仍然有循环,但这并不重要,因为一旦两个对象都不可达,它们都会消失。
唯一能做的就是手动打破循环。您可以通过从另一个数组中删除一个数组来实现这一点。

你能解释一下吗?你说当两个对象都无法访问时,它们都会消失。但我理解的是只有在保留计数为零时对象才会消失。这是GC特有的吗? - xcoder
@xcoder:是的,它是特定于GC的。GC通过获取一组根对象(全局变量+堆栈上的对象+其他一些对象)并查看哪些其他对象可以从这些根对象访问(即具有引用)来工作。其他任何东西都将被释放。 - JeremyP

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