iOS的消息傳遞方式-3.BLOCK

屏幕快照 2017-05-24 09.34.13.png

Block結(jié)構(gòu)

block的代碼是內(nèi)聯(lián)的荐吉,效率高于函數(shù)調(diào)用
block對于外部變量默認(rèn)是只讀屬性淑倾,即在Block函數(shù)體里面不能改變Block之外的變量脚猾,只可以讀取力喷。否則會報(bào)錯(cuò)滋尉。
block被Objective-C看成是對象處理自脯,其實(shí)真實(shí)的存儲情況是一個(gè)結(jié)構(gòu)體的形式
struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};
1.這里主要是研究下block的調(diào)用順序
typedef void(^myBlockTest)(int);//聲明一個(gè)block塊(block 的重命名)
-(void)testBlock:(NSString *)str andBlock:(myBlockTest)block
{

   3. NSLog(@"str===%@",str);
   4. //block(100);  //將100傳給參數(shù)intParameter,回調(diào)block的實(shí)現(xiàn)函數(shù)

//block塊的回調(diào)敦跌,當(dāng)這行代碼不注釋時(shí)憋沿,
控制臺輸出:NonAtomicTest[2841:113472] str===我是字符竄
          NonAtomicTest[2841:113472] 實(shí)現(xiàn)block
          NonAtomicTest[2898:116086] 大于10

//當(dāng)這行代碼注釋時(shí)
控制臺輸出:NonAtomicTest[2841:113472] str===我是字符竄

}
-(void)test1
{
 1.     NSString *str = @"我是字符竄";
 2.   [self testBlock:str andBlock:^(int intParameter) {

       // str = @"1111”;
//這行代碼是錯(cuò)誤的,因?yàn)檫@個(gè)大括號里面是block塊的實(shí)現(xiàn)季稳,它只可以對block塊的參數(shù)intParameter賦值擅这,不可以對testBlock:andBlock:這個(gè)函數(shù)的參數(shù)str賦值。

     5.   NSLog(@"實(shí)現(xiàn)block");
     6.   if (intParameter>10) {
            NSLog(@"大于10");
        }
        else
        {
            NSLog(@"不大于10");
        }
    }];
   
}
這個(gè)例子已經(jīng)標(biāo)出調(diào)用順序景鼠,這里將block塊可以看作是內(nèi)聯(lián)函數(shù)
補(bǔ)充:如何在block中修改外部變量
__block int a = 0;
void  (^blockTest)(void) = ^{
    a = 1;
}
blockTest();

2.block中__strong和__weak的使用

問:為什么使用weakSelf

答:通過 clang -rewrite-objc 源代碼文件名 將代碼轉(zhuǎn)為c++代碼(實(shí)質(zhì)是c代碼)仲翎,可以看到block是一個(gè)結(jié)構(gòu)體,它會將全局變量保存為一個(gè)屬性(是_ _strong的)铛漓,而self強(qiáng)引用了block這會造成循環(huán)引用溯香。所以需要使用__weak修飾的weakSelf。self 持有block浓恶,block持有self玫坛。

問:在block中weak 和strong的使用場景是什么?

答:(摘自http://www.reibang.com/p/36342264d6dfApple )官方的建議是包晰,傳進(jìn) Block 之前湿镀,把 ‘self’ 轉(zhuǎn)換成 weak automatic 的變量,這樣在 Block 中就不會出現(xiàn)對 self 的強(qiáng)引用伐憾。如果在 Block 執(zhí)行完成之前勉痴,self 被釋放了,weakSelf 也會變?yōu)?nil塞耕。

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf doSomething];
});

clang 的文檔表示蚀腿,在 doSomething 內(nèi),weakSelf 不會被釋放扫外。但莉钙,下面的情況除外:

__weak __typeof__(self) weakSelf = self;  
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    [weakSelf doSomething];
    [weakSelf doOtherThing];

});

在 doSomething 中,weakSelf 不會變成 nil筛谚,不過在 doSomething 執(zhí)行完成磁玉,調(diào)用第二個(gè)方法 doOtherThing 的時(shí)候,weakSelf 有可能被釋放驾讲,于是蚊伞,strongSelf 就派上用場了:

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    __strong typeof(self) strongSelf = strongSelf;

    [strongSelf doSomething];
    [strongSelf doOtherThing];

});

如果僅僅使用__weak去修飾變量席赂,當(dāng)別處把變量釋放后,block中該變量也會被釋放掉时迫。

__strong 確保在 Block 內(nèi)颅停,strongSelf 不會被釋放。當(dāng)加上修飾符strong時(shí)掠拳,當(dāng)別處(別的block 內(nèi))把變量釋放掉癞揉,但調(diào)用該變量的block如果仍然沒有執(zhí)行結(jié)束,那么系統(tǒng)就會等待block執(zhí)行完成后再釋放溺欧,對該變量在block中的使用起到了保護(hù)作用喊熟。當(dāng)block執(zhí)行結(jié)束后會自動釋放掉。

總結(jié)

arc
>當(dāng)在block內(nèi)需要訪問self相關(guān)屬性的時(shí)候姐刁,添加 __weak 防止循環(huán)引用芥牌。

>當(dāng)block內(nèi)的變量不僅僅在block中使用的時(shí)候,為了防止變量提前釋放聂使,添加 __strong壁拉。

>當(dāng)block內(nèi)需要引用外部變量的時(shí)需要添加 __block  ,因?yàn)榫植孔兞砍隽朔秶蜁尫叛乙牛莃lock調(diào)用的時(shí)機(jī)是不確定的扇商。所以使用 __ block 將block復(fù)制到堆區(qū)凤瘦,引用的變量也在堆區(qū)宿礁,以后的操作也都在這里進(jìn)行。

>但是__block 本身無法避免循環(huán)引用的問題蔬芥,所以我們可以通過在 block 內(nèi)部手動把 blockObj 賦值為 nil 的方式來避免循環(huán)引用的問題梆靖。另外一點(diǎn)就是 __block 修飾的變量在 block 內(nèi)外都是唯一的,要注意這個(gè)特性可能帶來的隱患笔诵。

注:__block有一點(diǎn):這只是限制在ARC環(huán)境下返吻。在非arc下,__block是可以避免引用循環(huán)的乎婿。

block中各種情況的出現(xiàn)及解決辦法

#define BLog(prefix,obj) {NSLog(@"位置和指針變量名:%@ ,指針內(nèi)存地址:%p, 指針值:%p ,指向的對象:%@ ",prefix,&obj,obj,obj);} 

// 強(qiáng)引用
- (void)blockVariableStrongReferenceTest
{
    NSLog(@"\n");
    NSObject *obj = [[NSObject alloc] init];//+1
    BLog(@"StrongRef obj",obj);
    void(^testBlock)()= ^(){
        BLog(@"StrongRef in block",obj);//+1Block中obj指針已經(jīng)不是外部的obj指針了,它是外部變量obj的拷貝,內(nèi)存引用計(jì)數(shù)加一
    };
    testBlock();
    // Block外部嘗試將obj置為nil
    obj = nil;//-1
    testBlock();  // 第二次調(diào)用block
}

解釋:block內(nèi)部的obj 指針是外部obj指針的拷貝测僵,有2個(gè)指針指向同一個(gè)NSObject對象,(沒有重新分配空間)但只將外部的obj指針置為nil,NSObject對象的引用計(jì)數(shù)不為0谢翎,無法回收捍靠。

// 弱引用
- (void)blockVariableWeakReferenceTest
{
    NSObject *obj = [[NSObject alloc] init];//+1
    __weak NSObject *weakObj = obj;//+0   weakObj和obj是2個(gè)不同的指針,指向同一塊內(nèi)存地址森逮,但是因?yàn)槭侨跻谜テ牛砸糜?jì)數(shù)不改變
    BLog(@"WeakRef weakObj", weakObj);
    void(^testBlock)()= ^(){
        BLog(@"weakObj in block",weakObj);
    };
    testBlock();
    obj = nil; //    -1=0
    testBlock();//
}

解釋:在block中__weak聲明的指針去引用對象 可以避免循環(huán)引用的問題,但是當(dāng)外部對象被釋放了褒侧,block 內(nèi)部會訪問不到這個(gè)對象.

//多線程時(shí)Block生命周期內(nèi)對象安全
- (void)blockVariableMutiThreadTest
{
    NSObject *obj = [[NSObject alloc]init]; //obj強(qiáng)引用,<NSObject: 0x7f9413c1c040>對象引用計(jì)數(shù)+1良风,=1
    BLog(@"obj", obj);
    __weak NSObject *weakObj = obj;//weakObj弱引用,<NSObject: 0x7f9413c1c040>對象引用計(jì)數(shù)不變谊迄,=1
    BLog(@"weakObj-0", weakObj);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        __strong NSObject *strongObj = weakObj; //strongObj強(qiáng)引用,<NSObject: 0x7f9413c1c040>對象引用計(jì)數(shù)+1,=2
        sleep(3);
        BLog(@"weakObj - block", weakObj);
        BLog(@"strongObj - block", strongObj);
    });
    sleep(1);
    obj = nil; //obj被置為nil烟央,<NSObject: 0x7f9413c1c040>對象引用計(jì)數(shù)-1统诺,=1
    BLog(@"weakObj-1", weakObj);  //沒被釋放
    sleep(4); //block在異步線程中執(zhí)行完畢(在另一塊內(nèi)存中執(zhí)行),block內(nèi)存被釋放疑俭,<NSObject: 0x7f9413c1c040>對象引用計(jì)數(shù)-1篙议,=0;ARC開始把0x7f9413c1c040對象內(nèi)存回收怠硼,把弱引用weakObj置為nil
    BLog(@"weakObj-2", weakObj);
}

總結(jié) :
多線程的時(shí)候鬼贱,在 block 外部用__weak聲明的變量指向一個(gè)對象, 通過把weak聲明的變量值賦值給block內(nèi)部的```strong變量,實(shí)現(xiàn)在block內(nèi)對該對象進(jìn)行強(qiáng)引用香璃,這樣可以在block生命周期內(nèi)保留該對象不被釋放这难,在block生命周期結(jié)束后,對象內(nèi)存被釋放葡秒。

 (void)blockVariable

{

    NSObject *obj = [[NSObject alloc]init]; //指向的對象:<NSObject: 0x7fb4039063b0>

    BLog(@"obj",obj);

    __block NSObject *blockObj = obj;  //blockObj指向?qū)ο?0x7fb4039063b0

    obj = nil;

    BLog(@"blockObj -1",blockObj);  //blockObj,0x7fff52365c90

    void(^testBlock)() = ^(){

        BLog(@"blockObj - block",blockObj);   //blockObj,0x7fb401c7d7f8,指向的對象:<NSObject: 0x7fb4039063b0>

        NSObject *obj2 = [[NSObject alloc]init]; // obj2 ,0x7fff52365bc8,指向的對象:<NSObject: 0x7fb401c83c40>

        BLog(@"obj2",obj2);

        blockObj = obj2;

        BLog(@"blockObj - block",blockObj); //blockObj,0x7fb401c7d7f8,指向?qū)ο?<NSObject: 0x7fb401c83c40>

    };

    NSLog(@"%@",testBlock);         //block 的地址  <__NSMallocBlock__: 0x7fb401c07d20>

    BLog(@"blockObj -2",blockObj);  //blockObj地址發(fā)生變化姻乓,0x7fb401c7d7f8,/指向的對象:<NSObject: 0x7fb4039063b0>

    testBlock();

    BLog(@"blockObj -3",blockObj);  //blockObj,0x7fb401c7d7f8,指向?qū)ο?<NSObject: 0x7fb401c83c40>

 

}

分析 :
第3處日志打印了一個(gè)testBlock對象,blockObj的地址發(fā)生變化眯牧。此時(shí)蹋岩,block對象 從棧拷貝到堆上学少,__block變量blockObj剪个,也被拷貝到堆上。block對象擁有blockObj指針指向的對象版确。注意:這是個(gè)強(qiáng)引用哦扣囊。
關(guān)注4到8 處日志,用__block關(guān)鍵字聲明blockObj指針后绒疗,block內(nèi)外的變量blockObj都是0x7fa084905838侵歇,也就是block內(nèi)外的blockObj指針是同一個(gè)指針。
block內(nèi)部改變 blockObj指針指向的對象吓蘑,改動在 block外部可見狡赐。

常用的出現(xiàn)循環(huán)使用:(只要你在block里用到了self所擁有的東西块仆!)

block在copy時(shí)都會對block內(nèi)部用到的對象進(jìn)行強(qiáng)引用(ARC)或者retainCount增1(非ARC)靠汁。在ARC與非ARC環(huán)境下對block使用不當(dāng)都會引起循環(huán)引用問題急黎,一般表現(xiàn)為,某個(gè)類將block作為自己的屬性變量棋嘲,然后該類在block的方法體里面又使用了該類本身酒唉,簡單說就是

self.someBlock = ^(Type var){
       [self dosomething];
      或者self.otherVar = XXX;
      或者_(dá)otherVar = ...XXX;
};

//block的這種循環(huán)引用會被編譯器捕捉到并及時(shí)提醒沸移。

  • 即使在你的block代碼中沒有顯式地出現(xiàn)"self"痪伦,也會出現(xiàn)循環(huán)引用侄榴!只要你在block里用到了self所擁有的東西!
  • 但對于這種情況网沾,我們無法通過加__weak聲明或者_(dá)_block聲明去禁止block對self進(jìn)行強(qiáng)引用或者強(qiáng)制增加引用計(jì)數(shù)癞蚕。但我們可以通過其他指針來避免循環(huán)引用,具體是這么做的:
__weak typeof(self) weakSelf = self;
self.blkA = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//加一下強(qiáng)引用辉哥,避免weakSelf被釋放掉
NSLog(@"%@", strongSelf->_xxView); //不會導(dǎo)致循環(huán)引用.
};

self.arr

1)ARC環(huán)境下:
ARC環(huán)境下可以通過使用_weak聲明一個(gè)代替self的新變量代替原先的self桦山,我們可以命名為weakSelf。通過這種方式告訴block醋旦,不要在block內(nèi)部對self進(jìn)行強(qiáng)制strong引用:(如果要兼容ios4.3恒水,則用__unsafe_unretained代替__weak,不過目前基本不需考慮這么low的版本)

 self.arr = @[@111, @222, @333]; 
 __weak typeof(self) weakSelf=self; 
 self.block = ^(NSString *name){ 
  NSLog(@"arr:%@", weakSelf.arr); 
   };

2)MRC環(huán)境下:解決方式與上述基本一致饲齐,只不過將__weak關(guān)鍵字換成__block即可钉凌,這樣的意思是告訴block:小子,不要在內(nèi)部對self進(jìn)行retain了捂人!

委托delegate

一字訣:聲明delegate時(shí)請用assign(MRC)或者weak(ARC)御雕,

對block想要深入研究的 :http://www.reibang.com/p/ee9756f3d5f6 http://www.cocoachina.com/ios/20170527/19308.html
https://yq.aliyun.com/articles/62662

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市滥搭,隨后出現(xiàn)的幾起案子酸纲,更是在濱河造成了極大的恐慌,老刑警劉巖瑟匆,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闽坡,死亡現(xiàn)場離奇詭異,居然都是意外死亡脓诡,警方通過查閱死者的電腦和手機(jī)无午,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祝谚,“玉大人,你說我怎么就攤上這事酣衷〗还撸” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵穿仪,是天一觀的道長席爽。 經(jīng)常有香客問我,道長啊片,這世上最難降的妖魔是什么只锻? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮紫谷,結(jié)果婚禮上齐饮,老公的妹妹穿的比我還像新娘捐寥。我一直安慰自己,他們只是感情好祖驱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布握恳。 她就那樣靜靜地躺著,像睡著了一般捺僻。 火紅的嫁衣襯著肌膚如雪乡洼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天匕坯,我揣著相機(jī)與錄音束昵,去河邊找鬼。 笑死葛峻,一個(gè)胖子當(dāng)著我的面吹牛妻怎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泞歉,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼逼侦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了腰耙?” 一聲冷哼從身側(cè)響起榛丢,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挺庞,沒想到半個(gè)月后晰赞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡选侨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年掖鱼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片援制。...
    茶點(diǎn)故事閱讀 38,747評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡戏挡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晨仑,到底是詐尸還是另有隱情褐墅,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布洪己,位于F島的核電站妥凳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏答捕。R本人自食惡果不足惜逝钥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拱镐。 院中可真熱鬧艘款,春花似錦持际、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至岳枷,卻和暖如春芒填,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背空繁。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工殿衰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盛泡。 一個(gè)月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓闷祥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親傲诵。 傳聞我的和親對象是個(gè)殘疾皇子凯砍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評論 2 350

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