【发布时间】:2019-06-25 05:27:47
【问题描述】:
我来自 C/C++ 背景,目前正在学习一些有关 Cocoa 和 Objective-C 的知识。
我有一个涉及延迟初始化的奇怪行为(除非我弄错了),并且觉得我错过了一些非常基本的东西。
设置:
- Xcode 10.1 (10B61)
- macOS High Sierra 10.13.6
- 从头开始 Cocoa 项目
- 使用情节提要
- 添加文件TestMainView.m/.h
- 在main.storyboard的View Controller下,将
NSView自定义类设置为TestMainView - 在调试和发布版本下测试
基本上,我在视图控制器中创建了一个NSTextView 以便能够编写一些文本。
在 TestMainView.m 中,我以编程方式创建对象链,如 here 所述
有两条路径:
- 第一个通过将
USE_FUNCTION_CALL设置为0来启用,它使整个代码在awakeFromNib()中运行。 - 通过将
USE_FUNCTION_CALL设置为1 来启用第二条路径。它使文本容器和文本视图从函数调用addNewPage() 中分配,并返回文本容器以供进一步使用。
第一个代码路径按预期工作:我可以写一些文本。
但是第二个代码路径不起作用,因为返回时,textContainer.textView 为零(textContainer 值本身完全没问题)。
更麻烦的是(我怀疑惰性初始化是罪魁祸首)是,如果我在函数调用中“强制”textContainer.textView 值,那么一切正常。您可以通过将FORCE_VALUE_LOAD 设置为 1 来尝试此操作。
它不一定是if(),它也适用于NSLog()。如果您在返回行设置断点并使用调试器打印值(“p textContainer.textView”),它甚至可以工作
所以我的问题是:
- 这与延迟初始化有关吗?
- 这是一个错误吗?有解决方法吗?
- 我是否认为 Cocoa/ObjC 编程方式错误?
我真的希望我在这里遗漏了一些东西,因为不能指望我在 Cocoa 类中到处随机检查变量,希望它们不会变成nil。它甚至会静默失败(没有错误消息,什么都没有)。
TestMainView.m
#import "TestMainView.h"
#define USE_FUNCTION_CALL 1
#define FORCE_VALUE_LOAD 0
@implementation TestMainView
NSTextStorage* m_mainStorage;
- (void)awakeFromNib
{
[super awakeFromNib];
m_mainStorage = [NSTextStorage new];
NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init];
#if USE_FUNCTION_CALL == 1
NSTextContainer* textContainer = [self addNewPage:self.bounds];
#else
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:NSMakeSize(FLT_MAX, FLT_MAX)];
NSTextView* textView = [[NSTextView alloc] initWithFrame:self.bounds textContainer:textContainer];
#endif
[layoutManager addTextContainer:textContainer];
[m_mainStorage addLayoutManager:layoutManager];
// textContainer.textView is nil unless forced inside function call
[self addSubview:textContainer.textView];
}
#if USE_FUNCTION_CALL == 1
- (NSTextContainer*)addNewPage:(NSRect)containerFrame
{
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:NSMakeSize(FLT_MAX, FLT_MAX)];
NSTextView* textView = [[NSTextView alloc] initWithFrame:containerFrame textContainer:textContainer];
[textView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
#if FORCE_VALUE_LOAD == 1
// Lazy init ? textContainer.textView is nil unless we force it
if (textContainer.textView)
{
}
#endif
return textContainer;
}
#endif
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
@end
TestMainView.h
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface TestMainView : NSView
@end
NS_ASSUME_NONNULL_END
【问题讨论】:
-
textView是NSTextContainer的弱属性,textView在addNewPage:末尾超出范围时会被释放。 -
@Willeke 你知道为什么如果你强制它会起作用吗?
-
请注意,
m_mainStorage不是实例变量。至于它为什么“起作用”,很可能是巧合,也可能是因为该调用最终触发了保留/自动释放。 -
textViewofNSTextContainer调用 objc_loadWeak。
标签: objective-c macos cocoa memory-management automatic-ref-counting