想必很多開發人員知道一般用copy修飾block,接下來就講解為什么需要用copy,甚至會講到其實用strong修飾block也是可以的
在 Objective-C 語言中,一共有 3 種類型的 block:
- _NSConcreteGlobalBlock 全局的靜態 block,沒有訪問外部局部變量(基本數據、OC對象)、成員屬性變量或只用到全局變量、靜態變量(局部或者全局靜態變量)。
- _NSConcreteStackBlock 保存在棧中的 block,只用到外部局部變量(基本數據、OC對象)、成員屬性變量,且沒有強指針引用的block。當函數返回時會被銷毀。
- _NSConcreteMallocBlock 保存在堆中的 block,有強指針引用或copy修飾的成員屬性引用的block會被復制一份到堆中成為MallocBlock,沒有強指針引用即銷毀,生命周期由程序員控制。
新建一個項目,分別在ARC環境和MRC環境測試一遍
把測試文件改成MRC環境的方法:
把完整的測試代碼顯示如下:
#import "ViewController.h" @interface ViewController () @property (nonatomic,copy)void(^demoBolck)(); @property (nonatomic,strong)void(^demoBolck1)(); @end @implementation ViewController int b=8;//全局變量 - (void)viewDidLoad { [super viewDidLoad]; void (^demoBolck)() = ^{ NSLog(@"indemoBolck"); }; NSLog(@"demoBolck %@",demoBolck); //<__NSGlobalBlock__: 0x1085af0e0> 無論ARC還是MRC下,因不訪問外部局部變量(基本數據、OC對象)、成員屬性變量或只用到全局變量、靜態變量(局部或者全局靜態變量),NSGlobalBlock表示在全局區 void (^demoBolck4)() = ^{ NSLog(@"indemoBolck4 %d",b); }; NSLog(@"demoBolck4 %@",demoBolck4); //<__NSGlobalBlock__: 0x10150b120> 全局區 __block int a = 6; //block內部引用a,並修改其值,需要用block修飾,不然可以不用 void (^demoBolck2)() = ^{ NSLog(@"indemoBolck2 %d",a++); }; demoBolck2(); NSLog(@"demoBolck2 %@,%d",demoBolck2,a); //<__NSMallocBlock__: 0x600000056c50> ARC下堆區,在ARC模式下,系統也會默認對Block進行copy操作,Block的內存地址這時候便顯示在堆區; <__NSStackBlock__: 0x7fff5d0ada28>MRC下在棧區 NSLog(@"demoBolck2.Copy %@",[demoBolck2 copy]); //<__NSMallocBlock__: 0x600000056c50>copy操作不管MRC或者ARC都在堆區,只是在MRC下進行copy會改變地址 self.demoBolck = demoBolck2; NSLog(@"self.demoBolck %@",self.demoBolck);//堆區<__NSMallocBlock__: 0x608000052630> self.demoBolck1 = demoBolck2; self.demoBolck1(); //demoBolck2 7 能執行無問題 NSLog(@"self.demoBolck1 %@",self.demoBolck1); //<__NSMallocBlock__: 0x600000056c50> strong修飾ARC和MRC都並沒有問題, 但是assign和retain在MRC環境下是還是在棧區的,會有問題 } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //注意:MRC環境下:demoBolck1用assign,retain修飾,棧區:<__NSStackBlock__: 0x7fff50915a50>,提前釋放了所以運行到下面語句程序會崩潰。ARC環境下:用copy,strong, assign, retain修飾是可以正常打印出結果的。 無論什么環境,用copy,strong修飾是可以正常打印出結果的 self.demoBolck1(); } @end
總結:
- block內部沒有調用外部局部變量時存放在全局區(ARC和MRC下均是)
- block使用了外部局部變量,這種情況也正是我們平時所常用的方式。MRC:Block的內存地址顯示在棧區,棧區的特點就是創建的對象隨時可能被銷毀,一旦被銷毀后續再次調用空對象就可能會造成程序崩潰,在對block進行copy后,block存放在堆區.所以在使用Block屬性時使用copy修飾。但是ARC中的Block都會在堆上的,系統會默認對Block進行copy操作
- 用copy,strong修飾block在ARC和MRC都是可以的,都是在堆區
補充:一個block要使用self,會處理成在外部聲明一個weak變量指向self,然而為何有時會出現在block里又聲明一個strong變量指向weakSelf?
原因:block會把寫在block里的變量copy一份,如果直接在block里使用self,(self對變量默認是強引用)self對block持有,block對self持有,導致循環引用,所以這里需要聲明一個弱引用weakSelf,讓block引用weakSelf,打破循環引用。
而這樣會導致另外一個問題,因為weakSelf是對self的弱引用,如果這個時候控制器pop或者其他的方式引用計數為0,就會釋放,如果這個block是異步調用而且調用的時候self已經釋放了,這個時候weakSelf已就變成了nil。
當控制器(也可以是其他的控件)pop回來之后(或者一些其他的原因導致釋放),網絡請求完成,如果這個時候需要控制器做出反映,需要strongSelf再對weakSelf強引用一下。
但是,你可能會疑問,strongSelf對weakSelf強引用,weakSelf對self弱引用,最終不也是對self進行了強引用,會導致循環引用嗎。不會的,因為strongSelf是在block里面聲明的一個指針,當block執行完畢后,strongSelf會釋放,這個時候將不再強引用weakSelf,所以self會正確的釋放。
原因:block會把寫在block里的變量copy一份,如果直接在block里使用self,(self對變量默認是強引用)self對block持有,block對self持有,導致循環引用,所以這里需要聲明一個弱引用weakSelf,讓block引用weakSelf,打破循環引用。
而這樣會導致另外一個問題,因為weakSelf是對self的弱引用,如果這個時候控制器pop或者其他的方式引用計數為0,就會釋放,如果這個block是異步調用而且調用的時候self已經釋放了,這個時候weakSelf已就變成了nil。
當控制器(也可以是其他的控件)pop回來之后(或者一些其他的原因導致釋放),網絡請求完成,如果這個時候需要控制器做出反映,需要strongSelf再對weakSelf強引用一下。
但是,你可能會疑問,strongSelf對weakSelf強引用,weakSelf對self弱引用,最終不也是對self進行了強引用,會導致循環引用嗎。不會的,因為strongSelf是在block里面聲明的一個指針,當block執行完畢后,strongSelf會釋放,這個時候將不再強引用weakSelf,所以self會正確的釋放。