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

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

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

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

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

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

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

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

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

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

4.系統(tǒng)提供了一個(gè)定義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ù)類型)
//比如這個(gè)block的類型就是: int(^)(int)
    int(^block)(int) = ^(int a){
        return 1;
    };
//這個(gè)block的類型就是void(^)()
    void(^block)() = ^{
        NSLog(@"調(diào)用了block");
    }; 
//在ARC中把block定義成屬性要用strong類型,定義方式如下:
@property (nonatomic, strong) void(^block)();//這樣在類中可以拿到self.block
//當(dāng)然也可以取別名:
typedef void(^BlockType)();//BlockType不是變量名,而是這種類型的block的別名
//然后就可以這樣
@property (nonatomic, strong) BlockType block;
  • 使用block保存代碼

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

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

//這是cell的模型
typedef void(^optionBlock)();
@interface SettingItem : NSObject
@property(nonatomic,copy)NSString *icon;//圖標(biāo)
@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進(jìn)行傳值

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

//ViewControllerOne.m:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//點(diǎn)擊控制器View的時(shí)候調(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變量的傳遞

一個(gè)很簡(jiǎn)單的問(wèn)題:block使用外部變量,是值傳遞,還是指針傳遞
1.值傳遞

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

結(jié)論:// 如果block訪問(wèn)的變量是局部變量,那么變量是值傳遞
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)過(guò)其他測(cè)試總結(jié):
(1)如果是局部變量,Block是值傳遞
(2)如果是靜態(tài)變量,全局變量,__block修飾的變量,block都是指針傳遞

  • block做參數(shù)

第一次見到block當(dāng)做參數(shù)是在AFN框架中,AFN幫你拿到數(shù)據(jù)以后,執(zhí)行你傳給他的block:

//這一個(gè)個(gè)對(duì)AFN進(jìn)行簡(jiǎn)單封裝的方法:
+(void)requestWihtMethod:(RequestMethodType)methodType
                     success:(void (^)(id response))success//是一個(gè)block
                     failure:(void (^)(NSError* err))failure//是一個(gè)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
            }];
        }
}

接下來(lái)我們自定義一個(gè)計(jì)算器,在block里面自定義計(jì)算方式,將block傳入計(jì)算器來(lái)進(jìn)行計(jì)算:

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

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

//注:result是CalculatorManager的屬性,用來(lái)保存計(jì)算結(jié)果
//如果計(jì)算方法這么寫
-(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來(lái)執(zhí)行操作
//如果計(jì)算方法這么寫:
-(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];//返回值是計(jì)算器本身,可以繼續(xù)調(diào)用add方法

現(xiàn)在我們要用返回值是block的方法來(lái)實(shí)現(xiàn)鏈?zhǔn)骄幊?

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

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

  • block內(nèi)存管理

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

1.MRC:

block存放位置:

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

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

2.ARC:

block存放位置:

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

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

  • block循環(huán)引用

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

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

__weak typeof(self) weakSelf = self;

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

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

先來(lái)看這樣一個(gè)例子:

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

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

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

加上這一句:__strong typeof(weakSelf) strongSelf = weakSelf;
__strong 就相當(dāng)于定義為strong的property
那么當(dāng)self銷毀的之前(pop或者dismiss之前),有兩個(gè)人對(duì)self強(qiáng)引用(一個(gè)是push self或者present self的控制器,一個(gè)是這個(gè)block中定義的strongSelf).
當(dāng)控制器銷毀的時(shí)候(pop或者dismiss時(shí)),push self或者present self的控制器不在強(qiáng)引用self,self失去一個(gè)強(qiáng)引用,但是self不會(huì)銷毀,因?yàn)閎lock中定義的strongSelf還在對(duì)self強(qiáng)引用.
但是你會(huì)問(wèn),那這么不會(huì)造成循環(huán)引用嗎?不著急,繼續(xù)往下看:
當(dāng)延時(shí)2秒到了,block可以訪問(wèn)到strongSelf
當(dāng)延時(shí)block代碼塊過(guò)了,strongSelf就會(huì)指向nil了(因?yàn)閟trongSelf是局部變量,存在棧內(nèi)的強(qiáng)指針 當(dāng)這個(gè)block執(zhí)行完畢,這個(gè)指針就會(huì)釋放)
此時(shí)就沒(méi)有人對(duì)self進(jìn)行強(qiáng)引用了,self也會(huì)銷毀,
至此,大家都銷毀了..

感謝閱讀
你的支持是我寫作的唯一動(dòng)力

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

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

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