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源码分析
-
结论:
- Category(分类)中的所有对象方法最终都会放在class对象的方法列表中的,调用Category中的方法,最终都会通过instance的isa指针找到class对象,然后找到方法并执行
- Category(分类)中所有的类(+)方法最后都会放在meta-class对象的方法列表中,类方法的调用过程,最后会通过isa的指正找到class对象,在通过class的isa指正找到meta-class对象中,然后寻找类方法执行
- 分类的数据是在程序运行的过程中把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的加载过程和一些注意点
- Category在编译期还没有合并到某个类的数据中的, 是在运行时通过Runtime加载某个类的所有Category数据
- 在运行时,把所有Category的方法、属性、协议数据,合并到一个大数组中。过程如下
- 首先将原来类对象的方法列表进行一个扩容,然后把原来的方法列表挪到后面
- 在把分类中的方法列表添加到类对象的方法列表中, 最后就是分类中所有的方法都添加到类对象的方法列表中
- 注意:
- category和类中都存在同一个方法的时候,会优先调用category中的方法(由上面源码分析可知)
- 当多个分类都存在同一个方法时,优先调用哪个分类中的方法,取决于category的编译顺序,会优先加载最后编译的category,越是最后编译的category,方法列表等数据信息越在前(由上面源码分析可知)
- 分类中的方法并没有覆盖原来类中的方法,只是方法的执行顺序超前了,因为所有的方法最终都会合并到类对象的方法列表中,而方法执行,只需要找到方法的执行就不会往后面继续找
- 将合并后的分类数据(方法、属性、协议)插入到类原来的数据前面
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方法之前
- +load方法会在runtime加载类,分类的时候调用
- 每个类、分类的+laod,在程序运行过程中只调用一次
- +load方法调用顺序:
- 首先调类的+laod方法
- 按照变异顺序调用(先编译,先调用)
- 调用子类的+load方法之前会调用父类的+load方法
- 调用完类的+load方法之后,在调用分类的+load方法
- 分类的+load方法顺序也是按照编译顺序调用(先编译,先调用)
- 调用+load方法底层是直接通过指针(地址)直接调用的+load方法(直接通过在内存中的地址直接去调用),而调用分类的类方法,本质上是给对象发送一个消息(也就是通过
objc_msgSend()
),但是通过这种模式(消息机制),它的调用过程是首先通过isa指针找到meta-class对象,然后在去遍历方法列表中的方法,找到方法之后再调用,调用之后接结束不在往下寻找 - +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方法
-
+initialize()方法会在
类
的第一次接收到消息时调用。 -
调用顺序:
- 先调用父类的+initialize方法,在调用子类的+initialize方法(子类内部会主动调用父类的+initialize(),前提是父类没有初始化)
- 先初始化父类,在初始化子类,每个类只会初始化一次
-
+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()方法使用场景:
- +initialize():需要在类第一次被调用做些事情的时候,可在方法内部做些事情
- +load(): 类被加载内存中的时候需要做事情的时候,可在方法内部做些事情
4. 关联对象
4.1关联对象的概述和简单运用
- 在类中声明
@property (nonatomic, assign) int age;
做了什么事情:- 创建一个_age的成员变量
- 生成一个set方法和get方法的声明
- 生成set和get方法的实现
- 在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
- 定义一个全局的字典来储存,实现对象和睡醒一对一的关系,这种方法可以实现,也可行,但是还是存在一些问题:
- 如果你的需求不是要求这个类一直存在, 那么就会存在内存泄漏的问题
- 线程安全的问题
- 过程比较麻烦,没增加一个属性都需要对象增加一个全局的字典来储存
@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的用法:
- 定义一个指针变量
key const void *key
;如果没有初始化的可能造成共用一个属性值,可以static const void * key = &key
(如果不用static修改限定变量的范围,那别人可以在外部使用extern字段用用字段,然后在外面修改这个值) - 定义char类型的变量
static const char KEY
因为char类型的变量只占一个字节,相比于直接定义指针类型的变量(占8个字节)节省内存,不需要赋值,因为传递进去的需要的变量的地址值,而不是变量的值 - 或则key的值直接传递
@“key”
,因为直接写一个字符串,这个字符串是放在常量区的,是不会变的,因为我们字符串声明就NSString *p = @"key";
- 直接传递一个方法对象@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 关联对象的底层数据结构
- 实现关联对象的技术的核心对象有:
- 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的实现原理?
- Category编译之后的底层结构是 struct _category_t,里面储存着分类的对象方法、类方法、属性、协议信息
- 在程序运行的时候,runtime会将Category的数据后,合并到类信息中(类对象、元类对象中)
5.3 Category和 Class Extension的区别是什么?
- Calss Exyension在编译的时候,它的数据就已近包含在类信息中(类扩展- 在编译的时候,就把扩展中的属性、方法合并到类中的.m文件中,变成私有的)
- 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中的调用顺序?以及出现继承他们之间的调用过程?
-
区别:
- 调用方式区别:
- load是直接根据函数地址直接调用
- initialize是通过objc_msgSend调用(方式的不同导致后面很多调用结果不同)
- 调用时刻不同:
- load是runtime加载类、分类的时候调用,只会调用一次
- initialize是类第一次接受到消息的时候调用,每个类只会initialize一次(但是父类的initialize可能会被调用多次)
- 调用方式区别:
-
调用顺序:
- load是先调用类的load(先编译的类,优先调用load,调用子类的load之前,会先调用父类的load方法),其次在次调用分类的load方法,先编译的分类,优先调用load方法
- initialize 先初始化父类,在初始化子类(可能最终调用的是父类的方法)
5.6 Category能否添加成员变量?如果可以,如何给Category添加成员变量?
- 不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
- 使用Runtime来把需要添加的属性和对象关联起来