block入門和簡單使用(定義,做參數(shù),做返回值,內(nèi)存管理,循環(huán)引用).

本文略為全面的介紹block的使用:
block定義方式,
block傳值,
block循環(huán)引用,
block內(nèi)存管理,
block做參數(shù),
block做返回值(實現(xiàn)鏈式編程)等等,
此篇在手,block我有!
Action!

  • block的三種定義方式以及block類型

1.沒有返回值,沒有參數(shù)的定義方式

//返回值類型(^block的名字)(參數(shù)類型) = ^(參數(shù)類型和參數(shù)名) {};

    void(^block)() = ^(){
        NSLog(@"調(diào)用了block");
    };
//當然,沒有參數(shù)的時候可以把括號省去
    void(^block)() = ^{
        NSLog(@"調(diào)用了block");
    };

2.有返回值,有參數(shù)的定義方式

//返回值類型(^block的名字)(參數(shù)類型) = ^(參數(shù)類型和參數(shù)名) {};
//如果有參數(shù),定義的時候,必須要寫參數(shù),而且必須要有參數(shù)變量名
    int(^block)(int) = ^(int a){
        return 1;
    };

3.定義時帶有返回類型的,(不常用)

    int(^block)() = ^int{//這里的int就是這個block的返回值類型
        return 1;
    };

4.系統(tǒng)提供了一個定義block的宏

// block快捷方式   輸入:inline
//    returnType(^blockName)(parameterTypes) = ^(parameters) {
//        statements
//    };

5.block的調(diào)用

//定義block
    void(^block)() = ^{
        NSLog(@"調(diào)用了block");
    };
//調(diào)用block
    block();

6.block的類型

//block有自己的類型,就想@"string"是NSString類型一樣
//格式就是 返回值(^)(參數(shù)類型)
//比如這個block的類型就是: int(^)(int)
    int(^block)(int) = ^(int a){
        return 1;
    };
//這個block的類型就是void(^)()
    void(^block)() = ^{
        NSLog(@"調(diào)用了block");
    }; 
//在ARC中把block定義成屬性要用strong類型,定義方式如下:
@property (nonatomic, strong) void(^block)();//這樣在類中可以拿到self.block
//當然也可以取別名:
typedef void(^BlockType)();//BlockType不是變量名,而是這種類型的block的別名
//然后就可以這樣
@property (nonatomic, strong) BlockType block;
  • 使用block保存代碼

這種方式我在app的"用戶設(shè)置"界面中使用到,需求大約是這樣:

用戶設(shè)置界面每一行cell會做不同的事,有的是跳轉(zhuǎn)界面,有的是switch開關(guān),有的是需要顯示一下AlertView.
這樣的話,我們可以把需要執(zhí)行的代碼包裝成block,把block放在cell的模型里面,當點擊cell的時候,拿出模型中的block來執(zhí)行,感覺還不錯.

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

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

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
    if (item.option != nil) {//判斷block是否為空 不為空就執(zhí)行
        item.option();
    }else if ([item isKindOfClass:[SettingArrowItem class]]) {//block為空 執(zhí)行其他操作(比如界面跳轉(zhuǎn)):
        SettingArrowItem *newItem = (SettingArrowItem *)item;
        UIViewController *vc = [[newItem.destVc alloc]init];
        vc.title = item.title;
        [self.navigationController pushViewController:vc animated:YES];
    }
}
  • 使用block進行傳值

在學習block之前,一直在使用代理傳值,但是用了block以后,再也不想用代理了.
下面介紹block傳值(逆?zhèn)?簡單使用
需求如下:
在ViewControllerOne跳到ViewControllerTwo
ViewControllerTwo拿到數(shù)據(jù)后dismiss
在ViewControllerOne打印數(shù)據(jù)

//ViewControllerOne.m:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//點擊控制器View的時候調(diào)用
    ModalViewController *modalVc = [[ModalViewController alloc] init];
    modalVc.block = ^(NSString *value) {
          NSLog(@"%@",value);
    };//給他block賦值
    [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
{
    // 傳值給ViewControllerOne
    if (_block) {
        _block(@"123");
    }
[self dismissViewControllerAnimated:YES completion:nil];
}
  • block變量的傳遞

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

//block為值傳遞只有一種情況:
    int a = 3;
    void(^block)() = ^{
        NSLog(@"%d",a);
    };
    a = 5;
    block();//這里調(diào)用block打印出的是3,是值傳遞

結(jié)論:// 如果block訪問的變量是局部變量,那么變量是值傳遞
2.指針傳遞

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

結(jié)論:如果是靜態(tài)變量,那么變量是指針傳遞
3.經(jīng)過其他測試總結(jié):
(1)如果是局部變量,Block是值傳遞
(2)如果是靜態(tài)變量,全局變量,__block修飾的變量,block都是指針傳遞

  • block做參數(shù)

第一次見到block當做參數(shù)是在AFN框架中,AFN幫你拿到數(shù)據(jù)以后,執(zhí)行你傳給他的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);//拿到數(shù)據(jù)后執(zhí)行你傳入的block
                }
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    failure(error);//拿到數(shù)據(jù)后執(zhí)行你傳入的block
            }];
        }
}

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

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

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

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

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

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

下面簡單介紹一下調(diào)用原理:
mgr.add相當于get方法的調(diào)用: [mgr add];
mgr.add返回的是一個block,所以你可以給他一個參數(shù)5,于是寫成這樣:mgr.add(5)
然后block返回的又是CalculatorManager,所以繼續(xù)調(diào)用add
如此循環(huán)下去.......
點這里是我用 block 做返回值寫的一個屏幕適配小工具

  • block內(nèi)存管理

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

1.MRC:

block存放位置:

    int a = 3;//這是一個局部變量
    void(^block)() = ^{
        NSLog(@"調(diào)用block%d",a);
    };
    NSLog(@"%@",block);
//打印結(jié)果:<__NSStackBlock__: 0x7fc498746000>
//此時引用了外部局部變量,block放在棧里面
static int b = 2;
-(void)viewDidLoad {
    [super viewDidLoad];
    void(^block)() = ^{
        NSLog(@"調(diào)用block%d",b);
    };
    NSLog(@"%@",block);
}
//打印結(jié)果:<__NSGloBalBlock__: 0x7fc498746000>
//此時引用了全局變量,block放在全局區(qū)

MRC:管理block總結(jié):
只要block沒有引用外部局部變量,block放在全局區(qū)
只要Block引用外部局部變量,block放在棧里面.
定義屬性時:
@property (nonatomic, copy) void(^block)();
block只能使用copy,不能使用retain,
因為使用retain,block還是在棧里面 代碼塊過了方block就銷毀了,再次訪問self.block會出現(xiàn)壞內(nèi)存訪問
使用copy是放在堆里面,代碼塊過了不會銷毀

2.ARC:

block存放位置:

    int a = 3;//這還是一個局部變量
    void(^block)() = ^{
        NSLog(@"調(diào)用block%d",a);
    };
    NSLog(@"%@",block);
//打印結(jié)果:<__NSMallocBlock__: 0x7fc498746000>
//此時引用了外部局部變量,block放在堆里面
static int b = 2;
-(void)viewDidLoad {
    [super viewDidLoad];
    void(^block)() = ^{
        NSLog(@"調(diào)用block%d",b);
    };
    NSLog(@"%@",block);
}
//打印結(jié)果:<__NSGloBalBlock__: 0x7fc498746000>
//此時引用了全局變量,block放在全局區(qū)

ARC:管理block總結(jié):
只要block沒有引用外部局部變量,block放在全局區(qū)
只要block引用外部局部變量,block放在堆里面.
定義屬性時:
@property (nonatomic, strong) void(^block)();
block只能使用strong,不要使用copy
因為當使用copy的時候,set方法是調(diào)用了copy幫你深拷貝一次,沒有這個必要.
就像NSString一樣,他一般都是@"a"這種常量,沒必要再去深拷貝一次,所以NSString常量也用strong不用copy.

  • block循環(huán)引用

1. block的循環(huán)引用問題

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

__weak typeof(self) weakSelf = self;

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

2.接下來是雙層block的循環(huán)引用問題:

先來看這樣一個例子:

    __weak typeof(self) weakSelf = self;
    block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延時2秒執(zhí)行下面的代碼
             NSLog(@"%@",weakSelf);
        });
    };
    block();

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

    __weak typeof(self) weakSelf = self;
    block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;//這個是局部變量 棧內(nèi)的強指針 當這個block執(zhí)行完畢  這個指針就會釋放  
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延時2秒執(zhí)行下面的代碼
             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強引用.
但是你會問,那這么不會造成循環(huán)引用嗎?不著急,繼續(xù)往下看:
當延時2秒到了,block可以訪問到strongSelf
當延時block代碼塊過了,strongSelf就會指向nil了(因為strongSelf是局部變量,存在棧內(nèi)的強指針 當這個block執(zhí)行完畢,這個指針就會釋放)
此時就沒有人對self進行強引用了,self也會銷毀,
至此,大家都銷毀了..

感謝閱讀
你的支持是我寫作的唯一動力

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容