ios-Block

概述:

  • 能夠截取自動變量的匿名函數
  • 指向函數的指針
  • 結構體
  • oc對象

使用:

- 聲明
格式:返回值類型(^變量名稱)(參數列表)
int(^myBlock)(int a,int b)
void(^myBlock)(void)
- 定義(變量賦值)
格式:變量 = ^(參數列表){函數體};
myBlock = ^(int a, int b){
     return 1;
};
myBlock = ^(void){

};
- 調用
格式:變量(參數列表);
myBlock(1,2);
myBlock();
- 使用typedef定義Block類型
格式:typedef 返回值類型(^變量名稱)(參數列表)
typedef void(^MyBlock)();
MyBlock myBlock = ^(){

};
- Block作為函數參數
int(^MyBlock)(int a, int b) = ^(int a, int b){ // 聲明定義一個參數為a,b,返回值為int類型的block
        return 3;
};
[self blockTest:MyBlock]; 
    
void(^MyBlcok)(void) = ^(){
    
};
[self blockTest1:MyBlcok];

- (void)blockTest:(int(^)(int a, int b))myBlock{
}
- (void)blockTest1:(void(^)())myBlock{
}

注意:為了簡化block的聲明,一般使用typedef聲明block類型

- Block作為函數返回值
int(^MyBlock)(int a, int b) = [self blockTest];

- (int(^)(int a, int b))blockTest{
    return ^(int a,int b){
        return a+b;
    };
}

block類型:

  • 全局區:NSConcreteGlobalBlock
  • 棧區:NSConcreteStackBlock
  • 堆區:NSConcreteMallocBlock
- NSConcreteGlobalBlock
block定義在函數之外(和全局變量一個地方)
void (^globalBlock)() = ^{
    NSLog(@"大家好我是NSConcreteGlobalBlock-->");
};
int main(int argc, char * argv[]) {
    @autoreleasepool {
        globalBlock();
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

轉換成c++代碼:
struct __globalBlock_block_impl_0 {
  struct __block_impl impl;
  struct __globalBlock_block_desc_0* Desc;
  __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;  <--- 看這,這里。。
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

注意:在block沒有截取任何自動變量的時候,也是NSConcreteGlobalBlock的,但我在ARC環境下,查看c++源碼,是NSConcreteStackBlock,具體原因請看唐巧大神的說明,自行驗證。

- NSConcreteStackBlock
定義在函數內部的block
int main(int argc, char * argv[]) {
    @autoreleasepool {
        void(^StackBlock)() = ^(){
            NSLog(@"大家好,我是StackBlock");
        };
        StackBlock();
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

轉換成c++代碼:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
注意:NSConcreteMallocBlock無法直接創建,當執行了以下操作時,系統會自動從NSConcreteStackBlock copy到NSConcreteMallocBlock中:

1.調用Block的copy實例方法時
2.Block作為函數返回值返回時
3.將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
4.將方法名中含有usingBlock的Cocoa框架方法或GCD的API中傳遞Block時

截取自動變量:

- 局部變量
  • 局部變量在block中不可修改。
  • 局部變量是值傳遞,所以在block外面修改不影響block里截取的變量值。
oc代碼:
int main(int argc, char * argv[]) {
    @autoreleasepool {
        int i = 0;
        void(^MyBlock)() = ^(){
            i++; // 報錯
            NSLog(@"大家好,我是MyBlock-->%d",i); // 注釋掉上面的報錯代碼,輸出的還是0;
        };
        i++;
        MyBlock();
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

c++代碼:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int i = __cself->i; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_k__jbgrnnkx4ds0dh6rz0rglp540000gn_T_main_658820_mi_1,i);
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int i = 0;
        void(*MyBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));

        ((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);

        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

- 靜態局部變量
  • 局部靜態變量在block中可修改。
  • 局部靜態變量是地址傳遞,所以在block外面修改會影響block里截取的變量值。
oc代碼:
int main(int argc, char * argv[]) {
    @autoreleasepool {
        static int i = 0;
        void(^MyBlock)() = ^(){
            i++; // 可以修改,不會報錯
            NSLog(@"大家好,我是MyBlock-->%d",i);
        };
        i++;
        MyBlock();

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

c++代碼:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *i = __cself->i; // bound by copy

            (*i)++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_k__jbgrnnkx4ds0dh6rz0rglp540000gn_T_main_e314e4_mi_0,(*i));
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        static int i = 0;
        void(*MyBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &i));

        i++;
        ((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);

        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}
- __block修飾局部變量(只能用于修飾普通局部變量,static,全局都不行)
  • __block修飾的局部變量在block中可修改。
  • __block修飾的局部變量,注意看下面c++源碼,系統會把用__block修飾的變量包裝成一個__Block_byref_i_0結構體對象。main_block_impl_0 中引用的是 Block_byref_i_0 的結構體指針,這樣就可以達到修改外部變量的作用,因為傳遞的是該結構體的地址,所以我們在外面修改變量,會影響block中的值。
oc代碼:
int main(int argc, char * argv[]) {
    @autoreleasepool {
        __block int i = 0;
        void(^MyBlock)() = ^(){
            i++; // 可以修改,不會報錯
            NSLog(@"大家好,我是MyBlock-->%d",i);
        };
        
        i++;
        MyBlock();
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

c++代碼:
struct __Block_byref_i_0 {  // 當用__block來修飾變量時,系統會吧變量包裝成一個結構體對象。
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref

            (i->__forwarding->i)++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_k__jbgrnnkx4ds0dh6rz0rglp540000gn_T_main_be9fd2_mi_0,(i->__forwarding->i));
 }

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
        void(*MyBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));

        (i.__forwarding->i)++;
        ((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);

        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

- 全局變量
  • 全局變量在block中可修改。
  • 全局變量存儲在靜態數據區,在程序銷毀前不會銷毀,所以在block中可以直接訪問,因為訪問的是一份地址,所以在外面修改會影響block里面變量的值。
oc代碼:
int i = 0;
int main(int argc, char * argv[]) {
    @autoreleasepool {
        void(^MyBlock)() = ^(){
            i++; // 可以修改,不會報錯
            NSLog(@"大家好,我是MyBlock-->%d",i);
        };
        
        i++;
        MyBlock();
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

c++代碼:
int i = 0;
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            i++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_k__jbgrnnkx4ds0dh6rz0rglp540000gn_T_main_ba21a2_mi_0,i);
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void(*MyBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        i++;
        ((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);

        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

- 靜態全局變量
  • 靜態全局變量在block中可修改。
  • 靜態全局變量存儲在靜態數據區,在程序銷毀前不會銷毀,所以在block中可以直接訪問,因為訪問的是一份地址,所以在外面修改會影響block里面變量的值。
oc代碼:
static int i = 0;
int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        void(^MyBlock)() = ^(){
            i++; // 可以修改,不會報錯
            NSLog(@"大家好,我是MyBlock-->%d",i);
        };
        
        i++;
        MyBlock();
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

c++代碼:
static int i = 0;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
       i++;
       NSLog((NSString *)&__NSConstantStringImpl__var_folders_k__jbgrnnkx4ds0dh6rz0rglp540000gn_T_main_e20576_mi_0,i);
 }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void(*MyBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        i++;
        ((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);

        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

內存管理:

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    int i = 0;
    MyBlock = ^(){
        NSLog(@"我是MyBlock--->%d",i);
    };
    MyBlock();
    
    NSLog(@"myBlock--->%@",MyBlock);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    MyBlock();
}

  • MRC
    引用了局部變量i,所以block在NSStackBlock中,出了viewDidLoad方法,MyBlock會自動銷毀,touchesBegan再調用MyBlock就會報野指針錯誤。
    需要手動調用Block_copy方法把block復制到NSConcreteMallocBlock中,記得調用Block_release方法使計數器-1。
  • ARC
    會自動調用copy方法,把block復制到NSConcreteMallocBlock中。

注意:在Block中無論是MRC/ARC,沒有調用局部變量時,都是NSConcreteGlobalBlock類型的,也就不會出現block銷毀了,在調用的情況了。

image.png

循環引用

  • 調用系統的block不會循環引用,self并不持有該方法。
[UIView animateWithDuration:0.5 animations:^{
        NSLog(@"%@", self);
}];
  • 當self持有block時,block中又調用了self,會循環引用。
@property (nonatomic, copy)MyBlock myBlock;
self.myBlock = ^(){
   self.xxxx;
};

// 不會循環引用
__weak typeof(self)weakSelf = self;
self.myBlock = ^(){
  weakSelf.xxxx;
};
  • 自定義對象中有個block屬性,在調用該對象的block屬性中,調用該對象的其他屬性,會循環引用。
typedef void(^MyBlock)();

@interface MyBlockModel : NSObject
@property (nonatomic, copy) MyBlock myBlock;
@property (nonatomic, copy) NSString *text;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
    MyBlockModel *model = [[MyBlockModel alloc] init];
    model.myBlock = ^(){
        NSLog(@"--->%@",model.text);
    };

   // 不會循環引用
    MyBlockModel *model1 = [[MyBlockModel alloc] init];
    __weak MyBlockModel *weakModel = model1;
    model1.myBlock = ^(){
        NSLog(@"--->%@",weakModel.text);
    };
}
  • 注意:在異步(多線程)環境下。防止self被釋放,應該在block內部用強引用引用該弱引用。
__weak typeof(self)weakSelf = self;
self.myBlock = ^(){
    __strong typeof (weakSelf)strongSelf = weakSelf;
    if(strongSelf){
        strongSelf.xxxx; 
    }
};

參考:
http://www.lxweimin.com/p/51d04b7639f1
http://www.cocoachina.com/ios/20161025/17198.html
http://blog.devtang.com/2013/07/28/a-look-inside-blocks/

小提示:
1.如和把oc代碼轉成c++代碼:
通過終端cd到你要編譯的文件所在目錄,輸入:clang -rewrite-objc xxxx.m即可。(xxxx:你要編譯的文件名稱)。
2.如果出現main.m:9:9: fatal error: 'UIKit/UIKit.h' file not found怎么辦?
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode8.3.3.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxxx.m(xxxx:你要編譯的文件名稱)。

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

推薦閱讀更多精彩內容