【问题标题】:NSViewController on Mac OS X 10.4Mac OS X 10.4 上的 NSViewController
【发布时间】:2013-03-16 21:13:27
【问题描述】:

我正在开发一个捆绑代码库(而不是应用程序),其中开发从 10.4 开始,需要在 10.4 上运行,但一直到 10.8。它手动从 nib 文件加载其视图,我最近才意识到存在大量内存泄漏,因为 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 做了什么来防止这种情况发生?

【问题讨论】:

    标签: objective-c macos cocoa memory-management nib


    【解决方案1】:

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

    @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 以我见过的最有趣的方式之一处理顶级对象的保留。当它获得对包含它们的数组的引用时,它会制作数组的浅拷贝,然后 -releases 所有旧数组的对象。实际上,NSViewController 会从 NSCoder 中抢走对 NIB 的解冻对象的每个引用,从而保证当数组在 -dealloc 中消失时安全释放。

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

    【讨论】:

    • 这真的比我预期的任何人都付出更多的努力!谢谢你,我会研究一下。当有赏金时,我会奖励你一些额外的积分。
    • @dreamlax 谢谢!我真的很喜欢窥探 AppKit 的内部结构并使它更易于管理。您可以在以后的任何项目中随意使用它。
    • 如果我现在接受你的回答,赏金选项就会消失,所以我暂时不接受它(但别担心我会回来的!)
    • 很棒的代码!但是,可能缺少咖啡,但我看不出您的代码中的_setTopLevelObjects: 如何制作对象的浅表副本。它确实使数组之一,但不是包含的对象。我错过了什么吗?
    • @Monolo 完全正确。它浅拷贝数组,然后从 NSCoder 下释放旧值。它有效地保留了数组的末端(在 Cocoa 中,这意味着现在它和 NSCoder 拥有它),然后断开 NSCoder 与对象的链接。
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2010-09-23
      • 2011-07-14
      • 2010-09-16
      • 2023-03-12
      • 2010-09-22
      • 1970-01-01
      • 2017-04-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多