OC底层相关iOS

(一)Category

2019-03-12  本文已影响54人  dandelionYD

面试题:

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

2.Category能否添加成员变量?如果可以,如何给Category添加成员变量?
析:
不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果

3.Category和Class Extension的区别是什么?
析:
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中

4.Category中有load方法?load方法什么时候调用?load方法能继承?
析:
有load方法
load方法在runtime加载类、分类的时候调用
load方法可以继承,但是在一般的情况下不会主动去调用load方法,都是让系统自动调用

5.load、initialize方法的区别是啥?它们在Category中的调用顺序?以及出现继承时他们之间的调用过程?

接下来我们逐步分析:
准备:首先搭建好可以跑runtime源码的过程,参考配置运行objc4-750和使用

gitHub_Demo

1.Category简单的使用

//Person类
@interface Person : NSObject{
    double   _height;
}
-(void)run;
@end

@implementation Person
-(void)run{
    NSLog(@"%s",__FUNCTION__);
}
@end

//Person+addCategory  --->Person的分类
@interface Person (addCategory)
-(void)speak;
@property (nonatomic,strong)NSString  *name;
@end

@implementation Person (addCategory)
-(void)speak{
    NSLog(@"%s--->%f",__FUNCTION__,_height);
}
@end

//myPerson --->Person的子类
@interface myPerson : Person
@end

@implementation myPerson
-(void)speak{
    NSLog(@"%s",__FUNCTION__);
}
@end

//Person+addCategory2   ----->Person的分类
@interface Person (addCategory2)
-(void)speak;
@end

@implementation Person (addCategory2)
-(void)speak{
    NSLog(@"%s",__FUNCTION__);
}
-(void)run{
    NSLog(@"%s",__FUNCTION__);
}
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        myPerson *p  = [[myPerson alloc]init];
        [p run];
        [p speak];
//        NSLog(@"name-->%@",p.name);//-[Person name]: unrecognized selector sent to instance 0x600001c9cf50
    }
    return 0;
}

发现:---------------------
 1.在不修改原有的类的基础上,为这个类添加一些方法
 2.分类是用于给原有类添加方法的, 它只能添加方法, 不能添加属性(成员变量)
 3.可以在分类中访问原有类中公开的属性
 4.方法的调用顺序:分类->本类->父类
 5.如果分类中有和原有类同名的方法, 会调用分类中的方法(说会忽略原有类的方法)
 6.分类中的@property, 只会生成setter/getter方法的声明, 不会生成实现以及私有的成员变量
 7.如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定
    会执行最后一个参与编译的分类中的方法(即编译的时候加的.m文件)

2.Category的本质

category_01.png

OBJC_PRINT_CLASS_SETUP:YES

//创建一个Student类
#import <Foundation/Foundation.h>
#import "myProtocol.h"
@interface Student : NSObject
@property (assign,nonatomic) id<myProtocol> delegate;
@end

#import "Student.h"
@implementation Student
@end

========创建一个protocol======
#import <Foundation/Foundation.h>
@protocol myProtocol <NSObject>
@required
-(void)test;
@optional
-(void)test2;
@end


=======创建分类=======
#import "Student.h"
@interface Student (addCategory)
-(void)run;
+(void)play;
@property (strong,nonatomic) NSString *testPro;
@end

#import "Student+addCategory.h"
@implementation Student (addCategory)
-(void)run{
    NSLog(@"%s",__FUNCTION__);
    [self.delegate test];
    [self.delegate test2];
}
+(void)play{
    NSLog(@"%s",__FUNCTION__);
}
-(void)setTestPro:(NSString *)testPro{
    NSLog(@"setTestPro");
}
-(NSString*)testPro{
    NSLog(@"getTestPro");
    return  @"111";
}
@end

======创建一个Preson对象=====
#import <Foundation/Foundation.h>
#import "myProtocol.h"
@interface Person : NSObject<myProtocol>
@end

#import "Person.h"
@implementation Person
-(void)test{
    NSLog(@"执行protocol的方法---test");
}
-(void)test2{
    NSLog(@"执行protocol的方法---test2");
}
@end


=====在main里面=====
#import <Foundation/Foundation.h>
#import "Student.h"
#import "Student+addCategory.h"
#import <objc/runtime.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc]init];
        Person *p = [[Person alloc]init];
        stu.delegate = p;
        [stu run];
        [Student play];

        unsigned int count;
        //实例对象方法
        Method *methodList = class_copyMethodList(object_getClass(stu), &count);
        for (unsigned int i = 0; i < count; i++) {
            Method method = methodList[I];
            NSString *methodName = NSStringFromSelector(method_getName(method));
            NSLog(@"实例_方法名:%d----%@",i,methodName);
        }
        free(methodList);
        
        //类对象方法
        Method *methodList2 = class_copyMethodList(object_getClass([Student class]), &count);
        for (unsigned int i = 0; i < count; i++) {
            Method method = methodList2[I];
            NSString *methodName = NSStringFromSelector(method_getName(method));
            NSLog(@"类对象_方法名:%d----%@",i,methodName);
        }
        free(methodList2);
        
        stu.testPro = @"111";
        NSLog(@"---%@",stu.testPro);
    }
    return 0;
}

=====================部分控制台log=======================
objc[20404]: CLASS: realizing class 'Student' (meta) 0x100002818 0x100002268 #0
objc[20404]: CLASS: methodizing class 'Student' (meta)
objc[20404]: METHOD +[Student play]
objc[20404]: CLASS: realizing class 'Student' 0x100002840 0x1000022d8 #0
objc[20404]: CLASS: methodizing class 'Student' 
objc[20404]: METHOD -[Student setTestPro:]
objc[20404]: METHOD -[Student testPro]
objc[20404]: METHOD -[Student delegate]
objc[20404]: METHOD -[Student setDelegate:]
objc[20404]: METHOD -[Student run]
objc[20404]: CLASS: realizing class 'Person' (meta) 0x100002868 0x100002678 #0
objc[20404]: CLASS: methodizing class 'Person' (meta)
objc[20404]: CLASS: realizing class 'Person' 0x100002890 0x100002740 #0
objc[20404]: CLASS: methodizing class 'Person' 
objc[20404]: METHOD -[Person test2]
objc[20404]: METHOD -[Person test]
02.Category的底层分析[20404:2857276] -[Student(addCategory) run]
02.Category的底层分析[20404:2857276] 执行protocol的方法---test
02.Category的底层分析[20404:2857276] 执行protocol的方法---test2
02.Category的底层分析[20404:2857276] +[Student(addCategory) play]
02.Category的底层分析[20404:2857276] 实例_方法名:0----setTestPro:
02.Category的底层分析[20404:2857276] 实例_方法名:1----testPro
02.Category的底层分析[20404:2857276] 实例_方法名:2----delegate
02.Category的底层分析[20404:2857276] 实例_方法名:3----setDelegate:
02.Category的底层分析[20404:2857276] 实例_方法名:4----run
02.Category的底层分析[20404:2857276] 类对象_方法名:0----play
02.Category的底层分析[20404:2857276] setTestPro
02.Category的底层分析[20404:2857276] getTestPro
02.Category的底层分析[20404:2857276] ---111

现象—>在runtime进行加载【镜像】的时候,底层做了处理

5.
/***********************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
*************************************************/
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    // 加锁,保证线程安全
    runtimeLock.assertLocked();

    //判断是否为元类
    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    //获取该类未h挂载的分类列表
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        //添加分类到指定类中
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

//说明:
(1)将 Category 的内容添加到已经存在的 Class 中,最后刷新下 method caches
(2)调用 attachCategories 函数之前,会先使用 unattachedCategoriesForClass 函数获取类中还未添加的类别列表。这个列表类型为 locstamped_category_list_t,它封装了 category_t 以及对应的 header_info。header_info 存储了实体在镜像中的加载和初始化状态,以及一些偏移量.
6.
// 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;//如果没有分类列表 return返回
     if (PrintReplacedMethods) printReplacements(cls, cats);
     bool isMeta = cls->isMetaClass();//是否是元类

    //分配相应的实例/类方法、属性、协议列表指针,相当于二维链表,一个分类对应一个一维链表(分别分配内存空间)
    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {//循环分类列表
        //取出第 i 个分类
        auto& entry = cats->list[I];
        
        //从分类里取出对应的实例/类方法表-->存入到mlist数组里面
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        //从分类里取出对应的实例/类属性列表,并加到对应的二维链表中-->存入到proplist数组里面
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
        //从分类里取出遵守的协议列表,并加到对应的二维链表中-->存入到protolist数组里面
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
   
    //遍历完分类后,取出类/元类加载到内存(堆区)的 class_rw_t 结构体
    //class_rw_t中存放着类对象的方法,属性和协议等数据
    auto rw = cls->data();
    
    //准备方法列表:加锁扫描方法列表,将新方法放在每一个分类的方法前面(对每个分类方法进行排序)
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    
    //attachList方法内部将分类和本类相应的对象方法,属性,和协议进行了合并
   // 添加方法到类/元类中
    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);  
}
7.
  // attachList方法内部将分类和本类相应的对象方法,属性,和协议进行了合并
    //addedLists:需要添加的列表
    //addedCount:列表个数
    void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        //realloc ->memmove -> memcpy
        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;
            
            
            /*
             void  *memmove(void *__dst, const void *__src, size_t __len);
             __dst:移动内存的目的地
             __src:被移动的内存首地址
             __len : 被移动的内存长度
             作用:将__src的内存移动__len块内存 到 __dst中
             */
            //内存移动
            memmove(
                    array()->lists + addedCount,// array()->lists->类对象原来的方法列表,属性列表,协议列表 + 分类数组长度
                    array()->lists,
                    oldCount * sizeof(array()->lists[0])//原来数组占据的空间
                    );
            
            /*内存拷贝
              void    *memcpy(void *__dst, const void *__src, size_t __n);
              __dst : 拷贝内存的拷贝目的地
              __src : 被拷贝的内存首地址
              __n : 被移动的内存长度
              将__src的内存拷贝__n块内存到__dst中
             */
            
            //内存复制
            memcpy(
                   array()->lists,//原来方法、属性、协议列表数组
                   addedLists,//分类方法、属性、协议列表数组
                   addedCount * sizeof(array()->lists[0]) //增加的数组占据的空间
                   );
            /*
             发现原来指针并没有改变,至始至终指向开头的位置。并且经过memmove和memcpy方法之后,分类的方法,属性,协议列表被放在了类对象中原本存储的方法,属性,协议列表前面。
             那么为什么要将分类方法的列表追加到本来的对象方法前面呢,这样做的目的是为了保证分类方法优先调用,我们知道当分类重写本类的方法时,会覆盖本类的方法。
             其实经过上面的分析我们知道本质上并不是覆盖,而是优先调用
             */
        }
        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]));
        }
    }

对于最后的内存移动和复制 —>具体看下图

category_02.jpeg

大概的流程如下:

objc-os.mm

objc_runtime-new.mm


3.我们从另外一个角度看看本质

在控制台输入:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Student+addCategory.m -o category.cpp

发现.cpp文件里面有:

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

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Student_$_addCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    3,
    {{(struct objc_selector *)"run", "v16@0:8", (void *)_I_Student_addCategory_run},
    {(struct objc_selector *)"setTestPro:", "v24@0:8@16", (void *)_I_Student_addCategory_setTestPro_},
    {(struct objc_selector *)"testPro", "@16@0:8", (void *)_I_Student_addCategory_testPro}}
};

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Student_$_addCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"play", "v16@0:8", (void *)_C_Student_addCategory_play}}
};

总结:

category的底层原理、Category 不能直接添加成员变量的原因

友情链接:

上一篇下一篇

猜你喜欢

热点阅读