properties docker容器 d3 sass pagination swiper vue钩子函数 十大erp系统 nodejs视频教程 jquery去除空格 css获取最后一个元素 python中get函数 python抛异常 python代码 python环境变量配置 python用什么数据库 java中的多态 java怎么安装 java数组输出 java中map java流程 nginx安装教程 linux如何安装 广告代码 matlab2016a安装教程 考试练习系统 wow怎么赚钱 视频加字幕软件 华为下拉开关设置 脚本列表 python图片处理 oemdiy 淘宝抽奖活动 金水疑云 python编辑器 ipad内存怎么清理 nginx启动命令 python字符串 c4dr20 java大数据语言
当前位置: 首页 > 学习教程  > 编程语言

OC底层学习-Category

2020/8/11 20:25:00 文章标签:

OC底层学习-Category

  • 1. Category
    • 1.1分类的简单引用场景
    • 1.2 Category编译之后的底层结构
    • 1.3 Category源码分析
    • 1.4 Category的加载过程和一些注意点
  • 2.+load方法
  • 3.+initialize方法
  • 4. 关联对象
    • 4.1关联对象的概述和简单运用
    • 4.2 关联对象的底层数据结构
  • 5. 面试题
    • 5.1 Category的使用场合是什么?
    • 5.2 Category的实现原理?
    • 5.3 Category和 Class Extension的区别是什么?
    • 5.4 Category中又load方法吗?load方法是在什么时候调用的?load方法能继承吗?
    • 5.5 load、initialize方法的区别是什么?它们在category中的调用顺序?以及出现继承他们之间的调用过程?
    • 5.6 Category能否添加成员变量?如果可以,如何给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来把需要添加的属性和对象关联起来

本文链接: http://www.dtmao.cc/news_show_100345.shtml

附件下载

上一篇:lvs-安装准备

下一篇:探秘 Spring IoC

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?