1.atomic和nonatomic的區(qū)別?atomic一定是線程安全的嗎?atomic如何實(shí)現(xiàn)atomic唬渗?
atomic是原子操作(原子性是指事務(wù)的一個(gè)完整操作,操作成功就提交奋渔,否則回滾)镊逝,在iOS中g(shù)etter/setter函數(shù)是原子操作,如果多線程同時(shí)調(diào)用嫉鲸,不會(huì)出現(xiàn)某一個(gè)線程執(zhí)行完setter所有語句之前撑蒜,其他線程開始執(zhí)行setter,相當(dāng)于給setter函數(shù)加了鎖,確保原子性操作座菠,但是這樣做的弊端就是很消耗性能狸眼。
nonatomic是非原子操作,一般在不在并發(fā)的時(shí)候使用浴滴,這樣做的好處是會(huì)提高性能拓萌。在oc中通常對(duì)對(duì)象類型都聲明為非原子性。
在多線程中升略,atomic只保證getter微王、setter方法安全,并不保證其它操作品嚣,例如字符串拼接炕倘,數(shù)組移除元素等,并沒有執(zhí)行g(shù)etter和setter方法翰撑,顧不是絕對(duì)安全的罩旋。
2.內(nèi)存區(qū)域的劃分和分配,內(nèi)存分配方式有哪些 ?
內(nèi)存分配方式有三種:
- 從靜態(tài)存儲(chǔ)區(qū)域分配眶诈。內(nèi)存在程序編譯的時(shí)候就已經(jīng)分配好瘸恼,這塊內(nèi)存在程序的整個(gè)運(yùn)行期間都存在。例如全局變量册养,static變量东帅。
- 在棧上創(chuàng)建。在執(zhí)行函數(shù)時(shí)球拦,函數(shù)內(nèi)局部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建靠闭,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中坎炼,效率很高愧膀,但是分配的內(nèi)存容量有限。
- 從堆上分配谣光,亦稱動(dòng)態(tài)內(nèi)存分配檩淋。程序在運(yùn)行的時(shí)候用malloc或new申請(qǐng)任意多少的內(nèi)存,程序員自己負(fù)責(zé)在何時(shí)用free或delete釋放內(nèi)存萄金。動(dòng)態(tài)內(nèi)存的生存期由程序員決定蟀悦,使用非常靈活,但如果在堆上分配了空間氧敢,就有責(zé)任回收它日戈,否則運(yùn)行的程序會(huì)出現(xiàn)內(nèi)存泄漏,頻繁地分配和釋放不同大小的堆空間將會(huì)產(chǎn)生堆內(nèi)碎塊孙乖。
3.weak原理
- Runtime維護(hù)了一個(gè)weak表浙炼,用于存儲(chǔ)指向某個(gè)對(duì)象的所有weak指針份氧。weak表其實(shí)是一個(gè)hash(哈希)表,Key是所指對(duì)象的地址弯屈,Value是weak指針的地址(這個(gè)地址的值是所指對(duì)象指針的地址)數(shù)組蜗帜。
weak 的實(shí)現(xiàn)原理可以概括一下三步:
- 初始化時(shí):runtime會(huì)調(diào)用objc_initWeak函數(shù),初始化一個(gè)新的weak指針指向?qū)ο蟮牡刂贰?/li>
- 添加引用時(shí):objc_initWeak函數(shù)會(huì)調(diào)用 objc_storeWeak() 函數(shù)资厉, objc_storeWeak() 的作用是更新指針指向厅缺,創(chuàng)建對(duì)應(yīng)的弱引用表。
- 釋放時(shí)酌住,調(diào)用clearDeallocating函數(shù)店归。clearDeallocating函數(shù)首先根據(jù)對(duì)象地址獲取所有weak指針地址的數(shù)組阎抒,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil酪我,最后把這個(gè)entry從weak表中刪除,最后清理對(duì)象的記錄且叁。
4.談?wù)刢ategory和extension區(qū)別
- extension看起來很像一個(gè)匿名的category都哭,但是extension和有名字的category幾乎完全是兩個(gè)東西。 extension在編譯期決議,它就是類的一部分,在編譯期和頭文件里的@interface以及實(shí)現(xiàn)文件里的@implement一起形成一個(gè)完整的類仰剿,它伴隨類的產(chǎn)生而產(chǎn)生鳄梅,亦隨之一起消亡。extension一般用來隱藏類的私有信息次绘,你必須有一個(gè)類的源碼才能為一個(gè)類添加extension,所以你無法為系統(tǒng)的類比如NSString添加extension。(詳見2)
- 但是category則完全不一樣未妹,它是在運(yùn)行期決議的。就category和extension的區(qū)別來看空入,我們可以推導(dǎo)出一個(gè)明顯的事實(shí)络它,extension可以添加實(shí)例變量,而category是無法添加實(shí)例變量的(因?yàn)樵谶\(yùn)行期歪赢,對(duì)象的內(nèi)存布局已經(jīng)確定化戳,如果添加實(shí)例變量就會(huì)破壞類的內(nèi)部布局,這對(duì)編譯型語言來說是災(zāi)難性的)埋凯。
5.系統(tǒng)如何底層實(shí)現(xiàn)category
在runtime層点楼,category用結(jié)構(gòu)體category_t(在objc-runtime-new.h中可以找到此定義),它包含了 - 類的名字(name)
- 類(cls)
- category中所有給類添加的實(shí)例方法的列表(instanceMethods)
- category中所有添加的類方法的列表(classMethods)
- category實(shí)現(xiàn)的所有協(xié)議的列表(protocols)
- category中添加的所有屬性(instanceProperties)
typedef struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
} category_t;
- Objective-C的運(yùn)行是依賴OC的runtime的白对,而OC的runtime和其他系統(tǒng)庫一樣盟步,是OS X和iOS通過dyld動(dòng)態(tài)加載的。category被附加到類上面是在map_images的時(shí)候發(fā)生的躏结,在new-ABI的標(biāo)準(zhǔn)下却盘,_objc_init里面的調(diào)用的map_images最終會(huì)調(diào)用objc-runtime-new.mm里面的_read_images方法,而在_read_images方法的結(jié)尾:
1.把category的實(shí)例方法狰域、協(xié)議以及屬性添加到類上
2.把category的類方法和協(xié)議添加到類的metaclass上
- addUnattachedCategoryForClass只是把類和category做一個(gè)關(guān)聯(lián)映射,而remethodizeClass才是真正去處理添加事宜的功臣.而對(duì)于添加類的實(shí)例方法而言黄橘,又會(huì)去調(diào)用attachCategoryMethods這個(gè)方法兆览,我們?nèi)タ聪耡ttachCategoryMethods:attachCategoryMethods做的工作相對(duì)比較簡(jiǎn)單,它只是把所有category的實(shí)例方法列表拼成了一個(gè)大的實(shí)例方法列表塞关,然后轉(zhuǎn)交給了attachMethodLists方法.
需要注意的有兩點(diǎn):
1抬探、category的方法沒有“完全替換掉”原來類已經(jīng)有的方法,也就是說如果category和原來類都有methodA帆赢,那么category附加完成之后小压,類的方法列表里會(huì)有兩個(gè)methodA
2)、category的方法被放到了新方法列表的前面椰于,而原來類的方法被放到了新方法列表的后面怠益,這也就是我們平常所說的category的方法會(huì)“覆蓋”掉原來類的同名方法,這是因?yàn)檫\(yùn)行時(shí)在查找方法的時(shí)候是順著方法列表的順序查找的瘾婿,它只要一找到對(duì)應(yīng)名字的方法蜻牢,就會(huì)罷休_,殊不知后面可能還有一樣名字的方法偏陪。
6.談?wù)剬?duì)自動(dòng)釋放池的理解
- autorelease是一種支持引用計(jì)數(shù)的內(nèi)存管理方式抢呆,只要給對(duì)象發(fā)送一條autorelease消息,會(huì)將對(duì)象放到一個(gè)自動(dòng)釋放池中笛谦,當(dāng)自動(dòng)釋放池被銷毀時(shí)抱虐,會(huì)對(duì)池子里面的所有對(duì)象做一次release操作-
注意,這里只是發(fā)送release消息,如果當(dāng)時(shí)的引用計(jì)數(shù)(reference-counted)依然不為0饥脑,則該對(duì)象依然不會(huì)被釋放
- autorelease方法會(huì)返回對(duì)象本身恳邀,且調(diào)用完autorelease方法后,對(duì)象的計(jì)數(shù)器不變
7.autorelease的原理實(shí)質(zhì)上是什么?多層自動(dòng)釋放池嵌套的對(duì)象在哪一層釋放
- autorelease實(shí)際上只是把對(duì)release的調(diào)用延遲了好啰,對(duì)于每一個(gè)autorelease轩娶,系統(tǒng)只是把該對(duì)象放入了當(dāng)前的autorelease pool中,當(dāng)該pool被釋放時(shí),該pool中的所有對(duì)象會(huì)被調(diào)用release。
- 對(duì)于多層自動(dòng)釋放池嵌套使用框往,由于自動(dòng)釋放池是以棧的形式存在鳄抒,棧只有一個(gè)入口, 所以調(diào)用autorelease會(huì)將對(duì)象放到棧頂?shù)淖詣?dòng)釋放池。
@autoreleasepool { // 棧底自動(dòng)釋放池
@autoreleasepool {
@autoreleasepool { // 棧頂自動(dòng)釋放池
Person *p = [[[Person alloc] init] autorelease];
}
Person *p = [[[Person alloc] init] autorelease];
}
}
8.對(duì)于block椰弊,理解许溅,mrc和arc下有什么區(qū)別,使用注意事項(xiàng)
block本質(zhì)上也是一個(gè)oc對(duì)象秉版,他內(nèi)部也有一個(gè)isa指針贤重。block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象。
block對(duì)變量的捕獲規(guī)則:
1.靜態(tài)存儲(chǔ)區(qū)的變量:例如全局變量清焕、方法中的static變量引用并蝗,可修改祭犯。
2.block接受的參數(shù)
傳值,可修改滚停,和一般函數(shù)的參數(shù)相同沃粗。
3.棧變量 (被捕獲的上下文變量)
const,不可修改键畴。 當(dāng)block被copy后最盅,block會(huì)對(duì) id類型的變量產(chǎn)生強(qiáng)引用。
每次執(zhí)行block時(shí),捕獲到的變量都是最初的值起惕。
4.棧變量 (有__block前綴)
引用涡贱,可以修改。如果時(shí)id類型則不會(huì)被block retain,必須手動(dòng)處理其內(nèi)存管理惹想。
如果該類型是C類型變量问词,block被copy到heap后,該值也會(huì)被挪動(dòng)到heap
- 使用注意事項(xiàng):
注意1.內(nèi)存
Block_copy()和Block_release()必須一一匹配,否則會(huì)內(nèi)存泄漏或crash勺馆。__block這個(gè)修飾詞會(huì)將原本的簡(jiǎn)單類型轉(zhuǎn)化為較大的struct戏售,這會(huì)給內(nèi)存侨核、調(diào)用帶來額外的開銷草穆,使用時(shí)需要注意。
注意2.ARC
在開啟ARC后搓译,block的內(nèi)存會(huì)比較微妙悲柱。ARC會(huì)自動(dòng)處理block的內(nèi)存,不用手動(dòng)copy/release些己。但是豌鸡,和非ARC的情況有所不同:
void (^aBlock)(void);
aBlock = ^{ printf("ok"); };
block是對(duì)象,所以這個(gè)aBlock默認(rèn)是有__strong修飾符的段标,即aBlock對(duì)該block有strong references涯冠。即aBlock在被賦值的那一刻,這個(gè)block會(huì)被copy逼庞。所以蛇更,ARC開啟后,所能接觸到的block基本都是在堆上的赛糟。
注意3.循環(huán)引用
當(dāng)block被copy之后(如開啟了ARC派任、或把block放入dispatch queue),該block對(duì)它捕獲的對(duì)象產(chǎn)生strong references (非ARC下是retain),所以有時(shí)需要避免block copy后產(chǎn)生的循環(huán)引用璧南。如果用self引用了block掌逛,block又捕獲了self,這樣就會(huì)有循環(huán)引用司倚。因此豆混,需要用weak來聲明self篓像。
9.簡(jiǎn)述下block的實(shí)現(xiàn)
- block的常見類型有3種
_NSConcreteGlobalBlock(全局)
_NSConcreteStackBlock(棧)
_NSConcreteMallocBlock(堆)
void (^globalBlock)() = ^{
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^stackBlock1)() = ^{
};
}
return 0;
}
對(duì)其進(jìn)行編譯轉(zhuǎn)換后得到以下縮略代碼:
// globalBlock
struct __globalBlock_block_impl_0 {
struct __block_impl impl;
struct __globalBlock_block_desc_0* Desc;
__globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
...
// stackBlock
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
...
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*stackBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
}
return 0;
}
可以看出globalBlock的isa指向了_NSConcreteGlobalBlock,即在全局區(qū)域創(chuàng)建皿伺,編譯時(shí)具體的代碼就已經(jīng)確定在上圖中的代碼段中了遗淳,block變量存儲(chǔ)在全局?jǐn)?shù)據(jù)存儲(chǔ)區(qū);stackBlock的isa指向了_NSConcreteStackBlock心傀,即在棧區(qū)創(chuàng)建屈暗。
接下來是在堆中的block,堆中的block無法直接創(chuàng)建脂男,其需要由_NSConcreteStackBlock類型的block拷貝而來(也就是說block需要執(zhí)行copy之后才能存放到堆中)养叛。由于block的拷貝最終都會(huì)調(diào)用_Block_copy_internal函數(shù),所以觀察這個(gè)函數(shù)就可以知道堆中block是如何被創(chuàng)建的了:
函數(shù)通過memmove將棧中的block的內(nèi)容拷貝到了堆中宰翅,并使isa指向了_NSConcreteMallocBlock弃甥。
- 捕捉變量對(duì)block結(jié)構(gòu)的影響
局部變量
全局變量
局部靜態(tài)變量
__block修飾的變量
self隱式循環(huán)引用
局部變量只是一次值傳遞。并且當(dāng)我們想在block中進(jìn)行以下操作時(shí)汁讼,將會(huì)發(fā)生錯(cuò)誤:
- (void)test
{
int a;
^{a = 10;};//error: variable is not assignable(missing __block type specifier)
}
全局變量都是在靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū)淆攻,在程序結(jié)束前不會(huì)被銷毀,所以block直接訪問了對(duì)應(yīng)的變量
靜態(tài)局部變量是存儲(chǔ)在靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū)域的嘿架,也就是和程序擁有一樣的生命周期瓶珊,也就是說在程序運(yùn)行時(shí),都能夠保證block訪問到一個(gè)有效的變量耸彪。但是其作用范圍還是局限于定義它的函數(shù)中伞芹,所以只能在block通過靜態(tài)局部變量的地址來進(jìn)行訪問。
__block修飾的變量
會(huì)編譯成結(jié)構(gòu)體蝉娜,這個(gè)結(jié)構(gòu)體中含有isa指針唱较,所以也是一個(gè)對(duì)象,它是用來包裝局部變量a的召川。當(dāng)block被copy到堆中時(shí)block的拷貝輔助函數(shù)會(huì)將結(jié)構(gòu)體拷貝至堆中南缓,所以即使局部變量所在堆被銷毀,block依然能對(duì)堆中的局部變量進(jìn)行操作荧呐。其中變量的結(jié)構(gòu)體成員指針__forwarding用來指向它在堆中的拷貝汉形,
self隱式循環(huán)引用
@implementation Person
{
int _a;
void (^_block)();
}
- (void)test
{
void (^_block)() = ^{
_a = 10;
};
}
@end
如果在編譯轉(zhuǎn)換前,將_a改成self.a坛增,能很明顯地看出是產(chǎn)生了循環(huán)引用(self強(qiáng)引用block获雕,block強(qiáng)引用self)。那么使用_a呢收捣?經(jīng)過編譯轉(zhuǎn)換后届案,依然可以在__Person__test_block_impl_0看見self的身影“瞻可以看出楣颠,不管是用什么形式訪問實(shí)例變量尽纽,最終都會(huì)轉(zhuǎn)換成self+變量?jī)?nèi)存偏移的形式。
10.對(duì)于深拷貝和淺拷貝的理解
- 淺拷貝:指針拷貝童漩,復(fù)制一個(gè)新的指針弄贿,只想同一塊內(nèi)存區(qū)域。實(shí)際內(nèi)存并沒有發(fā)生拷貝
- 深拷貝:內(nèi)容拷貝矫膨,拷貝數(shù)據(jù)到一塊新內(nèi)存區(qū)域差凹,指針指向拷貝的數(shù)據(jù)區(qū)
iOS中的深淺拷貝strong,copy侧馅,mutableCopy危尿。
copy出來的字符串一定是不可變字符串,如果傳入的是可變字符串馁痴,會(huì)發(fā)生深拷貝為不可變字符串谊娇,否則為淺拷貝。 2. mutablecopy罗晕,一定是深拷貝济欢,拷貝出來的一定是可變字符串或者數(shù)組,即使傳入的是不可變字符串或者數(shù)組小渊。
11.消息轉(zhuǎn)發(fā)機(jī)制實(shí)現(xiàn)
- 進(jìn)行一次發(fā)送消息會(huì)在相關(guān)的類對(duì)象中搜索方法列表法褥,如果找不到則會(huì)沿著繼承樹向上一直搜索知道繼承樹根部(通常為NSObject),如果還是找不到并且消息轉(zhuǎn)發(fā)都失敗了就回執(zhí)行doesNotRecognizeSelector:方法報(bào)unrecognized selector錯(cuò)粤铭。
下圖就是消息轉(zhuǎn)發(fā)的流程:
第一次: 所屬類動(dòng)態(tài)方法解析
首先會(huì)調(diào)用+ (BOOL)resolveInstanceMethod:(SEL)sel
這個(gè)方法,如果返回True就會(huì)再次執(zhí)行相關(guān)方法.
第二次機(jī)會(huì): 備援接收者
其次當(dāng)對(duì)象所屬類不能動(dòng)態(tài)添加方法后挖胃,runtime就會(huì)詢問當(dāng)前的接受者是否有其他對(duì)象可以處理這個(gè)未知的selector杂靶,- (id)forwardingTargetForSelector:(SEL)aSelector;
該方法的參數(shù)就是那個(gè)未知的selector,這是一個(gè)實(shí)例方法吗垮,因?yàn)槭窃儐栐搶?shí)例對(duì)象是否有其他實(shí)例對(duì)象可以接收這個(gè)未知的selector垛吗,如果沒有就返回nil
第三次機(jī)會(huì): 消息重定向
當(dāng)沒有備援接收者時(shí)怯屉,就只剩下最后一次機(jī)會(huì),那就是消息重定向缅叠。這個(gè)時(shí)候runtime會(huì)將未知消息的所有細(xì)節(jié)都封裝為NSInvocation對(duì)象,然后調(diào)用下述方法:- (void)forwardInvocation: (NSInvocation*)invocation;
調(diào)用這個(gè)方法如果不能處理就會(huì)調(diào)用父類的相關(guān)方法领曼,一直到NSObject的這個(gè)方法瓢姻,如果NSObject都無法處理就會(huì)調(diào)用doesNotRecognizeSelector:方法拋出異常。
12.@property的底層實(shí)現(xiàn)
- 文件編譯成cpp文件后音诈,會(huì)看到@property會(huì)生成unsigned long類型的變量幻碱,代表實(shí)例變量在內(nèi)存中存儲(chǔ)的偏移量,通過這個(gè)值就能夠在內(nèi)存中定位到這個(gè)實(shí)例變量的位置细溅。
- get方法會(huì)使用類的內(nèi)存地址加上OBJC_IVAR__Person_cjmName計(jì)算的的偏移量來計(jì)算出屬性的位置并返回褥傍。
-
nsstring使用copy
的set方法會(huì)首先聲明objc_setProperty方法,然后使用類的內(nèi)存地址加上OFFSETOFIVAR(TYPE, MEMBER)方法計(jì)算的偏移量喇聊,用objc_setProperty來設(shè)置實(shí)例變量的值恍风。nsstring使用strong
的set方法沒有聲明objc_setProperty方法也沒有使用該方法,而是直接計(jì)算出實(shí)例變量的偏移量后將指針賦給實(shí)例變量誓篱。所以可以得出copy和strong在底層實(shí)現(xiàn)上是有區(qū)別的朋贬。