筆記:iOS Block的基本使用

目錄

  1. Block概述
  2. Block定義方式
  3. Block保存代碼
  4. Block傳值
  5. Block對外部變量的傳遞
  6. Block做參數
  7. Block做返回值(實現鏈式編程)
  8. Block內存管理
  9. Block循環引用
  10. Block其他注意點

1. Block概述

Block是C語言級別和運行時方面的一個特征。Block封裝了一段代碼邏輯,用{}括起,和標準C語言中的函數/函數指針很相似。

2. Block定義方式

  • 聲明:(返回類型)(^聲明的Block名稱)(參數列表);
  • 實現:^(返回類型)(參數列表){代碼塊}
  • 返回類型(^block變量名)(參數列表) = ^(形參列表) {};
  void(^MyBlock)(int a,int b) = ^(int a, int b){}
返回類型:void
Block名字:MyBlock
實際參數: int a ,int b
形式參數: int a ,int b
  • Block三種定義方式以及block類型
  1. 沒有返回值,沒有參數的定義方式
//返回值類型(^block的名字)(參數類型) = ^(參數類型和參數名) {};
  void(^block)() = ^(){
      NSLog(@"調用了block");
  };
//當然,沒有參數的時候可以把括號省去
  void(^block)() = ^{
      NSLog(@"調用了block");
  };
  1. 有返回值,有參數的定義方式
//返回值類型(^block的名字)(參數類型) = ^(參數類型和參數名) {};
//如果有參數,定義的時候,必須要寫參數,而且必須要有參數變量名
  int(^block)(int) = ^(int a){
      return 1;
  };
  1. 定義時帶有返回類型的(不常用)
  int(^block)() = ^int{//這里的int就是這個block的返回值類型
      return 1;
  };
  1. 系統提供了一個定義block的宏
// block快捷方式   輸入:inline
      returnType(^blockName)(parameterTypes) = ^(parameters) {       
                  statements
  };
  1. block的調用
//定義block
  void(^block)() = ^{
      NSLog(@"調用了block");
  };
//調用block
  block();
  1. block的類型
//block有自己的類型,就想@"string"是NSString類型一樣
//格式就是 返回值(^)(參數類型)
//比如這個block的類型就是: int(^)(int)
  int(^block)(int) = ^(int a){
      return 1;
  };
//這個block的類型就是void(^)()
  void(^block)() = ^{
      NSLog(@"調用了block");
  }; 
//在ARC中把block定義成屬性要用strong類型,定義方式如下:
@property (nonatomic, strong) void(^block)();//這樣在類中可以拿到self.block
//當然也可以取別名:
typedef void(^BlockType)();//BlockType不是變量名,而是這種類型的block的別名
//然后就可以這樣
@property (nonatomic, strong) BlockType block;

3. Block保存代碼

這種方式在app的設置界面中使用到,需求大約是這樣:
設置界面每一行cell會做不同的事,有的是跳轉界面,有的是switch開關,有的是需要顯示一下AlertView.
這樣的話,我們可以把需要執行的代碼包裝成block,放在cell的模型里面,當點擊cell的時候,拿出模型中的block來執行。

//這是cell的模型
typedef void(^optionBlock)();
@interface SettingItem : NSObject
@property(nonatomic,copy)NSString *icon;//圖標
@property(nonatomic,copy)NSString *title;//文字
@property(nonatomic,assign) Class destVc;//需要跳轉的界面
@property(nonatomic,copy) optionBlock option;//需要執行的代碼
@end

然后我們可以在didSelectRowAtIndexPath里面這么做:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
  if (item.option != nil) {//block存在就執行
      item.option();
  }else if ([item isKindOfClass:[SettingArrowItem class]]) {//執行跳轉界面
      SettingArrowItem *newItem = (SettingArrowItem *)item;
      UIViewController *vc = [[newItem.destVc alloc]init];
      vc.title = item.title;
      [self.navigationController pushViewController:vc animated:YES];
  }
}

4. Block進行傳值

需求如下:
在ViewControllerOne跳到ViewControllerTwo
ViewControllerTwo拿到數據后dismiss
在ViewControllerOne打印數據

//ViewControllerOne.m:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//點擊控制器View的時候調用
  ModalViewController *modalVc = [[ModalViewController alloc] init];
  //對Block屬性進行賦值
  modalVc.block = ^(NSString *value) {
        NSLog(@"%@",value);
  };
  [self presentViewController:modalVc animated:YES completion:nil];
}
//ViewControllerTwo.h:
@interface ModalViewController : UIViewController
@property (nonatomic, strong) void(^block)(NSString *value);
@end
//ViewControllerTwo.m:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
  // 調用Block 傳值給ViewControllerOne
  if (_block) {
      _block(@"123");
  }
  [self dismissViewControllerAnimated:YES completion:nil];
}

5. Block對外部變量的傳遞

一個很簡單的問題:block使用外部變量,是值傳遞,還是指針傳遞。

  1. 值傳遞
//block為值傳遞只有一種情況:
  int a = 3;
  void(^block)() = ^{
      NSLog(@"%d",a);
  };
  a = 5;
  block();//這里調用block打印出的是3,是值傳遞
*注意:因為block中使用的外面變量的值是拷貝過來的即值拷貝,所以在調用之前修改外界變量的值,不會影響到block中拷貝的值.

結論:如果block訪問的變量是局部變量,那么變量是值傳遞.

  1. 指針傳遞
static int a = 3;
  void(^block)() = ^{
      NSLog(@"%d",a);
  };
  a = 5;
  block();//這里調用block打印出的是5,是指針傳遞
//另外全局變量,靜態變量,__block修飾的變量都是指針傳遞

結論:如果是全局變量或者靜態變量,那么變量是指針傳遞。

  1. 經過其他測試總結:
    (1)如果是局部變量,Block是值傳遞
    (2)如果是靜態變量,全局變量,__block修飾的變量,block都是指針傳遞

6. Block做參數

第一次見到block當做參數是在AFN框架中,AFN幫你拿到數據以后,執行你傳給他的block:

//這一個個對AFN進行簡單封裝的方法:
+(void)requestWihtMethod:(RequestMethodType)methodType
                 success:(void (^)(id response))success  //是一個block
                 failure:(void (^)(NSError* err))failure //是一個block
{
     AFHTTPSessionManager *manage = [AFHTTPSessionManager manager];
     [manage GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
          } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
              if (success) {
                  success(responseObject);//拿到數據后執行你傳入的block
              }
          } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                  failure(error);//拿到數據后執行你傳入的block
          }];
      }
}

接下來我們自定義一個計算器,在block里面自定義計算方式,將block傳入計算器來進行計算:

//CalculatorManager.h:
@interface CacultorManager : NSObject
@property (nonatomic, assign) NSInteger result;//要計算的數據
-(void)calculatorWithMethod:(NSInteger(^)(NSInteger // 計算方法parameterresult))methodBlock;
@end
//CalculatorManager.m:
-(void)cacultor:(NSInteger (^)())cacultorBlock
{
  if (cacultorBlock) {//判斷block是否為空
    _result =  cacultorBlock(_result);//執行block中的計算方法
  }
}
//ViewController.m:使用計算器
-(void)viewDidLoad {
  [super viewDidLoad];
  // 創建計算器管理者
  CalculatorManager *mgr = [[CalculatorManager alloc] init];
  [mgr calculator:^(NSInteger result){//自定義計算方法block,作為參數傳進去
      result += 5;
      result += 6;
      result *= 2;
      return result;
  }];
  NSLog(@"%ld",mgr.result);

7. Block做方法返回值(鏈式編程)

我們平時寫一些工具類的方法的時候(比如計算器)

//注:result是CalculatorManager的屬性,用來保存計算結果
//如果計算方法這么寫
-(int)add:(int)value{
  _result += value;
  return result;
}
//就要這么調用:
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[mgr add:[mgr add:[mgr add:[mgr add:[mgr add:[mgr add:5]]]]]];//返回值是數字,要繼續用mar調用add來執行操作
//如果計算方法這么寫:
-(CalculatorManager *)add:(int)value
{
  _result += value;
  return self;
}
//就要這么調用:
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[[[[[mgr add:5] add:5] add:5] add:6] add:7];//返回值是計算器本身,可以繼續調用add方法

現在我們要用返回值是block的方法來實現鏈式編程:

//計算方法這么寫,返回值是 返回值為CalculatorManager的block.
-(CalculatorManager *(^)(int))add//相當于一個get方法
{
  return ^(int value){
      _result += value;
      return self;
  };
}
//就可以這么調用
  CalculatorManager *mgr = [[CalculatorManager alloc] init];
  mgr.add(5).add(5).add(5).add(5);

下面簡單介紹一下調用原理:
mgr.add相當于get方法的調用: [mgr add];
mgr.add返回的是一個block,所以你可以給他一個參數5,于是寫成這樣:mgr.add(5)
然后block返回的又是CalculatorManager,所以繼續調用add
如此循環下去.......

8. Block內存管理

首先,在oc中block是一個對象,只有對象才涉及到內存管理
block的內存管理在MRC和ARC中有不同的地方,接下來將分別介紹

1.MRC:
Block存放位置:

  int a = 3;//這是一個局部變量
  void(^block)() = ^{
      NSLog(@"調用block%d",a);
  };
  NSLog(@"%@",block);
//打印結果:<__NSStackBlock__: 0x7fc498746000>
//此時引用了外部局部變量,block放在棧里面
static int b = 2;
-(void)viewDidLoad {
  [super viewDidLoad];
  void(^block)() = ^{
      NSLog(@"調用block%d",b);
  };
  NSLog(@"%@",block);
}
//打印結果:<__NSGloBalBlock__: 0x7fc498746000>
//此時引用了全局變量,block放在全局區
定義屬性時:
@property (nonatomic, copy) void(^block)();
block只能使用copy,不能使用retain,
因為使用retain,block還是在棧里面 代碼塊過了方block就銷毀了,再次訪問self.block會出現壞內存訪問
使用copy是放在堆里面,代碼塊過了不會銷毀

MRC管理Block總結:
只要Block沒有引用外部局部變量,Block放在全局區
只要Block引用外部局部變量,Block放在棧里面.

2.ARC:
Block存放位置:

  int a = 3;//這還是一個局部變量
  void(^block)() = ^{
      NSLog(@"調用block%d",a);
  };
  NSLog(@"%@",block);
//打印結果:<__NSMallocBlock__: 0x7fc498746000>
//此時引用了外部局部變量,block放在堆里面
static int b = 2;
-(void)viewDidLoad {
  [super viewDidLoad];
  void(^block)() = ^{
      NSLog(@"調用block%d",b);
  };
  NSLog(@"%@",block);
}
//打印結果:<__NSGloBalBlock__: 0x7fc498746000>
//此時引用了全局變量,block放在全局區
定義屬性時:
@property (nonatomic, strong) void(^block)();
block只能使用strong,不要使用copy
因為當使用copy的時候,set方法是調用了copy幫你深拷貝一次,沒有這個必要.
就像NSString一樣,他一般都是@"a"這種常量,沒必要再去深拷貝一次,所以NSString常量也用strong不用copy.

ARC管理Block總結:
只要Block沒有引用外部局部變量,Block放在全局區
只要Block引用外部局部變量,Block放在堆里面.
block循環引用

9. Block的循環引用問題

  1. 為什么會產生循環引用:
    因為block會給內部的強指針對象進行一次強引用,比如常見的傳入block中的self進行強引用
    并且在self中,block又是strong的,self對block是強引用
    所以,你強引用我,我強引用你,誰也不會被釋放,就造成了循環引用
    所以,為了避免循環引用,我們要在block使用self之前,進行這一步操作:
__weak typeof(self) weakSelf = self;

在block中使用weakSelf,就不會產生循環引用問題了.
使用了__weak修飾符的對象,作用等同于定義為weak的property。自然不會導致循環引用問題.

  1. 接下來是雙層block的循環引用問題:
    先來看這樣一個例子:
  __weak typeof(self) weakSelf = self;
  block = ^{
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延時2秒執行下面的代碼
           NSLog(@"%@",weakSelf);
      });
  };
  block();

這樣執行下去,如果在延時時間2秒還沒到,控制器就dismiss或者pop(總之是銷毀)了,打印出的weakSelf是null,
為什么呢?
因為只有push self或者present self的控制器對self強引用,當self dismiss了或者pop了,就沒有人對self強引用了(block對self沒有強引用),根據ARC的內存管理原則,當沒有人對一個對象強引用的時候,該對象就會銷毀.
所以,當self dismiss了或者pop了,self就銷毀了,2秒后block再訪問self的時候,self已經不再了.
這時,我們要做如下處理:

  __weak typeof(self) weakSelf = self;
  block = ^{
      __strong typeof(weakSelf) strongSelf = weakSelf;//這個是局部變量 棧內的強指針 當這個block執行完畢  這個指針就會釋放  
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延時2秒執行下面的代碼
           NSLog(@"%@",strongSelf);
      });
  };
  block();

加上這一句:strong typeof(weakSelf) strongSelf = weakSelf; strong 就相當于定義為strong的property
那么當self銷毀的之前(pop或者dismiss之前),有兩個人對self強引用(一個是push self或者present self的控制器,一個是這個block中定義的strongSelf).
當控制器銷毀的時候(pop或者dismiss時),push self或者present self的控制器不在強引用self,self失去一個強引用,但是self不會銷毀,因為block中定義的strongSelf還在對self強引用.
但是你會問,那這么不會造成循環引用嗎?不著急,繼續往下看:
當延時2秒到了,block可以訪問到strongSelf
當延時block代碼塊過了,strongSelf就會指向nil了(因為strongSelf是局部變量,存在棧內的強指針 當這個block執行完畢,這個指針就會釋放)
此時就沒有人對self進行強引用了,self也會銷毀,
至此,大家都銷毀了..

10. Block其他注意點

  • block中可以定義和外界同名的變量,并且如果在block中定義了和外界同名的變量,在block中訪問的是block內部的變量。可以通過打印變量的地址看出兩個變量地址不同。

  • 因為block中使用的外面變量的值是拷貝過來的即值拷貝,所以在調用之前修改外界變量的值,不會影響到block中拷貝的值

  • 默認情況下,在block內部不能改變外面變量的值,如果想在block中修改外界變量的值,必須在外界變量前面加上__block。如果在block中修改了外界變量的值,會影響到外界變量的值。

  • 如果block中訪問到了外界的變量,block會將外界變量拷貝一份到堆內存中。

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

推薦閱讀更多精彩內容