iOS面試之屬性關(guān)鍵字(二):常見(jiàn)面試題

Q:ARC下,不顯式指定任何屬性關(guān)鍵字時(shí),默認(rèn)的關(guān)鍵字都有哪些?

對(duì)應(yīng)基本數(shù)據(jù)類型默認(rèn)關(guān)鍵字是:atomic,readwrite,assign
對(duì)于普通的 Objective-C 對(duì)象:atomic,readwrite,strong

Q:atomic 修飾的屬性是怎么樣保存線程安全的?

答:編譯器會(huì)自動(dòng)生成互斥鎖,對(duì) setter 和 getter 方法進(jìn)行加鎖,可以保證屬性的賦值和取值原子性操作是線程安全的,但不包括操作和訪問(wèn)。
比如說(shuō)atomic修飾的是一個(gè)數(shù)組的話,那么我們對(duì)數(shù)組進(jìn)行賦值和取值是可以保證線程安全的。但是如果我們對(duì)數(shù)組進(jìn)行操作,比如說(shuō)給數(shù)組添加對(duì)象或者移除對(duì)象,是不在atomic的負(fù)責(zé)范圍之內(nèi)的,所以給被atomic修飾的數(shù)組添加對(duì)象或者移除對(duì)象是沒(méi)辦法保證線程安全的。

Q:什么情況使用 weak 關(guān)鍵字,相比 assign 有什么不同?

答:
在 ARC 中,在有可能出現(xiàn)循環(huán)引用的時(shí)候,往往要通過(guò)讓其中一端使用 weak 來(lái)解決,比如: delegate、block。
自身已經(jīng)對(duì)它進(jìn)行一次強(qiáng)引用,沒(méi)有必要再?gòu)?qiáng)引用一次,此時(shí)也會(huì)使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak,使用 storyboard(xib 不行)創(chuàng)建的 vc,會(huì)有一個(gè)叫 _topLevelObjectsToKeepAliveFromStoryboard 的私有數(shù)組強(qiáng)引用所有 top level 的對(duì)象,所以這時(shí)即便 outlet 聲明成 weak 也沒(méi)關(guān)系。當(dāng)然,也可以使用 strong。
weak 和 assign 的不同點(diǎn):
① weak只能修飾對(duì)象,而assign既可以修飾對(duì)象也可以修飾基本數(shù)據(jù)類型;
② assign修飾的對(duì)象在被釋放后,指針仍然指向原對(duì)象地址;而weak修飾的對(duì)象在被釋放之后會(huì)自動(dòng)置指針為 nil;
③ 相同點(diǎn):在修飾對(duì)象的時(shí)候,assign和weak都不改變對(duì)象的引用計(jì)數(shù)。

Q:weak修飾的釋放則自動(dòng)被置為nil的實(shí)現(xiàn)原理

答:weak 實(shí)現(xiàn)原理的概括:
Runtime維護(hù)了一個(gè)weak表,用于存儲(chǔ)指向某個(gè)對(duì)象的所有weak指針。weak表其實(shí)是一個(gè)hash(哈希)表,Key是所指對(duì)象的地址,Value是weak指針的地址(這個(gè)地址的值是所指對(duì)象的地址)數(shù)組。

weak 的實(shí)現(xiàn)原理可以概括一下三步:

1、初始化時(shí):runtime會(huì)調(diào)用objc_initWeak函數(shù),初始化一個(gè)新的weak指針指向?qū)ο蟮牡刂贰?br> 2、添加引用時(shí):objc_initWeak函數(shù)會(huì)調(diào)用 objc_storeWeak() 函數(shù), objc_storeWeak() 的作用是更新指針指向,創(chuàng)建對(duì)應(yīng)的弱引用表。
3、釋放時(shí),調(diào)用clearDeallocating函數(shù)。clearDeallocating函數(shù)首先根據(jù)對(duì)象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個(gè)entry從weak表中刪除,最后清理對(duì)象的記錄。

Q:為什么iOS開(kāi)發(fā)中,控件一般為weak而不是strong?

IBOutlet的屬性一般可以設(shè)為weak是因?yàn)樗呀?jīng)被view引用了,除非view被釋放,否則IBOutlet的屬性也不會(huì)被釋放,另外IBOutlet屬性的生命周期和view應(yīng)該是一致的,所以IBOutlet屬性一般設(shè)為weak。
簡(jiǎn)單的說(shuō),如果IBOutlet對(duì)象是nib/xib scene的擁有者(File’s owner)所持有的對(duì)象,那么很顯然擁有者必須“擁有”對(duì)象的指針,因此屬性應(yīng)設(shè)置為strong。而其他的IBOutlet對(duì)象的屬性需要設(shè)置為weak,因?yàn)閾碛姓卟⒉恍枰皳碛小彼麄兊闹羔槨?/p>

答:因?yàn)関iew被添加到superView上面后,就被superView持有了。我們一般在IB里面的拖的view都是加在了根view或者它的子view上。而根view又被它的controller持有,所以IBOutlet可以用weak。如果,在IB里面拖出來(lái)的view是一個(gè)單獨(dú)的view沒(méi)有被加到任何其他view上,則需要用strong。

Q:以下代碼會(huì)出現(xiàn)什么問(wèn)題?(深淺拷貝)

@property (copy) NSMutableArray *array;
答:
1.把NSMutableArray用copy修飾有時(shí)就會(huì)crash,因?yàn)閷?duì)這個(gè)數(shù)組進(jìn)行了增刪改操作,而copy后的數(shù)組變成了不可變數(shù)組NSArray,沒(méi)有響應(yīng)的增刪改方法,所以對(duì)其進(jìn)行增刪改操作就會(huì)報(bào)錯(cuò)。
2.默認(rèn)atomic修飾,編譯器會(huì)自動(dòng)生成互斥鎖,對(duì) setter 和 getter 方法進(jìn)行加鎖操作,這個(gè)過(guò)程會(huì)消耗一些性能,執(zhí)行效率慢,一般使用nonatomic修飾

Q:用@property聲明的NSString(或NSArray,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么?如果改用strong關(guān)鍵字,可能造成什么問(wèn)題?

1.因?yàn)楦割愔羔樋梢灾赶蜃宇悓?duì)象,使用 copy 的目的是為了讓本對(duì)象的屬性不受外界影響,使用 copy 無(wú)論給我傳入是一個(gè)可變對(duì)象還是不可對(duì)象,我本身持有的就是一個(gè)不可變的副本.
2.如果我們使用是 strong ,那么這個(gè)屬性就有可能指向一個(gè)可變對(duì)象,如果這個(gè)可變對(duì)象在外部被修改了,那么會(huì)影響該屬性.
copy 此特質(zhì)所表達(dá)的所屬關(guān)系與 strong 類似。然而設(shè)置方法并不保留新值,而是將其“拷貝” (copy)。

Q:@property 的本質(zhì)是什么?ivar、getter、setter 是如何生成并添加到這個(gè)類中的?

@property的本質(zhì)是 ivar(實(shí)例變量) + getter + setter.

attributes的具體內(nèi)容包含: 類型, 原子性, 內(nèi)存語(yǔ)義和對(duì)應(yīng)的實(shí)例變量.

我們每次在增加一個(gè)屬性, 系統(tǒng)都會(huì)在 ivar_list 中添加一個(gè)成員變量的描述, 在 method_list 中增加 setter 與 getter 方法的描述, 在屬性列表中增加一個(gè)屬性的描述, 然后計(jì)算該屬性在對(duì)象中的偏移量, 然后給出 setter 與 getter 方法對(duì)應(yīng)的實(shí)現(xiàn), 在 setter 方法中從偏移量的位置開(kāi)始賦值, 在 getter 方法中從偏移量開(kāi)始取值, 為了能夠讀取正確字節(jié)數(shù), 系統(tǒng)對(duì)象偏移量的指針類型進(jìn)行了類型強(qiáng)轉(zhuǎn).

Q:weak屬性需要在dealloc中置nil么?

不需要。
在ARC環(huán)境無(wú)論是強(qiáng)指針還是弱指針都無(wú)需在 dealloc 設(shè)置為 nil , ARC 會(huì)自動(dòng)幫我們處理
即便是編譯器不幫我們做這些,weak也不需要在 dealloc 中置nil:因?yàn)樵趯傩运傅膶?duì)象遭到摧毀時(shí),屬性值也會(huì)清空(nil out)。
當(dāng)一個(gè)被weak修飾的對(duì)象被釋放后,weak對(duì)象怎么處理的?
清除weak變量,同時(shí)設(shè)置指向?yàn)閚il。當(dāng)對(duì)象被dealloc釋放后,在dealloc的內(nèi)部實(shí)現(xiàn)中,會(huì)調(diào)用弱引用清除的相關(guān)函數(shù),會(huì)根據(jù)當(dāng)前對(duì)象指針查找弱引用表,找到當(dāng)前對(duì)象所對(duì)應(yīng)的弱引用數(shù)組,將數(shù)組中的所有弱引用指針都置為nil。

Q:說(shuō)說(shuō)你理解weak屬性?

Runtime維護(hù)了一個(gè)weak表,用于存儲(chǔ)指向某個(gè)對(duì)象的所有weak指針。weak表其實(shí)是一個(gè)hash(哈希)表,Key是所指對(duì)象的地址,Value是weak指針的地址(這個(gè)地址的值是所指對(duì)象的地址)數(shù)組。
1、初始化時(shí):runtime會(huì)調(diào)用objc_initWeak函數(shù),初始化一個(gè)新的weak指針指向?qū)ο蟮牡刂贰?br> 2、添加引用時(shí):objc_initWeak函數(shù)會(huì)調(diào)用 objc_storeWeak() 函數(shù), objc_storeWeak() 的作用是更新指針指向,創(chuàng)建對(duì)應(yīng)的弱引用表。
3、釋放時(shí),調(diào)用clearDeallocating函數(shù)。clearDeallocating函數(shù)首先根據(jù)對(duì)象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個(gè)entry從weak表中刪除,最后清理對(duì)象的記錄。
追問(wèn)的問(wèn)題一:
1.實(shí)現(xiàn)weak后,為什么對(duì)象釋放后會(huì)自動(dòng)為nil?
runtime 對(duì)注冊(cè)的類, 會(huì)進(jìn)行布局,對(duì)于 weak 對(duì)象會(huì)放入一個(gè) hash 表中。 用 weak 指向的對(duì)象內(nèi)存地址作為 key,當(dāng)此對(duì)象的引用計(jì)數(shù)為 0 的時(shí)候會(huì) dealloc,假如 weak 指向的對(duì)象內(nèi)存地址是 a ,那么就會(huì)以 a 為鍵, 在這個(gè) weak 表中搜索,找到所有以 a 為鍵的 weak 對(duì)象,從而設(shè)置為 nil 。
追問(wèn)的問(wèn)題二:
2.當(dāng)weak引用指向的對(duì)象被釋放時(shí),又是如何去處理weak指針的呢?
1、調(diào)用objc_release
2、因?yàn)閷?duì)象的引用計(jì)數(shù)為0,所以執(zhí)行dealloc
3、在dealloc中,調(diào)用了_objc_rootDealloc函數(shù)
4、在_objc_rootDealloc中,調(diào)用了object_dispose函數(shù)
5、調(diào)用objc_destructInstance
6、最后調(diào)用objc_clear_deallocating,詳細(xì)過(guò)程如下:
a. 從weak表中獲取廢棄對(duì)象的地址為鍵值的記錄
b. 將包含在記錄中的所有附有 weak修飾符變量的地址,賦值為 nil
c. 將weak表中該記錄刪除
d. 從引用計(jì)數(shù)表中刪除廢棄對(duì)象的地址為鍵值的記錄
10.iOS本地?cái)?shù)據(jù)存儲(chǔ)安全
11.BAD_ACCESS的錯(cuò)誤嗎?你是怎樣調(diào)試的?
BAD_ACCESS:不管什么時(shí)候當(dāng)你遇到BAD_ACCESS這個(gè)錯(cuò)誤,那就意味著你向一個(gè)已經(jīng)釋放的對(duì)象發(fā)送消息。
BAD_ACCESS的本質(zhì):
在C和OC中,你一直在處理指針,指針無(wú)非是存儲(chǔ)另一個(gè)變量的內(nèi)存地址的變量。當(dāng)向一個(gè)對(duì)象發(fā)送消息時(shí),指向該對(duì)象的指針將會(huì)被引用,這意味著,你獲取了指針?biāo)傅膬?nèi)存地址,并訪問(wèn)該存儲(chǔ)區(qū)域的值。
當(dāng)該存儲(chǔ)器區(qū)域不再映射到你的應(yīng)用時(shí),或者換句話說(shuō),該內(nèi)存區(qū)域在你認(rèn)為使用的時(shí)候沒(méi)有使用,該內(nèi)存區(qū)域是無(wú)法訪問(wèn)的,這時(shí)內(nèi)核會(huì)拋出一個(gè)異常(EXC),表明你的應(yīng)用程序不能訪問(wèn)該存儲(chǔ)器區(qū)域(BAD_ACCESS).
當(dāng)你碰到BAD_ACCESS,這意味著你試圖發(fā)送消息到的內(nèi)存塊,但內(nèi)存塊無(wú)法執(zhí)行該消息。但是,在某些情況下,BAD_ACCESS是由被損壞的指針引起的,每當(dāng)你的應(yīng)用程序嘗試引用損壞的指針,一個(gè)異常就會(huì)被內(nèi)核拋出。

Q:@synthesize和@dynamic分別有什么作用?

1.@property有兩個(gè)對(duì)應(yīng)的詞,一個(gè)是 @synthesize,一個(gè)是 @dynamic。如果 @synthesize和 @dynamic都沒(méi)寫(xiě),那么默認(rèn)的就是@syntheszie var = _var;
2.@synthesize 的語(yǔ)義是如果你沒(méi)有手動(dòng)實(shí)現(xiàn) setter 方法和 getter 方法,那么編譯器會(huì)自動(dòng)為你加上這兩個(gè)方法。
3.@dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實(shí)現(xiàn),不自動(dòng)生成。(當(dāng)然對(duì)于 readonly 的屬性只需提供 getter 即可)。假如一個(gè)屬性被聲明為 @dynamic var,然后你沒(méi)有提供 @setter方法和 @getter 方法,編譯的時(shí)候沒(méi)問(wèn)題,但是當(dāng)程序運(yùn)行到 instance.var = someVar,由于缺 setter 方法會(huì)導(dǎo)致程序崩潰;或者當(dāng)運(yùn)行到 someVar = var 時(shí),由于缺 getter 方法同樣會(huì)導(dǎo)致崩潰。

///////////////////

@property有兩個(gè)關(guān)鍵詞, @synthesize(默認(rèn)值)和@dynamic.
@synthesize表示系統(tǒng)會(huì)默認(rèn)添加一個(gè)@syntheszie var = _var的實(shí)例變量, 并且自動(dòng)生成setter和getter方法. @synthesize 合成實(shí)例變量有以下幾點(diǎn)規(guī)則:
如果指定了成員變量的名稱, 會(huì)生成一個(gè)指定的名稱的成員變量.
如果這個(gè)成員已經(jīng)存在就不再生成了.
如果沒(méi)有指定成員變量的名稱會(huì)自動(dòng)生成一個(gè)屬性同名的成員變量.
@synthesize的使用場(chǎng)景:
同時(shí)重寫(xiě)了setter和getter時(shí)
重寫(xiě)了只讀屬性的getter時(shí)
在使用了@dynamic時(shí)
在@Protocol和category中定義屬性時(shí)
重載父類的屬性時(shí), 來(lái)手動(dòng)合成Ivar(實(shí)例變量/成員變量).
@dynamic表示我們不需要系統(tǒng)自動(dòng)生成, 由用戶自己實(shí)現(xiàn), 如果沒(méi)有手動(dòng)生成的話, 在使用過(guò)程中是會(huì)奔潰的.

Q:@synthesize合成實(shí)例變量的規(guī)則是什么?假如property名為foo,存在一個(gè)名為_(kāi)foo的實(shí)例變量,那么還會(huì)自動(dòng)合成新變量么?

總結(jié)下 @synthesize 合成實(shí)例變量的規(guī)則,有以下幾點(diǎn):
如果指定了成員變量的名稱,會(huì)生成一個(gè)指定的名稱的成員變量,
如果這個(gè)成員已經(jīng)存在了就不再生成了.
如果是 @synthesize foo; 還會(huì)生成一個(gè)名稱為foo的成員變量,也就是說(shuō):
如果沒(méi)有指定成員變量的名稱會(huì)自動(dòng)生成一個(gè)屬性同名的成員變量,
如果是 @synthesize foo = _foo; 就不會(huì)生成成員變量了.
假如 property 名為 foo,存在一個(gè)名為 _foo 的實(shí)例變量,那么還會(huì)自動(dòng)合成新變量么?
不會(huì)。

Q:在有了自動(dòng)合成屬性實(shí)例變量之后,@synthesize還有哪些使用場(chǎng)景?

什么情況下不會(huì)autosynthesis(自動(dòng)合成)?
同時(shí)重寫(xiě)了 setter 和 getter 時(shí)
重寫(xiě)了只讀屬性的 getter 時(shí)
使用了 @dynamic 時(shí)
在 @protocol 中定義的所有屬性
在 category 中定義的所有屬性
重載的屬性
當(dāng)你在子類中重載了父類中的屬性,你必須 使用 @synthesize 來(lái)手動(dòng)合成ivar。

@synthesize 語(yǔ)法還有一個(gè)應(yīng)用場(chǎng)景,但是不太建議大家使用:
可以在類的實(shí)現(xiàn)代碼里通過(guò) @synthesize 語(yǔ)法來(lái)指定實(shí)例變量的名字

@implementation CYLPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end

可變數(shù)組&不可變數(shù)組修飾符的使用結(jié)論:

當(dāng)修飾可變類型的屬性時(shí),如NSMutableArray、NSMutableDictionary、NSMutableString,用strong。
當(dāng)修飾不可變類型的屬性時(shí),如NSArray、NSDictionary、NSString,用copy。

深拷貝與淺拷貝

淺拷貝就是拷貝之后,并沒(méi)有真正的復(fù)制,而是復(fù)制對(duì)象和原對(duì)象都指向同一個(gè)地址
深拷貝是真正的復(fù)制了一份,復(fù)制的對(duì)象只想新的地址;
copy:對(duì)于可變對(duì)象為深拷貝,對(duì)于不可變對(duì)象為淺拷貝;
mutablecopy:始終為深拷貝;
淺拷貝簡(jiǎn)單點(diǎn)說(shuō)就是對(duì)內(nèi)存地址的復(fù)制,讓目標(biāo)對(duì)象指針和源對(duì)象指針指向同一片內(nèi)存空間;
深拷貝: 內(nèi)容拷貝,會(huì)創(chuàng)建一個(gè)新的對(duì)象。深拷貝就是拷貝地址中的內(nèi)容,讓目標(biāo)對(duì)象產(chǎn)生新的內(nèi)存區(qū)域,且將源內(nèi)存區(qū)域中的內(nèi)容復(fù)制到目標(biāo)內(nèi)存區(qū)域中;

Q:objc中的類方法和實(shí)例方法有什么本質(zhì)區(qū)別和聯(lián)系?

(實(shí)例變量 = 成員變量 = ivar)
類方法:
1. 類方法是屬于類對(duì)象的
2. 類方法只能通過(guò)類對(duì)象調(diào)用
3. 類方法中的self是類對(duì)象
4. 類方法可以調(diào)用其他的類方法
5. 類方法中不能訪問(wèn)成員變量
6. 類方法中不定直接調(diào)用對(duì)象方法
實(shí)例方法:
1. 實(shí)例方法是屬于實(shí)例對(duì)象的
2. 實(shí)例方法只能通過(guò)實(shí)例對(duì)象調(diào)用
3. 實(shí)例方法中的self是實(shí)例對(duì)象
4. 實(shí)例方法中可以訪問(wèn)成員變量
5. 實(shí)例方法中直接調(diào)用實(shí)例方法
6. 實(shí)例方法中也可以調(diào)用類方法(通過(guò)類名)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容