iOS學習-Block

一、是什么

Block本質上也是一個OC對象,底層也是一個結構體,內部也有isa指針。
封裝了函數調用以及函數調用環境的OC對象。
底層結構如 圖所示:


二、block的基本使用

2.1 block的形式

  • 無參無返回值
//定義一個block
void (^myblock1)()=^(){

  NSLog(@"myblock1");

};
//使用block
myblock1();
  • 無參有返回值
int (^myblock2)()=^(){

return 8;
  NSLog(@"myblock2");

};

myblock2();
  • 有參無返回值
void (^myblock3)(int,int)=^(int a, int b){
  NSLog(@"%d",a+b);
};

myblock3(3,7);

//也可以先定義變量,再賦值
myblock3 =^(int x,int y){
      NSLog(@"%d",x-y);
};

myblock3(8,7);
  • 有參有返回值
int (^myblock4)(int,int)=^(int a, int b){

  NSLog(@"%d",a+b);

  return a + b;
};
int sum = myblock4(3,4);
NSLog(@"%d",sum);

三、block的typedef

利用typedef定義block類型(和指向函數的指針很像)

  • 格式
    typedef 返回值類型 (^新別名)(參數類型列表);
    typedef int (^myblock)(int , int);

以后就可以利用這種類型來定義block變量了;

typedef void (^Block)();
Block b1;
//Block類型的變量
b1 = ^{
     NSLog(@"myblock");
};

//有參數有返回值
typedef int (^newType)(int , int);
newType nt1 = ^(int a, int b){
   
retutn a +b;
};
int s = nt1 (12 , 23);
NSLog(@"%d",s);

//聯系定義多個newType的變量
newType n1,n2,n3
n1 = ^(int x, int y){
   
retutn x*y;
};

四、block對變量的捕獲

image.png
   //auto 可以不寫,默認的值,離開作用域會自動銷毀
//    auto int age = 10;
    
    int a = 10;//傳的變量,block內部存儲的是個變量

    static int b = 10;//傳的地址值,block內部存儲的是個地址值
    
    void (^block)(void) = ^{
        
        NSLog(@"捕獲的值局部變量a=%d,靜態變量b=%d,全局變量=%d",a,b,c);
        
    };
    
    a = 20;
    b = 30;
    c = 40;
    
    block();
    
    
    //1、為什么局部變量需要捕獲,全局變量不需要捕獲?
//   因為在底層代碼實現里面: 局部變量需要跨函數訪問,所以需要捕獲,而全局變量在哪個函數都可以直接訪,不需要捕獲。
    
    //1、為什么2者有這的差異?
    //auto類型代碼執行完就要銷毀,所以是值傳遞,不然再去訪問就會出現問題,因為局部變量已釋放。
    //  而靜態變量一直在內存中, 不會銷毀,所以是指針傳遞
    
    void (^block1)(void) = ^{
        
        NSLog(@"self=%p",self);
    };
    
    block1();
//  self也會被捕獲 
2021-09-18 15:16:46.086277+0800 OCStudy[90772:4443186] 捕獲的值a=10,b=30

代碼內部實現:


image.png

五、block的類型

block 有3種類型,可以通過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型

image.png
  • MRC環境下的Block類型:
image.png

打開MRC


image.png
  void (^block)(void) = ^{
            
            NSLog(@"hello");
        };
        NSLog(@"block的類型=%@",[block class]);
        NSLog(@"block的父類=%@",[[block class] superclass]);
        NSLog(@"block的父類的父類=%@",[[[block class] superclass] superclass]);
block的類型=__NSGlobalBlock__
block的父類=NSBlock
block的父類的父類=NSObject
void (^block)(void) = ^{//__NSGlobalBlock__

            NSLog(@"hello");
        };
        
        NSLog(@"block的類型=%@",[block class]);

         
        int a = 10;
        void (^block1)(void) = ^{//__NSStackBlock__

            NSLog(@"age=%d",a);
        };
        NSLog(@"block1的類型=%@",[block1 class]);
        
        NSLog(@"block2的類型=%@",[[block1 copy] class]);//__NSMallocBlock__

        NSLog(@"block3的類型=%@",[[block copy] class]);//__NSGlobalBlock__
2021-09-20 15:50:48.324228+0800 dddd[50990:5734435] block的類型=__NSGlobalBlock__
2021-09-20 15:50:48.325460+0800 dddd[50990:5734435] block1的類型=__NSStackBlock__
2021-09-20 15:50:48.325632+0800 dddd[50990:5734435] block2的類型=__NSMallocBlock__
2021-09-20 15:50:48.325756+0800 dddd[50990:5734435] block3的類型=__NSGlobalBlock__
image.png

所以在MRC時代,block用Copy修飾。
將棧的數據拷貝到堆上,防止局部變量的數據釋放掉以后,block還去訪問無數據的問題。

  • ARC環境下的Block類型:

ARC環境下,編譯器會根據情況自動將棧上的block復制到堆上,有以下情況:

  • block作為函數返回值時
  • 將block賦值給__strong指針時
  • cocoa API中,將block作為參數的,比如GCD的函數
void (^block)(void) = ^{//__NSGlobalBlock__
        
        NSLog(@"hello");
    };

 int a = 10;
    void (^block1)(void) = ^{//__NSMallocBlock__
        
        NSLog(@"age=%d",a);
    };
    NSLog(@"block1的類型=%@",[block1 class]);
2021-09-20 16:15:44.560817+0800 OCStudy[51711:5753274] block的類型=__NSGlobalBlock__
2021-09-20 16:15:44.561154+0800 OCStudy[51711:5753274] block1的類型=__NSMallocBlock__

六、 __block修改局部auto變量

__block不能修飾局部變量、靜態變量
編譯器會將__block變量包裝成一個對象

__block int age = 10;//會將age包裝成一個對象
    NSLog(@"age的值=%d,地址=%p",age,&age);
    
    NSMutableArray *arr = [NSMutableArray array];
    
    void (^block)(void) = ^{
        
        age = 20;
        NSLog(@"age的值=%d,地址=%p",age,&age);
//        arr = nil;//這是修改值
        [arr addObject:@"123"];//不是 修改指針,是使用指針
        [arr addObject:@"456"];

    };
    
    block();
    
    NSLog(@"age的值=%d,地址=%p",age,&age);
    NSLog(@"arr的值=%@",arr);
image.png

image.png
image.png

七、 循環引用

typedef void (^TestBlock)(void)
@interface Person 
@property (nonatomic , strong) TestBlock testblock;
@end
-(void)dealloc{
    
    NSLog(@"person-delloc,死了");
}
  • 不使用weakself的情況。

    Person *person = [[Person alloc] init];
    person.age = 100;
    person.testblock = ^{

       NSLog(@"age2 = %d",person.age);
    };
    person.testblock();
    NSLog(@"end");
image.png

person和block相互強引用,造成循環引用,所以person對象無法dealloc。

  • 使用weakself
    Person *person = [[Person alloc] init];
    person.age = 100;
    
    __weak typeof(person) weakPerson = person;
    person.testblock = ^{

       NSLog(@"age1 = %d",weakPerson.age);
    };
    person.testblock();
    NSLog(@"end");
image.png

由于一個使用__weak修飾,破除了相互強引用,所以peron可以delloc

  • 來看下面一段代碼 ,在block內部,有一個2秒后需要執行的代碼,也需要用到age的值
Person *person = [[Person alloc] init];
    person.age = 100;

    __weak typeof(person) weakPerson = person;
    person.testblock = ^{

       NSLog(@"age1 = %d",weakPerson.age);
       
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       
                   NSLog(@"age2 = %d",weakPerson.age);
        });
    };
    person.testblock();
    NSLog(@"end");
image.png

我們發現后面再去使用age的值時,person已經死掉了,這個時候可以使用使用__strong

Person *person = [[Person alloc] init];
    person.age = 100;

    __weak typeof(person) weakPerson = person;
    person.testblock = ^{

       NSLog(@"age1 = %d",weakPerson.age);
        
        __strong __typeof(weakPerson)strongPerson = weakPerson;
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
                   NSLog(@"age2 = %d",strongPerson.age);
        });
    };
    person.testblock();
    NSLog(@"end");
image.png

在 Block 內如果需要訪問 self 的方法、變量,建議使用 weakSelf。
如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。

八、關于block的問題

1、怎么解決block循環引用的問題?
__weak __typeof__(self) weakSelf = self; 
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
 __strong __typeof(self) strongSelf = weakSelf;  
 [strongSelf doSomething];  
}); 
2、什么時候在 block里面用self,不需要使用weakself?

block和其他的對象之間不會相互強引用

比如UIView的動畫代碼,我們在使用UIView animateWithDuration:方法做動畫的時候,并不需要使用weakself,因為引用持有關系是:
UIView 的某個負責動畫的對象持有block,block 持有了self因為 self 并不持有 block,所以就沒有循環引用產生,因為就不需要使用 weak self 了。

[UIView animateWithDuration:0.2 animations:^{
    self.alpha = 1;
}];
3、為什么 block 里面還需要寫一個 strong self,如果不寫會怎么樣?

在 block 中先寫一個 strong self,其實是為了避免在 block 的執行過程中,突然出現 self 被釋放的尷尬情況。通常情況下,如果不這么做的話,還是很容易出現一些奇怪的邏輯,甚至閃退。

我們以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代碼舉例:

__weak__typeof(self)weakSelf =self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {    
__strong__typeof(weakSelf)strongSelf = weakSelf;    
strongSelf.networkReachabilityStatus= status;

if(strongSelf.networkReachabilityStatusBlock) {  
      strongSelf.networkReachabilityStatusBlock(status);    
}};

如果沒有 strongSelf 的那行代碼,那么后面的每一行代碼執行時,self 都可能被釋放掉了,這樣很可能造成邏輯異常。

4、block 里 strong self 后,block 不是也會持有 self 嗎?而 self 又持有 block ,那不是又循環引用了?
__weak __typeof(self)weakSelf = self;    //1

[self.context performBlock:^{      
    [weakSelf doSomething];          //2
     __strong __typeof(weakSelf)strongSelf = weakSelf;  //3
    [strongSelf doAnotherSomething];        
}];

1.使用__weak __typeof是在編譯的時候,另外創建一個局部變量weak對象來操作self,引用計數不變。block 會將這個局部變量捕獲為自己的屬性,訪問這個屬性,從而達到訪問 self 的效果,因為他們的內存地址都是一樣的
2.因為weakSelf和self是兩個變量,doSomething有可能就直接對self自身引用計數減到0了,所以在[weakSelf doSomething]的時候,你很難控制這里self是否就會被釋放了.weakSelf只能看著
3.__strong __typeof在編譯的時候,實際是對weakSelf的強引用.指針連帶關系self的引用計數會增加.但是你這個是在block里面,生命周期也只在當前block的作用域.所以,當這個block結束, strongSelf隨之也就被釋放了.不會影響block外部的self的生命周期.

5、關于Masonry里面的Block,為什么不需要使用?

關于Masonry里面的Block:函數參數里面的Block是局部的block(棧上),block內部引用self不會造成循環引用;是否會循環引用只看函數內部是否copy了這個block(比如把它付給全局的Block)

6、block的原理是怎樣的?本質是什么?

Block本質上也是一個OC對象,底層也是一個結構體,內部也有isa指針。
封裝了函數調用以及函數調用環境的OC對象

7、__block的作用是什么?有什么使用注意點?

_block可以用于解決block內部無法修改auto變量值的問題
__block不能修飾全局變量、靜態變量(static)
編譯器會將__block變量包裝成一個對象
注意:循環引用問題

8、block的屬性修飾詞為什么是copy?

在MRC環境下,block用Copy修飾。將棧的數據拷貝到堆上,防止局部變量的數據釋放掉以后,block還去訪問無數據的問題。
在ARC環境下,使用strong修飾也可以,編譯器會根據情況自動將棧上的block復制到堆上

9、block在修改NSMutableArray,需不需要添加__block?

不需要

NSMutableArray *arr = [NSMutableArray array];
  
  void (^block)(void) = ^{

      arr = nil;   這是修改值
      [arr addObject:@"123"];  不是 修改指針,是使用指針進行操作
      [arr addObject:@"456"];

  };
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容