Block篇-block剖析

導(dǎo)語

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

block類型
  • _NSConcreteGlobalBlock 全局的靜態(tài) block怨咪,不會訪問任何外部變量
  • _NSConcreteStackBlock 保存在棧中的 block沛申,當(dāng)函數(shù)返回時會被銷毀劫笙。但在ARC下,會被復(fù)制到堆上.
  • _NSConcreteMallocBlock 保存在堆中的 block纫普,當(dāng)引用計數(shù)為 0 時會被銷毀匿辩。 所以在ARC下,只有_NSConcreteGlobalBlock和_NSConcreteMallocBlock這兩種類型

寫個例子來測試一下block類型

/** 在.h中定義和.m中定義對結(jié)果沒有影響,已驗證完畢*/
//.h中定義類型,聲明屬性htestBlock 
typedef NSString *(^testBlock)(NSString *); 
@property (nonatomic ,copy)testBlock htestBlock; 
//.m中聲明屬性mtestBlock及變量_ctestBlock 
@property (nonatomic ,copy)testBlock mtestBlock; @implementation ViewController 
{ 
    testBlock _ctestBlock; 
} 
//下面進(jìn)行測試 
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被銷毀.png
](http://upload-images.jianshu.io/upload_images/1602974-b4f8e1d1d0f64b63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

    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é)果,個人猜測,block聲明后,就是全局的靜態(tài)block,因為現(xiàn)在使用的是ARC,所以當(dāng)你訪問外部變量,會自動copy一份到堆上.
若理解不對請大家指正,感謝!

__block

再做個小測試

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

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

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

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


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,作為類的變量或?qū)傩允褂?br> 1- 實現(xiàn)界面反向傳值
    2- 可以實現(xiàn)界面A做完某一操作后,界面B立即實現(xiàn)其他操作
  • 內(nèi)聯(lián)block,作為方法的參數(shù)使用
    1-配合disptch_queue,可以方便的實現(xiàn)簡單的多線程編程和異步編程

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

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

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

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

接上面的Demo1,再做個小測驗
在界面2聲明一個數(shù)組,我們在界面2初始化的時候就調(diào)用block,并將值作為對象存在數(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

這說明,block就是OC對象,因為它能存在數(shù)組里!

Demo2 - 使用內(nèi)聯(lián)block,實現(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
{
   //可以在這里做判斷,比如有一個BOOL值控制,為True就調(diào)用success(name),為False就調(diào)用faile(name)
    success(name);
    faile(name);
}
/** 在界面1聲明界面2對象,并實現(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);  
    }];

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

循環(huán)引用

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

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

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

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

/** 在界面2.m給屬性name賦值,并在點(diǎn)擊按鈕返回界面1時,調(diào)用block,注意此時沒傳值*/
- (void)viewDidLoad
{
    [super viewDidLoad];
     self.name = @"lele";
}
- (IBAction)onClick:(id)sender
{
    if (self.myBlock)
    {
        self.myBlock(@"");
    }
    [self .navigationController popViewControllerAnimated:NO];
}
/** 在界面1聲明界面2對象,并在界面1實現(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];
}

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

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

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

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

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

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

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

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

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

 self.myBlock(@"");

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

- (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é)果如圖,因為被銷毀了,所以取不到他的屬性值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
{
   //可以在這里做判斷,比如有一個BOOL值控制,為True就調(diào)用success(name),為False就調(diào)用faile(name)
    success(name);
    faile(name);
}
/** 在界面1聲明界面2對象,并實現(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)會先調(diào)用
TwoViewController *secondController = [[TwoViewController alloc] init];
2.然后調(diào)用
[secondController insideBlock:@"name"
successBlock:^(NSString *response)
3.走到這里去調(diào)用這個方法內(nèi)部

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

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

  • 2-內(nèi)聯(lián)block嵌套
本例子使用了FMDB,需導(dǎo)入<FMDB.h>
// 1.block類_初始化
#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(@"這個db是什么%@",_db);
    [_db open];
    // 初始化數(shù)據(jù)表
    NSString *personSql = @"CREATE TABLE 'animal' ('animal_name' VARCHAR(255))";
    [_db executeUpdate:personSql];
    [_db close];
}

// 2.block類_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.其他類_調(diào)用block
    TwoViewController *secondController = [TwoViewController sharedDBSingleton];

    [secondController insideBlock1:@"test" successBlock:
    ^BOOL(FMDatabase *db, SInt32 lastVersion)
    {
        NSLog(@"傳來的db是什么%@,傳來的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)
    的時候,是在block類內(nèi)部調(diào)用,先執(zhí)行
    SInt32 lastVersion = 5555;

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

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

下面的這個判斷,就會去其他類_調(diào)用block中調(diào)用,并傳過去_db和5555


可以看到下圖是返回YES


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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末苍柏,一起剝皮案震驚了整個濱河市肪康,隨后出現(xiàn)的幾起案子荚恶,更是在濱河造成了極大的恐慌,老刑警劉巖磷支,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谒撼,死亡現(xiàn)場離奇詭異,居然都是意外死亡雾狈,警方通過查閱死者的電腦和手機(jī)廓潜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來善榛,“玉大人辩蛋,你說我怎么就攤上這事∫婆瑁” “怎么了悼院?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咒循。 經(jīng)常有香客問我据途,道長,這世上最難降的妖魔是什么叙甸? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任颖医,我火速辦了婚禮,結(jié)果婚禮上蚁署,老公的妹妹穿的比我還像新娘便脊。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布哪痰。 她就那樣靜靜地躺著遂赠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晌杰。 梳的紋絲不亂的頭發(fā)上跷睦,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機(jī)與錄音肋演,去河邊找鬼抑诸。 笑死,一個胖子當(dāng)著我的面吹牛爹殊,可吹牛的內(nèi)容都是我干的蜕乡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼梗夸,長吁一口氣:“原來是場噩夢啊……” “哼层玲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起反症,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辛块,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后铅碍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體润绵,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年胞谈,在試婚紗的時候發(fā)現(xiàn)自己被綠了尘盼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡呜魄,死狀恐怖悔叽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤睹晒,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站呆盖,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏株扛。R本人自食惡果不足惜尤筐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一洞就、第九天 我趴在偏房一處隱蔽的房頂上張望盆繁。 院中可真熱鬧,春花似錦旬蟋、人聲如沸躁染。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锭环。三九已至,卻和暖如春玫锋,著一層夾襖步出監(jiān)牢的瞬間吧寺,已是汗流浹背赖条。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工茧泪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人干毅。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓叫乌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親板甘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容