iOS Block

block的本質(zhì)
block本質(zhì)上也是一個(gè)oc對(duì)象唯卖,他內(nèi)部也有一個(gè)isa指針椿访。block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象垮媒。block其實(shí)也是NSObject的子類
block的類型
共有三種類型的block分別是:全局的,棧上的术浪,堆上的

__NSGlobalBlock__ ( _NSConcreteGlobalBlock )//直到程序結(jié)束才會(huì)被回收
__NSStackBlock__ ( _NSConcreteStackBlock )//棧中的內(nèi)存由系統(tǒng)自動(dòng)分配和釋放,作用域執(zhí)行完畢之后就會(huì)被立即釋放
__NSMallocBlock__ ( _NSConcreteMallocBlock )//平時(shí)編碼過(guò)程中最常使用到的寿酌。存放在堆中需要我們自己進(jìn)行內(nèi)存管理胰苏。

block是如何定義其類型

block是如何定義其類型

三種block的創(chuàng)建

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 1. 內(nèi)部沒(méi)有調(diào)用外部變量的block
        void (^block1)(void) = ^{
            NSLog(@"Hello");
        };
        // 2. 內(nèi)部調(diào)用外部變量的block
        int a = 10;
        void (^block2)(void) = ^{
            NSLog(@"Hello - %d",a);
        };
       // 3. 直接調(diào)用的block的class
        NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
            NSLog(@"%d",a);
        } class]);
    }
    return 0;
}

打印結(jié)果:

2021-09-28 11:30:11.568128+0800 OC-Review[44456:6774536] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

block的變量捕獲

局部變量

  • auto變量 - 值傳遞
  • static變量 - 指針傳遞
    全局變量
  • 直接訪問(wèn)
  1. 一旦block中捕獲的變量為對(duì)象類型,block結(jié)構(gòu)體中的__main_block_desc_0會(huì)出兩個(gè)參數(shù)copy和dispose醇疼。因?yàn)樵L問(wèn)的是個(gè)對(duì)象硕并,block希望擁有這個(gè)對(duì)象,就需要對(duì)對(duì)象進(jìn)行引用秧荆,也就是進(jìn)行內(nèi)存管理的操作倔毙。比如說(shuō)對(duì)對(duì)象進(jìn)行retarn操作,因此一旦block捕獲的變量是對(duì)象類型就會(huì)會(huì)自動(dòng)生成copy和dispose來(lái)對(duì)內(nèi)部引用的對(duì)象進(jìn)行內(nèi)存管理乙濒。
  2. 當(dāng)block內(nèi)部訪問(wèn)了對(duì)象類型的auto變量時(shí)陕赃,如果block是在棧上卵蛉,block內(nèi)部不會(huì)對(duì)person產(chǎn)生強(qiáng)引用。不論block結(jié)構(gòu)體內(nèi)部的變量是__strong修飾還是__weak修飾么库,都不會(huì)對(duì)變量產(chǎn)生強(qiáng)引用傻丝。
  3. 如果block被拷貝到堆上。copy函數(shù)會(huì)調(diào)用_Block_object_assign函數(shù)诉儒,根據(jù)auto變量的修飾符(__strong葡缰,__weak,unsafe_unretained)做出相應(yīng)的操作忱反,形成強(qiáng)引用或者弱引用
  4. 如果block從堆中移除泛释,dispose函數(shù)會(huì)調(diào)用_Block_object_dispose函數(shù),自動(dòng)釋放引用的auto變量缭受。

block內(nèi)修改變量的值

局部變量

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int a = 5;
        void(^blk)(void) = ^{
            a = 10;
        };

    }
    return 0;
}

block不能修改外部的局部變量,age是在main函數(shù)內(nèi)部聲明的胁澳,說(shuō)明age的內(nèi)存存在于main函數(shù)的椄没ィ空間內(nèi)部米者,但是block內(nèi)部的代碼在__main_block_func_0函數(shù)內(nèi)部。__main_block_func_0函數(shù)內(nèi)部無(wú)法訪問(wèn)age變量的內(nèi)存空間宇智,兩個(gè)函數(shù)的椔悖空間不一樣,__main_block_func_0內(nèi)部拿到的age是block結(jié)構(gòu)體內(nèi)部的age随橘,因此無(wú)法在__main_block_func_0函數(shù)內(nèi)部去修改main函數(shù)內(nèi)部的變量喂分。
在最新的xcode中,已經(jīng)對(duì)這種情況進(jìn)行了錯(cuò)誤代碼提示


錯(cuò)誤代碼提示圖

修改方式
方式一:a使用static修飾机蔗。


int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        static int a = 5;
        void(^blk)(void) = ^{
            a = 10;
        };
        blk();
    }
    return 0;
}
-----
2021-09-29 14:40:10.580041+0800 OC-Review[71142:7289047] 修改前5
2021-09-29 14:40:10.581801+0800 OC-Review[71142:7289047] 修改后10

方式二:__block


int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        __block int a = 5;
        void(^blk)(void) = ^{
            a = 10;
        };
        NSLog(@"修改前%d",a);
        blk();
        NSLog(@"修改后%d",a);
    }
    return 0;
}
2021-09-29 14:42:12.904192+0800 OC-Review[71207:7291575] 修改前5
2021-09-29 14:42:12.905207+0800 OC-Review[71207:7291575] 修改后10

查看源碼:


struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

            (a->__forwarding->a) = 10;
}

首先被__block修飾的a變量聲明變?yōu)槊麨閍ge的__Block_byref_a_0結(jié)構(gòu)體蒲祈,也就是說(shuō)加上__block修飾的話捕獲到的block內(nèi)的變量為__Block_byref_a_0類型的結(jié)構(gòu)體。

__isa指針 :__Block_byref_age_0中也有isa指針也就是說(shuō)__Block_byref_age_0本質(zhì)也一個(gè)對(duì)象萝嘁。
__forwarding :__forwarding是__Block_byref_age_0結(jié)構(gòu)體類型的梆掸,并且__forwarding存儲(chǔ)的值為(__Block_byref_age_0 *)&age,即結(jié)構(gòu)體自己的內(nèi)存地址牙言。
__flags :0
__size :sizeof(__Block_byref_age_0)即__Block_byref_age_0所占用的內(nèi)存空間酸钦。
a :真正存儲(chǔ)變量的地方,這里存儲(chǔ)局部變量10咱枉。

__block將變量包裝成對(duì)象卑硫,然后在把a(bǔ)ge封裝在結(jié)構(gòu)體里面,block內(nèi)部存儲(chǔ)的變量為結(jié)構(gòu)體指針蚕断,也就可以通過(guò)指針找到內(nèi)存地址進(jìn)而修改變量的值欢伏。

循環(huán)引用

  1. 情景一
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.age = 10;
        person.block = ^{
            NSLog(@"%d",person.age);
        };
    }
    NSLog(@"大括號(hào)結(jié)束啦");
    return 0;
} 

可以發(fā)現(xiàn)大括號(hào)結(jié)束之后,person依然沒(méi)有被釋放亿乳,產(chǎn)生了循環(huán)引用
Person對(duì)象和block對(duì)象相互之間產(chǎn)生了強(qiáng)引用硝拧,導(dǎo)致雙方都不會(huì)被釋放,進(jìn)而造成內(nèi)存泄漏


循環(huán)引用示意
  1. 情景二
#import "Person.h"

@implementation Person


- (instancetype)init
{
    self = [super init];
    if (self) {
        self.block = ^{
            NSLog(@"%@",[self class]);
        };
    }
    return self;
}

這是開發(fā)中經(jīng)常遇到的一個(gè)場(chǎng)景。之前我們說(shuō)過(guò)block會(huì)捕獲局部河爹,上面的OC函數(shù)調(diào)用轉(zhuǎn)化為runtime代碼為
objc_msgSend(self,@selector(init)) 在OC的方法中 有2個(gè)隱藏參數(shù) self和_cmd 這2個(gè)參數(shù)作為函數(shù)的形參,在方法作用域中屬于局部變量 匠璧, 所以在block中使用self就滿足之前提到的 block會(huì)捕獲局部變量
查看源碼

struct __Person__init_block_impl_0 {
  struct __block_impl impl;
  struct __Person__init_block_desc_0* Desc;
  Person *self;
  __Person__init_block_impl_0(void *fp, struct __Person__init_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Person__init_block_func_0(struct __Person__init_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_sr_m_cfkwyx2h56vh4_kf65_vw40000gn_T_Person_3f840b_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
}

這里可以看到 __Person__init_block_impl_0結(jié)構(gòu)體中 創(chuàng)建了一個(gè)Person *self的強(qiáng)指針 指向init方法中self
指針?biāo)赶虻膒erson對(duì)象,使person引用計(jì)數(shù)+1 而person對(duì)block也有一個(gè)強(qiáng)引用咸这。這里就造成了循環(huán)引用夷恍。
解決方法
首先為了能隨時(shí)執(zhí)行block,我們肯定希望person對(duì)block對(duì)強(qiáng)引用媳维,而block內(nèi)部對(duì)person的引用為弱引用最好酿雪。
使用__weak 和 __unsafe_unretained修飾符可以解決循環(huán)引用的問(wèn)題。

  • __weak 和 __unsafe_unretained的區(qū)別
    a __weak不會(huì)產(chǎn)生強(qiáng)引用侄刽,指向的對(duì)象銷毀時(shí)指黎,會(huì)自動(dòng)將指針置為nil。因此一般通過(guò)__weak來(lái)解決問(wèn)題州丹。
    b __unsafe_unretained不會(huì)產(chǎn)生強(qiáng)引用醋安,不安全,指向的對(duì)象銷毀時(shí)墓毒,指針存儲(chǔ)的地址值不變吓揪。會(huì)造成野指針問(wèn)題
  1. 情景三
    在block中調(diào)用super也會(huì)造成循環(huán)引用 :
#import "Person.h"
@implementation Person
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.block = ^{
            [super init];
        };
    }
    return self;
}

查看源碼

static void __Person__init_block_func_0(struct __Person__init_block_impl_0 *__cself) {
  ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"));
}

當(dāng)使用[self class]時(shí),會(huì)調(diào)用objc_msgSend函數(shù)所计,第一個(gè)參數(shù)receiver就是self柠辞,而第二個(gè)參數(shù),要先找到self所在的這個(gè)class的方法列表

當(dāng)使用[super class]時(shí)主胧,會(huì)調(diào)用objc_msgSendSuper函數(shù)叭首,此時(shí)會(huì)先構(gòu)造一個(gè)__rw_objc_super的結(jié)構(gòu)體作為objc_msgSendSuper的第一個(gè)參數(shù)。 該結(jié)構(gòu)體第一個(gè)成員變量receiver仍然是self踪栋,而第二個(gè)成員變量super_class即是所在類的父類

struct __rw_objc_super {
    struct objc_object *object;
    struct objc_object *superClass;
    __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};

runtime對(duì)外暴露的類型為:

struct objc_super {
    __unsafe_unretained _Nonnull id receiver;

    __unsafe_unretained _Nonnull Class super_class;
};

結(jié)構(gòu)體第一個(gè)成員receiver 就代表方法的接受者  第二個(gè)成員代表方法接受者的父類

所以

self.block = ^{
    [super init];
};
轉(zhuǎn)化后是:
self.block = ^{
        struct objc_super  superInfo = {
            .receiver = self,
            .super_class = class_getSuperclass(objc_getClass("Person")),
        };

        ((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,@selector(init));
    };

可以很明顯的看到問(wèn)題焙格,block強(qiáng)引用了self,而self也強(qiáng)持有了這個(gè)block
解決方法:

#import "Person.h"
@implementation Person
- (instancetype)init
{
    self = [super init];
    if (self) {
        __weak __typeof(self) weakSelf = self;
        self.block = ^{
            [super init];
        };
    }
    return self;
}



?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末己英,一起剝皮案震驚了整個(gè)濱河市间螟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌损肛,老刑警劉巖厢破,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異治拿,居然都是意外死亡摩泪,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門劫谅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)见坑,“玉大人嚷掠,你說(shuō)我怎么就攤上這事≤衤浚” “怎么了不皆?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)熊楼。 經(jīng)常有香客問(wèn)我霹娄,道長(zhǎng),這世上最難降的妖魔是什么鲫骗? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任犬耻,我火速辦了婚禮,結(jié)果婚禮上执泰,老公的妹妹穿的比我還像新娘枕磁。我一直安慰自己,他們只是感情好术吝,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布计济。 她就那樣靜靜地躺著,像睡著了一般顿苇。 火紅的嫁衣襯著肌膚如雪峭咒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天纪岁,我揣著相機(jī)與錄音,去河邊找鬼则果。 笑死幔翰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的西壮。 我是一名探鬼主播遗增,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼款青!你這毒婦竟也來(lái)了做修?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤抡草,失蹤者是張志新(化名)和其女友劉穎饰及,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體康震,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡燎含,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腿短。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屏箍。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绘梦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赴魁,到底是詐尸還是另有隱情卸奉,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布颖御,位于F島的核電站择卦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏郎嫁。R本人自食惡果不足惜秉继,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泽铛。 院中可真熱鬧尚辑,春花似錦、人聲如沸盔腔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)弛随。三九已至瓢喉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舀透,已是汗流浹背栓票。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留愕够,地道東北人走贪。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像惑芭,于是被迫代替她去往敵國(guó)和親坠狡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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