Block篇-block剖析

導(dǎo)語(yǔ)

block是一些簡(jiǎn)短代碼片段的封裝.本質(zhì)是函數(shù)指針. 雖然block聲明的時(shí)候不帶星,而且默認(rèn)情況下存放在棧里,但是,他確實(shí)是OC對(duì)象,往下看會(huì)有示例說(shuō)明

block類(lèi)型
  • _NSConcreteGlobalBlock 全局的靜態(tài) block,不會(huì)訪問(wèn)任何外部變量
  • _NSConcreteStackBlock 保存在棧中的 block,當(dāng)函數(shù)返回時(shí)會(huì)被銷(xiāo)毀。但在ARC下,會(huì)被復(fù)制到堆上.
  • _NSConcreteMallocBlock 保存在堆中的 block,當(dāng)引用計(jì)數(shù)為 0 時(shí)會(huì)被銷(xiāo)毀。 所以在ARC下,只有_NSConcreteGlobalBlock和_NSConcreteMallocBlock這兩種類(lèi)型

寫(xiě)個(gè)例子來(lái)測(cè)試一下block類(lèi)型

/** 在.h中定義和.m中定義對(duì)結(jié)果沒(méi)有影響,已驗(yàn)證完畢*/
//.h中定義類(lèi)型,聲明屬性htestBlock 
typedef NSString *(^testBlock)(NSString *); 
@property (nonatomic ,copy)testBlock htestBlock; 
//.m中聲明屬性mtestBlock及變量_ctestBlock 
@property (nonatomic ,copy)testBlock mtestBlock; @implementation ViewController 
{ 
    testBlock _ctestBlock; 
} 
//下面進(jìn)行測(cè)試 
self.htestBlock = ^(NSString *str) 
{ 
    return str; 
};
self.mtestBlock = ^(NSString *str) 
{ 
    return str;
 };
_ctestBlock = ^(NSString *str) 
{ 
    return str; 
}; 
NSLog(@"%@",self.htestBlock(@"h")); 
NSLog(@"%@",self.mtestBlock(@"m"));
NSLog(@"%@",_ctestBlock(@"c")); 

VC被銷(xiāo)毀.png
](http://upload-images.jianshu.io/upload_images/1602974-b4f8e1d1d0f64b63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

結(jié)果如上圖,你會(huì)發(fā)現(xiàn),全部都是GlobalBlock額,但傳聞GlobalBlock不會(huì)訪問(wèn)任何外部變量,所以接著測(cè)試

    NSString *name = @"daqianqian";
    self.htestBlock = ^(NSString *str)
    {
        return name;
    };
    self.mtestBlock = ^(NSString *str)
    {
        return name;
    };
    _ctestBlock = ^(NSString *str)
    {
        return name;
    };
    NSLog(@"%@",self.htestBlock(@"h"));
    NSLog(@"%@",self.mtestBlock(@"m"));
    NSLog(@"%@",_ctestBlock(@"c"));
1EB11A80-21E7-4544-AF8D-2DD7270FCDD0.png

這下全部變成MallocBlock了

根據(jù)上面的結(jié)果,個(gè)人猜測(cè),block聲明后,就是全局的靜態(tài)block,因?yàn)楝F(xiàn)在使用的是ARC,所以當(dāng)你訪問(wèn)外部變量,會(huì)自動(dòng)copy一份到堆上.
若理解不對(duì)請(qǐng)大家指正,感謝!

__block

再做個(gè)小測(cè)試

//聲明block及屬性myBlock
typedef NSString * (^Block) (int);
@property(nonatomic, copy)Block myBlock;
40294132-AAB3-41DD-B571-E0F2001E1DDA.png

你想在在內(nèi)部更改外部變量,你會(huì)發(fā)現(xiàn),無(wú)法更改!
因?yàn)閎lock如果要訪問(wèn)外部變量,他會(huì)拷貝進(jìn)來(lái)一份外部變量,并且這個(gè)外部變量是只讀的
外部變量改變也并不影響block內(nèi)部拷貝的那一份變量
請(qǐng)接著看下面的例子

FC2CF82E-CCE9-4EDC-B3AE-E2B30147D291.png

這個(gè)輸出結(jié)果如下,驗(yàn)證了上面的判斷


199AFC83-591B-4366-8C4F-29B077F3AC3A.png

如果不想讓block拷貝變量,而是想讓內(nèi)部使用的變量和外部使用的變量指向同一地址的話,需要在變量前面加上__block關(guān)鍵字,則外部變量不再是只讀的,在block內(nèi)部也可以改變它的值

![6D53A005-84F6-4A1E-B7B1-E2965AA15305.png](http://upload-images.jianshu.io/upload_images/1602974-dea636bc30dc88c3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
定義和使用block
  • 獨(dú)立block,作為類(lèi)的變量或?qū)傩允褂?br> 1- 實(shí)現(xiàn)界面反向傳值
    2- 可以實(shí)現(xiàn)界面A做完某一操作后,界面B立即實(shí)現(xiàn)其他操作
  • 內(nèi)聯(lián)block,作為方法的參數(shù)使用
    1-配合disptch_queue,可以方便的實(shí)現(xiàn)簡(jiǎn)單的多線程編程和異步編程

Demo1 - 使用獨(dú)立block,實(shí)現(xiàn)界面反向傳值
獨(dú)立block格式:
typedef 返回值類(lèi)型 (^block名稱)(參數(shù)列表)

/** 在界面2聲明block,*/
//自定義格式
typedef void(^CCBlock)(NSString *);
//聲明屬性
@property (nonatomic, copy)CCBlock myCCBlock;
@implementation TwoViewController
{
    //聲明變量
    CCBlock myCCBlock2;
}
//在你需要的地方,寫(xiě)好調(diào)用方法
 if(self.myCCBlock)
{
    self.myCCBlock(@"你好");
}

/** 在界面1聲明界面2的對(duì)象,并實(shí)現(xiàn)回調(diào)*/
TwoViewController *secondVC = [[TwoViewController alloc] init];
secondController.myCCBlock = ^(NSString *str)
{
     //在此處你可取到傳來(lái)的"你好",可以做刷新界面等其他操作
};

就這樣,輕松實(shí)現(xiàn)了界面的反向傳值

接上面的Demo1,再做個(gè)小測(cè)驗(yàn)
在界面2聲明一個(gè)數(shù)組,我們?cè)诮缑?初始化的時(shí)候就調(diào)用block,并將值作為對(duì)象存在數(shù)組里,打印結(jié)果如下:

self.myCCBlock(@"lalala");
NSArray *array = [[NSArray alloc] initWithObjects:@"1",self.myCCBlock,@"2",nil];
NSLog(@"myCCBlock內(nèi)容為%@,數(shù)組內(nèi)容為為%@",self.myCCBlock,array);
AA868EF4-41F9-4B11-A604-74729015616E.png

這說(shuō)明,block就是OC對(duì)象,因?yàn)樗艽嬖跀?shù)組里!

Demo2 - 使用內(nèi)聯(lián)block,實(shí)現(xiàn)
內(nèi)聯(lián)block格式:
(返回值 (^)(參數(shù)列表))此方法的參數(shù)名

/** 在界面2聲明內(nèi)聯(lián)block并調(diào)用*/
- (void)insideBlock: (NSString *)name successBlock:(void (^)(NSString *))success faileBlock:(void (^)(NSString *))fails;

- (void)insideBlock: (NSString *)name successBlock:(void (^)(NSString *))success faileBlock:(void (^)(NSString *))fails
{
   //可以在這里做判斷,比如有一個(gè)BOOL值控制,為T(mén)rue就調(diào)用success(name),為False就調(diào)用faile(name)
    success(name);
    faile(name);
}
/** 在界面1聲明界面2對(duì)象,并實(shí)現(xiàn)回調(diào)*/
    TwoViewController *secondController = [[TwoViewController alloc] init];
    [secondController insideBlock:@"name" successBlock:^(NSString *response)
    {
        NSLog(@"內(nèi)聯(lián)函數(shù)成功的回調(diào)是%@",response);
    }
    faileBlock:^(NSString *response)
    {
        NSLog(@"內(nèi)聯(lián)函數(shù)失敗的回調(diào)是%@",response);  
    }];

就這樣,我們實(shí)現(xiàn)了將一段程序塊寫(xiě)在函數(shù)里面

循環(huán)引用

還是做個(gè)測(cè)驗(yàn)來(lái)分享
大家應(yīng)該知道,navigationController在pop掉界面的時(shí)候,這個(gè)界面會(huì)被銷(xiāo)毀,在界面上加上這句話,會(huì)發(fā)現(xiàn)pop時(shí)這句話會(huì)打印出來(lái)

- (void)dealloc
{
    NSLog(@"被銷(xiāo)毀了");
}

若出現(xiàn)循環(huán)引用,會(huì)出現(xiàn)什么情況呢,就是A引用B,B引用A,引用計(jì)數(shù)都為2,調(diào)用dealloc的時(shí)候釋放一次,但結(jié)果是AB的引用計(jì)數(shù)仍為1,無(wú)法銷(xiāo)毀,占用內(nèi)存.

/** 在界面2.h聲明block類(lèi)型及屬性,另外聲明一個(gè)屬性name*/
typedef void (^Block) (NSString *);
@property(nonatomic, copy)Block myBlock;
@property(nonatomic, strong)NSString *name;

/** 在界面2.m給屬性name賦值,并在點(diǎn)擊按鈕返回界面1時(shí),調(diào)用block,注意此時(shí)沒(méi)傳值*/
- (void)viewDidLoad
{
    [super viewDidLoad];
     self.name = @"lele";
}
- (IBAction)onClick:(id)sender
{
    if (self.myBlock)
    {
        self.myBlock(@"");
    }
    [self .navigationController popViewControllerAnimated:NO];
}
/** 在界面1聲明界面2對(duì)象,并在界面1實(shí)現(xiàn)"在界面2點(diǎn)擊按鈕調(diào)用block方法"后的回調(diào)*/
- (IBAction)onClick:(id)sender
{
    TwoViewController *secondVC = [[TwoViewController alloc] init];
    secondVC.myBlock = ^(NSString * str)
    {
        NSLog(@"%@",secondVC.name);
    };
    [self.navigationController pushViewController:secondVC animated:NO];
}

此時(shí)運(yùn)行程序你會(huì)發(fā)現(xiàn),點(diǎn)擊界面2的按鈕后,界面2pop掉,返回界面1,"le'le"被傳了過(guò)來(lái),但卻沒(méi)有執(zhí)行delloc,沒(méi)有打印"被銷(xiāo)毀了"

2F7AF849-28B0-4254-81E2-96E8DA4762CE.png

原因
1- 在界面1中,執(zhí)行此句,VC的引用計(jì)數(shù)為1

 TwoViewController *secondVC = [[TwoViewController alloc] init];

2- 在界面1中,執(zhí)行此句,block的引用計(jì)數(shù)為1

    secondVC.myBlock = ^(NSString * str)
    {
    };

3- 在界面1中,執(zhí)行此句,VC的引用計(jì)數(shù)為2

 NSLog(@"%@",secondVC.name);

4- 在界面2中,執(zhí)行此句,block的引用計(jì)數(shù)為2

 self.myBlock(@"");

5- 界面pop時(shí),引用計(jì)數(shù)只會(huì)減1,所以造成循環(huán)引用
解決辦法
手動(dòng)將VC改為弱引用,使用"__weak typeof(原來(lái)VC類(lèi)名) 自己再起個(gè)名字"方法.

- (IBAction)onClick:(id)sender
{
    TwoViewController *secondVC = [[TwoViewController alloc] init];
    __weak typeof(secondVC) vc;
    secondVC.myBlock = ^(NSString * str)
    {
        NSLog(@"%@",vc.name);
    };
    [self.navigationController pushViewController:secondVC animated:NO];
}

打印結(jié)果如圖,因?yàn)楸讳N(xiāo)毀了,所以取不到他的屬性值name

補(bǔ)充

  • 1-內(nèi)聯(lián)block普通

(返回值 (^)(參數(shù)列表))此方法的參數(shù)名

/** 在界面2聲明內(nèi)聯(lián)block并調(diào)用*/
- (void)insideBlock: (NSString *)name successBlock:(void (^)(NSString *))success faileBlock:(void (^)(NSString *))fails;

- (void)insideBlock: (NSString *)name successBlock:(void (^)(NSString *))success faileBlock:(void (^)(NSString *))fails
{
   //可以在這里做判斷,比如有一個(gè)BOOL值控制,為T(mén)rue就調(diào)用success(name),為False就調(diào)用faile(name)
    success(name);
    faile(name);
}
/** 在界面1聲明界面2對(duì)象,并實(shí)現(xiàn)回調(diào)*/
    TwoViewController *secondController = [[TwoViewController alloc] init];
    [secondController insideBlock:@"name" successBlock:^(NSString *response)
    {
        NSLog(@"內(nèi)聯(lián)函數(shù)成功的回調(diào)是%@",response);
    }
    faileBlock:^(NSString *response)
    {
        NSLog(@"內(nèi)聯(lián)函數(shù)失敗的回調(diào)是%@",response);  
    }];

1.系統(tǒng)會(huì)先調(diào)用
TwoViewController *secondController = [[TwoViewController alloc] init];
2.然后調(diào)用
[secondController insideBlock:@"name"
successBlock:^(NSString *response)
3.走到這里去調(diào)用這個(gè)方法內(nèi)部

- (void)insideBlock: (NSString *)name
       successBlock:(void (^)(NSString *))success
         faileBlock:(void (^)(NSString *))fails
{
    success(name);
    fails(name);
}

如果這里有success(name);則去回調(diào)下圖中的第一個(gè)方框
如果這里有fails(name);則去回調(diào)下圖中的第二個(gè)方框

  • 2-內(nèi)聯(lián)block嵌套
本例子使用了FMDB,需導(dǎo)入<FMDB.h>
// 1.block類(lèi)_初始化
#import "TwoViewController.h"
#import "objc/runtime.h"
#import <FMDB.h>

static TwoViewController *_vcSingleton = nil;
@interface TwoViewController ()
{
    FMDatabase      *_db;
}

@end


@implementation TwoViewController

#pragma mark - 單例
+(instancetype)sharedDBSingleton
{
    if (_vcSingleton == nil)
    {
        _vcSingleton = [[TwoViewController alloc] init];
        [_vcSingleton initVCSingleton];
    }
    return _vcSingleton;
}

- (void)initVCSingleton
{
    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *filePath = [documentsPath stringByAppendingPathComponent:@"model.sqlite"];
    NSLog(@"地址是%@",filePath);
    _db = [FMDatabase databaseWithPath:filePath];
    NSLog(@"這個(gè)db是什么%@",_db);
    [_db open];
    // 初始化數(shù)據(jù)表
    NSString *personSql = @"CREATE TABLE 'animal' ('animal_name' VARCHAR(255))";
    [_db executeUpdate:personSql];
    [_db close];
}

// 2.block類(lèi)_block
#pragma mark - Block
- (void)insideBlock1: (NSString *)name successBlock:(BOOL (^)(FMDatabase *db,SInt32 lastVersion))success
{
    SInt32 lastVersion = 5555;
    
    [self inTransaction:^(FMDatabase *db, BOOL *shouldRollback)
     {
         if (success(db, lastVersion))
         {
             NSLog(@"這是什么");

         }
         else
         {
              NSLog(@"這又是什么");
         }
     }];
}


- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *shouldRollback))block
{
    BOOL shouldRollback = NO;
    block(_db, &shouldRollback);
}


//3.其他類(lèi)_調(diào)用block
    TwoViewController *secondController = [TwoViewController sharedDBSingleton];

    [secondController insideBlock1:@"test" successBlock:
    ^BOOL(FMDatabase *db, SInt32 lastVersion)
    {
        NSLog(@"傳來(lái)的db是什么%@,傳來(lái)的lastVersion是什么%d",db,(int)lastVersion);
        return YES;
    }];

  • 1.先執(zhí)行

    TwoViewController *secondController = [TwoViewController sharedDBSingleton];
    在initVCSingleton中初始化了db為<FMDatabase: 0x60000008e920>

  • 2.調(diào)用

    [secondController insideBlock1:@"test" successBlock:
    ^BOOL(FMDatabase *db, SInt32 lastVersion)
    的時(shí)候,是在block類(lèi)內(nèi)部調(diào)用,先執(zhí)行
    SInt32 lastVersion = 5555;

  • 3.然后執(zhí)行了

[self inTransaction:^(FMDatabase *db, BOOL *shouldRollback)
方法內(nèi)部,因?yàn)閎lock(_db, &shouldRollback);
所以執(zhí)行的是下面的回調(diào),傳進(jìn)來(lái)的兩個(gè)參數(shù)就是_db和shouldRollback = NO

下面的這個(gè)判斷,就會(huì)去其他類(lèi)_調(diào)用block中調(diào)用,并傳過(guò)去_db和5555


可以看到下圖是返回YES


所以執(zhí)行下面這段

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 《Objective-C高級(jí)編程》這本書(shū)就講了三個(gè)東西:自動(dòng)引用計(jì)數(shù)、block、GCD,偏向于從原理上對(duì)這些內(nèi)容...
    WeiHing閱讀 10,103評(píng)論 10 69
  • 近來(lái)把《iOS與OS X多線程和內(nèi)存管理》這本書(shū)又掏出來(lái)看了一遍,這本書(shū)前前后后加起來(lái)看了能有三四遍了,每次看都有...
    老司機(jī)Wicky閱讀 2,424評(píng)論 5 46
  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,625評(píng)論 30 472
  • 把網(wǎng)上的一些結(jié)合自己面試時(shí)遇到的面試題總結(jié)了一下,以后有新的還會(huì)再加進(jìn)來(lái)。 1. OC 的理解與特性 OC 作為一...
    AlaricMurray閱讀 2,667評(píng)論 0 20
  • 1.項(xiàng)目經(jīng)驗(yàn) 2.基礎(chǔ)問(wèn)題 3.指南認(rèn)識(shí) 4.解決思路 ios開(kāi)發(fā)三大塊: 1.Oc基礎(chǔ) 2.CocoaTouch...
    扶光啟玄閱讀 5,205評(píng)論 0 13

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