Runtime最全總結
本系列詳細講解Runtime知識點,由于運行時的內容較多,所以將內容拆分成以下幾個方面屋剑,可以自行選擇想要查看的部分
- OC運行時機制Runtime(一):從isa指針開始初步結識Runtime
- OC運行時機制Runtime(二):探索Runtime的消息轉發(fā)機制和分類Category
- OC運行時機制Runtime(三):關聯對象Associated Object和分類Category
-
OC運行時機制Runtime(四):嘗試使用黑魔法 Method Swizzling
本文主要分析Category和Associated Object,接前兩篇文章詳細分析一下一些細節(jié)內容。
Category
分類是我們開發(fā)過程中必不可少的一個重要技術手段然痊,包括動態(tài)添加方法,更換原有方法等屉符,那么首先常規(guī)套路分析一下Category的結構剧浸。
struct objc_category {
char *category_name OBJC2_UNAVAILABLE;
char *class_name OBJC2_UNAVAILABLE;
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
}
這里看到結構體里分別有分類名,類名矗钟,實例方法列表唆香,類方法列表,協議列表
這幾項吨艇,所以我們通炒恚可以在分類中動態(tài)添加方法而不能添加實例變量,當然我們也可以更詳細的分析一下秸应,方法列表和實例變量列表的區(qū)別虑凛。
struct objc_class {
Class isa;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
這里看到,objc_ivar_list
結構體里面有ivar_count
以及一個變長結構體ivar_list
软啼,這個結構體里面存儲了ivar的各種屬性桑谍,包括name、type
等祸挪,同樣objc_method_list
里面也存儲了method_count
和變長結構體method_list
锣披,方法結構體包括了方法名和方法實現
,這里SEL類型表示的是選擇子的名字,IMP類型表示的方法的具體實現
雹仿,這里有一個問題*ivars和**methodLists分別是一級指針和二級指針
增热,二者的區(qū)別是使用一級指針做參數傳遞時,如果函數改變傳入參數的值胧辽,原參數指針指向的值不會改變峻仇,而使用二級指針做參數傳遞時,原參數指針指向的值是可以改變的
邑商,所以當使用添加方法時摄咆,*methodList
值可以改變,所以可以添加方法人断。那如果添加屬性是否可以呢吭从,我們寫一個分類嘗試一下
//UIImage+Detection.h
@interface UIImage (Detection)
@property (nonatomic, copy) NSString * remarkName;
@end
//ViewController.m
- (void)viewDidLoad {
UIImage * image = [UIImage imageNamed:@"image_name"];
image.imageName = @"image_name";
NSLog(@"%@", image.imageName);
}
可以看到控制臺報出-[UIImage setImageName:]: unrecognized selector sent to instance 0x6000000b1040
找不到setter的錯,是因為Category沒有給屬性自動添加setter和getter方法恶迈,但是我們如果使用_remarkName = remarkName;
這種方式重寫setter方法涩金,會因為Category不能添加變量而報錯,所以這里引出一個概念Associated Object
暇仲。
Associated Object——關聯對象
當我們給一個系統(tǒng)類添加方法步做,我們常用的是使用類別來進行擴展,但是如果我們想添加一個系統(tǒng)類的屬性熔吗,我們通常是使用繼承的方式辆床,但是只是添加一個屬性就使用繼承有些小題大做,這里我們可以使用Associated Object
桅狠,下面繼續(xù)之前的例子讼载,將setter和getter
用關聯對象的方式實現,首先請出兩個當事人中跌,哦不當事函數咨堤。
//為一個實例對象添加一個關聯對象,用鍵來區(qū)分漩符,由于是C函數只能使用C字符串一喘,這個key就是關聯對象的名稱,value為具體的關聯對象的值嗜暴,policy為存儲策略凸克,用以維護相應的內存管理語義
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
//通過key和實例對象獲取關聯對象的值
id objc_getAssociatedObject(id object, const void *key);
//刪除實例對象的關聯對象
void objc_removeAssociatedObjects(id object);
由于都是c函數,所以oc的內存管理語義在這里也受到了影響闷沥,需要做相應的改變萎战,詳細的objc_AssociationPolicy
如下
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
這里語義內容很清晰了,就不多做解釋了舆逃,那針對上面的案例蚂维,如何用關聯對象完成setter和getter
- (void)setImageName:(NSString *)imageName {
objc_setAssociatedObject(self, @selector(imageName), imageName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)imageName {
return objc_getAssociatedObject(self, _cmd);
}
這里的key
戳粒,可以使用c字符串等形式,我這里放入表示方法名的SEL類型也可以虫啥,_cmd 關鍵字表示當前方法選擇子蔚约,也就是@selector(imageName)
,當然這里也可以用靜態(tài)指針static void *
但是為了保證key
的唯一性涂籽,我們還是用當前的方法苹祟,可以看到成功打印出結果image_name
,下面對個函數進行詳細分析又活。
objc_setAssociatedObject
我們在ojbc-runtime.m
文件中找到以下代碼片段
#if SUPPORT_GC
PRIVATE_EXTERN void objc_setAssociatedObject_gc(id object, const void *key, id value, objc_AssociationPolicy policy) {
if ((policy & OBJC_ASSOCIATION_COPY_NONATOMIC) == OBJC_ASSOCIATION_COPY_NONATOMIC) {
value = objc_msgSend(value, SEL_copy);
}
auto_zone_set_associative_ref(gc_zone, object, (void *)key, value);
}
#endif
PRIVATE_EXTERN void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
#if SUPPORT_GC
if (UseGC) {
if ((policy & OBJC_ASSOCIATION_COPY_NONATOMIC) == OBJC_ASSOCIATION_COPY_NONATOMIC) {
value = objc_msgSend(value, SEL_copy);
}
auto_zone_set_associative_ref(gc_zone, object, (void *)key, value);
} else
#endif
{
// Note, creates a retained reference in non-GC.
_object_set_associative_reference(object, (void *)key, value, policy);
}
}
在這里我們確定方法調用棧的內部方法為_objc_set_associative_reference
這個方法苔咪,那么我們跳轉到這個方法內部看其具體實現锰悼。
PRIVATE_EXTERN void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
uintptr_t old_policy = 0; // NOTE: old_policy is always assigned to when old_value is non-nil.
id new_value = value ? acquireValue(value, policy) : nil, old_value = nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &old_entry = j->second;
old_policy = old_entry.policy;
old_value = old_entry.value;
old_entry.policy = policy;
old_entry.value = new_value;
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
_class_setInstancesHaveAssociatedObjects(_object_getClass(object));
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &old_entry = j->second;
old_policy = old_entry.policy;
old_value = (id) old_entry.value;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_value) releaseValue(old_value, old_policy);
}
由于代碼量較多柳骄,我們忽略大部分的邏輯部分,看到產生作用的有如下幾個類:AssociationsManager,AssociationsHashMap,ObjectAssociationMap,ObjcAssociation
下面一個個看這幾個類的作用
AssociationsManager關聯對象管理類
class AssociationsManager {
static OSSpinLock _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { OSSpinLockLock(&_lock); }
~AssociationsManager() { OSSpinLockUnlock(&_lock); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new(::_malloc_internal(sizeof(AssociationsHashMap))) AssociationsHashMap();
return *_map;
}
};
OSSpinLock AssociationsManager::_lock = OS_SPINLOCK_INIT;
AssociationsHashMap *AssociationsManager::_map = NULL;
這里實現了OSSpinLock
和AssociationsHashMap
這兩個單例箕般,在構造這個方法的時候耐薯,會調用OSSpinLockLock
,而在析構的時候會調用OSSpinLockUnlock
丝里,associations
方法可以取得一個AssociationsHashMap
單例曲初,很明顯這個管理類通過持有一個自旋鎖
保證了操作AssociationsHashMap
是線程安全的,所以每次只有一個線程可以對AssociationsHashMap進行操作
杯聚。
ObjcAssociation關聯對象實際存儲的方式
從ObjectAssociation臼婆,ObjectAssociationMap
結構體名定義來看,這兩個分別是關聯對象結構體和關聯對象結構體映射表
幌绍,下面上源碼
struct ObjcAssociation {
uintptr_t policy;
id value;
ObjcAssociation(uintptr_t newPolicy, id newValue) : policy(newPolicy), value(newValue) { }
ObjcAssociation() : policy(0), value(0) { }
};
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjcAllocator<std::pair<void * const, ObjcAssociation> > > {
public:
void *operator new(size_t n) { return ::_malloc_internal(n); }
void operator delete(void *ptr) { ::_free_internal(ptr); }
};
typedef hash_map<void *, ObjectAssociationMap *, ObjcPointerHash, ObjcPointerEqual, ObjcAllocator<void *> > AssociationsHashMap;
這里ObjcAssociation
關聯對象結構體存儲了policy存儲策略
和value關聯對象值
這兩個重要屬性颁褂,ObjcAssociationMap
這里關聯了key
到ObjcAssociation
的映射,這個類存儲了所有這個對象所關聯對象的信息傀广。
拿出我們的經典demo案例颁独,
- (void)setImageName:(NSString *)imageName {
objc_setAssociatedObject(self, @selector(imageName), imageName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
用圖來表示一下這段代碼的具體實現
下面接著分析_objc_set_associative_reference
這個方法,根據new_value
出現了第一次邏輯判斷
uintptr_t old_policy = 0; // NOTE: old_policy is always assigned to when old_value is non-nil.
id new_value = value ? acquireValue(value, policy) : nil, old_value = nil;
用個demo看一下這個new_value
的作用
UIImage * image = [UIImage imageNamed:@"image_name"];
NSLog(@"%@", objc_getAssociatedObject(image, @selector(imageName)));
objc_setAssociatedObject(image, @selector(imageName), @"image_name", OBJC_ASSOCIATION_COPY_NONATOMIC);
NSLog(@"%@", objc_getAssociatedObject(image, @selector(imageName)));
objc_setAssociatedObject(image, @selector(imageName), nil, OBJC_ASSOCIATION_COPY_NONATOMIC);
NSLog(@"%@", objc_getAssociatedObject(image, @selector(imageName)));
2019-03-19 18:21:08.667240+0800 Demo[5301:197907] (null)
2019-03-19 18:21:08.667557+0800 Demo[5301:197907] image_name
2019-03-19 18:21:08.667789+0800 Demo[5301:197907] (null)
這里看得出伪冰,如果我們在value
字段設置為nil
誓酒,相當于清除了這個關聯對象的key
,所以我們初步可以認為贮聂,如果new_value為真
靠柑,那么邏輯中應該是創(chuàng)建一個關聯對象或者修改一個關聯對象的值
,如果new_value為假
吓懈,那么應該是清除這個關聯對象
歼冰。為了驗證,我們找到邏輯為假這部分代碼骄瓣。
if (j != refs->end()) {
ObjcAssociation &old_entry = j->second;
old_policy = old_entry.policy;
old_value = (id) old_entry.value;
refs->erase(j);
}
這里明顯看到調用了erase
函數擦出了這個關聯對象的key
停巷。
所以_objc_set_associative_reference
方法流程如下
1.從AssociationsManager
單例中耍攘,在線程安全的狀態(tài)下,取得全局關聯對象哈希表AssociationsHashMap
2.根據關聯對象所屬類畔勤,從AssociationsHashMap
取得這個類的關聯對象哈希表ObjectAssociationMap
蕾各,如果ObjectAssociationMap
這個表不存在則創(chuàng)建一個新的表。
3.根據void * key
庆揪,從ObjectAssociationMap
中查找到ObjcAssociation
結構體式曲,如果沒有這個結構體則新創(chuàng)建這個結構體。
4.如果new_value
為空缸榛,ObjectAssociationMap
會調用erase
函數擦除這個key
吝羞。
4.ObjcAssociation
結構體中應存有policy存儲策略
和value值
。
以上就是objc_setAssociatedObject
方法的實現流程内颗,另外兩個方法原理雷同這里不加以多贅述钧排。
總結
Category結構體中不可以添加實例變量
,可以添加方法
均澳,添加屬性時不會自動生成setter和getter方法
恨溜,需要我們手動實現,由于不能添加實例變量所以實現這兩個方法需要用到關聯對象
找前,它的實質是ObjcAssociation
這個結構體糟袁,里面主要存儲了存儲策略和具體值
,這個結構體存在于ObjectAssociationMap
哈希表中躺盛,這個表存儲了每個對象具體的關聯對象项戴,鍵為void *類型
的一段字符串,這個哈希表存在于AssociationsHashMap
這個表中槽惫,這個表實際根據對象的不同為key
存儲了全部關聯對象周叮,這個表被AssociationsManager
這個單例所持有,每次調用方法時會以單例的形式創(chuàng)建
躯枢,它是線程安全的
则吟。
后續(xù)
到這里已經將最重要的三個部分分析好了,分別是運行時結構和消息機制以及關聯對象锄蹂,感興趣的朋友們可以移步下一篇文章 OC運行時機制Runtime(四):嘗試使用黑魔法 Method Swizzling氓仲,如果覺得本文對您有些作用,請在下方點個贊再走哈~