Ios 内存泄漏(ARC)
在我的后续调查中,我得出结论,我确实存在内存泄漏。总之,内存从9.7MB开始,每运行10次动画,内存就会增加0.1MB,或者看起来是这样。我测试了大约12MB 使用仪器,我运行了一个测试,包括:Ios 内存泄漏(ARC),ios,objective-c,memory-management,memory-leaks,Ios,Objective C,Memory Management,Memory Leaks,在我的后续调查中,我得出结论,我确实存在内存泄漏。总之,内存从9.7MB开始,每运行10次动画,内存就会增加0.1MB,或者看起来是这样。我测试了大约12MB 使用仪器,我运行了一个测试,包括: 登记第一代 运行动画10次 登记下一代 重复几次 以下是我得到的: 所以记忆确实在增长。然而,检查这几代人,似乎我对这些泄漏不负责任。例如,检查统计面板时,列出的类别似乎表示CF、CG、NS等,以及Malloc和\uuuu NSMallocBlock\uuu 我还检查了调用树,并跟踪内存消耗最高的分支
Malloc
和\uuuu NSMallocBlock\uuu
我还检查了调用树,并跟踪内存消耗最高的分支
同样,大多数内存消耗似乎与CoreGraphics有关。在分配列表中,我可以更清楚地看到这些malloc是什么。结论是一样的
提供完整的源代码并不实际,因为应用程序已经达到几千行。因此,我将概述什么似乎很重要:
- (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.updateFunctions
是一个NSMutableArray
,因此它保留了对这些块的强引用。因此,如果未释放HyAnimation
,这些块也不会释放,这意味着创建它们的初始范围也不会释放。但是,HyAnimation是在一个方法中声明的,到目前为止,我看不出有什么理由不发布它
这就是为什么我认为这应该是因为动画本身,即HyAnimationCollection
的[collection start]代码>。有趣的是:
for (HyAnimation * anim in self.animations) {
[anim 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];
}
}
这几乎会延迟运行并委托给scheduleAnimationWithTimer:
。但是,此方法设置了一个重复的计时器,因此将一直持续到动画结束(我希望不会再继续)
现在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
中定义的块
首先,我相信HyAnimation集合
和HyAnimation
实例被困在块中,并且HyAnimation
对这些块有很强的引用。你同意吗?我怎样才能解决这个问题?我尝试使用\uuu block
声明这两个变量,但在这方面似乎没有效果
无论如何,我也很难把仪器的记忆分析和这个问题联系起来,这就是为什么这篇文章这么长的原因
谢谢你对我的宽容,我为你读了这么久的书而道歉
更新:
看来我是对的。继@Stephen Darlington在my上的帖子之后,我在HyAnimationCollection
中重写了dealoc
方法。与他的建议相反,我没有设置断点,而是编写了一个NSLog
。到现在为止,我从未记录过任何东西
- (void)dealloc
{
NSLog(@"dealloced");
}
我所做的是将另一个属性添加到HyAnimation
,应该清理完成。如果为true,animateWithTimer:
在完成时调用此函数:
- (void)cleanUp
{
// Get rid of everything
self.animations = [[NSMutableArray alloc] init];
self.updateFunctions = [[NSMutableArray alloc] init];
self.completionFunctions = [[NSMutableArray alloc] init];
}
我立即在控制台上看到了日志,因此肯定存在一个保留周期。问题是,我怎样才能解决它?\u block
不应该解决这个问题吗
更新2
我刚刚意识到这已经足够了:
- (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;
}
}
也就是说,保留周期必须在这里,对吗?但是在哪里呢?它可能是第一个@同步的块吗
更新
用\uuu-weak
替换\uu-block
似乎成功了。即使没有cleanUp
,对象也会释放,但不会正确释放。首先,它发布HyAnimationCollection
,然后发布HyLockCollection
,最后发布所有HyAnimation
sHyLock
不应该发布,因为我在属性中保留了对它的强引用
让我们再看看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;
}
}
问题是,只有在动画完成时才会调用此闭包。因为HyAnimationCollection
是第一个被释放的,这意味着当所有动画完成时,HyAnimationCollection
已经被释放,正如您所看到的,这导致它不会释放锁
现在我有了完全相反的问题=)编码是如此有趣好吧,问题似乎是这样的:
- 如果在
-addAnimation:
内的块中有一个对self
的强引用,则会得到一个保留周期
- 如果在
-addAnimation:
内的块中对self
的引用较弱,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
指向一个数组
数组指向一个动画
动画
指向一个块
该块指向self
当其中一个连接断开时,整个循环就会中断。打破循环的最简单方法是通过调用数组上的-removeAllObjects
来销毁数组指针(1)
顺便说一句,代码的另一个问题是HyAnimationCollection
中的static
变量。当您同时运行多个HyAnimationCollection
对象时,这将是一个问题。我只需要创建一个实例变量unsigned int\u completeCount代码>:
(你可以
- (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;
}
}
// 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;
}
}
@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;
}
}
// 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;
}
}