可插入自定义视图 Nib(Nib-in-a-Nib):内存泄漏 –为什么?
我们当前的自定义视图的最佳实践是:
- 在 Nib 中构建自定义视图。
- 在视图控制器中,以编程方式加载 Nib,从加载的对象数组中获取自定义视图(我们在 UIView 类别方法
+loadInstanceFromNib
中执行此操作)。 - 添加自定义视图作为子视图,设置其框架。
我们实际上想要的是将自定义视图Nib“嵌入”视图控制器Nib中。如果做不到这一点,至少我们想添加并定位一个自定义视图Nib。 -view 视图控制器 Nib 内的实例(看不到其内容)。
我们已经非常接近以下解决方案:
@implementation CustomView
static BOOL loadNormally;
- (id) initWithCoder:(NSCoder*)aDecoder {
id returnValue = nil;
if (loadNormally) { // Step 2
returnValue = [super initWithCoder:aDecoder];
loadNormally = !loadNormally;
} else { // Step 1
loadNormally = !loadNormally;
returnValue = [CustomView loadInstanceFromNib];
}
return returnValue;
}
- (id) initWithFrame:(CGRect)frame {
loadNormally = YES;
self = (id) [[CustomView loadInstanceFromNib] retain];
self.frame = frame;
return self;
}
// ...
@end
如果我们以编程方式实例化自定义视图,我们使用 -initWithFrame:
,它将从 Nib 加载视图(它将调用 -initWithCoder:< /code> 并直接转到标有“步骤 2”的 if 分支),设置其框架,并将其保留计数设置为 1。
但是,如果我们在视图控制器 Nib 内实例化自定义视图,(诚然相当难看) static loadNormally
变量最初为 NO
:我们从“第 1 步”开始,在确保我们将立即使用-initWithCoder:
的“正常”if 分支。从自定义视图 Nib 加载意味着我们回到 -initWithCoder:
,这次使用 loadNormally==YES
,即我们让 Nib 加载机制完成其工作并返回自定义视图实例。
结果,总而言之:
- 好处:它有效!我们在 Interface Builder 中有“可插入”自定义视图!
- 坏处: 一个丑陋的静态变量… :-/
- 丑陋: 自定义视图的实例被泄露! 这就是我要的地方喜欢你的帮助——我不明白为什么。有什么想法吗?
Our current best-practice for custom views is:
- Build the custom view in a Nib.
- In the view controller, programmatically load the Nib, get the custom view from the array of loaded objects (we do this in a UIView category method
+loadInstanceFromNib
). - Add custom view as subview, set its frame.
What we actually want is to "embed" the custom-view Nib inside the view-controller Nib. Failing that, at least we'd like to add and position a custom-view instance inside the view-controller Nib (without seeing its contents).
We have come very close with the following solution:
@implementation CustomView
static BOOL loadNormally;
- (id) initWithCoder:(NSCoder*)aDecoder {
id returnValue = nil;
if (loadNormally) { // Step 2
returnValue = [super initWithCoder:aDecoder];
loadNormally = !loadNormally;
} else { // Step 1
loadNormally = !loadNormally;
returnValue = [CustomView loadInstanceFromNib];
}
return returnValue;
}
- (id) initWithFrame:(CGRect)frame {
loadNormally = YES;
self = (id) [[CustomView loadInstanceFromNib] retain];
self.frame = frame;
return self;
}
// ...
@end
If we instantiate the custom view programmatically, we use -initWithFrame:
, which will load the view from the Nib (which will call -initWithCoder:
and go right to the if-branch labeled "Step 2"), set its frame, and set its retain count to 1.
However if we instantiate the custom view inside a view-controller Nib, the (admittedly rather ugly) static loadNormally
variable is initially NO
: We start in "Step 1", where we load and return the instance loaded from its Nib, after making sure that we will forthwith use the "normal" if-branch of -initWithCoder:
. Loading from the custom-view Nib means that we come back into -initWithCoder:
, this time with loadNormally==YES
, i.e. we let the Nib loading mechanism do its job and return the custom-view instance.
Results, in summary:
- The good: IT WORKS!!! We have "pluggable" custom views in Interface Builder!
- The bad: An ugly static variable… :-/
- The ugly: An instance of the custom view is leaked! This is where I'd love your help – I don't understand why. Any ideas?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我们最终找到了一种更好的方法,其中涉及在自定义视图中重写
-awakeAfterUsingCoder:
,将从视图控制器 Nib 加载的对象替换为从“嵌入式”Nib (CustomView.xib) 加载的对象)。我在一篇内容丰富的博客文章中写了我们如何将自定义视图 Nib 嵌入到其他 Nib 中 。
代码是这样的:
We ended up with a nicer way, which involves overriding
-awakeAfterUsingCoder:
in our custom view, replacing the object loaded from the view-controller Nib with the one loaded from the "embedded" Nib (CustomView.xib).I wrote up how we embed custom-view Nibs inside other Nibs in an extensive blog post.
The code goes something like this:
杨的回答很好......但是“消息发送到已释放的实例”仍然可能发生。我通过使用“自我”分配解决了这个问题。
因此,如果您使用 ARC,则必须允许这种“自我”分配。 (阅读https://blog.compeople.eu/apps/?p=142了解更多信息)
要在 ARC 项目中实现此目的,请在文件中添加“-fno-objc-arc”标志编译器设置。
然后在此文件中进行 NO-ARC 编码(例如 dealloc 设置 nils、调用 super dealloc 等)。
此外,客户端 nib 的视图控制器应该使用强属性来保存 awakeFromNib 返回的实例。在我的示例代码中,customView 的引用如下:
@property (strong, nonatomic) IBOutlet CustomView* customView;
最后,我使用 UIView+Util 类别中定义的 copyUIPropertiesTo: 和 loadNibNamed 对属性处理和笔尖加载进行了一些其他改进。
所以 awakeAfterUsingCoder: 代码现在是
UIView+Util 类别代码
及其实现
Yang's answer is great... but 'messages send to deallocated instance' can still occurs. I solved this problem by using 'self' assignation.
So if you use ARC, you will have to allow this 'self' assignation. (read https://blog.compeople.eu/apps/?p=142 for more info)
To achieve this in an ARC project, add the '-fno-objc-arc' flag compiler setting on your file.
Then do NO-ARC coding in this file (like dealloc setting nils, calling super dealloc, etc..)
Also, client nib's viewcontroller should use strong property to hold the instance returned by awakeFromNib. In the case of my sample code, the customView is referenced like this:
@property (strong, nonatomic) IBOutlet CustomView* customView;
I finally added some other improvements to properties handling and nib loading using copyUIPropertiesTo: and loadNibNamed defined in my UIView+Util category.
So awakeAfterUsingCoder: code is now
The UIView+Util category code is
along with its implementation
还有一种替代方法可以做到这一点:
假设您在
Interface Builder
中使用View1
,然后创建另一个名为View2
的视图,View2
有一个相应的View2.xib
文件,您已经链接了View2.m
和View2.xib
中的插座。然后,在
View1.m
中写入:这样,您就可以在
Interface Builder
中需要放置自定义视图的地方使用View1
>,从而使View1
可以在Interface Builder
中重用,而无需编写任何更多代码。There is an alternative way to do this:
say you use
View1
in yourInterface Builder
, then you create another view calledView2
,View2
has a correspondingView2.xib
file, you have linked the outlets inView2.m
andView2.xib
.Then, in
View1.m
, write this:With this, you can use
View1
in places where you need to put your custom view inInterface Builder
, thus makeView1
reusable inInterface Builder
without writing any more code.