探究系列已發(fā)布文章列表,有興趣的同學(xué)可以翻閱一下:
第一篇 | iOS 屬性 @property 詳細(xì)探究
第三篇 | iOS 類別 Category 和擴(kuò)展 Extension 及關(guān)聯(lián)對(duì)象詳解
第四篇 | iOS 常用鎖 NSLock ,@synchronized 等的底層實(shí)現(xiàn)詳解
------- 正文開(kāi)始 -------
引言
@property
應(yīng)該是面試過(guò)程中被問(wèn)到最多的一個(gè)技術(shù)點(diǎn)溢十,既能考察一個(gè)人的基礎(chǔ)菊霜,又能挖掘一個(gè)人對(duì)技術(shù)細(xì)節(jié)的掌握度屯曹,本文著重全面抄谐,細(xì)致的介紹一下@property
都有哪些技術(shù)點(diǎn)值得我們關(guān)注摹蘑。
-
代碼規(guī)范
聲明 @property
時(shí)筹燕,注意關(guān)鍵詞及字符間的空格。
@property (nonatomic, copy) NSString *name;
-
本質(zhì)
@property
的本質(zhì)其實(shí)是:ivar
(實(shí)例變量) + getter
+ setter
衅鹿;
-
常用關(guān)鍵詞
// 存取器方法
1. getter=getterName
2. setter=setterName
// 讀寫(xiě)權(quán)限
1. readonly
2. readwrite
// 內(nèi)存管理
1. strong
2. assign
3. copy
4. weak
5. retain
6. unsafe_unretained
// 原子性
1. nonatomic
2. atomic
接下來(lái)逐個(gè)介紹一下撒踪,每個(gè)關(guān)鍵詞的作用:
存取器方法
- getter=getterName
- setter=setterName:
指定獲取屬性對(duì)象的名字為 getterName
,如果你沒(méi)有使用 getter
指定getterName
大渤,系統(tǒng)默認(rèn)直接使用 propertyName
訪問(wèn)即可制妄。通常來(lái)說(shuō),只有所指屬性需要我們指定 isPropertyName
對(duì)應(yīng)的 Bool
值時(shí)泵三,才使用指定 getterName
忍捡,一般直接用 PropertyName
即可。setter=setterName:
則是用來(lái)指定設(shè)置屬性所使用的的 setter
方法切黔,即設(shè)置屬性值時(shí)使用 setterName:
方法砸脊,此處 setterName
是一個(gè)方法名,因此要以":"結(jié)尾纬霞,具體示例如下:
// 指定getter訪問(wèn)名為`isHappy`
@property (nonatomic, assign, getter=isHappy) BOOL happy;
// 使用系統(tǒng)默認(rèn)getter/setter方法
@property (nonatomic, assign) NSInteger age;
// 指定setter方法名為`setNickName:`
@property (nonatomic, copy, setter=setNickName:) NSString *name;
讀寫(xiě)權(quán)限
- readwrite
- readonly
- readwrite:表示自動(dòng)生成對(duì)應(yīng)的
getter
和setter
方法凌埂,即可讀可寫(xiě)權(quán)限,readwrite
是編譯器的默認(rèn)選項(xiàng)诗芜。 - readonly:表示只生成
getter
瞳抓,不需要生成setter
,即只可讀伏恐,不可以修改孩哑。
內(nèi)存管理
- strong // 強(qiáng)引用,引用計(jì)數(shù)+1
- assign // assign是指針賦值翠桦,不對(duì)引用計(jì)數(shù)操作横蜒,對(duì)象銷毀后不會(huì)自動(dòng)置為nil
- copy // copy出一個(gè)新對(duì)象胳蛮,引用計(jì)數(shù)為1
- weak // 弱引用,不對(duì)引用計(jì)數(shù)操作丛晌,對(duì)象銷毀時(shí)自動(dòng)置為nil
- retain // 強(qiáng)引用仅炊,對(duì)象引用計(jì)數(shù)+1
- unsafe_unretained // 弱引用,不對(duì)引用計(jì)數(shù)操作澎蛛,對(duì)象銷毀時(shí)不會(huì)自動(dòng)置為nil
- strong
表示強(qiáng)引用關(guān)系抚垄,即修飾對(duì)象的引用計(jì)數(shù)會(huì)+1,通常用來(lái)修飾對(duì)象類型谋逻,可變集合及可變字符串類型呆馁。當(dāng)對(duì)象引用計(jì)數(shù)為0,即不被任何對(duì)象持有毁兆,且此對(duì)象不再顯示在列表中時(shí)浙滤,對(duì)象就會(huì)從內(nèi)存中釋放。
- assign
對(duì)象不進(jìn)行 retain
操作荧恍,即不改變對(duì)象引用計(jì)數(shù)瓷叫。通常用來(lái)修飾基本數(shù)據(jù)類型( NSInteger, CGFloat, Bool, NSTimeInterval
等)屯吊,內(nèi)存在棧上由系統(tǒng)自動(dòng)回收送巡。
assign
也可以用來(lái)修飾 NSObject
類型對(duì)象,因?yàn)?assign
不會(huì)改變修飾對(duì)象的引用計(jì)數(shù)盒卸,所以當(dāng)修飾對(duì)象的引用計(jì)數(shù)為0骗爆,對(duì)象銷毀的時(shí)候,對(duì)象指針不會(huì)被自動(dòng)清空蔽介。而此時(shí)對(duì)象指針指向的地址已被銷毀摘投,這時(shí)再訪問(wèn)該屬性會(huì)產(chǎn)生野指針錯(cuò)誤:EXC_BAD_ACCESS
,因此 assign
通常用來(lái)修飾基本數(shù)據(jù)類型虹蓄。
- copy
當(dāng)調(diào)用修飾對(duì)象的 setter
方法時(shí)犀呼,會(huì)建立一個(gè)引用計(jì)數(shù)為 1 的新對(duì)象,即對(duì)象會(huì)在內(nèi)存里拷貝一份副本薇组,兩個(gè)指針指向不同的內(nèi)存地址外臂。一般用于修飾字符串( NSString
)和集合類( NSArray
, NSDictionary
)的不可變變量,Block
也是用 copy
修飾律胀。
針對(duì) copy
宋光,這里又牽涉到了深 copy
和淺 copy
的問(wèn)題,這里做一下簡(jiǎn)單介紹炭菌,后續(xù)會(huì)有文章專門探討這個(gè)問(wèn)題:
淺 copy :是對(duì)指針的 copy 罪佳,指針指向的內(nèi)容是同一個(gè)地址,對(duì)象的引用計(jì)數(shù)+1;
深 copy :是對(duì)內(nèi)容的 copy 黑低,會(huì)開(kāi)辟新的內(nèi)存空間赘艳,將內(nèi)容重新 copy 一份;
- 非集合對(duì)象的 copy 與 mutableCopy
不可變對(duì)象:copy 操作為淺 copy,mutableCopy 操作為深 copy第练。
可變對(duì)象:copy 操作為深 copy阔馋,mutableCopy 操作也為深 copy。
- 集合類對(duì)象的 copy 與 mutableCopy
不可變對(duì)象:copy 操作為深 copy 娇掏,mutableCopy 操作為深copy呕寝。
可變對(duì)象:copy 操作為深 copy ,可變對(duì)象的 mutableCopy 也為深 copy 婴梧。
注意:當(dāng)使用 copy
修飾的屬性賦值時(shí)下梢,copy
出來(lái)的是一份不可變對(duì)象。因此當(dāng)對(duì)象是一個(gè)可變對(duì)象時(shí)塞蹭,切記不要使用 copy
進(jìn)行修飾孽江。如果這時(shí)使用 copy
修飾,當(dāng)使用 copy
出來(lái)的對(duì)象調(diào)用可變對(duì)象所特有的方法時(shí)番电,會(huì)因?yàn)檎也坏綄?duì)應(yīng)的方法而 Crash岗屏。
- weak
表示弱引用關(guān)系,修飾對(duì)象的引用計(jì)數(shù)不會(huì)增加漱办,當(dāng)修飾對(duì)象被銷毀的時(shí)候这刷,對(duì)象指針會(huì)自動(dòng)置為 nil
,防止出現(xiàn)野指針娩井。weak
也用來(lái)修飾 delegate
暇屋,避免循環(huán)引用。另外 weak
只能用來(lái)修飾對(duì)象類型洞辣,且是在 ARC
下新引入的修飾詞咐刨,MRC
下相當(dāng)于使用 assign
。
weak的底層實(shí)現(xiàn)原理
weak
的底層實(shí)現(xiàn)是基于 Runtime 底層維護(hù)的 SideTables
的 hash 數(shù)組扬霜,里面存儲(chǔ)的是一個(gè) SideTable
的數(shù)據(jù)結(jié)構(gòu):
struct SideTable {
spinlock_t slock; // 確保原子性操作的鎖定鸟,雖然名字還叫 spinlock_t ,其實(shí)本質(zhì)已經(jīng)是 mutex_t著瓶,具體可見(jiàn) objc-os 源碼( `using spinlock_t = mutex_tt<LOCKDEBUG>;` )
RefcountMap refcnts; // 用來(lái)存儲(chǔ)對(duì)象引用計(jì)數(shù)的 hash 表
weak_table_t weak_table // 存儲(chǔ)對(duì)象 weak 引用指針的 hash 表
//...
};
- weak 功能實(shí)現(xiàn)核心的數(shù)據(jù)結(jié)構(gòu) weak_table_t:
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries; // 存儲(chǔ) weak 對(duì)象信息的 hash 數(shù)組
size_t num_entries; // 數(shù)組中元素的個(gè)數(shù)
uintptr_t mask; // 計(jì)數(shù)輔助量
uintptr_t max_hash_displacement; // hash 元素最大偏移值
};
- weak_entry_t也是一個(gè)hash結(jié)構(gòu):
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
// 動(dòng)態(tài)數(shù)組
struct {
weak_referrer_t *referrers; // 弱引用該對(duì)象的對(duì)象指針的hash數(shù)組
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
// 定長(zhǎng)數(shù)組联予,最大值為4,蘋(píng)果考慮到一半弱引用的指針個(gè)數(shù)不會(huì)超過(guò)這個(gè)數(shù)蟹但,因此為了提升運(yùn)行效率躯泰,一次分配一整塊的連續(xù)內(nèi)存空間
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
// 判斷當(dāng)前是動(dòng)態(tài)數(shù)組,還是定長(zhǎng)數(shù)組
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
這里重點(diǎn)說(shuō)一下 weak_entry_t
定長(zhǎng)數(shù)組到動(dòng)態(tài)數(shù)組的切換华糖,首先會(huì)將原來(lái)定長(zhǎng)數(shù)組中的內(nèi)容轉(zhuǎn)移到動(dòng)態(tài)數(shù)組中麦向,然后再在動(dòng)態(tài)數(shù)組中插入新的元素。
而對(duì)于動(dòng)態(tài)數(shù)組中元素個(gè)數(shù)大于或等于總空間的 3/4 時(shí)客叉,會(huì)對(duì)動(dòng)態(tài)數(shù)組進(jìn)行總空間 * 2 的擴(kuò)容
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
每次動(dòng)態(tài)數(shù)組擴(kuò)容诵竭,都會(huì)將原先數(shù)組中的內(nèi)容重新插入到新的數(shù)組中话告。
當(dāng)對(duì)象的引用計(jì)數(shù)為 0 時(shí),底層會(huì)調(diào)用
_objc_rootDealloc
方法對(duì)對(duì)象進(jìn)行釋放卵慰,而在_objc_rootDealloc
方法里面會(huì)調(diào)用rootDealloc
方法沙郭,如果對(duì)象有被 weak 引用,則會(huì)進(jìn)入object_dispose
, 之后會(huì)在objc_destructInstance
函數(shù)里面調(diào)用obj->clearDeallocating();
根據(jù)對(duì)象地址獲取所有weak
指針地址數(shù)組裳朋,遍歷數(shù)組找到對(duì)應(yīng)的值病线,將其值為nil
,然后將entry
從weak
表中移除鲤嫡,最后從引用計(jì)數(shù)表中刪除以廢棄對(duì)象的地址為鍵值的記錄送挑。
備注: 此處省略了 weak
底層實(shí)現(xiàn)的很多細(xì)節(jié),具體詳細(xì)實(shí)現(xiàn)暖眼,后續(xù)會(huì)單獨(dú)發(fā)文介紹惕耕。
- retain 是在 MRC 下常用的修飾詞:
ARC 下已不再使用 retain
,而是使用 strong
代替诫肠。retain
同 strong
類似司澎,用來(lái)修飾對(duì)象類型,強(qiáng)引用對(duì)象栋豫,其修飾對(duì)象的引用計(jì)數(shù)會(huì) +1挤安,不會(huì)對(duì)對(duì)象分配新的內(nèi)存空間。
- unsafe_unretained 同 weak 類似:
unsafe_unretained
不會(huì)對(duì)對(duì)象的引用計(jì)數(shù) +1笼才,只能用來(lái)修飾對(duì)象類型漱受,修飾的對(duì)象在被銷毀時(shí)络凿,其指針不會(huì)自動(dòng)清空骡送,指向的仍然是已銷毀的對(duì)象,這時(shí)再調(diào)用該指針會(huì)產(chǎn)生野指針 :EXC_BAD_ACCESS
錯(cuò)誤絮记。
原子性
atomic
原子性:系統(tǒng)會(huì)自動(dòng)給生成的getter/setter
方法進(jìn)行加鎖操作摔踱;
nonatomic
非原子性:系統(tǒng)不會(huì)給自動(dòng)生成的getter/setter
方法進(jìn)行加鎖操作;
設(shè)置屬性函數(shù) reallySetProperty(...)
的原子性非原子性實(shí)現(xiàn)如下:
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
獲取屬性函數(shù) objc_getProperty(...)
的內(nèi)部實(shí)現(xiàn)如下:
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
由此可見(jiàn)怨愤,對(duì)屬性對(duì)象的加鎖操作僅限于對(duì)象的 getter/setter
操作派敷,如果是 getter/setter
以外的操作,該加鎖并沒(méi)有意義撰洗。因此 atomic
的原子性篮愉,僅能保障對(duì)象的 getter/setter
的線程安全,并不能保障多線程下對(duì)對(duì)象的其他操作安全差导。如一個(gè)線程在 getter/setter
操作试躏,另一個(gè)線程進(jìn)行 release
操作,可能會(huì)導(dǎo)致 crash设褐。此種場(chǎng)景的線程安全颠蕴,還需要由開(kāi)發(fā)者自己進(jìn)行處理泣刹。
拓展知識(shí)
- Category 中添加屬性 @property
在 Category
中添加 @property
,只會(huì)生成 setter/getter
方法的聲明犀被,并不會(huì)有具體的代碼實(shí)現(xiàn)椅您。這是因?yàn)?Category
在運(yùn)行期對(duì)象的內(nèi)存布局已經(jīng)確定,此時(shí)如果添加實(shí)例變量就會(huì)破壞對(duì)象的內(nèi)存布局寡键,這將會(huì)是災(zāi)難性的掀泳。因此 Category
無(wú)法添加實(shí)例變量。
那如何給 Category
實(shí)現(xiàn)類似實(shí)例變量功能呢西轩?簡(jiǎn)單列舉兩種方式开伏,此處暫時(shí)不做具體詳解,后續(xù)會(huì)有文章單獨(dú)介紹:
- 使用臨時(shí)全局變量代替成員變量遭商,并在屬性的
setter/getter
函數(shù)中進(jìn)行存取值操作固灵; - 通過(guò)
Runtime
添加關(guān)聯(lián)對(duì)象實(shí)現(xiàn)成員變量,并在屬性的setter/getter
函數(shù)中進(jìn)行存取值操作劫流,其關(guān)鍵調(diào)用有兩個(gè):
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) // 設(shè)置關(guān)聯(lián)對(duì)象值調(diào)用
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key) // 獲取關(guān)聯(lián)對(duì)象值調(diào)用
- Protocol 中添加屬性 @property
在 protocol
中添加屬性 @property
巫玻,其實(shí)就是聲明該屬性的 setter/getter
方法,在實(shí)現(xiàn)該 protocol
時(shí)祠汇,并沒(méi)有生成對(duì)應(yīng)的成員變量仍秤,此時(shí)有動(dòng)態(tài)實(shí)現(xiàn)和自動(dòng)實(shí)現(xiàn)兩種方式:
- 動(dòng)態(tài)實(shí)現(xiàn),需要在實(shí)現(xiàn)類中添加
protocol
中聲明屬性對(duì)應(yīng)的setter/getter
方法可很,并聲明一個(gè)私有成員變量用來(lái)進(jìn)行存取操作诗力。備注: 實(shí)現(xiàn)類如果沒(méi)有實(shí)現(xiàn)對(duì)應(yīng)的setter/getter
方法,在調(diào)用protocol
屬性的setter/getter
方法時(shí)會(huì)因?yàn)榉椒ㄕ也坏蕉?Crash
我抠; - 自動(dòng)實(shí)現(xiàn)苇本,可以通過(guò)
@synthesize propertyName;
告訴編譯器自動(dòng)添加對(duì)應(yīng)的setter/getter
方法,并生成對(duì)應(yīng)的成員變量菜拓;
- @synthesize 作用
@synthesize
的作用是告訴編譯器瓣窄,自動(dòng)創(chuàng)建屬性的 setter/getter
方法,同時(shí)生成成員變量纳鼎,并且可以給屬性指定別名俺夕。如:
@synthesize name = nickName;
- @dynamic 作用
@dynamic
作用是告訴編譯器,無(wú)需自動(dòng)創(chuàng)建屬性的 setter/getter
方法贱鄙,這個(gè)時(shí)候就需要我們手動(dòng)實(shí)現(xiàn)相應(yīng)的 setter/getter
劝贸,否則,在使用到相應(yīng)屬性的 setter/getter
方法時(shí)逗宁,會(huì)因找不到方法而 Crash
映九。
-
null
相關(guān)的一些關(guān)鍵詞
- nullable : 可以為空
- nonnull : 不可以為空
- null_unspecified : 未知類型
- null_resettable : get不能為空,set可以為空
- __nullable : 可以為 Null 或 nil
- __nonnull : 不可以為空
- Swift下的 unowned 與 weak 區(qū)別:
- Swift下
weak
的使用同 Objective-C 下weak
的使用相同疙剑。 -
unowned
無(wú)主引用標(biāo)記對(duì)象氯迂,即使它的原引用已經(jīng)被釋放践叠,它仍然會(huì)保持對(duì)已釋放對(duì)象的引用,它不是Optional
嚼蚀,也不會(huì)被置為nil
禁灼。當(dāng)我們?cè)噲D訪問(wèn)這樣的unowned
引用時(shí),程序會(huì)發(fā)生錯(cuò)誤轿曙。而weak
在引用的對(duì)象被釋放后弄捕,標(biāo)記為weak
的對(duì)象將會(huì)自動(dòng)置為nil
。
Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.
Conversely, define a capture as a weak reference when the captured reference may become nil at some point in the future. Weak references are always of an optional type, and automatically become nil when the instance they reference is deallocated. This enables you to check for their existence within the closure’s body.
NOTE:
If the captured reference will never become nil, it should always be captured as an unowned reference, rather than a weak reference.
根據(jù)蘋(píng)果官方文檔的建議导帝,如果捕獲的引用永遠(yuǎn)不會(huì)變?yōu)?nil
守谓,我們應(yīng)該使用 unowned
,否則應(yīng)該使用 weak
您单。
總結(jié)
@property
延展相關(guān)的技術(shù)點(diǎn)有很多斋荞,如:copy
相關(guān)的 NSCopying
協(xié)議,weak
底層詳細(xì)的實(shí)現(xiàn)原理虐秦,如何保障對(duì)象的多線程安全平酿。還有很多技術(shù)點(diǎn)跟 Runtime
、Runloop
有關(guān)悦陋,后續(xù)文章會(huì)陸續(xù)介紹蜈彼。
知識(shí)點(diǎn)完整說(shuō)下來(lái)就是一整套系統(tǒng)的協(xié)同運(yùn)轉(zhuǎn),各個(gè)環(huán)節(jié)緊密相扣俺驶,最終才成為我們現(xiàn)在看到的樣子幸逆。本文及以后的文章都會(huì)盡可能的收縮一下單篇文章探討的范圍,以期能夠讓話題更加緊密暮现。
參考資料:
The Objective-C Programming Language
關(guān)于技術(shù)組
iOS 技術(shù)組主要用來(lái)學(xué)習(xí)还绘、分享日常開(kāi)發(fā)中使用到的技術(shù),一起保持學(xué)習(xí)送矩,保持進(jìn)步蚕甥。文章倉(cāng)庫(kù)在這里:https://github.com/minhechen/iOSTechTeam
微信公眾號(hào):iOS技術(shù)組哪替,歡迎聯(lián)系交流栋荸,感謝閱讀。