目錄:
- NSObject本質(zhì)
- OC對象的分類
- isa指針和superclass指針+窺探Class
- KVO和KVC
- Category分類
- load和initialize方法
- 關(guān)聯(lián)對象
- block原理
一. NSObject本質(zhì)
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é)理张。兩個獲取內(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)存大小织狐。一個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ù)獲得)管行。對于一個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ù))
二. OC對象的分類
OC對象分為幾種废赞?
Objective-C中的對象,簡稱OC對象叮姑,主要可以分為3種:
instance對象(實例對象)唉地、class對象(類對象)、meta-class對象(元類對象)關(guān)于instance對象
instance對象就是通過類alloc出來的對象,每次調(diào)用alloc都會產(chǎn)生新的instance對象渣蜗,分別占據(jù)著兩塊不同的內(nèi)存屠尊,instance對象在內(nèi)存中存儲的信息包括:
isa指針和其他成員變量。
- 關(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)
......
- 關(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)
......
- 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對象
- - (Class)class忠售、+ (Class)class的底層實現(xiàn)
- (Class) {
return self->isa;
}
//如果是對象方法,返回isa就返回了當(dāng)前的類對象
+ (Class) {
return self;
}
//如果是類方法,返回自己就是類對象
綜上传惠,這兩個class方法返回的就是類對象。
博客地址:iOS-底層-OC對象的分類
三. isa指針和superclass指針+窺探Class
- isa指針和superclass指針的作用
關(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
- 方法調(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
- 窺探Class結(jié)構(gòu)
現(xiàn)在我們明白了吗跋,無論是類對象還是元類對象,他們在內(nèi)存中的結(jié)構(gòu)都是objc_class這種結(jié)構(gòu)體類型的宁昭,但是他們存儲的信息不一樣跌宛,比如對于元類對象,只有isa积仗、superclass疆拘、類方法有值,其他的都是空寂曹。
- OC的類信息存放在哪里哎迄?
① 屬性回右、成員變量、協(xié)議漱挚、對象方法翔烁,存放在class對象中
② 類方法,存放在meta-class對象中
③ 成員變量的具體值旨涝,存放在instance對象中
博客地址:iOS-底層-isa指針和superclass指針+窺探Class
四. KVO和KVC
- 給person添加監(jiān)聽
① 使用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)生成一個類。
NSKVONotifying_MJPerson類對象的isa指向哪里更鲁?
NSKVONotifying_MJPerson是MJPerson的子類霎箍,所以NSKVONotifying_MJPerson類對象的isa也是指向它自己的元類對象。為什么重寫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指蚜。如何手動觸發(fā)KVO?(就算沒有人修改age值涨椒,也想觸發(fā)監(jiān)聽方法observeValueForKeyPath)
手動調(diào)用willChangeValueForKey:和didChangeValueForKey:方法摊鸡。直接修改成員變量會觸發(fā)KVO嗎绽媒?
不會觸發(fā)KVO,因為沒調(diào)用重寫后的set方法免猾。通過KVC給屬性賦值會觸發(fā)KVO嗎是辕?為什么?
會猎提,其實KVC內(nèi)部調(diào)用了下面方法才會觸發(fā)KVO的:
[person willChangeValueForKey:@"age"];
person->_age = 10;
[person didChangeValueForKey:@"age"];
- KVC設(shè)值原理
- KVC取值原理
博客地址:iOS-底層-KVO和KVC
五. Category分類
Category的使用場合是什么获三?
主要是給一些類添加新的方法,或者拆分類忧侧。分類的底層結(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é)議松逊、對象方法、類方法肯夏。
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ù)的前面Category和類擴展有什么區(qū)別呢槽奕?
類擴展就是在類的.m里面添加一些屬性,成員變量房轿,方法聲明粤攒。其實類擴展就是相當(dāng)于將原來寫在.h文件里面的東西剪掉放到.m里面,把原來公開的屬性囱持,成員變量夯接,方法聲明私有化。所以纷妆,類擴展里面的東西在編譯的時候就已經(jīng)存在類對象里面了盔几,這點和分類不同。
博客地址:Category分類
六. load和initialize方法
- +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找方法)這一套了。
- +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)對象
分類可以添加屬性嗎佑力?成員變量呢式散?
分類可以添加屬性筋遭,這從分類的底層結(jié)構(gòu)也可以看出來,但是在分類中添加屬性只會生成set暴拄、get方法的聲明漓滔,不會生成_開頭的成員變量,也不會生成set乖篷、get方法的實現(xiàn)响驴。
分類中不能直接添加成員變量,否則報錯撕蔼,其實從分類的底層結(jié)構(gòu)也能看出來豁鲤,分類并沒有存放成員變量的地方秽誊。如何給分類添加成員變量?
可以使用關(guān)聯(lián)對象間接實現(xiàn)Category有成員變量的效果琳骡。使用關(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);
}
- 關(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)蝶锋、變量捕獲、類型
什么是block什往?
block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象驗證上面的結(jié)論
如上圖所示扳缕,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封裝到自己里面了训堆。
- block如何進行變量捕獲?
① 如果是被auto修飾的局部變量白嘁,會被捕獲坑鱼,是值傳遞
② 如果是被static修飾的局部變量,會被捕獲絮缅,是指針傳遞
③ 如果是全局變量鲁沥,不會被捕獲呼股,因為可以直接訪問
- auto自動變量,離開作用域就銷毀画恰,默認(rèn)省略auto卖怜。比如我們常見的 int age = 10,其實就是默認(rèn)省略了auto阐枣,本來應(yīng)該是auto int age = 10马靠。
- block中會捕獲self嗎?
block中會捕獲self蔼两,并且是指針捕獲甩鳄。既然被捕獲,就說明self是局部變量额划,為什么self是局部變量呢妙啃?其實每個方法都兩個隱式參數(shù),一個是self一個是_cmd俊戳,self是方法調(diào)用者揖赴,_cmd是方法名,既然self被當(dāng)做參數(shù)了抑胎,那self肯定是局部變量了燥滑。self都被捕獲了,肯定可以獲取到self中的其他信息了阿逃。
接下來我們講的都是在MRC環(huán)境下铭拧。
block有三種類型,最終都是繼承于NSBlock類型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )三種block在內(nèi)存中的分布
現(xiàn)在我們知道了三種block在內(nèi)存中的分布:
__NSGlobalBlock__是放在數(shù)據(jù)段的恃锉,也就是和全局變量放在一起搀菩。
__NSMallocBlock__是放在堆區(qū)的,和一般的OC對象一樣的破托,需要我們自己管理內(nèi)存肪跋。
__NSStackBlock__是放在棧區(qū)的,和局部變量是一樣的土砂,系統(tǒng)自動管理內(nèi)存州既。
- 什么樣的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就能正常使用了分尸。
block2-copy操作箩绍、對象類型的auto變量孔庭、__block
在ARC環(huán)境下,編譯器會根據(jù)情況自動將棧上的block復(fù)制到堆上材蛛,比如以下情況:
① block作為函數(shù)返回值時
② 將block賦值給__strong指針時
③ block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時
④ block作為GCD API的方法參數(shù)時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)
block內(nèi)部如何修改auto變量值己单?
① 使用static修飾
② 使用全局變量
③ 使用__block修飾:
__block可以用于解決block內(nèi)部無法修改auto變量值的問題,編譯器會將__block變量包裝成一個對象耙饰。
__block不能修飾全局變量纹笼、靜態(tài)變量(static)(因為__block的作用就是上句??)。為什么__block修飾的auto變量可以修改變量值苟跪?
使用__block修飾age廷痘,會將age包裝成__Block_byref_age_0結(jié)構(gòu)體(對象),對象里面存著isa件已,對象的地址笋额,對象的大小,age的值篷扩,然后通過對象里面的__forwarding指針拿到自己兄猩,再拿到自己的age值,進行修改。如果沒修改外面的變量就不要加__block枢冤,因為又包裝了一層對象鸠姨,等用到的時候再加。執(zhí)行下面代碼會報錯嗎淹真?
NSMutableArray *arr = [NSMutableArray array];
MJBlock block = ^{
[arr addObject:@"123"];
};
回答:不會讶迁。因為“ [arr addObject:@"123"];”是使用arr而不是修改它的值(例如:arr = nil)。
- 使用__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ā)者知道有這么個操作咱筛,所以你訪問變量,就把里面那個變量的地址給你了杆故。
- __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)引用
- 對象類型的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)
- __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)
- ARC如何解決循環(huán)引用?
用__weak解決
用__unsafe_unretained解決
__weak:不會產(chǎn)生強引用衍锚,指向的對象銷毀時友题,會自動讓指針置為nil。
__unsafe_unretained:不會產(chǎn)生強引用戴质,不安全度宦,指向的對象銷毀時,指針存儲的地址值不變告匠,這時候如果再去訪問指針指向的地址就會報野指針錯誤戈抄。
MRC如何解決循環(huán)引用?
使用__unsafe_unretained解決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
- __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)引用畜埋。
block的原理是怎樣的?本質(zhì)是什么沈跨?
block是封裝了函數(shù)調(diào)用以及調(diào)用環(huán)境的OC對象由捎,比如函數(shù)的調(diào)用地址、捕獲的變量都封裝到了里面饿凛。__block的作用是什么狞玛?有什么使用注意點?
編譯器會將__block變量包裝成一個對象涧窒,就是__Block_byref_person_0這種結(jié)構(gòu)體心肪,可以解決block內(nèi)部無法修改auto變量的問題(自己思考為什么不能修改)。
使用注意:__block變量內(nèi)部自己也會進行內(nèi)存管理纠吴,而且MRC環(huán)境下硬鞍,__block修飾對象,對象不會被retain的戴已。為什么使用__block修飾auto變量固该,在block內(nèi)部就能修改此變量了?
block內(nèi)部有個指針指向__Block_byref_person_0結(jié)構(gòu)體糖儡,通過訪問結(jié)構(gòu)體伐坏,再通過結(jié)構(gòu)體訪問變量進行修改的。block的屬性修飾詞為什么是copy握联?使用block有哪些使用注意桦沉?
block一旦沒有進行copy操作每瞒,就不會在堆上。使用注意:循環(huán)引用問題纯露。
博客地址:block3-__block變量的內(nèi)存管理剿骨、__forwarding、__block修飾的對象類型埠褪、循環(huán)引用