Mac OS X 10.4上的NSViewController

时间:2013-03-26 21:34:30

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

我正在开发一个捆绑代码库 - 而不是一个应用程序 - 在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类承诺会这样做?如果视图控制器是笔尖的“文件所有者”,那么我不会只是将参考周期从当前类推送到我的视图控制器吗? NSViewController做了什么来防止这种情况?

2 个答案:

答案 0 :(得分:2)

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

  

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

NSViewController以我见过的最有趣的方式处理顶级对象的保留。当它获得对包含它们的数组的引用时,它会生成数组的浅表副本,然后-release是所有旧数组的对象。实际上,NSViewController会从NSCoder中捕获对NIB的去磨损对象的每个引用,从而保证在-dealloc中阵列消失时安全释放。

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

答案 1 :(得分:1)

我建议你只使用NSWindowController或自定义子类。您的子类可以具有view出口和符合KVO的representedObject属性。这应该足以支持10.4兼容的替换。