iOS --關於循環引用

iOS之循環引用問題

  • 循環引用的產生
  • 內存分佈區域
  • 內存管理機制
  • 循環引用常見問題

循環引用的產生

內存中和變量有關的分區:堆、棧、靜態區。其中,棧和靜態區是操作系統自己管理的,對程序員來說相對透明,所以,一般我們只需要關注堆的內存分配,而循環引用的產生,也和其息息相關,即循環引用會導致堆裏的內存無法正常回收,從而導致內存泄漏。

內存分佈區域

這裏寫圖片描述

 

1、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。分配內存的時候,棧區的地址是 從上往下分配(越分配地址越小 i 地址比j大) 
2、堆區(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。堆區的地址是 由下往上的 
3、全局區(靜態區)(static)— 全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。 - 程序結束後有系統釋放 
4、文字常量區— 常量字符串就是放在這裏的。 程序結束後由系統釋放 
5、程序代碼區— 存放函數體的二進制代碼。

內存管理模型

在iOS中有2套內存管理機制:MRC(MannulReference Counting)和ARC(Automatic Reference Counting)。其中ARC起源於iOS 4.3,在那之前,蘋果開發者只能手動使用retain和release來進行內存管理。這樣做的問題很明顯,如果你在開發過程中,一個不小心沒有retain和release成對出現,那麼很容易使得內存沒有釋放,最後導致程序內存不足而導致閃退。

MRC 
在你創建一個對象的時候,他會需要進行retain,然後在你不在持有他的時候進行release,所以每個對象都有一個retain count來進行計數。 
內存管理規則如下: 
1> 當調用alloc、new、copy(mutableCopy)方法產生一個新對象的時候,就必須在最後調用一次release或者autorelease 
2> 當調用retain方法讓對象的計數器+1,就必須在最後調用一次release或者autorelease

2.集合的內存管理細節 
1> 當把一個對象添加到集合中時,這個對象會做了一次retain操作,計數器會+1 
2> 當一個集合被銷燬時,會對集合裏面的所有對象做一次release操作,計數器會-1 
3> 當一個對象從集合中移除時,這個對象會一次release操作,計數器會-1

3.普遍規律 
1> 如果方法名是add\insert開頭,那麼被添加的對象,計數器會+1 
2> 如果方法名是remove\delete開頭,那麼被移除的對象,計數器-1

ARC 
iOS 5.0之後引用自動管理機制——自動引用計數(ARC),管理機制與手動機制一樣,只是不再需要調用retain、release、autorelease;它編譯時的特性,當你使用ARC時,在適當位置插入release和autorelease;它引用strong和weak關鍵字,strong修飾的指針變量指向對象時,當指針指向新值或者指針不復存在,相關聯的對象就會自動釋放,而weak修飾的指針變量指向對象,當對象的擁有者指向新值或者不存在時weak修飾的指針會自動置爲nil。

循環引用常見問題

類相互引用
- (void)viewDidLoad {
    [super viewDidLoad];
    ClassA *a = [[ClassA alloc] init];
    ClassB *b = [[ClassB alloc] init];
    a.classB = b;
    b.classA = a;
}
兩個類 A 和 B A強引用B B強引用A 所以下面的代碼 dealloc 不會走 發生循環引用 
解決方法 讓其中任意一個類 變成弱引用即可 即 @property (nonatomic, weak) ClassA *classA; 
運行結果如下: 
這裏寫圖片描述

2.Block問題

 - (void)blockFunc {
    self.block = ^{
        self.str = @"hello";//發生循環引用
        NSLog(@"%@", self.str);
    };
    self.block();
}

可以發現發生循環引用 解決辦法爲 用weak 進行弱引用

- (void)blockFunc {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        weakSelf.str = @"hello";
        NSLog(@"%@", weakSelf.str);
    };
    self.block();
}

注:此處爲自己的原創:

==========

/** 

第二種方式  此種方法需要確保再self釋放之後不會再有調用當前類的其他方法。

**/

        self.nameStr = @"sssss";

        __block ViewController2 *bSelf = self;

        self.pushBlock = ^{

           NSLog(@"name的值是:%@",bSelf.nameStr);

            bSelf = nil;

        };

        self.pushBlock();

/**

  第三種方式 

將當前對象作爲block的入參傳入到Block內部去

**/

self.nameStr = @"ssss"

    self.pushBlock = ^(ViewController2 *obj) {

        NSLog(@"name的值是:%@",obj.nameStr);

    };

    self.pushBlock(self);

=========

原創結束

當在block中添加延遲函數時 下面函數就會出現問題

- (void)blockFunc {
    self.str = @"hello";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakSelf.str);
        });
    };
    self.block();
}

1 打印null 5秒之內VC返回 此時VC 頁面消失 會走dealloc函數 故里面的weakSelf會爲空 
2 打印hello 5秒之後VC返回 延遲函數已執行完 打印完整的hello VC 消失 走dealloc 
解決辦法爲 用strong 進行 強引用

- (void)blockFunc {
    self.str = @"hello";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(self) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", strongSelf.str);
        });
    };
    self.block();
}

 

首先 weakSelf 就是弱引用 防止裏面循環引用 其次 內部的strongSelf是個 局部變量 僅存在於棧中 當strongSelf.str 執行完 strongSelf就會回收 不會造成循環引用 strongSelf 會使vc引用計數器加1 當其返回時 並不會立刻走dealloc函數 而是等strongSelf.str它執行完 引用計數爲0 纔會走dealloc 函數 所以不論什麼時候返回 strongSelf.str都是有值的 這就是爲什麼__weak 和 __strong 成對出現的原因
 

3.NSTimer

 - (void)viewDidLoad {
    [super viewDidLoad];
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerClicked) userInfo:nil repeats:YES];
    // 等同於 下面 RunLoop -> timer -> self
    self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerClicked) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}

- (void)timerClicked {
    NSLog(@"timerClicked ----");

這段代碼發生內存泄露 頁面消失 timerClicked函數一直執行 
解決辦法爲 didMoveToParentViewController函數 將timer 置爲空 或在合適位置將其timer取消

- (void)didMoveToParentViewController:(UIViewController *)parent {
    if (nil == parent) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章