引言
類(lèi)別
category
允許你在沒(méi)有源代碼情況下威蕉,仍然可以向已有的類(lèi)中添加方法的诵。它的功能很強(qiáng)大莫绣,允許你無(wú)需子類(lèi)化而擴(kuò)展現(xiàn)有類(lèi)喧枷。使用類(lèi)別,還可以將類(lèi)的實(shí)現(xiàn)分發(fā)到多個(gè)文件中沮焕。類(lèi)擴(kuò)展extension
與此類(lèi)似吨岭,但允許在主類(lèi)@interface 塊內(nèi)以外的位置為類(lèi)聲明額外的 API。
-
代碼示例
Category:
#import "ClassName.h"
@interface ClassName (CategoryName)
// method declarations
@end
Extension:
@interface MyClass : NSObject
@property (nonatomic, copy, readonly) NSString *name;
@end
// Private extension, typically hidden in the main implementation file.
@interface MyClass ()
@property (nonatomic, copy, readwrite) NSString *name;
@end
-
本質(zhì)
您可以通過(guò)在接口文件中遇汞,以類(lèi)別名稱(chēng)聲明它們未妹,并在實(shí)現(xiàn)文件中以相同名稱(chēng)定義它們來(lái)將方法添加到類(lèi)簿废。類(lèi)別名稱(chēng)表明這些方法是對(duì)在別處聲明的類(lèi)的添加空入,而不是一個(gè)新類(lèi)。但是族檬,不能通過(guò)類(lèi)別添加實(shí)例變量到類(lèi)中歪赢。
類(lèi)別添加的方法成為類(lèi)類(lèi)型的一部分。例如单料,在一個(gè)類(lèi)別中添加到 NSArray
類(lèi)中的方法埋凯,是編譯器期望 NSArray
實(shí)例在其配置表中包含的方法点楼。然而,子類(lèi)中添加到 NSArray
類(lèi)中的方法并不包含在 NSArray
類(lèi)型中白对。(這只對(duì)靜態(tài)類(lèi)型的對(duì)象有影響掠廓,因?yàn)殪o態(tài)類(lèi)型是編譯器知道對(duì)象類(lèi)的唯一方式。)
-
常用介紹
類(lèi)別:
Category
在經(jīng)歷過(guò)編譯后里面的內(nèi)容:對(duì)象方法甩恼、類(lèi)方法蟀瞧、協(xié)議、屬性都轉(zhuǎn)化為類(lèi)型為 category_t
的結(jié)構(gòu)體變量:
struct category_t {
const char *name;
classref_t cls;
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
WrappedPtr<method_list_t, PtrauthStrip> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
具體 category
都能做什么条摸,常用的大致有如下幾個(gè)場(chǎng)景:
- 在不修改原有類(lèi)的基礎(chǔ)上給原有類(lèi)添加方法悦污,因?yàn)?
category
的結(jié)構(gòu)體指針中沒(méi)有屬性列表,只有方法列表钉蒲。所以原則來(lái)說(shuō)只能給category
添加方法切端,不能添加屬性,如果需要給category
添加類(lèi)似屬性功能顷啼,可以通過(guò)關(guān)聯(lián)對(duì)象實(shí)現(xiàn)踏枣,下面會(huì)有具體介紹; -
Category
中的方法優(yōu)先于原有類(lèi)同名的方法钙蒙,即會(huì)優(yōu)先調(diào)用category
中的方法椰于,忽略原有類(lèi)的方法。即category
與原有類(lèi)同名方法調(diào)用的優(yōu)先級(jí)為:category
> 本類(lèi) > 父類(lèi)仪搔。開(kāi)發(fā)中盡量不要覆蓋本類(lèi)的方法瘾婿,如果覆蓋會(huì)導(dǎo)致本類(lèi)方法失效; - 如果給
category
添加屬性@property
烤咧,只會(huì)生成setter/getter
方法的聲明偏陪,并不會(huì)有具體的代碼實(shí)現(xiàn),詳細(xì)解釋可參考?xì)v史文章:iOS 屬性 @property 詳細(xì)探究 -
Category
中可以訪問(wèn)原有類(lèi)中.h
中聲明的成員變量煮嫌;
類(lèi)的擴(kuò)展 Extension:
@interface Person ()
@end
類(lèi)的 extension
看起來(lái)很像一個(gè)匿名的 category
笛谦。通常用來(lái)聲明私有方法,私有屬性和私有成員變量昌阿。
extension 在編譯期決議饥脑, category 在運(yùn)行期決議。
類(lèi)擴(kuò)展不能像類(lèi)別 category
那樣擁有獨(dú)立的實(shí)現(xiàn)部分(@implementation
部分)懦冰。也就是說(shuō)灶轰,類(lèi)的擴(kuò)展所聲明的方法必須依托原類(lèi)的實(shí)現(xiàn)代碼部分來(lái)實(shí)現(xiàn)。
因此刷钢,我們不能給系統(tǒng)類(lèi)添加類(lèi)擴(kuò)展笋颤。即擴(kuò)展的方法只能在原類(lèi)中實(shí)現(xiàn)。例如我們擴(kuò)展 NSString
内地,那么只能在 NSString的.m
中實(shí)現(xiàn)伴澄,但我們拿不到 NSString.m
的源碼赋除。因此,我們不能給 NSString
添加擴(kuò)展非凌,只能給 NSString
添加 category
举农。
定義在 .m
文件中的類(lèi)擴(kuò)展方法為私有的,如果需要聲明私有方法敞嗡,這種方式特別合適并蝗。定義在 .h 文件(頭文件)中的類(lèi)擴(kuò)展方法為公有的。
類(lèi)別 Category 與擴(kuò)展 Extension 的區(qū)別
- Category 有名字秸妥,extension 沒(méi)有名字滚停,像是一個(gè)匿名的 category;
- Category 是運(yùn)行時(shí)決議粥惧,而 extension 是編譯時(shí)決議键畴。所以 category 中的方法沒(méi)有實(shí)現(xiàn)不會(huì)警告,而 extension 聲明的方法不實(shí)現(xiàn)則會(huì)出現(xiàn)警告突雪;
- Category 原則上可以增加屬性起惕,實(shí)例方法,類(lèi)方法咏删,而且外部類(lèi)是可以訪問(wèn)的惹想。extension 能添加屬性、方法督函、實(shí)例變量嘀粱,且默認(rèn)是私有的;
- Category 有自己的實(shí)現(xiàn)部分辰狡,extension 沒(méi)有自己的實(shí)現(xiàn)部分锋叨,只能依賴(lài)類(lèi)本身來(lái)實(shí)現(xiàn);
- 可以為系統(tǒng)類(lèi)添加 category宛篇,而不能為系統(tǒng)類(lèi)添加 extension娃磺;
關(guān)于類(lèi)的 + (void)load
與 + (void)initialize
的區(qū)別
+ (void)load
+ (void)initialize
- 兩者的區(qū)別如下:
- 相同點(diǎn):
- 兩個(gè)函數(shù)都是系統(tǒng)自動(dòng)調(diào)用,因此無(wú)需手動(dòng)調(diào)用(如果手動(dòng)調(diào)用則與普通函數(shù)調(diào)用類(lèi)似)叫倍;
- 兩個(gè)函數(shù)都會(huì)隱士調(diào)用各自父類(lèi)對(duì)應(yīng)的
+ (void)load
或+ (void)initialize
方法偷卧,即子類(lèi)調(diào)用方法之前,會(huì)優(yōu)先調(diào)用其父類(lèi)對(duì)應(yīng)的方法吆倦;- 兩個(gè)函數(shù)內(nèi)部都使用了鎖听诸,因此兩個(gè)函數(shù)都是線(xiàn)程安全的;
- 不同點(diǎn):
- 調(diào)用時(shí)機(jī)不同:
+ (void)load
在main
函數(shù)之前執(zhí)行逼庞,即objc_init
Runtime初始化時(shí)調(diào)用蛇更,且只會(huì)調(diào)用一次。+ (void)initialize
在類(lèi)的方法首次被調(diào)用時(shí)執(zhí)行赛糟,每個(gè)類(lèi)只會(huì)調(diào)用一次派任,但父類(lèi)可能會(huì)調(diào)用多次;- 調(diào)用方式不同:
+ (void)load
是根據(jù)函數(shù)地址直接調(diào)用璧南,+ (void)initialize
是通過(guò)消息發(fā)送機(jī)制即objc_msgSend(id self, SEL _cmd, ...)
調(diào)用掌逛;- 子類(lèi)父類(lèi)調(diào)用關(guān)系不同:
- 如果子類(lèi)沒(méi)有實(shí)現(xiàn)
+ (void)load
,則不會(huì)調(diào)用其父類(lèi)的+ (void)load
方法司倚。- 如果子類(lèi)沒(méi)有實(shí)現(xiàn)
+ (void)initialize
豆混,則會(huì)調(diào)用其父類(lèi)的方法,因此父類(lèi)的+ (void)initialize
可能會(huì)調(diào)用多次动知;
- 類(lèi)別
category
對(duì)調(diào)用的影響不同:
- 如果
category
中實(shí)現(xiàn)了+ (void)load
皿伺,則會(huì)優(yōu)先調(diào)用原類(lèi)的的+ (void)load
,再調(diào)用category
的盒粮,即優(yōu)先級(jí)為:父類(lèi) > 原類(lèi) >category
- 沒(méi)有繼承關(guān)系的不同類(lèi)中的
+ (void)load
的調(diào)用順序跟Compile Sources
順序有關(guān)鸵鸥,即在前面的優(yōu)先編譯的類(lèi)或者category
先調(diào)用( 備注: 所有類(lèi)的+ (void)load
優(yōu)先級(jí)大于category
的優(yōu)先級(jí));- 同一個(gè)類(lèi)的
category
的+ (void)load
的調(diào)用順序跟Compile Sources
順序有關(guān)丹皱,即在前面的優(yōu)先編譯的category
會(huì)先調(diào)用妒穴;- 同一鏡像中主工程的
+ (void)load
方法優(yōu)先調(diào)用,然后再調(diào)用靜態(tài)庫(kù)的+ (void)load
方法摊崭。有多個(gè)靜態(tài)庫(kù)時(shí)讼油,靜態(tài)庫(kù)之間的執(zhí)行順序與編譯順序有關(guān),即它們?cè)?Link Binary With Libraries
中的順序呢簸;- 不同鏡像中矮台,動(dòng)態(tài)庫(kù)的
+ (void)load
方法優(yōu)先調(diào)用,然后再調(diào)用主工程的+ (void)load
根时,多個(gè)動(dòng)態(tài)庫(kù)的+ (void)load
方法的調(diào)用順序跟編譯順序有關(guān)嘿架,即它們?cè)?Link Binary With Libraries
中的順序;
- 如果
category
中實(shí)現(xiàn)了+ (void)initialize
啸箫,則原類(lèi)的+ (void)initialize
將不會(huì)再調(diào)用
- 多個(gè)
category
中同時(shí)實(shí)現(xiàn)了+ (void)initialize
方法時(shí)耸彪,Compile Sources中順序最下面的一個(gè),即最后一個(gè)被編譯 Category 的+ (void)initialize
會(huì)執(zhí)行忘苛;
類(lèi)別 Category
中添加關(guān)聯(lián)對(duì)象
Category
中添加屬性 @property
在前文已做過(guò)簡(jiǎn)單介紹蝉娜,具體可查看 iOS 屬性 @property 詳細(xì)探究,這里我們重點(diǎn)說(shuō)一下關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)原理:
操作關(guān)聯(lián)對(duì)象有三個(gè)核心方法:
- 設(shè)置關(guān)聯(lián)對(duì)象方法:
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
- id _Nonnull object: 給哪個(gè)對(duì)象添加關(guān)聯(lián)對(duì)象扎唾,通常是當(dāng)前對(duì)象召川,即用
self
即可;
- const void * _Nonnull key: 關(guān)聯(lián)對(duì)象的
key
胸遇,作為關(guān)聯(lián)對(duì)象的唯一標(biāo)識(shí)存在荧呐,它只要是一個(gè)非空指針即可;
- id _Nullable value: 關(guān)聯(lián)對(duì)象的值,通過(guò)關(guān)聯(lián)
key
進(jìn)行設(shè)值及獲取值倍阐,如果需要清除一個(gè)已存在的關(guān)聯(lián)對(duì)象概疆,將其值設(shè)置為nil
即可;
- objc_AssociationPolicy policy: 關(guān)聯(lián)策略峰搪,即關(guān)聯(lián)對(duì)象的存儲(chǔ)形式岔冀,其可選枚舉值如下:
public enum objc_AssociationPolicy : UInt {
case OBJC_ASSOCIATION_ASSIGN = 0 // 指定對(duì)關(guān)聯(lián)對(duì)象的弱引用
case OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1 // 指定對(duì)關(guān)聯(lián)對(duì)象的強(qiáng)引用,非原子性
case OBJC_ASSOCIATION_COPY_NONATOMIC = 3 // 指定復(fù)制關(guān)聯(lián)的對(duì)象概耻,非原子性
case OBJC_ASSOCIATION_RETAIN = 769 // 指定對(duì)關(guān)聯(lián)對(duì)象的強(qiáng)引用使套,原子性
case OBJC_ASSOCIATION_COPY = 771 // 指定復(fù)制關(guān)聯(lián)的對(duì)象,原子性
}
根據(jù)源碼鞠柄,我們可以知道 objc_setAssociatedObject
實(shí)際調(diào)用的是 _object_set_associative_reference
:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
bool isFirstAssociation = false;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
根據(jù)上述源碼可以發(fā)現(xiàn)侦高,ObjcAssociation
根據(jù)傳入的 value
及 policy
創(chuàng)建對(duì)象,并經(jīng)過(guò) acquireValue
函數(shù)處理生成新的 _value
厌杜。acquireValue
函數(shù)內(nèi)部是通過(guò)對(duì)策略 policy
的判斷進(jìn)行相應(yīng)處理奉呛,生成新值,其實(shí)現(xiàn)如下:
inline void acquireValue() {
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}
接下來(lái)我們首先需要了解一下 AssociationsManager
和 AssociationsHashMap
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
由源碼可以知道期奔,AssociationsManager
是以 DisguisedPtr<objc_object>
即一個(gè)指針地址作為 key
侧馅,以 ObjectAssociationMap
即一個(gè)關(guān)聯(lián)表作為 value
的哈希表來(lái)使用的。其內(nèi)部是使用一個(gè)全局靜態(tài)變量 static Storage _mapStorage
來(lái)存儲(chǔ)程序中所有的關(guān)聯(lián)對(duì)象呐萌。
這里重點(diǎn)介紹一下全局靜態(tài)變量 static Storage _mapStorage
的初始化時(shí)機(jī)馁痴。App 啟動(dòng)過(guò)程中,在 _objc_init
函數(shù)中會(huì)調(diào)用 void _dyld_objc_notify_register(...)
肺孤,具體如下:
void _objc_init(void)
{
//...
// 此處僅保留談到的函數(shù)
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
//...
}
在 dyld
源碼中可以看到罗晕,函數(shù) _dyld_objc_notify_register
中的三個(gè)參數(shù)為三個(gè)回調(diào)函數(shù)的指針,如下圖:
回調(diào)函數(shù)會(huì)在所有鏡像文件初始化完成之后赠堵,回調(diào) map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[])
函數(shù)小渊。詳細(xì)調(diào)用流程如下圖:
備注:圖中已對(duì)無(wú)關(guān)代碼進(jìn)行刪減,僅用來(lái)展示調(diào)用流程
從上圖我們可以知道茫叭,在 App 啟動(dòng)過(guò)程中 AssociationsManager
中的靜態(tài)變量 static Storage _mapStorage
的初始化時(shí)機(jī)酬屉。在 App 啟動(dòng)之后,所有用到關(guān)聯(lián)對(duì)象的地方揍愁,程序都是從這個(gè)全局靜態(tài)變量 _mapStorage
中獲取 AssociationsHashMap
來(lái)對(duì)關(guān)聯(lián)對(duì)象進(jìn)行進(jìn)一步處理呐萨。
在 AssociationsManager
中,我們可以看到是由一個(gè) AssociationsManagerLock
叫做 spinlock_t
的互斥鎖:
using spinlock_t = mutex_tt<LOCKDEBUG>;
它是用來(lái)保障 AssociationsManager
中對(duì) AssociationsHashMap
操作的線(xiàn)程安全莽囤。
AssociationsHashMap &get() {
return _mapStorage.get();
}
對(duì)于 AssociationsHashMap
這個(gè)哈希表谬擦,則是由全局靜態(tài)變量 _mapStorage
獲取而來(lái),因此不管任何時(shí)候操作關(guān)聯(lián)對(duì)象朽缎,程序始終都是在操作這個(gè) AssociationsHashMap
全局唯一的哈希表惨远。
再回到上面 _object_set_associative_reference
源碼中谜悟,當(dāng)我們添加一個(gè)關(guān)聯(lián)對(duì)象時(shí),AssociationsHashMap
會(huì)調(diào)用如下函數(shù):
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
try_emplace
函數(shù)的源碼如下:
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
首先根據(jù)傳來(lái)的 key
即 disguised
在 AssociationsHashMap
中查找對(duì)應(yīng)的 ObjectAssociationMap
是否已在映射表中北秽,如果不在則將元素插入葡幸。如果鍵不在,則創(chuàng)建一個(gè) BucketT
即一個(gè)空的桶羡儿。在第二次調(diào)用 try_emplace
時(shí)將 ObjcAssociation
(里面包含了 _policy
和 _value
)存儲(chǔ)到這個(gè) BucketT
空桶中礼患。
當(dāng)設(shè)置的關(guān)聯(lián) value
為空 nil
的時(shí)候會(huì)進(jìn)入 if
判斷的 else
里面:
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
先去 AssociationsHashMap
里面查找 disguised
是钥,如果找到則根據(jù) key
查找到指定的關(guān)聯(lián)對(duì)象掠归,然后進(jìn)行清除 erase
操作。之后判斷當(dāng)前 object
的關(guān)聯(lián)對(duì)象是否為0悄泥,如果為0虏冻,則將當(dāng)前關(guān)聯(lián)對(duì)象從全局的 AssociationsHashMap
中移除。
- 獲取關(guān)聯(lián)對(duì)象方法:
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
- id _Nonnull object: 獲取哪個(gè)對(duì)象里面的關(guān)聯(lián)對(duì)象弹囚;
- const void * _Nonnull key: 關(guān)聯(lián)對(duì)象的
key
厨相,與objc_setAssociatedObject
中的key
相對(duì)應(yīng),通過(guò)key
值取出value
即關(guān)聯(lián)對(duì)象鸥鹉;
其內(nèi)部調(diào)用的是 _object_get_associative_reference
蛮穿,內(nèi)部具體實(shí)現(xiàn)如下:
如果我們理解了設(shè)置關(guān)聯(lián)對(duì)象的過(guò)程,上面的代碼理解起來(lái)就比較簡(jiǎn)單了毁渗,從全局的 AssociationsHashMap
中取得 object
對(duì)象對(duì)應(yīng)的 ObjectAssociationMap
践磅,然后根據(jù) key
從 ObjectAssociationMap
獲取對(duì)應(yīng)的 ObjcAssociation
,然后根據(jù)關(guān)聯(lián)策略 _policy
判斷是否需要對(duì) _value
執(zhí)行 retain
操作灸异。最后根據(jù)關(guān)聯(lián)策略 _policy
判斷是否需要將 _value
添加到自動(dòng)釋放池府适,并返回 _value
。
- 移除關(guān)聯(lián)對(duì)象:
上面已經(jīng)提到肺樟,如果想要清除某一個(gè)特定關(guān)聯(lián)對(duì)象檐春,設(shè)置關(guān)聯(lián)對(duì)象的value
為nil
即可。如果想要移除所有關(guān)聯(lián)對(duì)象么伯,則可以使用:
objc_removeAssociatedObjects(id _Nonnull object)
- id _Nonnull object: 移除指定對(duì)象的所有關(guān)聯(lián)對(duì)象
其內(nèi)部實(shí)現(xiàn)代碼如下:
當(dāng)調(diào)用移除關(guān)聯(lián)對(duì)象操作時(shí)疟暖,會(huì)先判斷 object
是否為空及是否有關(guān)聯(lián)對(duì)象存在,如果存儲(chǔ)則會(huì)調(diào)用 _object_remove_assocations
函數(shù)田柔。
從上圖其內(nèi)部實(shí)現(xiàn)代碼可以看到俐巴,程序會(huì)獲取全局的 AssociationsHashMap
然后從中獲取對(duì)象對(duì)應(yīng)的 ObjectAssociationMap
,注釋說(shuō)如果不是 deallocating
凯楔,則系統(tǒng)的關(guān)聯(lián)對(duì)象將會(huì)保留窜骄。而 objc_removeAssociatedObjects
函數(shù)傳入的 deallocating
參數(shù)為 false
,因此我們可以推斷摆屯,解除關(guān)聯(lián)必定不是在調(diào)用 objc_removeAssociatedObjects
時(shí)邻遏。
于是糠亩,我搜索了一下 _object_remove_assocations
,發(fā)現(xiàn)了真正的調(diào)用時(shí)機(jī)准验,即在 objc_destructInstance
函數(shù)調(diào)用時(shí)赎线,如上圖。
那什么時(shí)候會(huì)調(diào)用 objc_destructInstance
函數(shù)呢糊饱?帶著這個(gè)疑問(wèn)垂寥,我查了一下源碼,這里簡(jiǎn)單說(shuō)一下調(diào)用流程另锋,后續(xù)會(huì)專(zhuān)門(mén)針對(duì) dealloc
寫(xiě)相關(guān)文章滞项,其大體流程如下:
圖中函數(shù)調(diào)用流程非常清晰,此處不做過(guò)多解釋夭坪。由此文判,我們知道解除關(guān)聯(lián)對(duì)象是在源對(duì)象 dealloc
時(shí)進(jìn)行的。
拓展知識(shí)
- iOS 中變量修飾詞
@public
室梅、@protected
戏仓、@package
、@private
的作用:
@package // 常用于框架類(lèi)的實(shí)例變量亡鼠,使用 @private 太限制赏殃,使用 @protected 或者 @public 又太開(kāi)放,這時(shí)可以使用 @package
@private // 作用范圍只能在自身類(lèi)间涵,即使子類(lèi)也無(wú)法使用仁热,但 category 及 extension 類(lèi)中可以使用
@protected // 系統(tǒng)默認(rèn)為 @protected,作用范圍在自身類(lèi)及子類(lèi)
@public // 公開(kāi)類(lèi)型浑厚,作用域大股耽,只要能拿到所屬實(shí)例對(duì)象就可以使用
實(shí)例變量范圍圖(@package
的范圍圖中未展示)
@interface Person : NSObject {
@package
NSString *_country; // 框架內(nèi)拿到 Person 及其子類(lèi)的實(shí)例變量都可以使用
@protected
NSString *_birthday; // 只能在自身類(lèi)及子類(lèi)中使用,包括 category 及 extension
@private
NSString *_weight; // 只能在自身類(lèi)中使用钳幅,包括 category 及 extension
@public
NSString *_height; // 全局任意拿到 Person 及其子類(lèi)實(shí)例變量的地方都可以使用
}
具體實(shí)例如下: Son
繼承自 Person
:
@interface Son : Person
@end
@protected
從上圖示例代碼可以看到物蝙,在子類(lèi)中是可以訪問(wèn)父類(lèi)的 @protected _birthday
成員變量,但不能訪問(wèn)父類(lèi)的 @private _weight
成員變量敢艰。
從上圖示例代碼可以看到诬乞,在其他類(lèi)中是可以訪問(wèn)父類(lèi)的 @protected _birthday
成員變量,但不能訪問(wèn)父類(lèi)的 @private _weight
成員變量钠导。
總結(jié)
類(lèi)別 category
和擴(kuò)展 extension
涉及到的東西還是挺多的震嫉,這里僅對(duì)其核心要關(guān)注的一些點(diǎn)進(jìn)行了詳細(xì)介紹。另外還有關(guān)于 category
裝載的過(guò)程牡属,有興趣的同學(xué)可以查閱一下票堵。以上就是本文對(duì)類(lèi)別 category
和 擴(kuò)展 extension
相關(guān)知識(shí)點(diǎn)的介紹,感謝閱讀逮栅。
參考資料:
關(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)系進(jìn)群學(xué)習(xí)交流,感謝閱讀捧存。