YYKit @autoreleasepool 使用,優化內存


寫在前面

  最近再看YY大神的YYKit工具,發現在代碼中經常使用@autoreleasepool,特別是在與for循環搭配使用的時候。剛開始很不能理解。

  先有個概念:

    自己創建的對象:使用 alloc new copy mutablecopy 以及他們的駝峰變形 allocObject newObject copyObject mutablecopyObject。這八種創建的才是自己創建的對象。

     不是自己創建的:除去以上八中都不是自己創建的。

     autoreleasepool:只有非自己創建的對象才會注冊到離該對象最近的autoreleasepool中去。

  

  之前對iOS的ARC的理解就是自己創建的對象,會在該對象所在是代碼快(當前作用域)結束時候自動釋放。無需程序員自己管理。但是如果仔細研究YYKit的代碼的話,發現如果注釋掉@autoreleasepool,程序一樣可以正常運行,但是觀察當前內存,會比在加上該句代碼時候占用比更大的空間。所以自己百度了一下,這里是sunny(百度一90后iOS程序員)大神寫的關於@autoreleasepool的理解,主要內容其實可以概括為 一句話"Autorelease對象是在當前的runloop迭代結束時釋放的,而它能夠釋放的原因是系統在每個runloop迭代中都加入了自動釋放池Push和Pop",具體我就不抄了,但是有些東西不適合初學者,我自己總結了一些,比較容易理解一點。具體看代碼。

 

@interface ViewController ()
@property(strong, nonatomic) NSMutableArray *memoryUsageList;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //Init
    _memoryUsageList1 = [NSMutableArray new];
    
        //創建一個串行隊列
    dispatch_queue_t serialQueue = dispatch_queue_create("test.autoreleasepool", DISPATCH_QUEUE_SERIAL);
    
    __block NSString *strTest;
    dispatch_sync(serialQueue, ^{
            for (int i = 0; i < kIterationCount; i++) {
                @autoreleasepool { 
                    NSNumber *num = [NSNumber numberWithInt:i];  // 1
                    NSString *str = [NSString stringWithFormat:@"%d ", i];  // 2
                    //Use num and str...whatever...
                    strTest = [NSString stringWithFormat:@"%@%@", num, str];  // 3
                    if (i % kStep == 0) {
                        [_memoryUsageList1 addObject:@(getMemoryUsage())];  // getM方法是獲取內存的函數
                        NSLog(@"000----%f", getMemoryUsage());
                    }
                }
            }
    });
}  // 4

 

double getMemoryUsage(void) {
    struct task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
    
    double memoryUsageInMB = kerr == KERN_SUCCESS ? (info.resident_size / 1024.0 / 1024.0) : 0.0;
    
    return memoryUsageInMB;
}

 

   讓我們來分析一下當有@autoreleasepool存在的時候,代碼中的1、2、3行中的內存分別是在什么時候釋放。此分析建立在已經有iOS引用計數器概念的基礎上。ARC其實就是編譯器自動幫我們加上了retain,release等,本質還是引用計數器。

    1、

    1和2是相同的,在堆區分別存在NSNumber的對象和NSString的對象,棧區有對應的指針,num、str指向兩個對象。當一次for循環結束的時候,棧區的兩個指針由系統釋放,此時堆區中的兩個對象的引用計數器為0,所以一次for循環結束的時候,兩個對象釋放。

    2、

    關鍵是3行,首先分析strTest,每次for循環都會重新覆蓋strTest之前的值,由於strTest是在viewDidLoad函數中,所以當函數結束,也就是如代碼中,4行時候strTest釋放,同時strTest指向的堆中的內存空間也釋放。也就是for循環最后一次的"[NSString stringWithFormat:@"%@%@", num, str]"的值會釋放。

    這么看來好像所有的內存都已經釋放了,那么@auto究竟做了什么呢?

    重點:注意觀察for循環,每一次for循環,在堆區都會生成一份"[NSString stringWithFormat:@"%@%@", num, str]"的內存,然后最新生成的一份內存會由strTest重新指一次,那么之前那些內存去哪了?我剛開始以為每一次for循環結束時候,之前生成的便會自動釋放。該內存不是通過系統自動創建的,也就是說不是通過 alloc /new/copy/mutablecopy 創建的,所以會注冊到autoreleasepool中 ,作用域無效,系統是無法自動釋放的。這時候是@auto的關鍵了。@autoreleasepool相當於給堆中的內存添加了一個作用域,超過這個作用域堆中的內存就自動釋放。所以每次遍歷完,一份新生成的"[NSString stringWithFormat:@"%@%@", num, str]"作用域結束,自動釋放。  

    那么,問題又來了。我不加@autoreleasepool,程序一樣運行的很好,沒出現問題。那是因為,系統在每一次的runloop中自動給我們加上了@autoreleasepool,一次runloop結束,系統就會把該循環中的所有需要release的對象清除。之所以在代碼中手動添加一次,是為了防止在某一操作的時候,該操作瞬時占用的內存過大(比如for循環),導致程序瞬時的閃退。

    

    總結:多看大神代碼,那寫你認為很無聊的寫法,可能里面包含這對整個系統更深層次的理解。

 

    但是發現有一些有趣的現象,例如如下的代碼

    for (int i = 0; i < 100000; i++) {
        NSNumber *num = [NSNumber numberWithInt:i]; // 1
        NSString *str = [NSString stringWithFormat:@"%d ", i];  //2
        NSString *str1 = [NSString stringWithFormat:@"%@", num]; // 3
         NSString *str2 = [NSString stringWithFormat:@"%@%@", num, str]; //4
//        NSString *str3 = [[NSString alloc] initWithFormat:@"%@%@", num, str]; // ****test****
        [NSArray arrayWithObject:@"1234"]; // 5
//            if (i % 10000 == 0) {
//                NSLog(@"000----%f", getMemoryUsage());
//            }
    }

 

自己個人猜測:

單獨執行代碼1、2,打印的內存不會上升。  

執行代碼1和3,內存不會上升

執行代碼 1+2+4  內存飆升。猜測,不同類型合並成string內存管理不一樣。

單獨執行5,內存飆升。     

 

個人理解:[NSNumber numberWithInt:i]和[NSString stringWithFormat:@"%@", num]等,並沒有發送autorelease消息,也就是沒將他們放到autoreleasepool中。所以在代碼快結束的時候他們就已經銷毀了,但是某些特殊的,如[NSArray arrayWithObject:@"1234"]; 會添加到pool中,由於處於autoreleasepool中的對象延遲執行(所在runloop完畢),所以代碼塊結束時候,runloop並未結束,導致無法釋放。

總結:當遇到大量的for循環時候,盡量使用@autoreleasepool來避免內存的延遲釋放導致瞬時內存飆升。蘋果推薦以下三中情況使用@autoreleasepool代碼快

   如果你編寫的程序不是基於 UI 框架的,比如說命令行工具;

         如果你編寫的循環中創建了大量的臨時對象;(常用)

         如果你創建了一個輔助線程。

 

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM