Category-初探

參考:objc4-750源碼編譯

什麼是Category?

分類是Objective-C 2.0之後新加的語言特性,一個特殊類,主要有一下作用:

  • 在不改變原有類的前提下爲已存在的類擴展新方法
  • 拆分類文件做功能分類,便於管理
  • 引用即用
  • 有多繼承的效果

分類不能添加成員變量,可以添加屬性,但是系統不會生成屬性對應的getter、setter方法,因此屬性是無效的,需要我們手動添加getter、setter方法,通過關聯屬性達到擴展屬性的效果。

什麼是Extension?

Extension一般稱爲擴展、延展、匿名分類,應用中不僅能夠聲明方法,還能聲明屬性,成員變量。

分類應用

1、NSDate

#pragma mark - 格式化時間
+(NSString*)dateFormat:(NSString*)format seconds:(NSTimeInterval)secs{
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [formatter setTimeZone:nil];
    [formatter setDateFormat:format];
     NSString *timeStr = [formatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:secs]];
    return timeStr;
}
#pragma mark - 將字符串轉換成時間戳
+(NSTimeInterval)getTimeIntervalFormat:(NSString *)format TimeString:(NSString *)timeString{
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:format];
    NSDate *date = [formatter dateFromString:timeString];
    NSInteger timeSp = [[NSNumber numberWithDouble:[date timeIntervalSince1970]] integerValue];
    return timeSp;
}
#pragma mark - 根據時間戳判斷這一天是周幾
+(NSString*)getWeekSeconds:(NSTimeInterval)secs{
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [formatter setTimeZone:nil];
    [formatter setDateFormat:@"EEEE"];
    NSString *timeStr = [formatter stringFromDate:[NSDate dateWithTimeIntervalSince1970:secs]];
    return timeStr;
}

2、NSString

#pragma mark - 判斷字符串是否爲空
+ (BOOL)isEmptyString:(NSString*)string{
    if ([string isKindOfClass:[NSNull class]]) {
        return YES ;
    }
    if([string isKindOfClass:[NSNumber class]]){
        string = [NSString stringWithFormat:@"%@%@",string,@""];
    }
    if (string == nil) {
        return YES ;
    }
    if (string.length == 0 ||
        [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]].length == 0){
        return YES ;
    }
    return NO ;
}
#pragma mark - json格式轉換爲json字符串
+(NSString *)jsonToJsonString:(id)obj{
    NSError *error = nil;
    NSData *jsonData = [NSJSONSerialization
                        dataWithJSONObject:obj options:NSJSONWritingPrettyPrinted error:&error];
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    jsonString = [jsonString stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    jsonString = [jsonString stringByReplacingOccurrencesOfString:@" " withString:@""];
    return jsonString;
}
#pragma mark- 獲取字符串字節數
+(NSUInteger)textByteLength: (NSString *) text{
    NSUInteger asciiLength = 0;
    for (NSUInteger i = 0; i < text.length; i++) {
        unichar uc = [text characterAtIndex: i];
        asciiLength += isascii(uc) ? 1 : 2;
    }
    NSUInteger unicodeLength = asciiLength;
    return unicodeLength;
}
#pragma mark - json字符串轉換爲json
-(id)jsonStringToJson{
    NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
    id obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
    return obj;
}

還有加密、手機號驗證、身份證驗證等等。

3、UIViewController

+ (void)load {
    //我們只有在開發的時候才需要查看哪個viewController將出現
    //所以在release模式下就沒必要進行方法的交換
#ifdef DEBUG
    //原本的viewWillAppear方法
    Method viewWillAppear = class_getInstanceMethod(self, @selector(viewWillAppear:));
    //需要替換成 能夠輸出日誌的viewWillAppear
    Method logViewWillAppear = class_getInstanceMethod(self, @selector(logViewWillAppear:));
    //兩方法進行交換
    method_exchangeImplementations(viewWillAppear, logViewWillAppear);
    Method viewWillDisappear = class_getInstanceMethod(self,@selector(viewWillDisappear:));
    Method logViewWillDisappear = class_getInstanceMethod(self,@selector(logViewWillDisappear:));
    method_exchangeImplementations(viewWillDisappear,logViewWillDisappear);
    
#endif
}
-(void)logViewWillDisappear:(BOOL)animated{
    NSString *className = NSStringFromClass([self class]);
    //在這裏,你可以進行過濾操作,指定哪些viewController需要打印,哪些不需要打印
    if ([className hasPrefix:@"UI"] == NO) {
        NSLog(@"%@ will disappear",className);
    }
    //下面方法的調用,其實是調用viewWillAppear
    [self logViewWillDisappear:animated];
}
- (void)logViewWillAppear:(BOOL)animated {
    NSString *className = NSStringFromClass([self class]);
    //在這裏,你可以進行過濾操作,指定哪些viewController需要打印,哪些不需要打印
    if ([className hasPrefix:@"UI"] == NO) {
        NSLog(@"%@ will appear",className);
    }
    //下面方法的調用,其實是調用viewWillAppear
    [self logViewWillAppear:animated];
}
  • 實現了+load方法,該方法在main方法之前調用,只調用一次,一般先執行原有類的+load方法再執行分類的+load方法
  • 交換函數指針,以便於調用時走分類擴展的方法
    等等,還有很多常用擴展,根據具體需求來擴展一個不影響原有類的實列方法或類方法。

Category源碼分析

創建一個Person類的分類,並添加一個屬性,一個方法。如下:

@interface Person (Test)
@property (nonatomic,strong) NSString *hibo_name;
-(void)hb_eat;
@end

終端進入類文件所在位置,通過命令將分類編譯成C++形式。如下:

clang -rewrite-objc Person+Test.m

打開文件找到_category_t如下:

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;
};

以結構體的形式存在的,存放方法列表、協議列表、屬性列表。

  • name:類名
  • classref_t:類對象,指向所屬的類,編譯時期沒有值,在runtime加載的時候指向對應的類
  • instance_methods:類擴展的實列方法列表
  • class_methods:類擴展的類方法列表
  • protocol_list_t:分類所實現的協議列表
  • _prop_list_t:屬性列表

有相關結構體就一定有對結構體賦值,繼續查找如下:

static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
 	"Person",
 	0, // &OBJC_CLASS_$_Person,
 	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,
 	0,
 	0,
 	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Test,
};

這裏對上面聲明的結構體進行初始化賦值。注意只有方法被實現後纔會賦值到結構體屬性。

繼續查找發現,下面還定義了_category_t類型的結構體變量。如下:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
 	&_OBJC_$_CATEGORY_Person_$_Test,
};

是一個數組類型的變量,將上面的_category_t對象地址存儲在了__DATA數據段中的__objc_catlist中的section段中。

Category是如何被加載的?

1、dyld是蘋果的動態加載器,用來加載二進制文件;

2、應用啓動時,系統內核先加載dylddyld再將應用中所依賴的各種庫加載到內存空間中,而這些工作是在main函數中執行完成的;

3、_objc_initObject-C runtime的入口函數,讀取Mach-O文件OC對應的segment section,並根據其中的數據代碼信息,完成爲OC的內存佈局,以及初始化runtime相關的數據結構。

通過符號斷點探索,下符號斷點,點擊斷點 -> 點擊加號,添加_objc_init編譯運行:
在這裏插入圖片描述
_dyld_start
在這裏插入圖片描述

1、_objc_init函數

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
  • _dyld_objc_notify_register:會註冊三個回調函數
  • map_images:``dyldimage加載到內存時調用
  • load_images:``dyld初始化imageload方法都在此時調用
  • unmap_image:image移除內存時調用

2、map_images

map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

3、map_images_nolock在objc-os.mm中

map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    firstTime = NO;
}
  • _read_images:讀取oc相關的section

4、_read_images在objc-runtime-new.mm中

當前方法內開始遍歷頭文件,看分類中的

for (EACH_HEADER) {
    category_t **catlist = 
        _getObjc2CategoryList(hi, &count);
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    for (i = 0; i < count; i++) {
        category_t *cat = catlist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) {
            // Category's target class is missing (probably weak-linked).
            // Disavow any knowledge of this category.
            catlist[i] = nil;
            if (PrintConnecting) {
                _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                             "missing weak-linked target class", 
                             cat->name, cat);
            }
            continue;
        }
        // Process this category. 
        // First, register the category with its target class. 
        // Then, rebuild the class's method lists (etc) if 
        // the class is realized. 
        bool classExists = NO;
        if (cat->instanceMethods ||  cat->protocols  
            ||  cat->instanceProperties) 
        {
            addUnattachedCategoryForClass(cat, cls, hi);
            if (cls->isRealized()) {
                remethodizeClass(cls);
                classExists = YES;
            }
            if (PrintConnecting) {
                _objc_inform("CLASS: found category -%s(%s) %s", 
                             cls->nameForLogging(), cat->name, 
                             classExists ? "on existing class" : "");
            }
        }
        if (cat->classMethods  ||  cat->protocols  
            ||  (hasClassProperties && cat->_classProperties)) 
        {
            addUnattachedCategoryForClass(cat, cls->ISA(), hi);
            if (cls->ISA()->isRealized()) {
                remethodizeClass(cls->ISA());
            }
            if (PrintConnecting) {
                _objc_inform("CLASS: found category +%s(%s)", 
                             cls->nameForLogging(), cat->name);
            }
        }
    }
}
  • 遍歷分類的頭文件
  • _getObjc2CategoryList:GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist")
  • addUnattachedCategoryForClass:將當前這個分類加載到這個類當中
  • 註冊實例方法到目標類
  • 註冊類方法到目標類
  • remethod對原有類的分佈重新佈局

5、addUnattachedCategoryForClass在objc-runtime-new.mm中

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertLocked();
    // DO NOT use cat->cls! cls may be cat->cls->isa instead
    NXMapTable *cats = unattachedCategories();
    category_list *list;
    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    NXMapInsert(cats, cls, list);
}
  • NXMapInsert(cats, cls, list):將分類和類建立關聯

6、remethodizeClass-重新佈局

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;
    runtimeLock.assertLocked();
    isMeta = cls->isMetaClass();
    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

7、attachCategories

attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);
    bool isMeta = cls->isMetaClass();
    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));
    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
    auto rw = cls->data();
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}
  • 先聲明內存大小
  • method_list_t:二維數組
  • perpareMethodLists:準備方法列表
  • attachLists:添加方法列表、屬性列表、協議列表
void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;
    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    } 
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}
  • oldCount:獲取原有類的方法(或屬性、協議)列表大小
  • newCount:加上分類中添加的方法列表大小得到新的大小
  • memmove:拷貝字節,如果目標區域和源區域有重疊的話,能夠保證源串在被覆蓋之前將重疊區域的字節拷貝到目標區域中,複製後源內容會被更改。沒有重疊和memcpy一樣
  • memcpy:拷貝,在發生內存重疊的時候有可能成功有可能失敗
  • 將源方法列表或屬性協議追加到新的方法列表等上,在獲取方法列表新的方法會首先命中

8、perpareMethodLists

prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, 
                   bool baseMethods, bool methodsFromBundle)
{
    runtimeLock.assertLocked();
    if (addedCount == 0) return;
    // Don't scan redundantly
    bool scanForCustomRR = !cls->hasCustomRR();
    bool scanForCustomAWZ = !cls->hasCustomAWZ();
    // There exist RR/AWZ special cases for some class's base methods. 
    // But this code should never need to scan base methods for RR/AWZ: 
    // default RR/AWZ cannot be set before setInitialized().
    // Therefore we need not handle any special cases here.
    if (baseMethods) {
        assert(!scanForCustomRR  &&  !scanForCustomAWZ);
    }
    // Add method lists to array.
    // Reallocate un-fixed method lists.
    // The new methods are PREPENDED to the method list array.
    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[i];
        assert(mlist);
        // Fixup selectors if necessary
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
        // Scan for method implementations tracked by the class's flags
        if (scanForCustomRR  &&  methodListImplementsRR(mlist)) {
            cls->setHasCustomRR();
            scanForCustomRR = false;
        }
        if (scanForCustomAWZ  &&  methodListImplementsAWZ(mlist)) {
            cls->setHasCustomAWZ();
            scanForCustomAWZ = false;
        }
    }
}
  • fixupMethodList:修復方法列表

關聯對象的探索

在分類中添加屬性,並使用關聯對象來實現值的存取。

//獲取關聯對象的值
id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}
//設置關聯對象的值
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}
//移除關聯屬性
void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}
  • object:綁定到的對象
  • key:設置綁定key,以便通過key獲取綁定值
  • value:綁定值
  • policy:綁定策略
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. */
};
  • 設置屬性修飾符
類型 說明
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic,strong
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic,copy
OBJC_ASSOCIATION_RETAIN atomic,strong
OBJC_ASSOCIATION_COPY atomic,copy

1、_objc_set_associative_reference

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;
        //初始化hashMap
        AssociationsHashMap &associations(manager.associations());
        //當前對象的地址按位取反(key)
        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);
}
  • acquireValue:根據內存管理語義進行內存管理
  • ObjcAssociation:該對象用來存儲關聯屬性的屬性值,兩個屬性一個存儲策略,一個屬性值
  • AssociationsManager:管理關ObjcAssociationHashMap對象
  • ObjectAssociationMap:關聯對象存儲屬性值的數據結構,key爲用戶自定義的字符串,value爲屬性值

acquireValue:

static id acquireValue(id value, uintptr_t policy) {
    switch (policy & 0xFF) {
    case OBJC_ASSOCIATION_SETTER_RETAIN:
        return objc_retain(value);
    case OBJC_ASSOCIATION_SETTER_COPY:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
    }
    return value;
}
  • 根據配置項,對value操作

AssociationsManager:

class AssociationsManager {
    // associative references: object pointer -> PtrPtrHashMap.
    static AssociationsHashMap *_map;
public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

AssociationsManager管理着AssociationsHashMap這個類,在該類中提供了對關聯對象的屬性添加、刪除、查找的函數,並且訪問是安全的。

總結如下:

  • AssociationsHashMap:key爲對象地址按位取反 valueObjectAssociationMap
  • ObjectAssociationMap:key爲用戶自定義字符串valueObjcAssociation對象
  • ObjcAssociation:包含兩個屬性,policy存儲策略,value爲關聯屬性的值
  • AssociationsManager -> AssociationsHashMap -> ObjcAssociation -> value

2、_object_get_associative_reference

通過對象地址和設置的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;
}

同存屬性值一樣,通過mananger來初始化AssociationsHashMap,通過對象地址指針取反作爲key來獲取ObjectAssociationMap對象,在通過用戶自定義key(字符串)在ObjectAssociationMap對象中獲取ObjcAssociation對象,該對象存在兩個屬性,一個爲屬性值存儲策略,另一個爲存儲的屬性值。

3、_object_remove_assocations

移除對象的關聯對象

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());
}

該方法不需要我們手動調用,系統在清理對象的時候會調用,及時清理關聯屬性。可以在objc-750源碼中搜索dealloc來驗證一下是否調用過該方法。如下:

- (void)dealloc {
    _objc_rootDealloc(self);
}
* 內部調用了_objc_rootDealloc來清理內存
_objc_rootDealloc(id obj)
{
    assert(obj);
    obj->rootDealloc();
}
繼續追蹤:
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
  • 對isa指針做了判斷,判斷是否存在一些關聯內容,如果都不存在直接釋放當前對象,判斷是否有弱引用對象、關聯對象
  • 如果存在繼續調用object_dispose
object_dispose(id obj)
{
    if (!obj) return nil;
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}
* objc_destructInstance:銷燬實例對象
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();
        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }
    return obj;
}

hasAssociatedObjects判斷是否存在關聯屬性,如果有就移除。在這裏就調用了_object_remove_assocations方法來移除關聯屬性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章