美文网首页
OC语言特性

OC语言特性

作者: 叔简 | 来源:发表于2021-06-22 11:12 被阅读0次

    分类

    用分类做了哪些事情

    • 声明私有方法
    • 分解体积庞大的类文件
    • 把framework的私有方法公开化

    特点

    • 运行时决议:编好分类文件,并没有分类中添加的内容附加到相应的宿主类,而是在运行时通过RunTime真实的添加到相应的宿主类
    • 可以为系统类添加分类

    分类中都可以添加哪些内容?

    • 实例方法
    • 类方法
    • 协议
    • 属性(分类中定义属性,实际上只是生成了对应的getter、setter方法)
      Runtime的源码分类的结构体:struct _category_t

    分类的调用栈(Category)

    • _objc_int -> map_2_images -> map_images_nolock -> _read_images -> remethodizeClass
      • images是镜像
      • _read_images 加载一些可执行文件到内存当中进行处理
      • remethodizeClass
    分类_objc_init调用栈.png

    如果两个分类添加同一个函数名称的函数,哪一个函数最终生效?

    取决于编译顺序,最后生成分类的函数会生效,前面的将被覆盖掉。

    总结:

    • 分类添加的方法可以”覆盖”原有方法
      ① 实际上是runtime在查找这个类的方法时,会查找分类一个宿主类的所有方法所在数组
      ② 而这个数组的分类方法是在宿主类前面的,所以说分类方法的调用有优先宿主类
      ③ 而并不是覆盖宿主类的方法。

    • 同名分类方法谁能生效取决于编译顺序
      最后被编译的分类,会优先生效

    • 名字相同的分类会引起编译报错
      生成具体分类,会把添加的分类名称,以下划线的形式拼接到宿主类中,这样在添加过程中,如果名称一样会报错。

    能否给分类添加”成员变量”?

    不能在分类的声明和定义实现的时候直接为分类添加成员变量,但是可以通过关联对象的技术来为分类添加”成员变量”,来达到分类可以添加”成员变量的效果”。
    关联对象的3个API:

    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_removeAssociatedObjects(<#id  _Nonnull object#>)
    

    ① 设定一个value值,通过key值建立一个映射关系,然后通过policy策略,关联到object上
    ② 根据指定的key到object中获取关联的值
    ③ 根据对象,移除所有相关它的对象

    我们用关联对象技术来实现为分类添加”成员变量”,这个成员变量会被添加到哪里?

    ① 我们为分类所添加的成员变量肯定不是在宿主类中的。
    ② 关联对象由AssociationsManager管理并在AssociationHashMap存储。
    ③ 我们创建的每一个对象的关联对象实际上都存储在了AssociationHashMap这样的容器中。
    ④ 所有对象的关联内容都在同一个全局容器中
    ⑤ 不同对象的关联对象的值都会放到全局容器中
    ⑥ 假如想消除一个关联对象时,可以传入value为nil操作来实现

    关联对象

    关联对象本质

    关联对象由AssociationsManager管理并在AssociationHashMap存储。
    我们创建的每一个对象的关联对象实际上都存储在了AssociationHashMap这样的容器中。
    所有对象的关联内容都在同一个全局容器中

    利用关联对象添加成员变量

    1. - (void)setName:(NSString *)name { objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    2. - (NSString *)name
    {
        // 隐式参数
        // _cmd == @selector(name)
        return objc_getAssociatedObject(self, _cmd);
    }
    (void)removeAssociatedObjects{
        // 移除所有关联对象
        objc_removeAssociatedObjects(self);
    }
    
    关联对象的本质.png 关联对象的Json形式.png

    扩展

    类扩展一般都在宿主类的.m文件中。

    一般用扩展做什么?

    • 声明私有属性
    • 声明私有方法(没有多大作用,方便阅读)
    • 声明私有成员变量
      声明私有属性、声明私有成员变量是有区别的

    特点

    1. 分类是运行时决议、扩展是编译时决议
    2. 分类可以有声明和实现,而扩展只有声明的形式,实现是直接在宿主类的
    3. 可以为系统类添加分类,但是不能为系统类添加扩展

    代理

    • 准确的说是一种软件设计模式
    • iOS 当中以@protocol形式体现
    • 传递方式一对一

    代理的工作流程

    三个角色:

    • 委托方
      ① 要求代理方需要实现的接口,也就是协议
      ② 调用代理方遵从协议的方法
      ③ 可能返回一个处理结果
    • 协议
      ① 可以定义方法(必须实现和可选类型)
      ② 可以定义属性
    • 代理方
      按照协议实现方法


      代理的实现流程.png

    代理方和委托方以什么关系存在?

    一般声明为weak来规避循环引用


    代理循环引用.png

    通知实现机制

    • 使用观察者模式,来实现用于跨层传递消息的机制
    • 传递方式一对多

    如何实现通知的机制?假如说自己实现这种机制如何实现?

    • 实现一个实现一个全局Map
    • key为notificationName
    • value为一个Serrvers(因为一对多), servers中的每个元素应该能明确哪一个对象,哪一个方法

    通知传递消息一对多的流程

    通知一对多的流程.png

    通知和代理的区别

    • 代理使用代理模式,而通知是由观察者模式实现的
    • 代理是一对一,而通知是一对多

    KVO

    什么是KVO?

    • KVO是key-value observing的缩写
    • KVO是Objective-C对观察者设计模式的又一实现
    • KVO是Objective-C对观察者设计模式的又一实现

    KVO的实现机制

    KVOisa混写.png
    • 系统调用addObserver添加KVO的时候
    • 会想被观察者对象的isa指向其名为NSKVONotifying_<ObjectClass>子类
    • NSKVONotifying_<ObjectClass>中重写了setter方法
    • 重写的setter方法负责通知观察者

    重写setter方法

    • -(void)willChangeValueForKey:(NSString *)key;
    • -(void)didChangeValueForKey:(NSString *)key;

    通过KVC设置value能够生效?

    • 通过KVC设置value,KVO的通知会生效
    • KVC设置value会调用setter方法

    通过成员变量直接赋值value能否生效

    • 不会触发系统的KVO的
    • 不会调用setter方法
    • 可以在变量赋值的代码块前后添加下面两个API来触发KVO
      ① -(void)willChangeValueForKey:(NSString *)key;
      ② -(void)didChangeValueForKey:(NSString *)key;

    观测方法的options

    • NSKeyValueObservingOptionOld 把更改之前的值提供给处理方法
    • NSKeyValueObservingOptionNew 把更改之后的值提供给处理方法
    • NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值。
    • NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后。

    总结

    • 使用setter方法改变值KVO才会生效
    • 使用setValue:forkey:改变KVO才会生效
    • 成员变量直接修改需手动添加KVO才会生效

    KVC

    什么是KVC?

    • KVC是Key-value coding的缩写
    • KVC的两个API
      ① -(void)setValue:(nullable id)value forKey:(NSString *)key
      ② -(nullable id)valueForKey:(NSString *)key

    使用KVC是否会违背于面向对象的编程思想

    KVC的key是没有限制的,如果我们知道这个对象内部的私有变量名称的话,我们可以在外界修改、访问它,从这个角度来考虑的话是会破坏面向对象的思想的

    valueForKey的实现流程

    KVCGetter方法的流程.png

    Accessor Method方法的定义

    • instance var
    • _key
    • _isKey
    • key
    • isKey

    setValue:forKey的实现流程

    KVCsetter方法的流程.png

    Accessor Method方法的定义

    • set:
    • _set:
    • setIs:

    instance var

    • _key
    • _isKey
    • key
    • isKey

    属性关键字

    属性关键字分为哪几类

    • 读写权限
      ① readonly
      ② readwrite iOS默认
    • 原子性
      ① atomic iOS默认
      -修饰的是一个数组,赋值和获取是线程安全的,
      -修改这个数组中的数据不是线程安全的 (例如在atomic修饰的NSMutableArray数组中,进行增删元素操作)
      ② nonatomic
    • 引用技计数
      ① retain mrc
      ② strong arc
      ③ assign 既可以修饰对象,也可以修饰值类型数据
      ④ unsafe_unretained MRC使用的比较多,ARC基本上不怎么用
      ⑤ weak
      ⑥ copy

    assign的特点

    • 修饰基本数据类型,如果int、bool等
    • 修饰对象类型时,不改变引用计数
    • 在对象释放以后,assign修饰的对象指针还是会指向这个对象地址,当使用这个对象的时候,会出现野指针。

    weak 特点

    • 不改变对象引用计数
    • 所指对象在释放之后会自动置为nil

    assign和weak的区别有哪些?

    1、weak只可以修饰对象、而assign可以修饰基本数据类型和对象
    2、assign修饰的对象的时候,当对象被释放掉以后,assign指针还是会指向对象的地址,而weak修饰的对象,在对象释放后,weak的指针会被置nil

    copy

    源对象类型 拷贝方式 目标对象类型 拷贝类型
    mutable对象 copy 不可变 深拷贝
    mutable对象 mutableCopy 可变 深拷贝
    immutable对象 copy 不可变 浅拷贝
    immutable对象 mutableCopy 可变 深拷贝

    ① 可变对象的copy和mutablecopy都是深拷贝
    ② 不可变对象的copy是浅拷贝,mutablecopy是深拷贝
    ③ copy方法返回的都是不可变对象

    浅拷贝

    • 浅拷贝会增加被拷贝对象的引用计数
    • 并没有发生新的内存分配
      浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间。


      浅拷贝.png

    深拷贝

    • 不会增加被拷贝对象的引用计数
    • 产生了内存分配
      深拷贝让目标对象指针和源对象指针指向两片内容相同的内存空间


      深拷贝.png

    深拷贝VS浅拷贝?

    • 是否开辟新的内存空间
    • 是否影响引用计数

    @property(copy) NSMutableArray *array, 会产生什么样的问题

    最终结果是一个不可变的对象,那么你在修改这个数组的时候会崩溃

    • 如果赋值过来的是NSMutableArray,copy之后虽然是深拷贝,但是返回的是NSArray
    • 如果赋值过来的是NSArray,copy之后是NSArray
    • 当修改array这个数组的时候就会出现崩溃问题

    MST

    1、MRC下如何重写retain修饰变量的setter方法?

    @property(nonatomic,retain)id obj;
    
    - (void)setObj:(id)obj{
        //对象不等判断
        //如果传递过来的obj对象是原来的obj对象
        //实际上也是在传递过来的obj对象release的操作,有可能会直接释放掉了传递过来的对象,
        //这时候如果还去访问被释放的对象就会崩溃
        if(_obj != obj){
            [_obj release];
            _obj = [obj retain]
        }
    }
    

    2、 分类的实现原理

    • 是由运行时来决议的
    • 不同分类,同名函数名称,谁最后生效,取决于,谁最后参与编译的同名分类方法会生效
    • 如果分类方法的名字和宿主的方法名称一样,那么分类的方法会覆盖宿主类的方法,这里的覆盖是runtime优先处理数组靠前的方法,如果找到就调用。宿主类的同名方法还是存在的

    3、KVO实现原理是怎么样的?

    • KVO是系统基于观察模式的一个实现
    • KVO通过isa的混写技术来动态运行时去为某一个类添加子类,并重写setter方法,同时把原有类的isa指针指向这个子类

    4、能否为分类添加实例变量?

    • 实例变量(成员变量),即它只能添加属性,setter/getter的声明,但是没有实现,不能使用其父类的一些成员方法
    • 属性是系统自动为我们生成的一个添加下划线的实例变量,系统帮我们实现了setter/getter方法
    • 不使用属性创建的实例变量,需要最后在@implementation中用synthesize生成set方法
      @synthesize name;(创建实例变量的setter、getter方法),@dynamic告诉编译器,不自动生成getter/setter方法,
    • 为分类添加实例变量
      ① 使用关联对象技术为分类添加“实例变量”(只是达到一个成员变量的效果,实际上并不是真正的添加了实例变量)
      ② 代码实例
      .h文件
    @interface CustomView (dd)
    
    - (NSString *)name;
    - (void)setName:(NSString *)name;
    
    @end
    

    .m

    @implementation CustomView (dd)
    
    /*
     * 使用关联对象模拟实例变量
     * 使用objc_getAssociatedObject、objc_setAssociatedObject模拟『属性』的存取方法
     */
    
    - (NSString *)name{
        
        return objc_getAssociatedObject(self, _cmd);
        
    }
    
    - (void)setName:(NSString *)name{
        
        objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
    }
    @end
    

    相关文章

      网友评论

          本文标题:OC语言特性

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