美文网首页面试
iOS底层(三)_Category原理

iOS底层(三)_Category原理

作者: MR_詹 | 来源:发表于2019-10-08 09:27 被阅读0次

    面试题

    问题1:Category的使用场合是什么?

    答复:
    为某个类拓展方法,分模块

    问题2:Category的实现原理

    答复:
    Category编译之后的底层是Struct Category_t,里面存储着分类的对象方法、类方法、属性、协议信息
    在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

    image.png
    问题3:Category和Class Extension的区别是什么?

    答复:
    Class Extension在编译的时候,它的数据就已经包含在类信息中 Category是在运行时,才会将数据合并到类信息中

    问题4:Category中有load方法吗?load方法是什么时候调用的?load方法能继承吗?

    答复:
    有load方法
    load 方法在runtime加载类、分类的时候调用
    load 方法是通过函数地址直接调用的,其他的方法是通过消息机制调用,也就是通过isa指针层层寻找方法
    load 方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统主动调用(备注:继承后,手动子类调用load方法,是通过消息机制执行的,那么就会通过isa去类中查找,如果类没有就会去父类查找)

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

    答复:
    1、调用方式

    • load是根据函数地址直接调用的
    • initialzer是通过objc_msgSend调用

    2、调用时刻

    • load是runtime加载类、分类的时候调用(只会调用1次)
    • initialize是类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能会被调用多次)

    3、load、initialeze的调用顺序
    load
    (1)先调用类的load

    • 按编译的先后顺序调用
    • 调用子类的+load之前会先调用父类的+load
      (2)再调用分类的load
    • 按编译的先后顺序调用

    initialize

    • 先初始化父类
    • 再初始化子类(可能最终调用的是父类的initialize方法)
    问题6:Category能否添加成员变量?如果可以,如何给Category添加成员变量?

    答复:

    知识点:

    • 通过runtime动态将分类的方法合并到类对象、元类对象中

    • 分类编译后,会转变为_category_t结构体


      image.png

    *类扩展(也叫做匿名分类)

    +load

    • +load 方法会在runtime加载类、分类时调用
    • 每个类、分类的+load,在程序运行过程中只调用一次

    调用顺序

    1、先调用类的+load

    • 按照编译先后顺序调用(先编译,先调用)
    • 调用子类的+load之前会先调用父类的+load

    2、再调用分类的+load

    • 按照编译先后顺序调用(先编译,先调用)

    +initialize方法

    • +initialize方法会在第一次接收到消息时调用

    • 调用顺序
      先调用父类的+initialize,再调用子类的+initialize
      (先初始化父类,再初始化子类,每个类只会初始化1次)

    +关联对象

    关联对象的主要作用就是给分类添加属性
    首先平时给一个类添加属性,做了哪几件事?比如给Person类添加一个age属性
    (1)添加成员变量:_age
    (2)声明并实现getter和setter方法

    @interface MJPerson : NSObject
    @property (nonatomic, assign, readwrite) NSInteger age;
    // =========  内容实现的内容 =========
    {
        NSInteger *_age;
    }
    - (NSInteger)age;
    - (void)setAge:(NSInteger)age;
    // =========  内容实现的内容 =========
    @end
    
    
    
    #import "MJPerson.h"
    @implementation MJPerson
    // =========  内容实现的内容 =========
    - (NSInteger)age {
        return _age;
    }
    - (void)setAge:(NSInteger)age {
        _age = age;
    }
    // =========  内容实现的内容 =========
    @end
    
    

    那在分类中添加属性,到底有做了哪些了?比如给Person的分类Person(test)添加属性name
    分类中直接添加属性,系统只对getter 和 setter 做声明,并不对其实现,而且也不会生成成员变量

    @interface MJPerson (test)
    @property (nonatomic, copy, readwrite) NSString *name;
    // =========  内容实现的内容 =========
    - (NSString * _Nonnull)name;
    - (void)setName:(NSString * _Nonnull)name;
    // =========  内容实现的内容 =========
    @end
    
    
    #import "MJPerson+test.h"
    #import <AppKit/AppKit.h>
    @implementation MJPerson (test)
    @end
    
    

    既然系统没有实现,那么只要我们手动将成员变量、setter方法和getter方法的实现添加就可以了,但是分类的编译结构体category_t中并没有成员变量的属性,所以不能手动添加成员变量存储属性值,那就需要用到上面讲到的“关联对象”

    Person分类添加属性"name"
    
    #import "MJPerson.h"
    @interface MJPerson (test)
    @property (nonatomic, copy, readwrite) NSString *name;
    @end
    
    
    #import "MJPerson+test.h"
    #import <objc/runtime.h>
    @implementation MJPerson (test)
    - (void)setName:(NSString *)name {
        objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    - (NSString *)name {
        //_cmd代表当前的@selector方法
        //_cmd = @selector(name)      
        return objc_getAssociatedObject(self, @"name");
    }
    @end
    
    
    关联对象原理
    • 关联对象并不是存储在关联对象本身内存中
    • 关联对象存储在全局的统一的一个AssociationsManager
    • 设置关联对象为nil,就相当于是移除关联对象


      关联对象原理

    相关文章

      网友评论

        本文标题:iOS底层(三)_Category原理

        本文链接:https://www.haomeiwen.com/subject/ksrcuctx.html