2013-03-26 182 views
4

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

予加载使用以下代码碎粒(这个码是在一个基类和子类重写+nibName):

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

由于我必须定位到10.4,我无法使用NSViewController。我想我需要实现我自己的视图控制器类,但是如何防止NSViewController类承诺的引用循环发生?如果视图控制器是笔尖的“文件的所有者”不是我只是将参考周期从我的当前类推到我的视图控制器? NSViewController做什么来阻止?

回答

1

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

2

对于内存管理甚至顶级对象,NSViewController没有什么特别之处。它只是提供一个安全的地方来加载一个笔尖,然后将其内容保存在笔记本生命周期的内存中,这意味着该类本身不过是外部文件的所有者。为了踢球,我重新实施了这个课程,并且评论了一些有趣的部分。有些东西,我只是彻底删除,因为它太冒险了,不值得实施,或者没有使用,以至于无法重新制作。完整的课程,包括文档和评论,可以找到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以及它的基础Core Data混乱。

如果视图控制器是笔尖的“文件的所有者”不是我,而只是将参考周期从我的当前类推到我的视图控制器? NSViewController做什么来防止这种情况?

NSViewController以我见过的最有趣的方式之一处理顶层对象的保留。当它获得对包含它们的数组的引用时,它将对该数组进行浅度复制,然后对所有旧数组的对象进行浅度复制。实际上,NSViewController会将对NIB的除霜对象的每个引用都从NSCoder中取出,从而在数组在-dealloc中消失时保证安全释放。

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

+0

这真的比我预料的任何人都付出了更多的努力!谢谢你,我会研究它。当有奖金时,我会给你一些额外的奖励。 – dreamlax 2013-03-27 23:17:38

+0

@dreamlax谢谢你!我真的很喜欢偷看AppKit的内部并使它更易于管理。随意在今后的任何项目中使用它。 – CodaFi 2013-03-27 23:18:17

+0

如果我现在接受你的答案,赏金选项就会消失,所以我暂时不接受它(但不用担心我会回来!) – dreamlax 2013-03-27 23:36:10