0%

iOS知识点梳理:Objective-C语言特性

分类(Category)


1. 分类的应用:
  • 声明私有方法
  • 分解体积庞大的类文件
  • 把Framework的私有方法公开
2. 分类的特点(或者说与扩展的区别):
  • 运行时决议:在编写完分类文件后,并没有直接把分类内容添加到相关的宿主类上。而是在运行时使用runtime把分类的内容添加到宿主类上。
  • 可以为系统类添加分类
3. 分类的优、缺点:

优点:

  • 不需要通过增加子类而增加现有类的行为(方法),且类目中的方法与原始类方法基本没有区别
  • 通过类目可以将庞大一个类的方法进行划分,从而便于代码的日后的维护、更新以及提高代码的阅读性

缺点:

  • 无法向类目添加实例变量,如果需要添加实例变量,只能通过定义子类的方式
  • 类目中的方法与原始类以及父类方法相比具有更高优先级,如果覆盖父类的方法,可能导致super消息的断裂。因此,最好不要覆盖原始类中的方法
4. 分类加载流程:

在运行APP的时候,加载完动态链接库,会加载可执行文件,通过runtime生成类、成员变量、方法列表,在宿主类的方法列表生成完之后,会开始加载分类的方法列表。

取到所有分类的列表数组(按编译时的顺序排序),然后按倒序从分类列表里取出每个分类的方法列表,生成一个二维数组。

把二维数组中的方法,按正序即从0索引开始,放入到宿主类的方法列表中(一维的数组)。
所以分类才拥有了“覆盖”原有类的方法功能,其实是原方法是存在的,只是分类的方法列表加入到了原有类的方法数组的前边,获得优先执行权。

5. 分类中都可以添加哪些内容:
  • 增加实例方法
  • 增加类方法
  • 增加协议
  • 增加实例属性。在分类中定义了一个属性,实际上只声明了对应的get方法和set方法,并没有在分类中添加实例变量 var
  • 增加实例变量(使用runtime关联对象技术来添加)⚠️

在编译时候,分类会被编译成一个category_t的结构体,储存如下信息:

1
2
3
4
5
6
7
8
struct category_t {
const char *name; 分类名称
classref_t cls; 该分类所属的宿主类
struct method_list_t *instanceMethods; 实例方法列表
struct method_list_t *classMethods; 类方法列表
struct protocol_list_t *protocols; 协议列表
struct property_list_t *instanceProperties; 实例属性列表
}
6. 分类添加过程:

在程序运行时候,runtime会把分类的实例方法等信息合并到类对象的实例方法列表中,会把分类的类方法合并到元类对象的类方法列表中。

以添加实例方法为例:
运行时候,会遍历分类列表,拿到每一个分类的实例方法列表

1
2
分类1的实例方法列表 array1 [method_t, method_t]
分类2的实例方法列表 array2 [method_t, method_t]

然后根据获取到的所有分类的实例方法列表数 和 原宿主类的实例方法列表,重新分配内存,数据结构为一个新的二维数组

1
new_array [所有分类的实例列表数目 + 宿主类的实例列表数][]

最后把宿主类的实例列表移动都数组后面,并把所有分类的实例列表加入到新申请的二维数组的前边

1
2
3
4
5
[
array1,
array2.
[原宿主类的实例方法列表]
]
7. 加载调用栈:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#系统库libSystem的Runtime入口函数
void _objc_init(void)

#处理由dyld映射的镜像
void map_images(unsigned count, const char * const paths[],const struct mach_header * const mhdrs[])
void map_images_nolock(unsigned mhCount, const char * const mhPaths[],const struct mach_header * const mhdrs[])

#读取镜像
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)

#重新编译当前类的方法列表
static void remethodizeClass(Class cls)

#将方法列表以及属性和协议从类别附加到类。
static void attachCategories(Class cls, category_list *cats, bool flush_caches)

关联对象


1. 关联对象技术: 可以给分类添加成员变量
1
2
3
4
5
6
7
8
#关联对象: 使用objc_setAssociatedObject函数可以给某个对象关联其他的对象。
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

#获取关联的对象: 使用objc_getAssociatedObject函数可以通过键来取出某个对象的关联对象。
id objc_getAssociatedObject(id object, const void *key)

#移除关联的对象:使用objc_removeAssociatedObjects函数可以移除某个对象身上的所有关联的对象。
void objc_removeAssociatedObjects(id object)

⚠️ 注意:
void objc_removeAssociatedObjects(id object)函数移除的是某个对象身上的所有关联的对象。
Objective-C 没有给我们提供移除对象身上单个关联对象的函数,所以,我们一般通过objc_setAssociatedObject 函数传入 nil 来达到移除某个关联对象的目的:

1
2
3
4
5
6
7
8
9
10
void objc_setAssociatedObject(object, key, nil, policy);

key: 要保证全局唯一,key与关联的对象是一一对应关系,必须全局唯一
value: 要关联的对象
policy: 关联策略。有五种关联策略
OBJC_ASSOCIATION_ASSIGN 等价于 @property(assign)
OBJC_ASSOCIATION_RETAIN_NONATOMIC 等价于 @property(strong, nonatomic)
OBJC_ASSOCIATION_COPY_NONATOMIC 等价于 @property(copy, nonatomic)
OBJC_ASSOCIATION_RETAIN 等价于 @property(strong,atomic)
OBJC_ASSOCIATION_COPY 等价于 @property(copy, atomic)
2. 常见问题:

问:关联对象技术给分类添加的成员变量放在了哪里?
答:关联对象由AssociationsManager管理并在AssociationsHashMap存储。所有对象的关联内容都在同一个容器中。

问:怎样清除某一个关联对象被关联的值?
答:setAssociatedObject方法中value值设为nil即可。

扩展(Extension)


1. 扩展的应用:
  • 声明私有属性
  • 声明私有方法
  • 声明私有成员变量
2. 扩展的特点:
  • 编译时决议
  • 只以声明的形式存在,多数情况下寄生于宿主类的.m实现文件中
  • 不能为系统类添加拓展

协议和代理


1. 代理的特点:
  • 软件设计模式,代理模式
  • 传递方式一对一

通知


1. 通知的特点:
  • 是使用观察者模式来实现的用于跨层传递消息的机制
  • 传递方式⼀对多
2. 通知的流程:
1
发送者 >  通知中心 > 多个观察者
3. 通知的实现机制
1
Notification_Map(notificationName: [observers])

KVO


1. 什么是KVO:
  • 观察者设计模式的⼜一实现
  • 使用isa-swizzling来实现KVO
2. isa-swizzling实现方式:

当我们调用了 addobvser 之后,系统会动态的⽣成一个NSKVONotifying_A类,并用 A 类的isa指针指向它,NSKOVNotifying_AA 的子类,为了重写 setter ⽅法,以达到通知的功能

3. 通过 KVC 设置 Value 能否生效?

系统在调用KVC赋值的时候,会先查找某一个变量是否有setter⽅法,如果有就会调用setter方法,如果没有,才会直接对成员变量直接赋值。所以,在调用setter⽅法的时候,KVO是生效的

4. 通过成员变量直接赋值Value能否生效?

直接赋值KVO是不会生效的,但是可以在赋值的时候手动调用 willChangeValueForKeydidChangeValueForKey 达到 KVO 通知的效果

KVC


1. 是否会破坏⾯向对象编程思想:

setValueForKey 中的 key 是没有任何限制的,如果当用户知道某个对象里面私有成员变量量的key,就可以通过key对私有变量赋值或者访问,从这个⻆角度看,是破坏⾯面向对象编程思想的

2. 访问器器⽅方法判断规则:
1
2
3
<getKey>
<key>
<isKey>
3. 成员变量量判断规则:
1
2
3
4
_key 
_isKey
key
isKey

属性关键字


1. 深拷贝和浅拷贝的区别:
  • 是否开辟新的内存空间
  • 是否影响了引⽤用计数