iOS-面試題1-NSObject本質(zhì)

目錄:

  1. NSObject本質(zhì)
  2. OC對象的分類
  3. isa指針和superclass指針+窺探Class
  4. KVO和KVC
  5. Category分類
  6. load和initialize方法
  7. 關(guān)聯(lián)對象
  8. block原理

一. NSObject本質(zhì)

  1. Objective-C的對象、類主要是基于C\C++的什么數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的期揪?
    Objective-C的對象奉芦、類主要是基于C\C++的結(jié)構(gòu)體實現(xiàn)的蔑滓,其內(nèi)部只有一個isa指針色解,點進入Class根悼,發(fā)現(xiàn)isa其實就是一個指向結(jié)構(gòu)體的指針却音,既然是指針楞抡,指針在64位系統(tǒng)占8個字節(jié)瓢湃,在32位系統(tǒng)占4字節(jié)理张。

  2. 兩個獲取內(nèi)存大小的函數(shù)
    ① size_t class_getInstanceSize(Class _Nullable cls)
    獲取實例對象的成員變量所占用內(nèi)存大小(內(nèi)存對齊后的)-> 其實就是實例對象至少占用的內(nèi)存大小绵患。
    sizeof同上雾叭,返回的是傳入類型至少占用的內(nèi)存大小,sizeof是個運算符落蝙。
    ② size_t malloc_size(const void *ptr)
    獲取指針?biāo)赶騼?nèi)存的大小 -> 其實就是實例對象實際占用的內(nèi)存大小织狐。

  3. 一個NSObject對象占用多少內(nèi)存?
    首先我們要知道指針占用8字節(jié)筏勒,int類型數(shù)據(jù)占用4字節(jié)移迫。
    系統(tǒng)分配了16個字節(jié)給NSObject對象(通過malloc_size函數(shù)獲得)
    但NSObject對象內(nèi)部只使用了8個字節(jié)的空間(64bit環(huán)境下,可以通過class_getInstanceSize函數(shù)獲得)管行。

  4. 對于一個NSObject對象厨埋,為什么我們需要8字節(jié),系統(tǒng)卻給我們分配16字節(jié)呢?
    OC對象至少占用16個字節(jié)捐顷,可查看源碼驗證荡陷,這是系統(tǒng)的硬性規(guī)定。
    分析對象內(nèi)存的時候不要忘記結(jié)構(gòu)體內(nèi)存對齊(結(jié)構(gòu)體的大小必須是最大成員大小的倍數(shù)迅涮,一般是8)和iOS內(nèi)存對齊(對象內(nèi)存大小必須是16的倍數(shù))

博客地址:iOS-底層-NSObject本質(zhì)

二. OC對象的分類

  1. OC對象分為幾種废赞?
    Objective-C中的對象,簡稱OC對象叮姑,主要可以分為3種:
    instance對象(實例對象)唉地、class對象(類對象)、meta-class對象(元類對象)

  2. 關(guān)于instance對象
    instance對象就是通過類alloc出來的對象,每次調(diào)用alloc都會產(chǎn)生新的instance對象渣蜗,分別占據(jù)著兩塊不同的內(nèi)存屠尊,instance對象在內(nèi)存中存儲的信息包括:

isa指針和其他成員變量。

  1. 關(guān)于class對象
    每個類在內(nèi)存中有且只有一個class對象耕拷,獲取class對象的三種方式:

Class objectClass1 = [object1 class]; //對象的class方法
Class objectClass2 = [NSObject class]; //類的class方法
Class objectClass3 = object_getClass(object1); //傳入對象

class對象在內(nèi)存中存儲的信息主要包括:

isa指針
superclass指針
類的屬性信息(@property)
類的成員變量信息(ivar)
類的協(xié)議信息(protocol)

類的對象方法信息(instance method)
......

  1. 關(guān)于meta-class對象
    每個類在內(nèi)存中有且只有一個meta-class對象讼昆,獲取meta-class對象只有一種方法:
Class objectMetaClass = object_getClass([NSObject class]);//傳入一個class對象

將類對象當(dāng)做參數(shù)傳入object_getClass,獲得元類對象骚烧。類對象和元類對象都是Class類型的浸赫。

meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的,但是用途不一樣赃绊,在內(nèi)存中存儲的信息主要包括:

isa指針
superclass指針
類的類方法信息(class method)
......

  1. object_getClass的底層實現(xiàn)
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

可以發(fā)現(xiàn)既峡,返回的是isa,所以:
如果傳進來的是instance對象碧查,返回class類對象
如果傳進來的是class類對象运敢,返回metra-class元類對象
如果傳進來的是metra-class對象,返回NSObject(基類)的metra-class對象

  1. - (Class)class忠售、+ (Class)class的底層實現(xiàn)
 - (Class) {
     return self->isa;
 }
//如果是對象方法,返回isa就返回了當(dāng)前的類對象

 + (Class) {
     return self;
 }
//如果是類方法,返回自己就是類對象

綜上传惠,這兩個class方法返回的就是類對象。

博客地址:iOS-底層-OC對象的分類

三. isa指針和superclass指針+窺探Class

  1. isa指針和superclass指針的作用
isa和superclass.png

關(guān)于isa指針:
① instance的isa指向class
② class的isa指向meta-class
③ meta-class的isa指向基類(NSObject)的meta-class
④ 基類(NSObject)的meta-class的isa指向它自己

關(guān)于superclass指針:
① class的superclass指向父類的class
② 如果沒有父類稻扬,superclass指針為nil卦方,(最后一直找不到方法會報錯:unrecognized selector sent to instance/class)
③ meta-class的superclass指向父類的meta-class
④ 基類的meta-class的superclass指向基類的class

  1. 方法調(diào)用軌跡

① instance實例對象調(diào)用對象方法的軌跡:實例對象的isa找到class,方法不存在泰佳,就通過superclass找父類盼砍。
② class類對象調(diào)用類方法的軌跡:
類對象的isa找meta-class,方法不存在逝她,就通過superclass找父類浇坐。

可以發(fā)現(xiàn),不管是類對象還是實例對象黔宛,調(diào)用流程都是:
isa -> superclass -> suerpclass -> superclass -> .... superclass -> nil

  1. 窺探Class結(jié)構(gòu)
窺探struct objc_class的結(jié)構(gòu).png

現(xiàn)在我們明白了吗跋,無論是類對象還是元類對象,他們在內(nèi)存中的結(jié)構(gòu)都是objc_class這種結(jié)構(gòu)體類型的宁昭,但是他們存儲的信息不一樣跌宛,比如對于元類對象,只有isa积仗、superclass疆拘、類方法有值,其他的都是空寂曹。

  1. OC的類信息存放在哪里哎迄?
    ① 屬性回右、成員變量、協(xié)議漱挚、對象方法翔烁,存放在class對象中
    ② 類方法,存放在meta-class對象中
    ③ 成員變量的具體值旨涝,存放在instance對象中

博客地址:iOS-底層-isa指針和superclass指針+窺探Class

四. KVO和KVC

  1. 給person添加監(jiān)聽
使用KVO.png

① 使用KVO蹬屹,系統(tǒng)會使用Runtime動態(tài)創(chuàng)建的一個NSKVONotifying_MJPerson類,這個類是MJPerson的子類白华。
② 添加監(jiān)聽的屬性的值改變的時候慨默,會調(diào)用NSKVONotifying_MJPerson類的setAge方法,setAge方法里面會調(diào)用_NSSetIntValueAndNotify方法弧腥,_NSSetIntValueAndNotify里面走如下步驟:
willChangeValueForKey 將要改變
setAge(原來的set方法) 真的去改變
didChangeValueForKey 已經(jīng)改變
observeValueForKeyPath:ofObject:change:context: 監(jiān)聽到MJPerson的age屬性改變了

  • 如果你自己寫了這個類厦取,就會報動態(tài)生成失敗。
  • KVO效率沒代理高管搪,因為代理是直接調(diào)用虾攻,KVO還要動態(tài)生成一個類。
  1. NSKVONotifying_MJPerson類對象的isa指向哪里更鲁?
    NSKVONotifying_MJPerson是MJPerson的子類霎箍,所以NSKVONotifying_MJPerson類對象的isa也是指向它自己的元類對象。

  2. 為什么重寫class岁经、dealloc朋沮、isKVO方法蛇券?
    在上圖中我們可以看出缀壤,創(chuàng)建NSKVONotifying_MJPerson之后會重寫setAge、class纠亚、dealloc塘慕、isKVO 這四個方法,setAge方法我們知道為什么重寫蒂胞,但是為什么要重寫后面三個方法呢图呢?
    因為NSKVONotifying_MJPerson是內(nèi)部創(chuàng)建的,不想讓用戶看到骗随,所以用戶調(diào)用class方法要把NSKVONotifying_MJPerson轉(zhuǎn)成MJPerson蛤织,所以系統(tǒng)才重寫了class方法。使用object_getClass函數(shù)(RuntimeAPI)獲取的就是真實的鸿染,不會被轉(zhuǎn)成MJPerson指蚜。

  3. 如何手動觸發(fā)KVO?(就算沒有人修改age值涨椒,也想觸發(fā)監(jiān)聽方法observeValueForKeyPath)
    手動調(diào)用willChangeValueForKey:和didChangeValueForKey:方法摊鸡。

  4. 直接修改成員變量會觸發(fā)KVO嗎绽媒?
    不會觸發(fā)KVO,因為沒調(diào)用重寫后的set方法免猾。

  5. 通過KVC給屬性賦值會觸發(fā)KVO嗎是辕?為什么?
    會猎提,其實KVC內(nèi)部調(diào)用了下面方法才會觸發(fā)KVO的:

[person willChangeValueForKey:@"age"];
person->_age = 10;
[person didChangeValueForKey:@"age"];
  1. KVC設(shè)值原理
設(shè)值原理.png
  1. KVC取值原理
取值原理.png

博客地址:iOS-底層-KVO和KVC

五. Category分類

  1. Category的使用場合是什么获三?
    主要是給一些類添加新的方法,或者拆分類忧侧。

  2. 分類的底層結(jié)構(gòu)是什么石窑?
    分類的底層結(jié)構(gòu)是_category_t結(jié)構(gòu)體:

struct _category_t {
    const char *name; //名稱
    struct _class_t *cls;
    const struct _method_list_t *instance_methods; //對象方法
    const struct _method_list_t *class_methods; //類方法
    const struct _protocol_list_t *protocols; //協(xié)議
    const struct _prop_list_t *properties; //屬性
};

所以分類的功能我們也知道了,分類可以添加屬性蚓炬、協(xié)議松逊、對象方法、類方法肯夏。

  1. Category的實現(xiàn)原理是什么经宏?
    ① 編譯完成之后,分類里的所有東西都在category_t結(jié)構(gòu)體中驯击,暫時和類是分開的
    ② 運行的時候烁兰,通過Runtime加載某個類的所有Category數(shù)據(jù)
    ③ 把所有Category的屬性、協(xié)議徊都、方法數(shù)據(jù)沪斟,合并到一個大數(shù)組中,后面參與編譯的Category數(shù)據(jù)暇矫,會在數(shù)組的前面
    ④ 將合并后的分類數(shù)據(jù)(屬性主之、協(xié)議、方法)李根,插入到類原來數(shù)據(jù)的前面

  2. Category和類擴展有什么區(qū)別呢槽奕?
    類擴展就是在類的.m里面添加一些屬性,成員變量房轿,方法聲明粤攒。其實類擴展就是相當(dāng)于將原來寫在.h文件里面的東西剪掉放到.m里面,把原來公開的屬性囱持,成員變量夯接,方法聲明私有化。所以纷妆,類擴展里面的東西在編譯的時候就已經(jīng)存在類對象里面了盔几,這點和分類不同。

博客地址:Category分類

六. load和initialize方法

  1. +load方法
    調(diào)用時機:+load方法會在Runtime加載類凭需、分類時調(diào)用问欠。
    調(diào)用順序:先調(diào)用父類的+load肝匆,后調(diào)用子類的+load,再調(diào)用分類的+load顺献,并且先編譯的先調(diào)用旗国。
    調(diào)用方式:根據(jù)函數(shù)地址直接調(diào)用。
    調(diào)用次數(shù):每個類注整、分類的+load方法能曾,在程序運行過程中只調(diào)用一次。
  • +load方法是根據(jù)方法地址直接調(diào)用肿轨,并不是經(jīng)過objc_msgSend函數(shù)調(diào)用(通過isa和superclass找方法)寿冕,所以不會存在方法覆蓋的問題。
  • 上面我們都沒有主動調(diào)用過+load方法椒袍,都是讓系統(tǒng)自動調(diào)用驼唱,系統(tǒng)會根據(jù)+load方法地址,直接調(diào)用驹暑。如果我們主動調(diào)用了+load方法玫恳,那走的就是objc_msgSend函數(shù)調(diào)用(通過isa和superclass找方法)這一套了。
  1. +initialize方法
    調(diào)用時機:+initialize方法會在類第一次接收到消息時調(diào)用(走的也是objc_msgSend這一套機制)优俘。
    調(diào)用順序:先調(diào)用父類的+initialize京办,再調(diào)用子類的+initialize(先初始化父類,再初始化子類)帆焕。如果子類沒有實現(xiàn)+initialize惭婿,會調(diào)用父類的+initialize(所以父類的+initialize可能會被調(diào)用多次)。如果分類實現(xiàn)了+initialize叶雹,就會覆蓋類本身的+initialize調(diào)用财饥。
    調(diào)用方式:通過objc_msgSend調(diào)用。
    調(diào)用次數(shù):每個類只會初始化一次浑娜。

博客地址:+load和+initialize方法

七. 關(guān)聯(lián)對象

  1. 分類可以添加屬性嗎佑力?成員變量呢式散?
    分類可以添加屬性筋遭,這從分類的底層結(jié)構(gòu)也可以看出來,但是在分類中添加屬性只會生成set暴拄、get方法的聲明漓滔,不會生成_開頭的成員變量,也不會生成set乖篷、get方法的實現(xiàn)响驴。
    分類中不能直接添加成員變量,否則報錯撕蔼,其實從分類的底層結(jié)構(gòu)也能看出來豁鲤,分類并沒有存放成員變量的地方秽誊。

  2. 如何給分類添加成員變量?
    可以使用關(guān)聯(lián)對象間接實現(xiàn)Category有成員變量的效果琳骡。

  3. 使用關(guān)聯(lián)對象給分類添加成員變量

//name
- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, @selector(name));
}
  • 理論上給什么對象添加關(guān)聯(lián)都可以锅论,上面我們是給實例對象添加關(guān)聯(lián)對象,因為每個實例對象都不一樣楣号。如果給類對象添加關(guān)聯(lián)對象最易,也是可以的,但是由于類對象只有一個炫狱,添加的關(guān)聯(lián)對象也是唯一的藻懒,這樣做沒什么意義(MJExtension中有給MJProperty類對象添加關(guān)聯(lián)對象)。

  • _cmd是當(dāng)前方法的@selector视译,就是@selector(當(dāng)前方法名)嬉荆。
    其實每個方法都有兩個隱式參數(shù):self和_cmd,比如上面的name方法也可以寫成:

 - (NSString *)name:(id)self _cmd:(SEL)_cmd
 {
 // 隱式參數(shù)
 // _cmd == @selector(name)
 return objc_getAssociatedObject(self, _cmd);
 }
  1. 關(guān)聯(lián)對象的原理
    ① 關(guān)聯(lián)對象不是存儲在原來實例對象和類對象里面酷含,關(guān)聯(lián)對象是通過Runtime實現(xiàn)的员寇。
    ② 關(guān)聯(lián)對象存儲在全局的統(tǒng)一的一個AssociationsManager中。
  • 設(shè)置某個關(guān)聯(lián)對象為nil第美,就相當(dāng)于是移除某個關(guān)聯(lián)對象
  • 移除所有的關(guān)聯(lián)對象用objc_removeAssociatedObjects

博客地址:關(guān)聯(lián)對象

八. block原理

block1-底層結(jié)構(gòu)蝶锋、變量捕獲、類型

  1. 什么是block什往?
    block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象

  2. 驗證上面的結(jié)論

block的本質(zhì).png

如上圖所示扳缕,block底層就是一個__main_block_impl_0結(jié)構(gòu)體,它由三個部分組成:
① 第一部分是impl别威,它是個結(jié)構(gòu)體躯舔,里面有isa指針和FuncPtr指針,F(xiàn)uncPtr指針指向__main_block_func_0函數(shù)省古,這個函數(shù)里面封裝了block需要執(zhí)行的代碼粥庄。
② 第二部分是desc,它是個指針豺妓,指向__main_block_desc_0結(jié)構(gòu)體惜互,它里面有一個Block_size用來保存block的大小。
③ 第三部分是age琳拭,它把外面訪問的成員變量age封裝到自己里面了训堆。

  1. block如何進行變量捕獲?
    ① 如果是被auto修飾的局部變量白嘁,會被捕獲坑鱼,是值傳遞
    ② 如果是被static修飾的局部變量,會被捕獲絮缅,是指針傳遞
    ③ 如果是全局變量鲁沥,不會被捕獲呼股,因為可以直接訪問
  • auto自動變量,離開作用域就銷毀画恰,默認(rèn)省略auto卖怜。比如我們常見的 int age = 10,其實就是默認(rèn)省略了auto阐枣,本來應(yīng)該是auto int age = 10马靠。
  1. block中會捕獲self嗎?
    block中會捕獲self蔼两,并且是指針捕獲甩鳄。既然被捕獲,就說明self是局部變量额划,為什么self是局部變量呢妙啃?其實每個方法都兩個隱式參數(shù),一個是self一個是_cmd俊戳,self是方法調(diào)用者揖赴,_cmd是方法名,既然self被當(dāng)做參數(shù)了抑胎,那self肯定是局部變量了燥滑。self都被捕獲了,肯定可以獲取到self中的其他信息了阿逃。

接下來我們講的都是在MRC環(huán)境下铭拧。

  1. block有三種類型,最終都是繼承于NSBlock類型
    __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
    __NSMallocBlock__ ( _NSConcreteMallocBlock )
    __NSStackBlock__ ( _NSConcreteStackBlock )

  2. 三種block在內(nèi)存中的分布

iOS程序的內(nèi)存布局:
iOS程序的內(nèi)存布局

三種block在內(nèi)存中的分布:
三種block內(nèi)存分配.png

現(xiàn)在我們知道了三種block在內(nèi)存中的分布:

__NSGlobalBlock__是放在數(shù)據(jù)段的恃锉,也就是和全局變量放在一起搀菩。
__NSMallocBlock__是放在堆區(qū)的,和一般的OC對象一樣的破托,需要我們自己管理內(nèi)存肪跋。
__NSStackBlock__是放在棧區(qū)的,和局部變量是一樣的土砂,系統(tǒng)自動管理內(nèi)存州既。

  1. 什么樣的block才是這三種block的某一種類型呢?
block類型 環(huán)境
__NSGlobalBlock__ 沒有訪問auto變量
__NSMallocBlock__ __NSStackBlock__調(diào)用了copy
__NSStackBlock__ 訪問了auto變量
  • GlobalBlock沒有訪問auto變量瘟芝,這種類型的block都可用方法代替易桃,不常用褥琐。
  • StackBlock訪問了auto變量锌俱,放在棧區(qū),這時候block捕獲了auto變量的值敌呈,然后存儲在block結(jié)構(gòu)體內(nèi)部贸宏,棧區(qū)是系統(tǒng)自動管理的造寝,所以在代碼塊結(jié)束之后,block內(nèi)存會被銷毀吭练,這時候block結(jié)構(gòu)體內(nèi)部的值就是亂七八糟的了诫龙,block就會有問題,如何解決這個問題呢鲫咽?
    可以把block從棧放到堆里面签赃,每一種類型的block調(diào)用copy后的結(jié)果如下所示:
block類型 副本源的配置存儲域 復(fù)制效果
__NSGlobalBlock__ 程序的數(shù)據(jù)區(qū)段 什么也不做
__NSMallocBlock__ 引用計數(shù)器增加
__NSStackBlock__ 從棧復(fù)制到堆

將上面的NSStackBlock加個copy變成NSMallocBlock,block就能正常使用了分尸。

博客地址:底層結(jié)構(gòu)锦聊、變量捕獲、類型

block2-copy操作箩绍、對象類型的auto變量孔庭、__block

  1. 在ARC環(huán)境下,編譯器會根據(jù)情況自動將棧上的block復(fù)制到堆上材蛛,比如以下情況:
    ① block作為函數(shù)返回值時
    ② 將block賦值給__strong指針時
    ③ block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時
    ④ block作為GCD API的方法參數(shù)時

  2. block如何捕獲對象類型的auto變量圆到?如何進行內(nèi)存管理的?

無論MRC卑吭、ARC芽淡,當(dāng)block內(nèi)部訪問了對象類型的auto變量時(這時就是__NSStackBlock__,放在棧區(qū)):

① 如果block是在棧上豆赏,將不會對auto變量產(chǎn)生強引用
② 如果棧上的block被拷貝到堆上(自己拷貝的或者ARC下系統(tǒng)自動拷貝的)
會調(diào)用block內(nèi)部的copy函數(shù)
copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
_Block_object_assign函數(shù)會根據(jù)auto變量的修飾符(__strong吐绵、__weak、__unsafe_unretained)做出相應(yīng)的操作河绽,形成強引用(retain)或者弱引用
③ 如果堆上的block被移除
會調(diào)用block內(nèi)部的dispose函數(shù)
dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
_Block_object_dispose函數(shù)會自動釋放引用的auto變量(或者release)

  1. block內(nèi)部如何修改auto變量值己单?
    ① 使用static修飾
    ② 使用全局變量
    ③ 使用__block修飾:
    __block可以用于解決block內(nèi)部無法修改auto變量值的問題,編譯器會將__block變量包裝成一個對象耙饰。
    __block不能修飾全局變量纹笼、靜態(tài)變量(static)(因為__block的作用就是上句??)。

  2. 為什么__block修飾的auto變量可以修改變量值苟跪?
    使用__block修飾age廷痘,會將age包裝成__Block_byref_age_0結(jié)構(gòu)體(對象),對象里面存著isa件已,對象的地址笋额,對象的大小,age的值篷扩,然后通過對象里面的__forwarding指針拿到自己兄猩,再拿到自己的age值,進行修改。如果沒修改外面的變量就不要加__block枢冤,因為又包裝了一層對象鸠姨,等用到的時候再加。

  3. 執(zhí)行下面代碼會報錯嗎淹真?

NSMutableArray *arr = [NSMutableArray array];
MJBlock block = ^{
    [arr addObject:@"123"];
};

回答:不會讶迁。因為“ [arr addObject:@"123"];”是使用arr而不是修改它的值(例如:arr = nil)。

  1. 使用__block修飾變量的確可以達到修改變量的值的目的核蘸,如果要再次訪問變量巍糯,到底訪問的是__Block_byref_age_0結(jié)構(gòu)體還是結(jié)構(gòu)體里面的“int age”呢?
    使用__block修飾變量,再次訪問客扎,訪問的是__Block_byref_age_0里面的int age變量鳞贷。

現(xiàn)在想一下,為什么蘋果要設(shè)計成訪問變量(使用了__block修飾)直接訪問的就是__Block_byref_age_0里面的變量呢虐唠?
因為對于一般的開發(fā)者來說搀愧,可能不知道被__block修飾后還會包裝一次,就像KVO蘋果重寫class方法一樣疆偿,是不讓開發(fā)者知道有這么個操作咱筛,所以你訪問變量,就把里面那個變量的地址給你了杆故。

  1. __forwarding指針的作用

我們知道__Block_byref_age_0結(jié)構(gòu)體中的__forwarding指針存的是自己的地址迅箩,當(dāng)我們想要訪問age,需要先通過age結(jié)構(gòu)體中的__forwarding獲取自己处铛,然后再獲取age饲趋,為什么設(shè)計這么奇怪呢?

回答:如果棧上的block復(fù)制到堆上了撤蟆,那么棧上堆上肯定都有一塊內(nèi)存奕塑。如果我們想把20賦值到堆上的block,如果不用__forwarding指針家肯,訪問棧上的block龄砰,就會把20賦值到棧上的block。如果使用__forwarding指針讨衣,不管訪問的block在哪换棚,最后賦值到的一定是堆上的block。

博客地址:block2-copy操作反镇、對象類型的auto變量固蚤、__block

block3-__block變量的內(nèi)存管理、__forwarding歹茶、__block修飾的對象類型夕玩、循環(huán)引用

  1. 對象類型的auto變量和__block變量內(nèi)存管理的區(qū)別你弦?

有如下三個auto變量:

int no = 20;

__block int age = 10;

NSObject *object = [[NSObject alloc] init];
__weak NSObject *weakObject = object;

MJBlock block = ^{
  age = 20;
  
  NSLog(@"%d", no);
  NSLog(@"%d", age);
  NSLog(@"%p", weakObject);
};

捕獲之后,結(jié)構(gòu)體多了三個成員风秤,分別對應(yīng)捕獲的三個auto變量鳖目,如下??:

 int no;  
 __Block_byref_age_0 *age; // by ref
 NSObject *__weak weakObject;  

相同點
① 當(dāng)block在棧上時扮叨,對它們都不會產(chǎn)生強引用
② 當(dāng)block拷貝到堆上時缤弦,都會通過copy函數(shù)來處理它們
③ 當(dāng)block從堆上移除時,都會通過dispose函數(shù)來釋放它們

不同點
① 對于對象類型的auto變量彻磁,_Block_object_assign函數(shù)會根據(jù)auto變量的修飾符(__strong碍沐、__weak、__unsafe_unretained)做出相應(yīng)的操作衷蜓,形成強引用(retain)或者弱引用
② 對于__block變量:_Block_object_assign函數(shù)只會產(chǎn)生強引用(retain)

  1. __block修飾的對象類型的內(nèi)存管理

① 當(dāng)__block變量在棧上時累提,不會對指向的對象產(chǎn)生強引用
② 當(dāng)__block變量被copy到堆時(自己拷貝的或者ARC下系統(tǒng)自動拷貝的)
會調(diào)用__block變量內(nèi)部的copy函數(shù)
copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
_Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃╛_strong、__weak磁浇、__unsafe_unretained)做出相應(yīng)的操作斋陪,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain,MRC時不會retain置吓,一直是弱的无虚。這個特例只會在MRC并且是__block修飾對象類型才有)
③ 如果__block變量從堆上移除
會調(diào)用__block變量內(nèi)部的dispose函數(shù)
dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
_Block_object_dispose函數(shù)會自動釋放指向的對象(release)

  1. ARC如何解決循環(huán)引用?
    用__weak解決
    用__unsafe_unretained解決

__weak:不會產(chǎn)生強引用衍锚,指向的對象銷毀時友题,會自動讓指針置為nil。
__unsafe_unretained:不會產(chǎn)生強引用戴质,不安全度宦,指向的對象銷毀時,指針存儲的地址值不變告匠,這時候如果再去訪問指針指向的地址就會報野指針錯誤戈抄。

  1. MRC如何解決循環(huán)引用?
    使用__unsafe_unretained解決

  2. block屬性的建議寫法后专?

MRC下block屬性的建議寫法
@property (copy, nonatomic) void (^block)(void);

ARC下block屬性的建議寫法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

① 使用copy會將棧上的block拷貝到堆上呛凶,如果不使用copy,block就不會拷貝到堆上行贪,因為MRC編譯器不會自動copy漾稀,所以只能用copy。
② 以前我們說過建瘫,ARC環(huán)境崭捍,并且把block賦值給強指針,編譯器會自動把block拷貝到堆上啰脚,所以ARC使用copy和strong都可以殷蛇,沒區(qū)別实夹。
③ 為了統(tǒng)一好記,我們統(tǒng)一對block使用copy

  1. __strong typeof(weakSelf) myself = weakSelf;為什么要這么寫粒梦?
__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) myself = weakSelf;
//報錯:
//Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to strong variable first
    NSLog(@"age is %d", myself->_age);
};

① 原因一亮航,如果在block內(nèi)部通過weakSelf->_age會報錯“弱指針不允許訪問,因為有可能為null匀们,讓你使用強指針”缴淋,所以我們就用__strong強指針來訪問self。
② 原因二泄朴,訪問self的時候我們使用一個臨時的強指針來訪問self重抖,這樣在整個block執(zhí)行期間,可以保證self對象不會被銷毀祖灰,同時钟沛,block調(diào)用完后,臨時的強指針被銷毀局扶,一切又回歸原來的樣子恨统。這樣既能保證整個block執(zhí)行期間,self對象不會被銷毀三妈,又能保證不會產(chǎn)生循環(huán)引用畜埋。

  1. block的原理是怎樣的?本質(zhì)是什么沈跨?
    block是封裝了函數(shù)調(diào)用以及調(diào)用環(huán)境的OC對象由捎,比如函數(shù)的調(diào)用地址、捕獲的變量都封裝到了里面饿凛。

  2. __block的作用是什么狞玛?有什么使用注意點?
    編譯器會將__block變量包裝成一個對象涧窒,就是__Block_byref_person_0這種結(jié)構(gòu)體心肪,可以解決block內(nèi)部無法修改auto變量的問題(自己思考為什么不能修改)。
    使用注意:__block變量內(nèi)部自己也會進行內(nèi)存管理纠吴,而且MRC環(huán)境下硬鞍,__block修飾對象,對象不會被retain的戴已。

  3. 為什么使用__block修飾auto變量固该,在block內(nèi)部就能修改此變量了?
    block內(nèi)部有個指針指向__Block_byref_person_0結(jié)構(gòu)體糖儡,通過訪問結(jié)構(gòu)體伐坏,再通過結(jié)構(gòu)體訪問變量進行修改的。

  4. block的屬性修飾詞為什么是copy握联?使用block有哪些使用注意桦沉?
    block一旦沒有進行copy操作每瞒,就不會在堆上。使用注意:循環(huán)引用問題纯露。

博客地址:block3-__block變量的內(nèi)存管理剿骨、__forwarding、__block修飾的對象類型埠褪、循環(huán)引用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浓利,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子组橄,更是在濱河造成了極大的恐慌荞膘,老刑警劉巖罚随,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玉工,死亡現(xiàn)場離奇詭異,居然都是意外死亡淘菩,警方通過查閱死者的電腦和手機遵班,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來潮改,“玉大人狭郑,你說我怎么就攤上這事』阍冢” “怎么了翰萨?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長糕殉。 經(jīng)常有香客問我亩鬼,道長,這世上最難降的妖魔是什么阿蝶? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任雳锋,我火速辦了婚禮,結(jié)果婚禮上羡洁,老公的妹妹穿的比我還像新娘玷过。我一直安慰自己,他們只是感情好筑煮,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布辛蚊。 她就那樣靜靜地躺著,像睡著了一般真仲。 火紅的嫁衣襯著肌膚如雪袋马。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音拴魄,去河邊找鬼。 笑死茎毁,一個胖子當(dāng)著我的面吹牛卧檐,可吹牛的內(nèi)容都是我干的墓懂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼霉囚,長吁一口氣:“原來是場噩夢啊……” “哼捕仔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盈罐,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤榜跌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后盅粪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钓葫,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年票顾,在試婚紗的時候發(fā)現(xiàn)自己被綠了础浮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡奠骄,死狀恐怖豆同,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情含鳞,我是刑警寧澤影锈,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蝉绷,受9級特大地震影響鸭廷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜潜必,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一靴姿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧磁滚,春花似錦佛吓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晒他,卻和暖如春吱型,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陨仅。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工津滞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铝侵,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓触徐,卻偏偏與公主長得像咪鲜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子撞鹉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345