iOS底層原理 Category與關聯對象本質 --(4)

上篇文章講了KVO本質和KVC的聯繫,想必你們都已經清楚了,今天咱們再看一下Category的底層原理。 先看一下Category的簡單使用,首先新增一個類的Category,而後添加須要的函數,而後在使用的文件中導入就能夠直接使用了。代碼以下:git

@interface FYPerson : NSObject
- (void)run;
@end
@implementation FYPerson
-(void)run{
  NSLog(@"run is run");
}
@end


//類別
@interface FYPerson (test)
- (void)test;
@end
@implementation FYPerson (test)
- (void)test{
  NSLog(@"test is run");
}
@end


//使用
#import "FYPerson.h"
#import "FYPerson+test.h"


FYPerson *person=[[FYPerson alloc]init];
[person test];
[person run];
複製代碼

類別使用就是這麼簡單。 那麼類別的本質是什麼呢?類的方法是存儲在什麼地方呢? 第一篇類的本質已經講過了,運行時中,類對象是有一份,方法都存儲在類對象結構體fy_objc_class中的class_data_bits_t->data()->method_list_t中的,那麼類別方法也是存儲在method_list_t和取元類對象的method_list_t中的。編譯的時候類別編譯成結構體_category_t,而後runtime在運行時動態將方法添加到method_list_t中。運行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc FYPerson+test.m -o FYPerson+test.cpp進入到FYPerson+test.cpp內部查看編譯以後的代碼github

struct _category_t {
  const char *name; //"FYPerson"
  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;
};
//存儲 test方法
static struct /*_method_list_t*/ {
  unsigned int entsize;  // sizeof(struct _objc_method)
  unsigned int method_count;
  struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_FYPerson_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
  sizeof(_objc_method),
  1,
  {{(struct objc_selector *)"test", "v16@0:8", (void *)_I_FYPerson_test_test}}
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_FYPerson;

//_category_t 存儲FYPerson的分類的數據
static struct _category_t _OBJC_$_CATEGORY_FYPerson_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
  "FYPerson",
  0, // &OBJC_CLASS_$_FYPerson,
  (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_FYPerson_$_test,//instace方法
  0,//類方法
  0,//協議方法
  0,//屬性
};
複製代碼

存儲在_category_t中的數據是什麼時間加載到FYPersonclass_data_bits_t.data呢?咱們探究一下,打開源碼下載打開工程閱讀源碼找到objc-os.mm,經過查找函數運行順序獲得_objec_init->map_images->map_images_noljock->_read_images->remethodizeClass(cls)->attachCategories(cls, cats, true /*flush caches*/),最終進入到attachCategories關鍵函數內部:bootstrap

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void 
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
  //方法數組[[1,2,3],[4,5,6],[7,8,9]]
  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];
//取出分類 的 instance方法或者class方法
      method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
      if (mlist) {
          mlists[mcount++] = mlist; //mlists 接受全部分類方法
          fromBundle |= entry.hi->isBundle();
      }
//proplist 接受全部分類屬性
      property_list_t *proplist = 
          entry.cat->propertiesForMeta(isMeta, entry.hi);
      if (proplist) {
          proplists[propcount++] = proplist;
      }
//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);//釋放數組
}
複製代碼

attachCategories是將全部的分類方法和協議,屬性倒序添加到類中,具體添加的優先級是怎麼操做的?進入到rw->protocols.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)));
			//賦值count
            array()->count = newCount;
			// array()->lists:原來的方法列表向後移動 oldCount * sizeof(array()->lists[0]個長度
            memmove(array()->lists + addedCount/*指針移動到數組末尾*/, array()->lists/*數組*/,
                    oldCount * sizeof(array()->lists[0])/*移動數據的大小*/);
			//空出來的 內存使用addedLists拷貝過去 大小是:addedCount * sizeof(array()->lists[0])
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
			/*
			圖示講解:
			array()->lists:A->B->C->D->E
		addedCount:3
		addedLists:P->L->V
			memmove以後:nil->nil->nil->A->B->C->D->E
			而後再講addedLists插入到數組前邊,最終array()->lists的值是:
			P->L->V->A->B->C->D->E
			 */
        }
        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]));
        }
    }
複製代碼

能夠看出來:緩存

  1. 首先經過runtime加載某個類的全部Category數據
  2. 把全部Category的方法,屬性,協議數據合併到一個大數組中,後面參與編譯的數組會出如今數組前邊
  3. 將合併後的分類數組(方法,屬性,協議)插入到類原來的數據的前面。

具體的編譯順序是project文件中->Build Phases->Complile Sources的順序。bash

調用順序

+load加載順序

每一個類和分類都會加載的時候調用+load方法,具體是怎麼調用呢?咱們查看源碼_objc_init->load_images->call_load_methods數據結構

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more //執行class+load直到完成 while (loadable_classes_used > 0) { call_class_loads(); } //執行Category +load 一次 // 2. Call category +loads ONCE more_categories = call_category_loads(); // 3. Run more +loads if there are classes OR more untried categories } while (loadable_classes_used > 0 || more_categories); objc_autoreleasePoolPop(pool); loading = NO; } 複製代碼

+loadCategory+load前邊執行,當類的+load執行完畢而後再去執行Category+load,並且只有一次。 當class有子類的時候加載順序呢?其實全部類都是基於NSObject,那麼咱們假設按照編譯順序加載Class+load,就有一個問題是父類+load執行的操做豈不是在子類執行的時候尚未執行嗎?這個假設明顯不對,基類+load中的操做是第一個執行的,其餘子類是按照superclass->class->sonclass的順序執行的。 查看源碼_objc_init->load_images->prepare_load_methods((const headerType *)mh)->schedule_class_loadobjc-runtime-new.mm2856行app

/***********************************************************************
* Schedule +load for classes in this image, any un-+load-ed 
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    //遞歸調用本身直到調用clas->self
    schedule_class_load(cls->superclass);
//添加class
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
複製代碼

能夠了解到該函數遞歸調用本身,直到+load方法已經調用過爲止,因此無論編譯順序是高低,+load的加載順序始終是NSObject->FYPrson->FYStudent。多個類平行關係的話,按照編譯順序加載。 下邊是稍微複雜點的類關係:iphone

NSObject
    Person
        Student
NSObjet
    Car
        BigCar
            BigOrSmallCar
複製代碼

編譯順序是ide

Person
Student
Car
BigOrSmallCar
複製代碼

那麼他們+load的加載順序是:

NSobject->Person->Student->Car->BigCar->BigOrSmallCar
複製代碼

看着不是很明白的 能夠再看一下剛纔的schedule_class_load函數。 加載成功以後,是按照objc_msgsend()流程發送的嗎?咱們進入到call_class_loads內部

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}
複製代碼

能夠找到(*load_method)(cls, SEL_load);該函數,該函數是直接使用IMP執行的,IMP就是函數地址,能夠直接訪問函數而不用消息的轉發流程。

+initialize調用

  • +initialize方法會在類第一次接收到消息時調用
  • 先調用父類的+initialize,再調用子類的+initialize
  • 先初始化父類,再初始化子類,每一個類只會初始化1次

objc源碼解讀過程objc-msg-arm64.x->objc_msgSend->objc->runtime-new->class_getinstanceMethod->lookUpImpOrNil->lookUpImpOrForward->_clas_initialize->callInitialize->objc_msgSend(cls,SEL_Initialize)runtime-new.h4819行

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that // wants a Method instead of an IMP. #warning fixme build and search caches // Search method lists, try method resolver, etc. lookUpImpOrNil(cls, sel, nil, NO/*initialize*/, NO/*cache*/, YES/*resolver*/); #warning fixme build and search caches return _class_getMethod(cls, sel); } 複製代碼

根據lookUpImpOrNil查看4916行

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.lock();
    checkIsKnownClass(cls);

    if (!cls->isRealized()) {
        realizeClass(cls);
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.lock();
      //當第一次收到消息,cls沒有初始化,則調用_class_initialize進行初始化
    }

    
 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache. imp = cache_getImp(cls, sel); if (imp) goto done; // Try this class's method lists.
    //在本類中查找method
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method // resolver for this class first. break; } } // Superclass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; } } } // No implementation found. Try method resolver once. if (resolver && !triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; goto retry; } // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}
複製代碼

當第一次收到消息,cls沒有初始化,則調用_class_initialize進行初始化 咱們進入到_class_initialize內部objc-initialize.mm484行

void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    //遞歸調用父類是否有初始化和是否有父類
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }
    
    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
        
        // Record that we're initializing this class so we can message it. _setThisThreadIsInitializingClass(cls); if (MultithreadedForkChild) { // LOL JK we don't really call +initialize methods after fork().
            performForkChildInitialize(cls, supercls);
            return;
        }
        
        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218 if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]", pthread_self(), cls->nameForLogging()); } // Exceptions: A +initialize call that throws an exception // is deemed to be a complete and successful +initialize. // // Only __OBJC2__ adds these handlers. !__OBJC2__ has a // bootstrapping problem of this versus CF's call to
        // objc_exception_set_functions().
#if __OBJC2__
        @try
#endif
        {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
        }
#if __OBJC2__
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             pthread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        // We couldn't set INITIALIZING because INITIALIZING was already set. // If this thread set it earlier, continue normally. // If some other thread set it, block until initialize is done. // It's ok if INITIALIZING changes to INITIALIZED while we're here, // because we safely check for INITIALIZED inside the lock // before blocking. if (_thisThreadIsInitializingClass(cls)) { return; } else if (!MultithreadedForkChild) { waitForInitializeToComplete(cls); return; } else { // We're on the child side of fork(), facing a class that
            // was initializing by some other thread when fork() was called.
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }
    
    else if (cls->isInitialized()) {
        // Set CLS_INITIALIZING failed because someone else already 
        //   initialized the class. Continue normally.
        // NOTE this check must come AFTER the ISINITIALIZING case.
        // Otherwise: Another thread is initializing this class. ISINITIALIZED 
        //   is false. Skip this clause. Then the other thread finishes 
        //   initialization and sets INITIALIZING=no and INITIALIZED=yes. 
        //   Skip the ISINITIALIZING clause. Die horribly.
        return;
    }
    
    else {
        // We shouldn't be here. _objc_fatal("thread-safe class init in objc runtime is buggy!"); } } 複製代碼

能夠看出來,和+load方法同樣,先父類後子類。而後賦值reallyInitialize = YES;,後邊使用try主動調用callInitialize(cls);,來到callInitialize(cls);內部:

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}
複製代碼

能夠看到最終仍是使用((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize)主動調用了該函數。

區別

+initialize和+load的很大區別是,+initialize是經過objc_msgSend進行調用的,因此有如下特色 若是子類沒有實現+initialize,會調用父類的+initialize(因此父類的+initialize可能會被調用屢次) 若是分類實現了+initialize,就覆蓋類自己的+initialize調用

用僞代碼實現如下思路:

if(class 沒有初始化){
        父類初始化
        子類初始化
        調用initialize
    }
    若是子類沒有實現initialize,則去調用父類initialize。
複製代碼

至於子類沒有實現的話是直接調用父類的initialize,是使用objc-msgsend的緣由。

驗證

@interface FYPerson : NSObject

@end
+(void)initialize{
	printf("\n%s",__func__);

}
+(void)load{
	printf("\n%s",__func__);

}
@interface FYPerson (test1)

@end

+(void)initialize{
	printf("\n%s",__func__);

}
+(void)load{
	printf("\n%s",__func__);

}
//輸出
+[FYPerson load]
+[FYPerson(test2) load]
+[FYPerson(test1) load]

複製代碼

總結

  • +load是根據函數地址直接調用,initialize是經過objc_msgSend調用
  • +load是runtime加載類、分類時候調用(只會調用一次)
  • initialize是第一次接受消息的時候調用,每一個類只會調用一次(子類沒實現,父類可能被調用屢次)
  • +load調用優先於initialize,子類調用+load以前會調用父類的+load,再調用分類的+load,分類之間先編譯,先調用。
  • initialize先初始化父類,再初始化子類(可能最終調用父類的initialize

關聯對象本質

關聯對象的本質-結構體

繼承NSObject是能夠能夠直接使用@property (nonatomic,assign) int age;,可是在Category中會報錯,那麼怎麼實現和繼承基類同樣的效果呢? 咱們查看Category結構體

struct _category_t {
	const char *name; //"FYPerson"
	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;
};
複製代碼

其中const struct _prop_list_t *properties;是存儲屬性的,可是缺乏成員變量,而咱們也不能主動在_category_t插入ivar,那麼咱們可使用objc_setAssociatedObject將屬性的值存儲全局的AssociationsHashMap中,使用的時候objc_getAssociatedObject(id object, const void *key),不使用的時候刪除使用objc_removeAssociatedObjects刪除。

咱們進入到objc_setAssociatedObject內部,objc-references.mm275行

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);
	//根據key value 處理
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
		//生成一個全局的 HashMap
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
		//有value 就處理
        if (new_value) {
            // break any existing association.
//			遍歷 hashMap是否有該obj
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
				//有的話 更新其 value
                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).
				//沒有的話 賦值給 refs
                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 
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

複製代碼

經過該函數咱們瞭解到

  • 關聯對象並非存儲在關聯對象的自己內存中
  • 關聯對象是存儲在全局統一的AssociationsManager管理的AssociationsHashMap
  • 傳入value =nil,會移除該關聯對線 AssociationsManager實際上是管理了已key爲id object對應的AssociationsHashMapAssociationsHashMap存儲了key對應的ObjcAssociationObjcAssociation是存儲了valuepolicyObjcAssociation的數據結構以下:
class ObjcAssociation {
        uintptr_t _policy;
        id _value;
        *****
        }
複製代碼

具體抽象關係見下圖

AssociationsManager --> AssociationsHashMap --> ObjectAssociationMap
-->void * ObjectAssociation -->uintprt_t _policy ,id _value;
複製代碼

簡單來說就是一個全局變量保存了以classkey對應的AssociationsHashMap,這個AssociationsHashMap存儲了一個key對應的ObjectAssociationObjectAssociation包含了value_policy。經過2層map保存了數據。

關聯對象的使用

objc_setAssociatedObject obj,key,value,policy
objc_getAssociatedObject 根據 obj 和 key獲取值
void objc_removeAssociatedObjects(id object) 根據obj 刪除關聯函數

objc_AssociationPolicy的類型:

OBJC_ASSOCIATION_ASSIGN weak 引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC 非原子強引用
OBJC_ASSOCIATION_COPY_NONATOMIC 非原子至關於copy
OBJC_ASSOCIATION_RETAIN 強引用
OBJC_ASSOCIATION_COPY 原子操做,至關於copy

代碼示例

@interface NSObject (test)
@property (nonatomic,assign) NSString * name;
@end

#import "NSObject+test.h"
#import "objc/runtime.h"
@implementation NSObject (test)
-(void)setName:(NSString *)name{
	objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name{
	return  objc_getAssociatedObject(self, @selector(name));
}
@end



NSObject *obj =[[NSObject alloc]init];
obj.name = @"老弟來了";
printf("%s",obj.name.UTF8String);
//老弟來了
複製代碼

這段代碼咱們實現了給基類添加一個成員變量name,而後又成功取出了值,標示咱們作新增的保存成員變量的值是對的。

總結

  • Category +load在冷啓動時候執行,執行順序和編譯順序成弱相關,先父類,後子類,並且每一個類執行一次,執行是直接調用函數地址。
  • Category +initialize在第一次接受消息執行,先父類,後子類,子類沒實現,會調用父類,利用objc-msgsend機制調用。
  • Category 能夠利用Associative添加和讀取屬性的值

資料下載


最怕一輩子碌碌無爲,還安慰本身平凡難得。

廣告時間

相關文章
相關標籤/搜索