4

我正在开发一个捆绑代码库——不是一个应用程序——开发从 10.4 开始,需要在 10.4 上运行,但一直到 10.8。它手动从 nib 文件加载其视图,我最近才意识到存在大量内存泄漏,因为 nib 利用绑定并绑定到文件的所有者,创建引用循环并阻止文件的所有者类解除分配。我认为让“文件的所有者”加载自己的 nib 会使情况变得更糟。

我使用以下代码加载笔尖(此代码在基类中,子类覆盖+nibName):

NSString *nibName = [[self class] nibName];
NSNib *nib = [[NSNib alloc] initWithNibNamed:nibName bundle:myBundle];
[nib instantiateNibWithOwner:self topLevelObjects:&topLevelObjects];

由于我必须以 10.4 为目标,因此无法使用NSViewController. 我想我需要实现我自己的视图控制器类,但是如何防止引用循环像NSViewController类所承诺的那样发生呢?如果视图控制器是 nib 的“文件所有者”,我不是只是将引用周期从我当前的类推送到我的视图控制器吗?做什么NSViewController来防止这种情况发生?

4

2 回答 2

2

NSViewController 在内存管理甚至顶级对象方面都没有做任何特别的事情。它只是提供了一个安全的地方来加载一个 nib,然后在 Nib 的生命周期内将其内容保存在内存中,这意味着该类本身只不过是一个外部文件的所有者。只是为了好玩,我重新实现了这个类,并注释掉了有趣的部分。有些东西,我直接删除了,因为它太老套了,不值得实施,或者没有使用,不值得重新制作。完整的课程,包括文档和评论,可以在这里找到;

@interface CFIViewController : NSResponder <NSCoding> {
@private
    NSString *_nibName;
    NSBundle *_nibBundle;
    id _representedObject;
    NSString *_title;
    IBOutlet NSView *view;
    NSArray *_topLevelObjects;
    id _autounbinder; 
    //NSString *_designNibBundleIdentifier;
}

- (id)initWithNibName:(NSString*)nibName bundle:(NSBundle *)nibBundleOrNil;

- (void)setRepresentedObject:(id)representedObject;
- (id)representedObject;

- (void)setTitle:(NSString *)title;
- (NSString *)title;

- (NSView *)view;
- (void)loadView;

- (NSString *)nibName;
- (NSBundle *)nibBundle;

- (void)setView:(NSView *)view;

@end

@implementation CFIViewController

- (void)loadView {
    NSArray *topLevelObjects = nil;

    NSNib *loadedNib = [[[NSNib alloc]initWithNibNamed:self.nibName bundle:self.nibBundle]autorelease];
    if (loadedNib == nil) {
        [NSException raise:NSInternalInconsistencyException format:@"-[%@ %@]", NSStringFromClass(self.class), NSStringFromSelector(_cmd)];
        return;
    }

    BOOL loaded = NO;


#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8
    loaded = [loadedNib instantiateWithOwner:self topLevelObjects:&topLevelObjects];
#else 
    loaded = [loadedNib instantiateNibWithOwner:self topLevelObjects:&topLevelObjects];
#endif

    if (loaded) {
        [self _setTopLevelObjects:topLevelObjects];
        [topLevelObjects makeObjectsPerformSelector:@selector(release)];
    } else {
        [NSException raise:NSInternalInconsistencyException format:@"CFIViewController could not instantiate the %@ nib.", self.nibName];
    }

    if (self.view != nil) {
        [self viewDidLoad];
        return;
    }

    [NSException raise:NSInternalInconsistencyException format:@"-[%@ %@]", NSStringFromClass(self.class), NSStringFromSelector(_cmd)];
}

@end

这确实是一个非常简单的机制。所有 NSViewController 真正添加到 Cocoa 中任何类型的控制器隐喻的是能够与 NSDocument 一起工作,并且它的底层核心数据非常混乱。

如果视图控制器是 nib 的“文件所有者”,我不是只是将引用周期从我当前的类推送到我的视图控制器吗?NSViewController 做了什么来防止这种情况发生?

NSViewController 以我见过的最有趣的方式之一处理顶级对象的保留。当它获得对包含它们的数组的引用时,它会制作数组的浅拷贝,然后-release是旧数组的所有对象。实际上,NSViewController 会从 NSCoder 中抢走对 NIB 解冻对象的每个引用,从而保证当数组在-dealloc.

然而,当涉及到绑定时,NSViewController 有一个名为 NSProxy 子类的内部 getter NSAutounbinder,KVO 在绑定和解除绑定对象时会查找它。通过调整 release 并为内部 autounbinder 指针提供 getter,控制器类可以毫不费力地释放自己及其绑定。绝对不建议您在未验证 KVO 是否仍在寻找 autounbinder getter 的情况下将 CFIViewController 中的实现用于未来的 OS X 版本,但对于大多数其他版本,它似乎没问题。CFIViewController 提供了在最新提交时使用内部 NSAutoUnbinder 类的选项,从而解决任何绑定保留周期。

于 2013-03-27T22:12:24.470 回答
1

我建议您只使用NSWindowController或自定义子类。您的子类可以有一个view插座和一个符合 KVO 的representedObject属性。对于 10.4 兼容的替代品,这应该足够了。

于 2013-03-29T09:22:49.060 回答