[英]Memory leak (ARC)
In the follow up of my previous question , I concluded that I do have a memory leak. 在上一个问题的后续中,我得出的结论是确实存在内存泄漏。 To sum up, memory starts at 9.7MB and goes up 0.1MB every 10 runs of the animation, or so it seems.
总而言之,内存开始时为9.7MB,每运行10次动画就增加0.1MB,或者看起来是这样。 I tested this to about 12MB.
我测试了大约12MB。
Using Instruments, I run a test that consisted of: 使用Instruments,我运行了一个包含以下内容的测试:
Here's what I got: 这是我得到的:
So the memory does grow. 因此内存确实会增加。 However, inspecting these generations it seems that I'm not responsible for these leaks.
但是,检查这些世代似乎对这些泄漏不承担任何责任。 For instance, inspecting the Statistics panel, listed categories seem to indicate CF, CG, NS, etc, and
Malloc
and __NSMallocBlock__
. 例如,检查“统计信息”面板时,列出的类别似乎指示CF,CG,NS等,以及
Malloc
和__NSMallocBlock__
。
I also inspected the Call Trees and followed the branches with highest memory consumption. 我还检查了调用树,并跟踪了具有最高内存消耗的分支。
Again, most memory consumption seems to be CoreGraphics related. 同样,大多数内存消耗似乎与CoreGraphics有关。 In Allocations List, I can see more clearly what those Mallocs are.
在分配列表中,我可以更清楚地看到这些Malloc是什么。 The conclusion is the same.
结论是相同的。
Providing full source code would not be practical as the application already reached a few thousands of lines. 提供完整的源代码是不切实际的,因为该应用程序已经达到了数千行。 As such, I'll give an overview of what seems to be important:
因此,我将概述似乎很重要的内容:
- (void)animateViewDidAppear
{
NSArray * buttons = @[self.registrationButton, self.facebookButton, self.twitterButton, self.linkedInButton];
// [...] A bunch of GLfloat calculations here
__block HyAnimationCollection * collection = [[HyAnimationCollection alloc] init];
for (int it=0 ; it < [buttons count] ; ++it) {
UIButton * button = [buttons objectAtIndex:it];
// [...] More GLfloat stuff
// Ease out back
__block HyAnimation * easeOutBack = [[HyAnimation alloc] init];
HyAnimationUpdateFunction easeOutBackUpdate = ^(id frame, BOOL done) {
[button setFrame:[frame CGRectValue]];
};
[easeOutBack setDelay:it * kHyAuthenticationViewControllerAnimationDelayFactor];
[easeOutBack setDuration:kHyAuthenticationViewControllerAnimationDuration];
[easeOutBack setEasing:^(GLfloat t) { return [HyEasing easeOutBack:t]; }];
[easeOutBack addRectAnimation:CGRectMake(origin.x, from, size.width, size.height)
to:CGRectMake(origin.x, to, size.width, size.height)];
[easeOutBack addUpdateFunction:easeOutBackUpdate];
[collection addAnimation:easeOutBack];
}
[collection addLock:self.animationLock];
[collection start];
}
self.animationLock
is a locking mechanism so animations don't overlap, but it's pretty much self contained and I can't imagine why it would be the source of the leak. self.animationLock
是一种锁定机制,因此动画不会重叠,但是它几乎是自包含的,我无法想象为什么会导致泄漏。 However, those blocks do get sent to HyAnimation
, which in turn is added to HyAnimationCollection
, and that troubles me more and that's where I have been focusing. 但是,这些块确实被发送到
HyAnimation
,后者又被添加到HyAnimationCollection
,这使我更加烦恼,这就是我一直关注的地方。 In sum, maybe these closures might be creating a circular retain, so lets take a look. 总而言之,也许这些闭包可能正在创建圆形保留,所以让我们看一下。 The
addUpdateFunction
in HyAnimation
is actually very simple: HyAnimation
的addUpdateFunction
实际上非常简单:
- (void)addUpdateFunction:(HyAnimationUpdateFunction)update
{
[self.updateFunctions addObject: update];
}
As self.updateFunctions
is an NSMutableArray
, it retains strong references to these blocks. 由于
self.updateFunctions
是NSMutableArray
,它保留了对这些块的强引用。 So if HyAnimation
is not freed, neither are those blocks, which means that the initial scope in which they were created isn't either. 因此,如果不释放
HyAnimation
,则这些块也不会释放,这意味着创建它们的初始范围也不会释放。 But, HyAnimation is declared inside a method so, so far, I see no reason why it wouldn't be released. 但是,HyAnimation是在方法内部声明的,所以到目前为止,我仍然没有理由不发布它。
Which is why I think it should be because of the animation itself, that is HyAnimationCollection
's [collection start];
这就是为什么我认为应该是因为动画本身,即
HyAnimationCollection
的[collection start];
. 。 Here's the interesting part:
这是有趣的部分:
for (HyAnimation * anim in self.animations) {
[anim start];
}
So far so good. 到现在为止还挺好。 Here's
HyAnimation
's start
: 这是
HyAnimation
的start
:
- (void)start
{
[NSTimer scheduledTimerWithTimeInterval:self.delay
target:self
selector:@selector(scheduleAnimationWithTimer:)
userInfo:nil
repeats:NO];
// Send an udate notification
if ([self shouldUpdateImmediatly]) {
[self animateAt:0.0f done:NO];
}
}
Which pretty much delays the run and delegates to scheduleAnimationWithTimer:
. 这几乎会延迟运行,并委托
scheduleAnimationWithTimer:
This method, however, sets a timer that repeats, and therefore will be around until the animation ends (and no further, I hope). 但是,此方法设置了一个重复的计时器,因此将一直持续到动画结束为止(我希望不要再重复了)。
- (void)scheduleAnimationWithTimer:(NSTimer*)timer
{
NSTimer * scheduled = [NSTimer scheduledTimerWithTimeInterval:self.frameRate
target:self
selector:@selector(animateWithTimer:)
userInfo:nil
repeats:YES];
// Trigger immediatly
[self setInitialDate:[NSDate date]];
[scheduled fire];
}
Now animateWithTimer:
现在设置
animateWithTimer:
- (void)animateWithTimer:(NSTimer*)timer
{
NSTimeInterval gone = [[NSDate date] timeIntervalSinceDate:self.initialDate];
GLfloat t = gone / self.duration;
BOOL done = gone >= self.duration;
// Ease
if (self.easing) {
t = self.easing(t);
}
// Make sure the last position is exact. This does not mean that t does not go over 1.0f during the animation, just the end
if (done && t > 1.0f) {
t = 1.0f;
}
// Animate
[self animateAt:t done:done];
// Finish
if (done) {
// Stop the timer
[timer invalidate];
// Notify completion
[self broadcastCompletion];
}
}
And finally animateAt:done:
最后是
animateAt:done:
- (void)animateAt:(GLfloat)t done:(BOOL)done
{
for (HyAnimationFunction anim in self.animations) {
anim(t, done);
}
}
That is, this last method calls the blocks I defined earlier in animateViewDidAppear
. 也就是说,这最后一个方法调用了我先前在
animateViewDidAppear
定义的块。
First of all, I believe that the HyAnimationCollection
and HyAnimation
instances are trapped inside the blocks, and HyAnimation
has strong references to those blocks. 首先,我相信
HyAnimationCollection
和HyAnimation
实例被困在块内,并且HyAnimation
对这些块有很强的引用。 Would you agree? 你同意吗? How can I solve this?
我该如何解决? I tried using
__block
to declare both variables, but it seems to have no effect, for this matter. 我尝试使用
__block
声明两个变量,但是对于这件事似乎没有任何作用。
Anyway, I'm also having trouble relating Instrument's memory analysis with this issue, which is why this post is so long. 无论如何,我在将此问题与Instrument的内存分析相关时也遇到了麻烦,这就是为什么这篇文章这么长的原因。
Thank you for bearing with me, and I apologise for the long read. 多谢您与我保持联系,对您的长时间阅读深表歉意。
UPDATE: 更新:
It seems I was right. 看来我是对的。 Following @Stephen Darlington's post on my previous question , I overrode the
dealloc
method in HyAnimationCollection
. 在@Stephen Darlington关于我之前的问题的帖子之后,我覆盖了
HyAnimationCollection
的dealloc
方法。 Contrary to his suggestion, I did not setup a break point, but instead wrote an NSLog
. 与他的建议相反,我没有设置断点,而是编写了
NSLog
。 Never logged anything, until now. 直到现在都没有记录任何东西。
- (void)dealloc
{
NSLog(@"dealloced");
}
What I did was add another property to HyAnimation
, shouldCleanUpOnCompletion
. 我所做的是在
HyAnimation
添加了另一个属性shouldCleanUpOnCompletion
。 If true, animateWithTimer:
calls this on completion: 如果为true,则
animateWithTimer:
在完成时调用此方法:
- (void)cleanUp
{
// Get rid of everything
self.animations = [[NSMutableArray alloc] init];
self.updateFunctions = [[NSMutableArray alloc] init];
self.completionFunctions = [[NSMutableArray alloc] init];
}
I immediately saw logs on the console, so there's definitely a retain cycle. 我立即在控制台上看到日志,因此肯定存在保留周期。 The question is, how can I solve it?
问题是,我该如何解决? Isn't
__block
supposed to solve this?? __block
应该解决吗?
UPDATE 2 更新2
I just realised this is enough: 我刚刚意识到这已经足够:
- (void)cleanUp
{
// Get rid of everything
// self.animations = [[NSMutableArray alloc] init];
// self.updateFunctions = [[NSMutableArray alloc] init];
self.completionFunctions = [[NSMutableArray alloc] init];
}
Which means that the completionFunctions
are the ones creating the closure, after all. 毕竟,这意味着
completionFunctions
是创建闭包的功能。 The only place I'm currently using those is in HyAnimationCollection
, more specifically here: 我当前使用的唯一位置是
HyAnimationCollection
,更具体的是:
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {
if (self.isRunning) {
return NO;
}
[self.animations addObject:animation];
__block HyAnimationCollection * me = self;
// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
static unsigned int complete = 0;
// We are only interested in knowing when the animations complete, so we can release the locks
++complete;
if (complete == [me.animations count]) {
// Reset, so the animation can be run again
complete = 0;
@synchronized(me) {
// Release all locks
[me.locks setLocked:NO];
// Done
me.isRunning = NO;
}
}
}];
return YES;
}
}
That is, the retain cycle must be here, right? 也就是说,保留周期必须在这里,对吗? But where?
但是哪里? Could it be the first
@synchronized
block? 可以是第一个
@synchronized
块吗?
UPDATE 更新
Replacing __block
with __weak
allover seems to have done the trick. 用
__weak
代替__block
似乎已经成功了。 Even without cleanUp
the objects do release, however not properly. 即使没有
cleanUp
,对象也会释放,但是不能正确释放。 First, it releases HyAnimationCollection
, then HyLockCollection
and finally all HyAnimation
s. 首先,它会释放
HyAnimationCollection
,然后HyLockCollection
最后全部HyAnimation
秒。 HyLock
is not supposed to be released because I keep it a strong reference to it in a property. HyLock
不应该发布,因为我在属性中始终强烈引用它。
Lets take another look at HyAnimationCollection
's addAnimation:
让我们再看一下
HyAnimationCollection
的addAnimation:
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {
if (self.isRunning) {
return NO;
}
[self.animations addObject:animation];
// Prevent a strong circular reference
__weak HyAnimationCollection * me = self;
// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
static unsigned int complete = 0;
// We are only interested in knowing when the animations complete, so we can release the locks
++complete;
if (complete == [me.animations count]) {
// Reset, so the animation can be run again
complete = 0;
@synchronized(me) {
// Release all locks
[me.locks setLocked:NO];
// Done
me.isRunning = NO;
}
}
}];
return YES;
}
}
The problem is that this closure is only called when animations finish. 问题在于仅在动画结束时才调用此闭包。 Because
HyAnimationCollection
is the first to be released, this means that when all animations finish, HyAnimationCollection
has already been released, which, as you can see, causes it not to release the locks. 因为
HyAnimationCollection
是第一个发布的,所以这意味着当所有动画结束时, HyAnimationCollection
已经发布,如您所见,这导致它不释放锁。
Now I have quite the opposite problem =) coding is so much fun <3 现在我遇到了相反的问题=)编码非常有趣<3
Well, the problem seems to be this: 好吧,问题似乎是这样的:
self
in your block inside -addAnimation:
, you get a retain-cycle -addAnimation:
中的块中强烈引用了self
,则会得到一个保留周期 self
in your block inside -addAnimation:
, self
is released too early, and your completion block is pointless. -addAnimation:
中的代码块中对self
引用较弱,那么self
被过早释放,而您的完成代码块将毫无意义。 What you need is a way to break the retain-cycle at runtime: 您需要的是一种在运行时打破保留周期的方法:
// Version 1:
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {
if (self.isRunning) {
return NO;
}
[self.animations addObject:animation];
// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
static unsigned int complete = 0;
// We are only interested in knowing when the animations complete, so we can release the locks
++complete;
if (complete == [self.animations count]) {
// Reset, so the animation can be run again
complete = 0;
@synchronized(self) {
// Release all locks
[self.locks setLocked:NO];
// Break retain cycle!!
[self.animations removeAllObjects];
// IF it still doesn't work, put a breakpoint at THIS LINE, and
// tell me if this code here runs ever.
// Done
self.isRunning = NO;
}
}
}];
return YES;
}
}
self
points to an array self
指向数组 animation
animation
animation
points to a block animation
指向一个块 self
self
When one of these connections is broken, the whole cycle breaks. 当这些连接之一断开时,整个周期就会中断。 The easiest way to break the cycle is to destroy the array pointer (1) by calling
-removeAllObjects
on the array. 打破循环的最简单方法是通过在数组上调用
-removeAllObjects
破坏数组指针(1)。
BTW, another problem with your code is the static
variable in your HyAnimationCollection
. 顺便说一句,您的代码的另一个问题是
HyAnimationCollection
的static
变量。 This will be a problem as soon as you have more than one HyAnimationCollection
object running at the same time. 一旦同时运行多个
HyAnimationCollection
对象,这将是一个问题。 I would just create an instance variable unsigned int _completeCount;
我只是创建一个实例变量
unsigned int _completeCount;
: :
(you could also create a synthesized property instead, but I prefer an iVar in this case: (您也可以创建一个综合属性,但在这种情况下,我更喜欢使用iVar:
@interface HyAnimationCollection .....
{
unsigned int _completeCount; //is automatically initialized to 0 by the objc-runtime.
}
...
) )
and in the implementation file: 并在实现文件中:
// Version 2:
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {
if (self.isRunning) {
return NO;
}
[self.animations addObject:animation];
// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
@synchronized(self) {
// We are only interested in knowing when the animations complete, so we can release the locks
++_completeCount;
if (_completeCount == [self.animations count]) {
// Reset, so the animation can be run again
_completeCount = 0;
// Release all locks
[self.locks setLocked:NO];
// Break retain cycle!!
NSLog(@"Breaking retain cycle (HyAnimationCollection)");
[self.animations removeAllObjects];
// Done
self.isRunning = NO;
}
}
}];
return YES;
}
}
2.: the inner @synchronized
block should be wrapped around the whole block, if it is needed at all. 2 .:如果需要,内部
@synchronized
块应包装在整个块周围。 Just putting it around the unlocking doesn't provide any thread safety. 只是将其放在解锁周围并不能提供任何线程安全性。 (To test it, you can also put the line
assert([NSThread isMainThread]);
at the beginning of that block: if it doesn't crash, it means that you can omit the whole @synchronized(self)
thing. The code then becomes:) The outer @synchronized
block should probably stay. (要对其进行测试,您还可以在该块的开头放置
assert([NSThread isMainThread]);
行:如果它没有崩溃,则意味着您可以省略整个@synchronized(self)
内容。代码然后变为:)外部@synchronized
块可能应该保留。
// Version 3.
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {
if (self.isRunning) {
return NO;
}
[self.animations addObject:animation];
// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
assert([NSThread isMainThread]);
// We are only interested in knowing when the animations complete, so we can release the locks
++_completeCount;
if (_completeCount == [self.animations count]) {
// Reset, so the animation can be run again
_completeCount = 0;
// Release all locks
[self.locks setLocked:NO];
// Break retain cycle!!
NSLog(@"Breaking retain cycle (HyAnimationCollection)");
[self.animations removeAllObjects];
// Done
self.isRunning = NO;
}
}];
return YES;
}
}
My tip: try version 3 first. 我的提示:首先尝试版本3。 if it crashes use version 2 instead.
如果崩溃,请改用版本2。 (the scheme should be DEBUG, not RELEASE during development, otherwise
assert()
may not work and you may get strange problems later.) hope it works... (该方案应该是DEBUG,而不是在开发过程中发布,否则
assert()
可能无法正常工作,以后您可能会遇到一些奇怪的问题。)希望它能起作用...
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.