OC底层学习-Category

1. Category

1.1分类的简单引用场景

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

//分类
@interface GYPerson (Test)
- (void)test;
@end
@implementation GYPerson (Test)
- (void)test {
    NSLog(@"test==========");
}
@end

@interface GYPerson (Eat)
- (void)eat;
@end
@implementation GYPerson (Eat)
- (void)eat {
    NSLog(@"eat============");
}
@end

//测试代码
 GYPerson *person = [[GYPerson alloc] init];
[person run];
[person test];
[person eat];

上述是一个简单的Category的引用场景

1.2 Category编译之后的底层结构

首先我们使用终端命令把其中一个分类编译成C++文件(编译指令:xcrun -sdk iphoneos clang -arc arm64 -rewrite-objc ob_name(文件名称) -> 直接把c++的文件编译在当前文件夹下),来查看其中分类的结构,

  • C++ 文件
//分类的底层结构:
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;//属性  分类中也可以增加属性
         };
         
//GYPerson+Test的分类结构
static struct _category_t _OBJC_$_CATEGORY_GYPerson_$_Test __attribute__ ((used, 
section ("__DATA,__objc_const"))) = 
{
	"GYPerson", 
	0, // &OBJC_CLASS_$_GYPerson,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_GYPerson_$_Test,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_GYPerson_$_Test,
	0,
	0,
};

//_method_list_t ->_OBJC_$_CATEGORY_INSTANCE_METHODS_GYPerson_$_Test
            static struct {
                unsigned int entsize;  // sizeof(struct _objc_method)
                unsigned int method_count;
                struct _objc_method method_list[1];
            } _OBJC_$_CATEGORY_INSTANCE_METHODS_GYPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
                sizeof(_objc_method),
                1,
                {{(struct objc_selector *)"test", "v16@0:8", (void *)_I_GYPerson_Test_test}}
            };
         
//_method_list_t -> _OBJC_$_CATEGORY_CLASS_METHODS_GYPerson_$_Test
            static struct  {
                unsigned int entsize;  // sizeof(struct _objc_method)
                unsigned int method_count;
                struct _objc_method method_list[1];
            } _OBJC_$_CATEGORY_CLASS_METHODS_GYPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
                sizeof(_objc_method),
                1,
                {{(struct objc_selector *)"test2", "v16@0:8", (void *)_C_GYPerson_Test_test2}}
            };

查看分类转成C++的代码之后, 我们可以发现,当程序编译完成的时候,category所有的数据都是存放在 category_t 这个底层结构中的,如果有多个分类, 编译完成的时候有多个 category_t 类型的变量,所有分类的结构都是一样的, 但是结构体的名称、结构体内的变量肯定是不一样的

上述描述的GYPerson+Test的C++代码的结构,我们可以猜测GYPerson+Eat的分类在C++ 文件中的结构:

static struct _category_t _OBJC_$_CATEGORY_GYPerson_$_Eat {
            "GYPerson",
            0, // &OBJC_CLASS_$_GYPerson,
            (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_GYPerson_$_Eat,//Eat分类中的对象方法列表
            (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_GYPerson_$_Eat,//Eat分类中的类方法列表
            0,//Eat 中的协议列表
            0,//Eat 中的属性列表
         }

1.3 Category源码分析

  • 结论:

    1. Category(分类)中的所有对象方法最终都会放在class对象的方法列表中的,调用Category中的方法,最终都会通过instance的isa指针找到class对象,然后找到方法并执行
    2. Category(分类)中所有的类(+)方法最后都会放在meta-class对象的方法列表中,类方法的调用过程,最后会通过isa的指正找到class对象,在通过class的isa指正找到meta-class对象中,然后寻找类方法执行
    3. 分类的数据是在程序运行的过程中把Category类中的方法合并到类对象中的,是通过Runtime动态将分类的方法合并到类对象、元类对象中,在编译的时候还没有合并到类对象、元类对象中
  • 由于源码太多,会过滤掉不重要源码,显示主要信息的源码:

//运行时的初始话方法
void _objc_init(void)
{	
//注册一些
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

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

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
   
    if (hCount > 0) {
    	//加载模块
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

}
//加载模块  
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    // Discover categories.    搜索和加载我们的分类信息
    for (EACH_HEADER) {
    	//category_t 分类的结构体  是一个二位数组 
        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;
            // instanceMethods 实例对象方法 判断
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                //核心代码  重新方法话 -> 重新组织这个类中的方法,把class对象传递进去
                    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()) {
                // class对象的isa指向meta-class对象,这里参数是传递meta-class对象
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

    ts.log("IMAGE TIMES: discover categories");

    // Category discovery MUST BE LAST to avoid potential races 
    // when other threads call the new category code before 
    // this thread finishes its fixups.
}

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();
	//判断传递进来是class对象还是 meta-class对象
    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)" : "");
        }
        
        //核心方法  把分类中的方法 合并到class对象中
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

//传递参数 ,class对象  category_list: 分类列表 可能装着好几个分类
// class: GYPerson
//cats = [catagory_t (Test),catagory_t(Eat)]结构可能是存放这两个东西
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
   // 方法数组
    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;
    //开一个循环 i--  
    while (i--) {
    	//去cats  也就是存放分类的数组中取出某个分类,并且是从后往前取出,也就是 先进后出(先编译的后取出,后编译的先取出)
        auto& entry = cats->list[i];
		// entry.cat 取出的是category_t *cat这种类型的对象 也就是category分类结构的对象
		//取出当前分类中的对象方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
        	//把分类的方法数组放到 class的方法列表数组中
        	/*
				大概的结构:
				[
					[method_t, method_t],
					[method_t, method_t],
				]
			最终每一个分类的方法列表都会放到class的这个大的方法列表中
			*/
            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;
        }
    }
	//cls->data()得到类中的数据 获取class_rw_t对象 ->管理着class对象中的方法列表、属性列表、协议列表等信息,也就是 class对象中的信息
    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    //rw->methods 得到rw对象中的方法列表method_array_t methods;  然后调用attachLists方法 把分类所有方法传递进去(mlists: 所有分类对象的方法)
    //也就是将所有分类的对象方法,附加到类对象的方法列表中
    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);
}

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;
            // array()->lists 原来的方法列表
            //将原来的方法列表移动到后面
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            // addedLists 所有分类的方法列表
            //把分类中的方法列表 copy到原来的方法列表中
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
            /*
			 memcpy 方法会一个一个 的复制,然后覆盖原来的值,这里是不会进行判断的统一从小地址开始
			 memmove ()是根据传递参数判断往哪个方法移动, 从哪个地方开始移动,达到最后移动的目的,
			 为什么源码中前面使用memmove() 保证数据能够完整的移动到新的地方
			*/
        }
};

源码的解读顺序:
objc-os.mm 表示运行时的入口文件
在这里插入图片描述

1.4 Category的加载过程和一些注意点

  1. Category在编译期还没有合并到某个类的数据中的, 是在运行时通过Runtime加载某个类的所有Category数据
  2. 在运行时,把所有Category的方法、属性、协议数据,合并到一个大数组中。过程如下
    • 首先将原来类对象的方法列表进行一个扩容,然后把原来的方法列表挪到后面
    • 在把分类中的方法列表添加到类对象的方法列表中, 最后就是分类中所有的方法都添加到类对象的方法列表中
    • 注意:
      • category和类中都存在同一个方法的时候,会优先调用category中的方法(由上面源码分析可知)
      • 当多个分类都存在同一个方法时,优先调用哪个分类中的方法,取决于category的编译顺序,会优先加载最后编译的category,越是最后编译的category,方法列表等数据信息越在前(由上面源码分析可知)
      • 分类中的方法并没有覆盖原来类中的方法,只是方法的执行顺序超前了,因为所有的方法最终都会合并到类对象的方法列表中,而方法执行,只需要找到方法的执行就不会往后面继续找
  3. 将合并后的分类数据(方法、属性、协议)插入到类原来的数据前面

2.+load方法

  • 测试代码:

@interface GYPerson : NSObject

@end
@implementation GYPerson
+ (void)load {
    NSLog(@"GYPerson +load");
}

@interface GYPerson (Test1)

@end
@implementation GYPerson (Test1)
+ (void)load {
    NSLog(@"GYPerson(Test1)  +load");
}
@interface GYPerson (Test2)

@end
+ (void)load {
    NSLog(@"GYPerson(Test2)  +load");
}
@interface GYStudent : GYPerson

@end
+ (void)load {
    NSLog(@"GYStudent   +load");
}

@interface GYStudent (Test1)

@end
@implementation GYStudent (Test1)
+ (void)load {
    NSLog(@"GYStudent(Test1)   +load");
}
@end

@interface GYStudent (Test2)

@end
@implementation GYStudent (Test2)
+ (void)load {
    NSLog(@"GYStudent(Test2)   +load");
}
@end

执行结果:不管怎么改变顺序GYPerson的+load方法调用始终执行在GYStudent的+load方法之前
在这里插入图片描述

  1. +load方法会在runtime加载类,分类的时候调用
  2. 每个类、分类的+laod,在程序运行过程中只调用一次
  3. +load方法调用顺序:
    • 首先调类的+laod方法
    • 按照变异顺序调用(先编译,先调用)
    • 调用子类的+load方法之前会调用父类的+load方法
    • 调用完类的+load方法之后,在调用分类的+load方法
    • 分类的+load方法顺序也是按照编译顺序调用(先编译,先调用)
  4. 调用+load方法底层是直接通过指针(地址)直接调用的+load方法(直接通过在内存中的地址直接去调用),而调用分类的类方法,本质上是给对象发送一个消息(也就是通过objc_msgSend()),但是通过这种模式(消息机制),它的调用过程是首先通过isa指针找到meta-class对象,然后在去遍历方法列表中的方法,找到方法之后再调用,调用之后接结束不在往下寻找
  5. +load方法子所以分类和类都会调用,是因为+laod方法的调用是直接通过函数指针指向那个函数,拿到那个函数地址,找到函数的代码,直接调用,是分开调用的,而不是通过objc_msgSend()消息发送机制来调用的
  • +load方法的底层源码
void _objc_init(void)
{
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
//加载模块
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        rwlock_writer_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
//准备加载方法
void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertWriting();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
    	// 加载方法
        schedule_class_load(remapClass(classlist[i]));
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

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
    //递归调用  把类的+load加入数组时 首先把父类的+load方法计入到数组中
    schedule_class_load(cls->superclass);
	//把方法加入到方法数组中
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

//调用方法
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
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

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

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方法的地址直接调用率,并不是经过objc-msgSend函数调用
        /*
		专门用来加载类的 method只指向load方法
		struct loadable_class {
		    Class cls;  // may be nil
		    IMP method;  
		};
		专门用来加载分类  method只指向分类load 方法
		struct loadable_category {
		    Category cat;  // may be nil
		    IMP method;
		};
		*/
        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方法的调用过程,category中+load方法调用过程和这个基本相似

  • +laod方法调用的源码查看步骤:
    在这里插入图片描述

3.+initialize方法

  1. +initialize()方法会在的第一次接收到消息时调用。

  2. 调用顺序:

    • 先调用父类的+initialize方法,在调用子类的+initialize方法(子类内部会主动调用父类的+initialize(),前提是父类没有初始化)
    • 先初始化父类,在初始化子类,每个类只会初始化一次
  3. +initialize()和+load的很大区别是+initialize()是通过objc_msgSend进行调用的,所以有以下特点

    • 如果子类没有实现+initialize(),会调用父类的+initialize()(所以父类的+initialize()可能会被调用多次)
    • 如果分类实现了+initialize(),就会覆盖本身的+initialize()调用(而分类的调用方法顺序是看编译顺序,后编译,先调用

由于objc_msgSend()的源码是汇编语言,只能通过其他的方式去验证initialize的调用过程,猜想
initialize方法是在类第一次接收到消息的时候调用那么我们可以查看 这个方法的一些调用过长,我们猜测可能是在第一次接收到方法的时候,内部做了调用+initialize方法的操作

  • 源码删减了不需要的
Method class_getInstanceMethod(Class cls, SEL sel)
{
  //调用方法之前,先去查查找方法
    lookUpImpOrNil(cls, sel, nil, 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
	//查找方法之前看 这个类有没有被初始化过, 如果需要初始化,单是类还没被初始化 ,那首先就会先初始化类
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }
}

//调用initialize方法
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.
    //调用类的initialize方法之前,需要判断这个类有没有父类,如果有父类,那会先去调用父类的+initialize方法,前提是父类没有初始化
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    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());
            }
        }
}

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

源码分析流程图:
在这里插入图片描述

  • +initialize()和+load()方法使用场景:
    1. +initialize():需要在类第一次被调用做些事情的时候,可在方法内部做些事情
    2. +load(): 类被加载内存中的时候需要做事情的时候,可在方法内部做些事情

4. 关联对象

4.1关联对象的概述和简单运用

  1. 在类中声明 @property (nonatomic, assign) int age;做了什么事情:
    • 创建一个_age的成员变量
    • 生成一个set方法和get方法的声明
    • 生成set和get方法的实现
  2. 在category声明一个属性,做了什么事情:
    • 只是生成了一个set和get方法的声明
    • category中不可以声明成员变量

问题:那我们怎么在分类中添加一个属性, 让分类间接实现有成员变量的方式?

  • 定义一个全局的变量来储存 声明属性的值,但是这种方式的确定也很明显,如果创建多个实例的话,那就容易造成多个实例共用一个属性值的问题,所以这种方法不可行
@interface GYPerson (Test)

/** <#descrption#> */
@property (nonatomic, assign) int weight;

@end
@implementation GYPerson (Test)
int weight_;

- (void)setWeight:(int)weight {
    weight_ = weight;
}

- (int)weight {
    return weight_;
}
@end
  • 定义一个全局的字典来储存,实现对象和睡醒一对一的关系,这种方法可以实现,也可行,但是还是存在一些问题:
    1. 如果你的需求不是要求这个类一直存在, 那么就会存在内存泄漏的问题
    2. 线程安全的问题
    3. 过程比较麻烦,没增加一个属性都需要对象增加一个全局的字典来储存
@implementation GYPerson (Test2)
NSMutableDictionary *names_;

+ (void)load
{
    names_ = [NSMutableDictionary dictionary];
}

- (void)setName:(NSString *)name
{
    NSString *key = [NSString stringWithFormat:@"%p", self];
    names_[key] = name;
}
- (NSString *)name
{
    NSString *key = [NSString stringWithFormat:@"%p", self];
    return names_[key];
}

@end
  • 使用关联对象的方式来完成 Runtime关联属性,关联API:
//设置关联对象
/**
	参数列表: 
	1. 给哪个对象设置关联对象
	2. 传递一个任意指针类型的参数 key
	3. 需要关联的值value
	4. 关联策略(如:objc_AssociationPolicy中的类型 OBJC_ASSOCIATION_ASSIGN等)
	
*/
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_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

  • 关联中的key的用法:
    1. 定义一个指针变量key const void *key;如果没有初始化的可能造成共用一个属性值,可以static const void * key = &key(如果不用static修改限定变量的范围,那别人可以在外部使用extern字段用用字段,然后在外面修改这个值)
    2. 定义char类型的变量static const char KEY因为char类型的变量只占一个字节,相比于直接定义指针类型的变量(占8个字节)节省内存,不需要赋值,因为传递进去的需要的变量的地址值,而不是变量的值
    3. 或则key的值直接传递@“key”,因为直接写一个字符串,这个字符串是放在常量区的,是不会变的,因为我们字符串声明就NSString *p = @"key";
    4. 直接传递一个方法对象@selector(key) 或则 _cmd(表示当前方法的selector对象)
//直接传递字符串
#define MJNameKey @"name"
- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
    return objc_getAssociatedObject(self, MJNameKey);
}

//把变量的地址值付给自己
static const void *MJNameKey = &MJNameKey;
- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, MJNameKey);
}

//直接定义char类型的变量 不用赋值。直接使用变量的地址值

static const char MJNameKey;
- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, &MJNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, &MJNameKey);
}

//直接使用方法对象作为key
- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    // 隐式参数
    // _cmd == @selector(name)
    return objc_getAssociatedObject(self, _cmd);
}
//- (NSString *)name
//{
//    return objc_getAssociatedObject(self, @selector(name));
//}

4.2 关联对象的底层数据结构

  1. 实现关联对象的技术的核心对象有:
    • AssociationsManager
    • AssociationsHashMap
    • ObjectAssociationMap
    • ObjcAssociation
  • 源码
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);
}

关联对象流程分析图:
在这里插入图片描述
在这里插入图片描述

  • 总结:
    1. 关联对象并不是存储在被关联对象本省内存中
    2. 关联对象存储在全局统一的AssociationsManager中
    3. 设置关联对象为nil,就相当于是移除关联对象
    4. 关联的对象如果被销毁了,那么缓存这个对象的所有属性会被移除

5. 面试题

5.1 Category的使用场合是什么?

  • 把比较复杂的类拆解的时候就用到分类

5.2 Category的实现原理?

  1. Category编译之后的底层结构是 struct _category_t,里面储存着分类的对象方法、类方法、属性、协议信息
  2. 在程序运行的时候,runtime会将Category的数据后,合并到类信息中(类对象、元类对象中)

5.3 Category和 Class Extension的区别是什么?

  1. Calss Exyension在编译的时候,它的数据就已近包含在类信息中(类扩展- 在编译的时候,就把扩展中的属性、方法合并到类中的.m文件中,变成私有的)
  2. Category是在运行中才会将数据合并到类信息中

5.4 Category中又load方法吗?load方法是在什么时候调用的?load方法能继承吗?

  • category中有load方法
  • load方法是在runtime加载类、分类的时候调用
  • laod方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用(子类调用父类的+load方法是按照消息机制来调用)
  • 子类调用父类的+laod方法,肯定是先调用父类分类的+load方法的(因为手动调用父类的+load方法是是通过消息机制调用的,而前面我们说过。调用方法的时候,分类的方法运行的时候是放在类的方法前面的(相同的方法)而消息机制是通过isa指针找到meta_class对象,在去寻找方法列表,遍历方法列表,找到方法调用)

5.5 load、initialize方法的区别是什么?它们在category中的调用顺序?以及出现继承他们之间的调用过程?

  1. 区别:

    • 调用方式区别:
      • load是直接根据函数地址直接调用
      • initialize是通过objc_msgSend调用(方式的不同导致后面很多调用结果不同)
    • 调用时刻不同:
      • load是runtime加载类、分类的时候调用,只会调用一次
      • initialize是类第一次接受到消息的时候调用,每个类只会initialize一次(但是父类的initialize可能会被调用多次)
  2. 调用顺序:

    • load是先调用类的load(先编译的类,优先调用load,调用子类的load之前,会先调用父类的load方法),其次在次调用分类的load方法,先编译的分类,优先调用load方法
    • initialize 先初始化父类,在初始化子类(可能最终调用的是父类的方法)

5.6 Category能否添加成员变量?如果可以,如何给Category添加成员变量?

  1. 不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
  2. 使用Runtime来把需要添加的属性和对象关联起来
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值