iOS晋级知识汇总(三)语言特性

分类

用分类干了什么事情?

  • 声明私有方法
  • 分解体积庞大类文件
  • 把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#>)

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

  • 我们为分类所添加的成员变量肯定不是在宿主类中的。
  • 关联对象由AssociationsManager管理并在AssociationHashMap存储。
  • 我们创建的每一个对象的关联对象实际上都存储在了AssociationHashMap这样的容器中。
  • 所有对象的关联内容都在同一个全局容器中
  • 不同对象的关联对象的值都会放到全局容器中

关联对象

关联对象的本质

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

关联对象的本质.png

关联对象的Json形式.png

扩展(Extension)

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

  • 一般用扩展做什么?
    • 声明私有属性
    • 声明私有方法(没有多大作用,方便阅读)
    • 声明私有成员变量

声明私有属性、声明私有成员变量是有区别的

特点

  • 编译时决议
  • 只义声明的形式存在,多数情况下寄生于宿主类的.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对观察者设计模式的又一实现
  • Apple使用了isa混写(isa-swizzling)来实现KVO

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;

总结

  • 使用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方法的定义

  • <getKey>
  • <key>
  • <isKey>

instance var

  • _key
  • _isKey
  • key
  • isKey

setValue:forKey的实现流程

KVCsetter方法的流程.png

Accessor Method方法的定义

  • set<Key>:
  • _set<Key>:
  • setIs<Key>:

instance var

  • _key
  • _isKey
  • key
  • isKey

属性关键字

属性关键字分为哪几类

  • 读写权限
    • readonly
    • readwrite iOS默认
  • 原子性
    • atomic iOS默认
      • 修饰的是一个数组,赋值和获取是线程安全的,
      • 修改这个数组中的数据不是线程安全的
    • 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

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这个数组的时候就会出现崩溃问题

面试题

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、能否为分类添加实例变量?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容