Block在開發(fā)中的入門學(xué)習(xí)

雞湯

“你知道一敗涂地有什么好處嗎?”
“那就是,你僅剩一事可做:絕地反擊”

前言

本人作為一個菜鳥干iOS開發(fā)一年左右,除了深感目前該行業(yè)的艱難以外,也在慢慢學(xué)習(xí)當(dāng)中,今天和大家談?wù)勱P(guān)于 我對Block的認(rèn)識,有什么不足的地方,還望大家多多指正

1.Block的認(rèn)識

Block實際上是OC語言對閉包的實現(xiàn),是帶有自動變量值的匿名函數(shù)
Block的作用: 保存一段代碼
閉包:一個函數(shù),或者一個指向函數(shù)的指針,加上這個函數(shù)執(zhí)行的非局部變量。通俗來說就是閉包允許一個函數(shù)訪問聲明該函數(shù)運行上下文中的變量,甚至可以訪問不同運行上文中的變量。

2.Block的基本語法 (聲明 -> 實現(xiàn) -> 調(diào)用)

書寫B(tài)lock的快捷鍵 : inline

  1. 無參數(shù)無返回值
 void(^b1)(void);   //聲明   ^托字符:表示后面的是 block變量
 void(^b1)(void)   //block的類型
//b1是 block名字,也叫 block 變量
 ^(void){ };  //block的實現(xiàn)部分
    void(^b1)(void) = ^(void){  //聲明實現(xiàn)放一起了
        NSLog(@"無參無返回值");
//      block 的實現(xiàn)部分
    };
//調(diào)用 block.
    b1();
  1. 無返回值,有參數(shù)
//定義block 參數(shù)只需要有一個類型,但是實現(xiàn)部分需要有參數(shù)名。
    void(^b2)(NSString *) = ^(NSString *string){
        NSLog(@"%@",[string stringByAppendingString:@"有人說寫字難看的男生長得很帥"]);
    };
    b2(@"照鏡子發(fā)現(xiàn),都TM是騙人的");  //調(diào)用block 
  1. 有參數(shù),有返回值
// 寫一個block實現(xiàn)兩個數(shù)的 乘積,并返回結(jié)果
    NSInteger(^b3)(NSInteger,NSInteger) = ^(NSInteger a,NSInteger b){
        return a * b;
    };
    NSInteger result1 = b3(3,5);
    NSLog(@"%ld",result1);
  1. 有返回值,無參數(shù)
// 寫一個 block實現(xiàn),返回一個[30,50]的隨機(jī)數(shù);
    int (^b5)(void) = ^(){
        int i = arc4random()%21+30;
        return i;
    };
    int i = b5();
    NSLog(@"%d",i);

3.Block在開發(fā)中的應(yīng)用場景

  • 在一個方法中定義,在另一個方法中調(diào)用
  • 在一個類中定義,在另一個類中調(diào)用 (常用)

聲明

//BlockType:類型的別名,這里不是變量名
typedef void(^BlockType)();

@interface ViewController ()
//block怎么聲明,就如何定義成屬性
@property (nonatomic, strong) void(^block)();  //開發(fā)中推薦用這種
//@property (nonatomic, strong) BlockType block; //和上面一樣
@end

實現(xiàn)

void(^block)() = ^() {
        NSLog(@"這里就是Block塊里的內(nèi)容,注意:只有在調(diào)用block的時候這個block塊里的內(nèi)容才會被執(zhí)行");
    };
    _block = block;

調(diào)用

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // block的調(diào)用:就去尋找保存的代碼,直接調(diào)用
    _block();
}

需求:TableView展示3個cell,打電話,發(fā)短信,發(fā)郵件

首先我們創(chuàng)建一個模型Cell item 在.h中代碼如下

#import <Foundation/Foundation.h>
@interface CellItem : NSObject

//設(shè)計模型:控件需要展示什么內(nèi)容,就定義什么屬性
@property (nonatomic, strong) NSString *title;

//保存每個cell做的事情
@property (nonatomic, strong) void(^block)();

+ (instancetype)itemWithTitle:(NSString *)title;
@end

在Cellitem.m中

#import "CellItem.h"
@implementation CellItem

+ (instancetype)itemWithTitle:(NSString *)title{
    CellItem *item = [[self alloc] init];
    item.title = title;   
    return item;
}
@end

在ViewController.m中,我們實現(xiàn)需求

#import "TableViewController.h"
#import "CellItem.h"

@interface TableViewController () <UITableViewDataSource,UITableViewDelegate>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *items;
@end

@implementation TableViewController
/*
 需求:tableView展示3個cell,打電話,發(fā)短信,發(fā)郵件
 */
- (void)viewDidLoad {
    [super viewDidLoad];
    _tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:(UITableViewStylePlain)];
    [self.view addSubview:_tableView];
    _tableView.delegate = self;
    _tableView .dataSource = self;
    
    //創(chuàng)建模型
    CellItem *item1 = [CellItem itemWithTitle:@"打電話"];
    item1.block = ^{
        NSLog(@"打電話");
    };
    CellItem *item2 = [CellItem itemWithTitle:@"發(fā)短信"];
    item2.block = ^(){
        NSLog(@"發(fā)短信");
    };
    CellItem *item3 = [CellItem itemWithTitle:@"發(fā)郵件"];
    item3.block = ^{
        NSLog(@"發(fā)郵件");
    };
    _items = @[item1,item2,item3];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return _items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *cellIdentifier = @"cellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:cellIdentifier];
    }
    CellItem *item = self.items[indexPath.row];
    cell.textLabel.text = item.title;
//    if (indexPath.row == 0) {
//        //打電話
//    }else if (indexPath.row == 1){
//        //發(fā)短信
//    }else if (indexPath.row == 2){
//        //發(fā)郵件
//    }
    return cell;
}

//點擊cell的時候就會調(diào)用
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    
    //把要做的事情(代碼)保存到模型
    CellItem *item = self.items[indexPath.row];
    if (item.block) {
        item.block();  //此處調(diào)用了Block,所以才會走block塊中保存的代碼
    }   
}
@end

接下來我們看一下Block中的逆向傳值

首先我們創(chuàng)建2個控制器, FirstViewController和SecondViewController, 在后者.h中我們實現(xiàn)block的聲明代碼如下

#import <UIKit/UIKit.h>

typedef void(^passValue)(NSString *str,NSURL *url);

@interface SecondViewController : UIViewController
@property (nonatomic, copy) passValue block;  //block作為屬性
@end

在SecondViewController.m中實現(xiàn)布局以及在返回上一頁的方法中進(jìn)行block的調(diào)用

- (void)leftAction:(UIBarButtonItem *)sender{
    [self.navigationController popViewControllerAnimated:YES];
    
    if (self.block) {
        self.block(self.secondTF.text,self.url);  //調(diào)用Block
    }
}

在FirstViewController.m的點擊跳轉(zhuǎn)的方法中實現(xiàn)Block塊的內(nèi)容

- (void)rightAction:(UIBarButtonItem *)sender{
    SecondViewController *secondVC = [SecondViewController new];
    [self.navigationController pushViewController:secondVC animated:YES];
    
    __weak FirstViewController *weakSelf = self;
//這個block只有在second中被調(diào)用時才會執(zhí)行,通過回調(diào),將second中的文本框的內(nèi)容傳遞給first
    secondVC.block = ^(NSString *str,NSURL *url){ //這里面的string就是回調(diào)second頁面中傳遞的參數(shù)
        weakSelf.textField.text = str;
        NSData *data = [NSData dataWithContentsOfURL:url];
        weakSelf.imageV.image = [UIImage imageWithData:data];
    };
}

接下來是Block作為參數(shù)的使用
什么時候需要把block當(dāng)做參數(shù)去使用呢? 做什么事情由外界決定,但是什么時候做由內(nèi)部決定

需求: 封裝一個計算器,提供一個計算方法,怎么計算由外界決定,什么時候計算由我內(nèi)部決定
首先,我們創(chuàng)建一個CacultorManager類,代碼如下:

#import <Foundation/Foundation.h>

@interface CacultorManager : NSObject
@property (nonatomic, assign) NSInteger result;
//計算
- (void)cacultor:(NSInteger(^)(NSInteger result))cacultorBlock;
@end
#import "CacultorManager.h"

@implementation CacultorManager

- (void)cacultor:(NSInteger (^)(NSInteger))cacultorBlock{
    if (cacultorBlock) {
        _result = cacultorBlock(_result);   
// 作為方法中的參數(shù),這里開始調(diào)用 參數(shù)cacultorBlock, 也就是由方法內(nèi)部來決定的
    }
}
@end

在ViewController.m中,我們實現(xiàn)如下代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
/**
 * 怎么區(qū)分參數(shù)是blcok,就看有沒有^,只要有^,把block當(dāng)做參數(shù)就行
 * 注意:把block當(dāng)做參數(shù),并不是馬上就去調(diào)用Block,什么時候調(diào)用,由方法內(nèi)部決定
 */
    //創(chuàng)建計算器管理者
    CacultorManager *mgr = [[CacultorManager alloc] init];

//調(diào)用了方法,block是參數(shù),不管block內(nèi)有多少東西,都只作為參數(shù)進(jìn)行了傳遞,并不一定會執(zhí)行。調(diào)用block才會執(zhí)行    
    [mgr cacultor:^(NSInteger result) { 
        result += 5;
        result += 6;
        result *= 2;
        return result;
//上面的計算器類調(diào)用了方法就走方法里面的東西,方法里面又調(diào)用block塊,就返回到這里走block塊中的內(nèi)容,也就是說具體執(zhí)行不執(zhí)行block塊中的內(nèi)容要看調(diào)沒調(diào)用block
    }];
    NSLog(@"%ld",mgr.result);
}

接下來我們再看一下Block作為方法返回值的用法
這里要提到關(guān)于第三方框架Masonry的鏈?zhǔn)骄幊趟枷?即把所有的語句用 點語法 連接起來,這樣優(yōu)點就是可讀性非常高,其核心就是返回值是一個block

- (void(^)())test{       //block 作為返回值
    NSLog(@"%s",__func__);
    return ^{
        NSLog(@"調(diào)用了blcok");
    };
}
----------------------- 分割線 ----------------------
- (void)viewDidLoad {
    [super viewDidLoad];
    self.test(); //這就是為什么masonry為什么能寫出這樣的效果,因為返回值是一個blcok
   
   //我們做個比較
    void(^block)() = ^{
        NSLog(@"調(diào)用了block");
    };
    block(); //這個和上面的 self.test()一樣
}

需求: 封裝一個計算器,提供一個加號方法
同樣的,我們創(chuàng)建一個CalculatorManager類, 在這個類中我們先實現(xiàn)普通的方法,然后和作為返回值的block做一個比較

#import <UIKit/UIKit.h>

@interface CalculatorManager : UIViewController

@property (nonatomic, assign) int result;

- (CalculatorManager *)add:(int)value;   //普通方法

- (CalculatorManager * (^)(int))add;  // block作為返回值方法

@end
#import "CalculatorManager.h"

@interface CalculatorManager ()

@end

@implementation CalculatorManager
- (CalculatorManager *)add:(int)value{
    _result += value;
    return self;
}

- (CalculatorManager * (^)(int))add{
    return ^(int value){
        _result += value;
        return self;
    };
}
@end

我們在ViewController.m中使用這個類,看看有什么不同點

- (void)viewDidLoad {
    [super viewDidLoad];
    CalculatorManager *mgr = [[CalculatorManager alloc] init];
       [mgr add:5];
       [mgr add:5];
       [mgr add:5]; //如果這樣的話,不太好,會有很多行
       NSLog(@"%d",mgr.result);
    
      mgr.add(5);
      mgr.add(5).add(6).add(7).add(8).add(9).add(10);
      NSLog(@"%d",mgr.result);
}

以上就是一些block的基礎(chǔ)使用,接下來我們來看一下使用block過程中的一些注意點

Block的內(nèi)存管理

在蘋果的api文檔中,block是一個對象,所以存在著內(nèi)存管理的方式(MRC/ARC)

在MRC中:

  • 只要block沒有引用外部的局部變量,block放在全局區(qū)
  • 只要block引用外部局部變量,block放在里面
  • block只能使用copy,不能使用retain,如果使用retain,block還是在棧里面(既然是在棧里面,代碼塊一過,block就會銷毀)

在ARC中:

  • 只要block沒有引用外部的局部變量,block放在全局區(qū)
  • 只要block引用外部局部變量,block放在里面
  • blcok使用Strong,最好不要使用copy

Block的循環(huán)引用:

  • Block造成循環(huán)引用:Block會對里面所有強(qiáng)指針變量(外部對象變量)全部強(qiáng)引用一次 所以加 WeakSelf。
  • 如果是局部變量,block是值傳遞
  • 如果是靜態(tài)變量,全局變量,__block修飾的變量,block是指針傳遞

總結(jié):

  1. 不能再block中直接修改局部變量,如有需要添加一個** __block** 來修飾,block可以訪問全局變量。
  2. 在block作為函數(shù)參數(shù)的時候,使用block的時候引用計數(shù)會自動+1,所以我們用__weak 去替換原有的 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ā)布平臺,僅提供信息存儲服務(wù)。

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

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