簡體   English   中英

內存泄漏(ARC)

[英]Memory leak (ARC)

上一個問題的后續中,我得出的結論是確實存在內存泄漏。 總而言之,內存開始時為9.7MB,每運行10次動畫就增加0.1MB,或者看起來是這樣。 我測試了大約12MB。

使用Instruments,我運行了一個包含以下內容的測試:

  1. 注冊初代
  2. 運行動畫10次
  3. 注冊另一代
  4. 重復幾次

這是我得到的:

在此處輸入圖片說明

因此內存確實會增加。 但是,檢查這些世代似乎對這些泄漏不承擔任何責任。 例如,檢查“統計信息”面板時,列出的類別似乎指示CF,CG,NS等,以及Malloc__NSMallocBlock__

我還檢查了調用樹,並跟蹤了具有最高內存消耗的分支。

在此處輸入圖片說明

同樣,大多數內存消耗似乎與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.animationLock是一種鎖定機制,因此動畫不會重疊,但是它幾乎是自包含的,我無法想象為什么會導致泄漏。 但是,這些塊確實被發送到HyAnimation ,后者又被添加到HyAnimationCollection ,這使我更加煩惱,這就是我一直關注的地方。 總而言之,也許這些閉包可能正在創建圓形保留,所以讓我們看一下。 HyAnimationaddUpdateFunction實際上非常簡單:

- (void)addUpdateFunction:(HyAnimationUpdateFunction)update
{
    [self.updateFunctions addObject: update];
}

由於self.updateFunctionsNSMutableArray ,它保留了對這些塊的強引用。 因此,如果不釋放HyAnimation ,則這些塊也不會釋放,這意味着創建它們的初始范圍也不會釋放。 但是,HyAnimation是在方法內部聲明的,所以到目前為止,我仍然沒有理由不發布它。

這就是為什么我認為應該是因為動畫本身,即HyAnimationCollection[collection start]; 這是有趣的部分:

for (HyAnimation * anim in self.animations) {
    [anim start];
}

到現在為止還挺好。 這是HyAnimationstart

- (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定義的塊。

首先,我相信HyAnimationCollectionHyAnimation實例被困在塊內,並且HyAnimation對這些塊有很強的引用。 你同意嗎? 我該如何解決? 我嘗試使用__block聲明兩個變量,但是對於這件事似乎沒有任何作用。

無論如何,我在將此問題與Instrument的內存分析相關時也遇到了麻煩,這就是為什么這篇文章這么長的原因。

多謝您與我保持聯系,對您的長時間閱讀深表歉意。

更新:

看來我是對的。 在@Stephen Darlington關於我之前的問題的帖子之后,我覆蓋了HyAnimationCollectiondealloc方法。 與他的建議相反,我沒有設置斷點,而是編寫了NSLog 直到現在都沒有記錄任何東西。

- (void)dealloc
{
    NSLog(@"dealloced");
}

我所做的是在HyAnimation添加了另一個屬性shouldCleanUpOnCompletion 如果為true,則animateWithTimer:在完成時調用此方法:

- (void)cleanUp
{
    // Get rid of everything
    self.animations = [[NSMutableArray alloc] init];
    self.updateFunctions = [[NSMutableArray alloc] init];
    self.completionFunctions = [[NSMutableArray alloc] init];
}

我立即在控制台上看到日志,因此肯定存在保留周期。 問題是,我該如何解決? __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;
    }
}

也就是說,保留周​​期必須在這里,對嗎? 但是哪里? 可以是第一個@synchronized塊嗎?

更新

__weak代替__block似乎已經成功了。 即使沒有cleanUp ,對象也會釋放,但是不能正確釋放。 首先,它會釋放HyAnimationCollection ,然后HyLockCollection最后全部HyAnimation秒。 HyLock不應該發布,因為我在屬性中始終強烈引用它。

讓我們再看一下HyAnimationCollectionaddAnimation:

- (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已經發布,如您所見,這導致它不釋放鎖。

現在我遇到了相反的問題=)編碼非常有趣<3

好吧,問題似乎是這樣的:

  • 如果您在-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;
    }
}
  1. self指向數組
  2. 數組指向animation
  3. animation指向一個塊
  4. 障礙指向self

當這些連接之一斷開時,整個周期就會中斷。 打破循環的最簡單方法是通過在數組上調用-removeAllObjects破壞數組指針(1)。


順便說一句,您的代碼的另一個問題是HyAnimationCollectionstatic變量。 一旦同時運行多個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,而不是在開發過程中發布,否則assert()可能無法正常工作,以后您可能會遇到一些奇怪的問題。)希望它能起作用...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM