在我的previous question的后续行动中,我断定我确实有内存泄漏。总而言之,内存从9.7MB开始,每10次动画运行上升0.1MB,或者看起来如此。我测试了这个大约12MB。
使用Instruments,我运行的测试包括:
这是我得到的:
因此内存 增长。然而,检查这些世代似乎我不负责这些泄漏。例如,检查“统计”面板,列出的类别似乎表示CF,CG,NS等,Malloc
和__NSMallocBlock__
。
我还检查了Call Trees并跟踪了内存消耗最高的分支。
同样,大多数内存消耗似乎与CoreGraphics相关。在Allocations List中,我可以更清楚地看到那些Mallocs是什么。结论是一样的。
提供完整的源代码是不切实际的,因为应用程序已经达到了几千行。因此,我将概述一些看起来很重要的内容:
- (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
是一种锁定机制,因此动画不会重叠,但它几乎是自包含的,我无法想象为什么它会成为泄漏的来源。但是,这些块会被发送到HyAnimation
,而HyAnimationCollection
又被添加到addUpdateFunction
,这让我更加麻烦,这就是我一直关注的地方。总之,也许这些闭包可能会创建一个圆形保留,所以让我们来看看。 HyAnimation
中的- (void)addUpdateFunction:(HyAnimationUpdateFunction)update
{
[self.updateFunctions addObject: update];
}
实际上非常简单:
self.updateFunctions
由于NSMutableArray
是HyAnimation
,它会保留对这些块的强引用。因此,如果未释放HyAnimationCollection
,则这些块也不会,这意味着创建它们的初始范围也不是。但是,HyAnimation是在一个方法中声明的,所以到目前为止,我认为没有理由不释放它。
这就是为什么我认为它应该是因为动画本身,[collection start];
的{{1}}。这是有趣的部分:
for (HyAnimation * anim in self.animations) {
[anim start];
}
到目前为止一切顺利。这是HyAnimation
的{{1}}:
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];
}
}
。但是,这个方法设置了一个重复的计时器,因此它将一直存在直到动画结束(我希望不再进一步)。
scheduleAnimationWithTimer:
现在- (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];
}
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];
}
}
animateAt:done:
也就是说,这最后一个方法调用我之前在- (void)animateAt:(GLfloat)t done:(BOOL)done
{
for (HyAnimationFunction anim in self.animations) {
anim(t, done);
}
}
中定义的块。
首先,我认为animateViewDidAppear
和HyAnimationCollection
实例被困在块中,HyAnimation
对这些块有强引用。你同意吗?我怎么解决这个问题?我尝试使用HyAnimation
来声明这两个变量,但似乎没有效果。
无论如何,我也无法将Instrument的内存分析与此问题联系起来,这就是为什么这篇文章很长的原因。
感谢您对我的承诺,我为长篇大论而道歉。
更新:
看来我是对的。在我的previous question上关注@Stephen Darlington的帖子后,我覆盖了__block
中的dealloc
方法。与他的建议相反,我没有设置断点,而是写了一个HyAnimationCollection
。从来没有记录任何东西,直到现在。
NSLog
我所做的是将另一个属性添加到- (void)dealloc
{
NSLog(@"dealloced");
}
,HyAnimation
。如果为true,则shouldCleanUpOnCompletion
在完成时调用此方法:
animateWithTimer:
我立即在控制台上看到了日志,所以肯定有一个保留周期。问题是,我该如何解决?是不是- (void)cleanUp
{
// Get rid of everything
self.animations = [[NSMutableArray alloc] init];
self.updateFunctions = [[NSMutableArray alloc] init];
self.completionFunctions = [[NSMutableArray alloc] init];
}
应该解决这个问题?
更新2
我刚刚意识到这已经足够了:
__block
这意味着- (void)cleanUp
{
// Get rid of everything
// self.animations = [[NSMutableArray alloc] init];
// self.updateFunctions = [[NSMutableArray alloc] init];
self.completionFunctions = [[NSMutableArray alloc] init];
}
毕竟是创建闭包的那个。我目前使用的唯一地方是completionFunctions
,更具体地说是:
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;
}
}
块吗?
更新
用@synchronized
allover替换__block
似乎已经成功了。即使没有__weak
,对象也会释放,但是不正确。首先,它会发布cleanUp
,然后发布HyAnimationCollection
,最后发布所有HyLockCollection
。 HyAnimation
不应该被释放,因为我在属性中对它进行了强有力的引用。
让我们再看看HyLock
的{{1}}
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;
}
}
已经被释放,正如您所看到的那样,导致它不释放锁。
现在我有相反的问题=)编码非常有趣&lt; 3
答案 0 :(得分:0)
嗯,问题似乎是这样的:
self
内的块中有-addAnimation:
的强引用,则会获得保留周期self
内的块中对-addAnimation:
的引用较弱,则self
过早发布,而您的完成块无意义。您需要的是一种在运行时中断保留周期的方法:
// 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
指向数组animation
animation
指向一个块self
当其中一个连接断开时,整个循环就会中断。打破循环的最简单方法是通过调用数组上的-removeAllObjects
来销毁数组指针(1)。
BTW,您的代码的另一个问题是static
中的HyAnimationCollection
变量。只要同时运行多个HyAnimationCollection
对象,这就成了问题。我只想创建一个实例变量unsigned int _completeCount;
:
(你也可以创建一个合成属性,但在这种情况下我更喜欢iVar:
@interface HyAnimationCollection .....
{
unsigned int _completeCount; //is automatically initialized to 0 by the objc-runtime.
}
...
)
并在实施文件中:
// 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:如果需要,内部@synchronized
块应该围绕整个块包裹。只是把它放在解锁周围并不能提供任何线程安全性。 (为了测试它,您还可以将行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;
}
}
我的提示:首先尝试第3版。如果它崩溃使用版本2而不是。 (该方案应该是DEBUG,而不是RELEASE在开发过程中,否则assert()
可能无效,你可能会在以后遇到奇怪的问题。)希望它有效...