分类
分类中l可以添加那些内容
实力方法
类方法
协议
属性(关联对象:)
为什么不能添加属性
我们知道在一个类中用@property声明属性,编译器会自动帮我们生成_成员变量和setter/getter,
但分类的指针结构体中,根本没有属性列表。所以在分类中用@property声明属性,
既无法生成_成员变量也无法生成setter/getter。
因此结论是:我们可以用@property声明属性,编译和运行都会通过,只要不使用程序也不会崩溃。
但如果调用了_成员变量和setter/getter方法,报错就在所难免了。
可以使用关联对象以及两个方法来模拟构成属性的三个要素。
分类的结构
Category 是表示一个指向分类的结构体的指针,其定义如下:
typedef struct objc_category *Category;
struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 分类名
char *class_name OBJC2_UNAVAILABLE; // 分类所属的类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}
类扩展与分类的区别如下:
>> OC分类属于Runtime运行时特性,是OC语言独有的创新,其他编程语言所不具备这样的特性! 类扩展属于编译器特性,在编译阶段就会被添加合并到原类中!
分类是如何实现的
获取cls中未完成整合的所有分类 unattachendCategoriesForClass
将分类拼接到class上 attachCategories
倒序遍历所有分类
获取该分类的方法、协议添加到主类上
// 添加方法
rw->methods.attachLists(mlists,mcount);
// 添加类方法
rw->properties.attachLists(proplists,propcount);
// 添加协议
rw->properties.attachLists(protolists,protocount);
计算拼接后的元素总数,根据新的总数重新分配内存
重新设置元素总数
执行内存移位
说明
分类添加的方法可以覆盖原类方法
原因:分类是在运行时添加到原类上的
同名分类方法是能生效决定于编译顺序(倒序遍历所有分类)
名字相同的分类会引起编译报错
关联对象
关联对象的实现
获取其维护的一个HashMap,是一个全局容器
根据对象指针,查找对象对应的ObjectAssociationMap中的map
添加关联对象
方法
void objc_setAssociatedObject (id object, void*key, id value, objc_AssociationPolicy policy)
此方法以给定的键和策略为某对象设置关联值。
id objc_getAssociatedObject(id object, void *key)
此方法根据给定的键从某对象中获取相应的关联对象值。
void objc_removeAssociatedObjects(id object)
此方法移除指定对象的全部关联对象。
为什么分类不能添加属性
在runtime 中,objc_class 结构体大小是固定的,不可能往这里添加数据,只能修改。
所以,ivars 指向了一个固定区域,只能修改成员变量的值,不能增加成员变量的个数。
方法列表是一个二维数组,可以修改 *methodLists的值来增加成员方法,虽然没办法扩展methodLists指向的内存区域,
却可以改变这个内存区域的值(里面存的是指针),因此,可以动态添加方法,不可以添加成员变量。
扩展
编译时决定
只以声明的形式存在,多数情况下寄生于宿主的.m类中
不能为系统类添加扩展
类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(
用范围只能在自身类,而不是子类或其他地方);
类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中
类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
其实.m文件中@interface和@implementation就是扩展声明和实现,扩展的实现必须依托原类
代理
代理模式是一种消息传递方式,一个完整的代理模式包括:委托对象、代理对象和协议。
代理属性使用weak
weak和assign是一种“非拥有关系”的指针,通过这两种修饰符修饰的指针变量,都不会改变被引用对象的引用计数。但是在一个对象被释放后,weak会自动将指针指向nil,而assign则不会。在iOS中,向nil发送消息时不会导致崩溃的,所以assign就会导致野指针的错误
代理更加面相过程,block则更面向结果。
从性能上来说,block的性能消耗要略大于delegate,因为block会涉及到栈区向堆区拷贝等操作,时间和空间上的消耗都大于代理。而代理只是定义了一个方法列表,在遵守协议对象的objc_protocol_list中添加一个节点,在运行时向遵守协议的对象发送消息即可。
通知
KVO
KVC
属性关键字
Objective-C具有相当多的动态特性,表现为三方面:动态类型(Dynamic typing)、动态绑定(Dynamic binding)和动态加载(Dynamic loading)。动态——必须到运行时(run time)才会做的一些事情。
动态类型:即运行时再决定对象的类型,这种动态特性在日常的应用中非常常见,简单来说就是id类型。事实上,由于静态类型的固定性和可预知性,从而使用的更加广泛。静态类型是强类型,而动态类型属于弱类型,运行时决定接受者。
动态绑定:基于动态类型,在某个实例对象被确定后,其类型便被确定了,该对象对应的属性和响应消息也被完全确定。
动态加载:根据需求加载所需要的资源,最基本就是不同机型的适配,例如,在Retina设备上加载@2x的图片,而在老一些的普通苹设备上加载原图,让程序在运行时添加代码模块以及其他资源,用户可根据需要加载一些可执行代码和资源,而不是在启动时就加载所有组件,可执行代码可以含有和程序运行时整合的新类。
#include与#import的区别、#import 与@class 的区别
#include 和#import其效果相同,都是查询类中定义的行为(方法);
#import不会引起交叉编译,确保头文件只会被导入一次;
@class 的表明,只定 义了类的名称,而具体类的行为是未知的,一般用于.h 文件;
@class 比#import 编译效率更高。
解释 const, static, inline 关键字
const 修饰指针,或者常量,比如不可变,
static 修饰变量表示作用域,比如全局的私有变量,函数内部的 static 是内部的私有变量。
Static 修饰函数表示函数是文件作用域
Inline 表示内联。一般来说 inline 需要和 static 联合用 一般用法是 static inline int max(int a, int b) {
return a>b?a:b; }
static inline作用是和宏类似,只不过是方便调试(宏不能断掉调 试,static inline 可以)。运行时候是一样的。
一般 c/c++短小的函数都用 static inline 内联函数
load不是去类对象方法列表,遍历查找的,而是直接找到对象,拿到内存地址去调用的
先调用父类的load方法再调用子类的load方法