目錄
-
面試題
-
僞代碼實現
-
Category添加屬性的幾種用法
-
Category爲什麼不能添加成員變量
-
底層源碼解讀
1、面試題
面試題一、Category能否添加成員變量?如果可以,如何給Category添加成員變量?
- 不能直接給Category添加成員變量
因爲Category是在運行時決定的。編譯時,對象的內存佈局已經確定,如果添加實例變量就會破壞類的內部佈局。
Category中使用@property只會生成setter和getter方法的聲明,並不會自動生成實例變量以及存取方法
- 可以間接實現Category有成員變量的效果
可以通過RunTime中關聯對象在Category中爲類添加成員變量
2、僞代碼實現
我們知道,如果我們自己手動實現setter/getter方法,剩下的就是一個成員變量的存取了,下面用兩種方法實現一下,看看是否存在瑕疵
-
創建全局對象(❌)
int main(int argc, const char * argv[]) {
@autoreleasepool {
LYPerson *p1 = [[LYPerson alloc] init];
p1.name = @"1";
LYPerson *p2 = [[LYPerson alloc] init];
p2.name = @"2";
NSLog(@"p1 name = %@ ; p2 name = %@", p1.name, p2.name);
}
}
@implementation LYPerson (Test)
NSString *name_;
- (void)setName:(NSString *)name {
name_ = name;
}
- (NSString *)name {
return name_;
}
@end
2019-07-24 22:17:32.291885+0800 test[88511:509458] p1 name = 2 ; p2 name = 2
結論:如果創建多個實例對象,那麼這些對象將公用一個全局對象,後面的將會把前面的值覆蓋掉
-
創建全局字典
@implementation LYPerson (Test)
NSMutableDictionary *names_;
+ (void)load {
names_ = [NSMutableDictionary dictionary];
}
- (void)setName:(NSString *)name {
NSString *key = [NSString stringWithFormat:@"%p", self];
weights_[key] = key;
}
- (NSString *)name {
NSString *key = [NSString stringWithFormat:@"%p", self];
return weights_[key];
}
@end
- 能實現,但會存在問題,例如線程安全,比較麻煩
3、基本用法
-
用法一
#import "LYPerson+Test.h"
#import <objc/runtime.h>
@implementation LYPerson (Test)
static const char LYNameKey;
- (void)setName:(NSString *)name {
/**
對象關聯
@param object 關聯對象
@param key
@param value 關聯值
@param policy 關聯策略
objc_AssociationPolicy 對應修飾符
- OBJC_ASSOCIATION_ASSIGN = 0, assign
- OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
- OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
- OBJC_ASSOCIATION_RETAIN strong, atomic
- OBJC_ASSOCIATION_COPY copy, atomic
@return
*/
objc_setAssociatedObject(self, &LYNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, &LYNameKey);
}
@end
-
用法二
#define MJNameKey @"name"
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, LYNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, LYNameKey);
}
// @"name" 直接寫出來的字符串,放在常量區
-
用法三
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, @selector(name));
}
//隱式參數:get方法可以用_cmd代替;_cmd = @selector(name)
- (NSString *)name {
return objc_getAssociatedObject(self, _cmd);
}
- 優點:1、可讀性更高,2、有編譯器提示
5、爲什麼不能添加成員變量
// Category在內存中的結構
struct _category_t {
const char *name; // 類名
struct _class_t *cls;
const struct _method_list_t *instance_methods; // 實例方法列表
const struct _method_list_t *class_methods; // 類方法列表
const struct _protocol_list_t *protocols; // 協議列表 (分類裏面也能遵守協議)
const struct _prop_list_t *properties; // 屬性列表 (分類裏能實現屬性)
};
- 分類底層結構決定了他不能添加成員變量,沒有數組是用來存放成員變量的
- 用關聯對象的方式給分類添加屬性
不是存儲在類對象、實例對象中,所以不會影響類對象、實例對象在內存中的存儲結構
6、底層源碼解讀(待完善)
實現關聯對象技術的核心對象有
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
總結:
- 關聯對象並不是存儲在被關聯對象本身中
- 關聯對象存儲在全局的統一的一個AssociationManager中
- 設置關聯對象爲nil,就相當於移除關聯對象
// 添加
objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>)
// 獲取
objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>)
// 移除
objc_removeAssociatedObjects(<#id _Nonnull object#>)
objc_setAssociatedObject
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
通過上圖我們可以總結爲:一個實例對象就對應一個ObjectAssociationMap,而ObjectAssociationMap中存儲着多個此實例對象的關聯對象的key以及ObjcAssociation,爲ObjcAssociation中存儲着關聯對象的value和policy策略。
由此我們可以知道關聯對象並不是放在了原來的對象裏面,而是自己維護了一個全局的map用來存放每一個對象及其對應關聯屬性表格。
通過分析可以得出:一個實例對象會有一個 AssociationsManager 操作類和一個存儲Map ObjectAssociationMap,
objc_getAssociatedObject
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
objc_removeAssociatedObjects
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}