Block使用注意點(diǎn)及常見(jiàn)問(wèn)題淺析

本文將淺分析幾個(gè)Block使用問(wèn)題:

  • 解析問(wèn)題一:Block作為類(lèi)變量屬性時(shí)為啥用copy修飾凯楔?堆棧存儲(chǔ)位置是怎樣的锦募?
  • 解析問(wèn)題二:為什么需要__block 修飾自動(dòng)變量后糠亩,才能在Block中更改?
  • 解析問(wèn)題三:關(guān)于常見(jiàn)block中self的循環(huán)引用問(wèn)題富弦,可以用__weak打破強(qiáng)引用鏈氛驮;那么為什么AFN或像UIView動(dòng)畫(huà)不需要__weak修飾self?

Block:帶有自動(dòng)變量(局部變量)的匿名函數(shù)盏缤。
Block類(lèi)型:
內(nèi)聯(lián)Block(inline):說(shuō)白了就是Block被嵌入到一個(gè)函數(shù)中唉铜,較常使用;
獨(dú)立Block:即在方法外定義的竞惋。不能直接訪(fǎng)問(wèn) self灰嫉,只能通過(guò)將 self 當(dāng)作參數(shù)傳遞到 Block 中才能使用,并且此時(shí)的 self 只能通過(guò) setter 或 getter 方法訪(fǎng)問(wèn)其屬性浑厚,不能使用句點(diǎn)式方法根盒。但內(nèi)聯(lián) Block 不受此限制.

// 獨(dú)立Block
void (^correctBlockObject)(id) = ^(id self){
    // 將self作為參數(shù)傳遞
    NSLog(@"self = %@", self);
    // 訪(fǎng)問(wèn)self變量
    NSLog(@"self = %@", [self strName]);
};

使用注意點(diǎn):

  • Block作為屬性用copy修飾:
    Block作為類(lèi)屬性炎滞,要用copy修飾,把Block從椖频迹拷貝到堆中击奶,防止被釋放掉柜砾;

  • 不能修改外部自動(dòng)變量問(wèn)題:
    Block里面能修改全局或靜態(tài)的變量换衬,但是不能修改在Block外并且定義在所在方法內(nèi)的自動(dòng)變量瞳浦,這時(shí)需要__block符修飾此變量;

例:

__block NSInteger val = 0;
void (^block)(void) = ^{
   val = 1;
};
block();
NSLog(@"val = %ld", val);
//(這是在ARC下這樣使用蝇完,MRC下__block含義是不增加此對(duì)象引用計(jì)數(shù),相當(dāng)于ARC下的__weak)

(自動(dòng)(automatic)變量氢架,即局部變量:不作專(zhuān)門(mén)說(shuō)明的局部變量朋魔,均是自動(dòng)變量。自動(dòng)變量也可用關(guān)鍵字auto作出說(shuō)明孙援,auto int c=3拓售;/c為自動(dòng)變量/洼裤。自動(dòng)變量只有一種存儲(chǔ)方式,就是存儲(chǔ)在棧中值骇。由于自動(dòng)變量存儲(chǔ)在棧中移国,所以自動(dòng)變量的作用域只在函數(shù)內(nèi)迹缀,其生命周期也只持續(xù)到函數(shù)調(diào)用的結(jié)束。)

  • 循環(huán)引用問(wèn)題:
    Block在方法中被定義時(shí)票摇,該方法執(zhí)行完是需要被釋放的砚蓬,Block會(huì)強(qiáng)引用自己持有的對(duì)象灰蛙,使其引用計(jì)數(shù)+1,如果所持有的對(duì)象也持有此Block時(shí)物延,需要__weak 在外修飾此對(duì)象仅父,標(biāo)識(shí)不+1浑吟,常見(jiàn)的是self或一些強(qiáng)類(lèi)型的對(duì)象:
    例如:(ARC下)
__weak HomeViewController * VC = self;
__weak typeof(self) weakSelf = self;
__weak __typeof(&*self)weakSelf = self;
__weak typeof(_tableView) weakTableView = _tableView;

(MRC下买置,無(wú)__weak)

__block HomeViewController * VC = self;

ARC與MRC下Block內(nèi)存分配機(jī)制不一樣强霎,ARC中iOS5+用__weak城舞,之前是用__unsafe_unretained修飾符;
__unsafe_unretained缺點(diǎn)是指針?biāo)玫膶?duì)象被回收后脱柱,自己不會(huì)置為nil拉馋,因此可能導(dǎo)致程序崩潰煌茴;而weak指針不同,對(duì)象被回收后矩乐,指針會(huì)被賦值為nil回论。一般來(lái)說(shuō)傀蓉,weak修飾符更加安全。

另一種寫(xiě)法:

有時(shí)候weakSelf會(huì)在Block未執(zhí)行完就會(huì)釋放掉成為nil误甚,為了防止這種情況出現(xiàn)萨蚕,在Block內(nèi)需要__strong對(duì)weakSelf強(qiáng)引用一下(更高級(jí)寫(xiě)法見(jiàn)RAC的@weakify(self)岳遥,@strongify(self))裕寨。

//宏定義 - Block循環(huán)引用
#define weakify(var)   __weak typeof(var) weakSelf = var
#define strongify(var) __strong typeof(var) strongSelf = var

weakify(self);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    strongSelf(weakSelf);
    [strongSelf doSomething];//防止weakSelf釋放掉
    [strongSelf doOtherThing];
});

這樣不會(huì)造成循環(huán)引用是因?yàn)閟trongSelf是在Block里面聲明的一個(gè)指針,當(dāng)Block執(zhí)行完畢后捻艳,strongSelf會(huì)釋放认轨,這個(gè)時(shí)候?qū)⒉辉購(gòu)?qiáng)引用weakSelf,所以self會(huì)正確的釋放恩急。

解析問(wèn)題一:Block的存儲(chǔ)位置

在Objective-C語(yǔ)言中纪蜒,一共有3種類(lèi)型的block:
1._NSConcreteGlobalBlock 全局的靜態(tài)block纯续,不會(huì)訪(fǎng)問(wèn)任何外部變量。
2._NSConcreteStackBlock 保存在棧中的block窗看,當(dāng)函數(shù)返回時(shí)會(huì)被銷(xiāo)毀兔魂。
3._NSConcreteMallocBlock 保存在堆中的block(從棧中copy過(guò)去的)析校,當(dāng)引用計(jì)數(shù)為0時(shí)會(huì)被銷(xiāo)毀。

Block內(nèi)捕獲變量會(huì)改變自身存儲(chǔ)位置遂唧,包括讀取變量和__block這種寫(xiě)變量吊奢,兩種方式(其實(shí)結(jié)果是一樣的)页滚。

【在MRC下】:存在棧、全局隧熙、堆這三種形式贞盯。
【在ARC下】:大部分情況下系統(tǒng)會(huì)把Block自動(dòng)copy到堆上。

Block作為變量:
  • 方法中聲明一個(gè) block 的時(shí)候是在棧上闷愤;
  • 引用了外部局部變量或成員變量,并且有賦值操作(有名字)件余,會(huì)被 copy 到堆上啼器;
  • 賦值給附有__strong修飾符的id類(lèi)型的類(lèi)或者Blcok類(lèi)型成員變量時(shí);
  • 賦值給一個(gè) weak 變量不會(huì)被 copy坟漱;
Block作為屬性:
  • 用 copy 修飾會(huì)被 copy更哄;
Block作為函數(shù)參數(shù):
  • 作為參數(shù)傳入函數(shù)不會(huì)被 copy成翩,依然在棧上,方法執(zhí)行完即釋放栅炒;
  • 作為函數(shù)的返回值會(huì)被 copy 到堆赢赊;

例如:

-(void)method { 
//在ARC環(huán)境下级历,Block也是存在__NSStackBlock的時(shí)候的寥殖,平時(shí)見(jiàn)到最多的是_NSConcreteMallocBlock,是因?yàn)槲覀儠?huì)對(duì)Block有賦值操作熏纯,所以ARC下粤策,block 類(lèi)型通過(guò)=進(jìn)行傳遞時(shí)掐场,會(huì)導(dǎo)致調(diào)用objc_retainBlock->_Block_copy->_Block_copy_internal方法鏈。并導(dǎo)致 __NSStackBlock__ 類(lèi)型的 block 轉(zhuǎn)換為 __NSMallocBlock__ 類(lèi)型萍膛。
  int a = 3;
  void(^block)() = ^{
      NSLog(@"調(diào)用block%d",a);//這里的變量a蝗罗,和self.string是一樣效果
  };
  NSLog(@"%@",block);
//打印結(jié)果:<__NSMallocBlock__: 0x7fc498746000>
//此時(shí)后面的匿名函數(shù)賦值給block指針(創(chuàng)建帶名字的block)蝌戒,且引用了外部局部變量,block會(huì)copy到堆

NSLog(@"%@",^{NSLog(@"調(diào)用block%d",a);});
//打印結(jié)果:<__NSStackBlock__: 0x7fff54f0c700>
//匿名函數(shù)無(wú)賦值操作北苟,只存于棧上,會(huì)不定釋放

static int b = 2;

  void(^block)() = ^{
      NSLog(@"調(diào)用block%d",b);//若不引用任何變量傻昙,也是__NSGloBalBlock__
  };
  NSLog(@"%@",block);
}
//打印結(jié)果:<__NSGloBalBlock__: 0x7fc498746000>
//此時(shí)引用了全局變量,block放在全局區(qū)

解析問(wèn)題二:為什么__block 修飾自動(dòng)變量后妆档,就能在Block中更改虫碉?

首先,為什么Block不能修改外部自動(dòng)變量须板?

自動(dòng)變量存于棧中逼纸,在當(dāng)前方法執(zhí)行完济蝉,會(huì)釋放掉王滤。一般來(lái)說(shuō),在 block 中用的變量值是被復(fù)制過(guò)來(lái)的雁乡,自動(dòng)變量是值類(lèi)型復(fù)制第喳,新開(kāi)辟棧空間踱稍,所以對(duì)于新復(fù)制變量的修改并不會(huì)影響這個(gè)變量的真實(shí)值(也稱(chēng)只讀拷貝)曲饱。大多情況下悠抹,block是作為參數(shù)傳遞以供后續(xù)回調(diào)執(zhí)行的。所以在你想要在block中修改此自動(dòng)變量時(shí)扩淀,變量可能已被釋放,所以不允許block進(jìn)行修改是合理的驻谆。
對(duì)于 static 變量卵凑,全局變量胜臊,在 block中是有讀寫(xiě)權(quán)限的勺卢,因?yàn)榇俗兞看嬗谌謹(jǐn)?shù)據(jù)區(qū)(非棧區(qū)),不會(huì)隨時(shí)釋放掉象对,也不會(huì)新開(kāi)辟內(nèi)存創(chuàng)建變量黑忱, block 拷貝的是指向這些變量的指針,可以修改原變量勒魔。

那么怎么讓自動(dòng)變量不被釋放杨何,還能被修改呢?

__block修飾符把所修飾的變量包裝成一個(gè)結(jié)構(gòu)體對(duì)象沥邻,即可完美解決危虱。Block既可以強(qiáng)引用此結(jié)構(gòu)體對(duì)象,使之不會(huì)自動(dòng)釋放唐全,也可以拷貝到指向該結(jié)構(gòu)體對(duì)象的指針埃跷,通過(guò)指針修改結(jié)構(gòu)體的變量,也就是__block所修飾的自動(dòng)變量邮利。

將下面main.m文件進(jìn)行clang -rewrite-objc main.m命令查看編譯時(shí)的c++代碼:

//寫(xiě)在main.m文件的main方法里
__block NSInteger val = 0;
void (^block)(void) = ^{
   val = 1;
};
block();
NSLog(@"val = %ld", val);

如下:

//把val變量封裝成了結(jié)構(gòu)體
struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;//注意這個(gè)__forwarding指針
 int __flags;
 int __size;
 NSInteger val;
};
//block變量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//結(jié)構(gòu)體作為參數(shù)是通過(guò)指針傳遞弥雹,這里__cself指針傳入變量所在結(jié)構(gòu)體,并在__main_block_func_0中實(shí)現(xiàn)變量值的改變
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

            (val->__forwarding->val) = 1;//通過(guò)__forwarding指針找到val變量
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

//block的描述
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

在__main_block_func_0里延届,發(fā)現(xiàn)是通過(guò)(val->__forwarding->val) = 1;找到val變量,這么做的原因是什么剪勿?

__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;

我們知道,ARC下Block很多情況會(huì)被自動(dòng)拷貝到堆方庭,而在棧上的__block變量被復(fù)制到堆上之后厕吉,會(huì)將結(jié)構(gòu)體__Block_byref_val_0的成員變量__Block_byref_val_0 *__forwarding;的值替換為堆上的__block變量的地址。因此使用 __forwarding 指針就是為了無(wú)論在棧還是在堆械念,都能正確訪(fǎng)問(wèn)到該__block變量头朱。

解析問(wèn)題三:循環(huán)引用問(wèn)題

循環(huán)引用的根本原因是Block和另一個(gè)對(duì)象互相持有,導(dǎo)致都無(wú)法釋放龄减,經(jīng)常碰到的是self(當(dāng)前控制器)项钮,導(dǎo)致控制器無(wú)法釋放,內(nèi)存泄漏嚴(yán)重。

解決循環(huán)引用問(wèn)題主要有兩個(gè)辦法:
  • 事前避免烁巫,我們?cè)跁?huì)產(chǎn)生循環(huán)引用的地方使用 weak 弱引用署隘,以避免產(chǎn)生循環(huán)引用。
  • 事后補(bǔ)救亚隙,我們明確知道會(huì)存在循環(huán)引用磁餐,但是我們?cè)诤侠淼奈恢弥鲃?dòng)斷開(kāi)環(huán)中的一個(gè)引用,使得對(duì)象得以回收恃鞋。
//巧神在YTKNetWorking是這么設(shè)計(jì)的:
//Block應(yīng)該結(jié)束完的時(shí)候,手動(dòng)把該釋放的Block置空
- (void)clearCompletionBlock {     
    // nil out to break the retain cycle.     
    self.successCompletionBlock = nil;     
    self.failureCompletionBlock = nil; 
}

有時(shí)候納悶為什么AFN或像UIView的Block動(dòng)畫(huà)不需要weakSelf也可以自己釋放掉亦歉。其實(shí)明白了無(wú)法釋放的原理恤浪,也就明白了。雖然Block強(qiáng)引用了self肴楷,但是self不強(qiáng)引用這個(gè)Block呀水由。

舉例說(shuō)明(需要和不需要使用__weak打破循環(huán)引用):
typedef void(^Block)();

@interface SecViewController ()
@property (nonatomic , strong) NSString *str;
@property (nonatomic , copy) Block blk;
@end

@implementation SecViewController

- (void)viewDidLoad {
    self.str = @"string";
    //以下2種Block都不會(huì)被self強(qiáng)引用
    //doSomthing1方法執(zhí)行完,pop后此控制器會(huì)被釋放掉
    [self doSomthing1:^{
        NSLog(@"str111:%@",self.str);
    }];
    //doSomthing2方法執(zhí)行完赛蔫,pop后此控制器也會(huì)被釋放掉
    [self doSomthing2:^(int a, int b) {
        NSLog(@"str111:%@",self.str);
    }];

    //self持有此Block砂客,要用__weak
    __weak typeof(self) weakSelf = self;
    self.blk = ^(){
        NSLog(@"str111:%@",weakSelf.str);
    };
    
    //在doSomthing3方法中block參數(shù)賦值給self.blk,這樣block參數(shù)就間接被self強(qiáng)引用,需用weakSelf呵恢,用self會(huì)導(dǎo)致循環(huán)引用鞠值,當(dāng)前控制器無(wú)法釋放;
    //經(jīng)常碰到的都是這種間接持有導(dǎo)致的循環(huán)引用問(wèn)題
    [self doSomthing3:^{
        NSLog(@"str111:%@",weakSelf.str);
    }];

}
- (void)doSomthing1:(void(^)())block{
    if(block){
        block();
    }
}
- (void)doSomthing2:(Block)block{
    if(block){
        block();
    }
}
- (void)doSomthing3:(Block)block{
    if(block){
        self.blk = block;
        block();
    }
}

-(void)dealloc{
    NSLog(@"SecVC釋放了");
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市渗钉,隨后出現(xiàn)的幾起案子彤恶,更是在濱河造成了極大的恐慌,老刑警劉巖鳄橘,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件声离,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瘫怜,警方通過(guò)查閱死者的電腦和手機(jī)术徊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鲸湃,“玉大人赠涮,你說(shuō)我怎么就攤上這事“堤簦” “怎么了世囊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)窿祥。 經(jīng)常有香客問(wèn)我株憾,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任嗤瞎,我火速辦了婚禮墙歪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贝奇。我一直安慰自己虹菲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布掉瞳。 她就那樣靜靜地躺著毕源,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陕习。 梳的紋絲不亂的頭發(fā)上霎褐,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音该镣,去河邊找鬼冻璃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛损合,可吹牛的內(nèi)容都是我干的省艳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼嫁审,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼跋炕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起律适,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤枣购,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后擦耀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體索抓,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铭乾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年缭受,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了想鹰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吁系,死狀恐怖德召,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汽纤,我是刑警寧澤上岗,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蕴坪,受9級(jí)特大地震影響肴掷,放射性物質(zhì)發(fā)生泄漏敬锐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一呆瞻、第九天 我趴在偏房一處隱蔽的房頂上張望台夺。 院中可真熱鬧,春花似錦痴脾、人聲如沸颤介。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滚朵。三九已至,卻和暖如春前域,著一層夾襖步出監(jiān)牢的瞬間辕近,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工话侄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亏推,地道東北人学赛。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓年堆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親盏浇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子变丧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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