一、类与对象基础数据结构
1、Class
Objective-C
类是由Class
类型来表示的,它实际上是一个指向objc_class
结构体的指针。它的定义如下:
typedef struct objc_class *Class;
objc_class
在runtime.h
定义如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;// metaclass
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;// 父类
const char *name OBJC2_UNAVAILABLE;// 类名
long version OBJC2_UNAVAILABLE;// 类的版本信息,默认为0,可以通过runtime函数class_setVersion或者class_getVersion进行修改、读取
long info OBJC2_UNAVAILABLE;// 类信息,供运行时期使用的一些位标识,如CLS_CLASS (0x1L) 表示该类为普通 class,其中包含实例方法和变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size OBJC2_UNAVAILABLE;// 该类的实例变量大小(包括从父类继承下来的实例变量)
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;// 该类的成员变量地址列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;// 方法地址列表,与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储实例方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache OBJC2_UNAVAILABLE;// 缓存最近使用的方法地址,用于提升效率;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;// 存储该类声明遵守的协议的列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
- (1)、isa:需要注意的是在
Objective-C
中,所有的类自身也是一个对象,这个对象的Class
里面也有一个isa
指针,它指向metaClass
(元类),我们会在后面介绍它。 - (2)、super_class:指向该类的父类,如果该类已经是最顶层的根类(如
NSObject
或NSProxy
),则super_class
为NULL
。 - (3)、cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是
methodLists
中遍历一遍,性能势必很差。这时,cache
就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache
列表中,下次调用的时候runtime
就会优先去cache
中查找,如果cache
没有,才去methodLists
中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。 - (4)、version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。
2、objc_object与id
objc_object
是表示一个类的实例的结构体,它的定义如下(objc/objc.h)
:
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
可以看到,这个结构体只有一个字体,即指向其类的isa指针。这样,当我们向一个Objective-C
对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类。Runtime
库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector
指向的方法。找到后即运行这个方法。
当创建一个特定类的实例对象时,分配的内存包含一个objc_object
数据结构,然后是类的实例变量的数据。NSObject
类的alloc
和allocWithZone:
方法使用函数class_createInstance
来创建objc_object
数据结构。
3、objc_cache
上面提到了objc_class
结构体中的cache
字段,它用于缓存调用过的方法。这个字段是一个指向objc_cache
结构体的指针,其定义如下:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
typedef struct objc_cache *Cache OBJC2_UNAVAILABLE;
该结构体的字段描述如下:
- (1)、mask:一个整数,指定分配的缓存
bucket
的总数。在方法查找过程中,Objective-C
runtime
使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index = (mask & selector))
。这可以作为一个简单的hash
散列算法。 - (2)、occupied:一个整数,指定实际占用的缓存bucket的总数。
- (3)、buckets:指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。
4、元类(Meta Class)
所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。如:
NSArray *array = [NSArray array];
这个例子中,+array
消息发送给了NSArray
类,而这个NSArray
也是一个对象。既然是对象,那么它也是一个objc_object
指针,它包含一个指向其类的一个isa
指针。那么这些就有一个问题了,这个isa
指针指向什么呢?为了调用+array
方法,这个类的isa
指针必须指向一个包含这些类方法的一个objc_class
结构体。这就引出了meta-class
的概念
meta-class是一个类对象的类。
当我们向一个对象发送消息时,runtime
会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class
的方法列表中查找。
meta-class
之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class
,因为每个类的类方法基本不可能完全相同。
再深入一下,meta-class
也是一个类,也可以向它发送一个消息,那么它的isa
又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C
的设计者让所有的meta-class
的isa
指向基类的meta-class
,以此作为它们的所属类。即,任何NSObject
继承体系下的meta-class
都使用NSObject
的meta-class
作为自己的所属类,而基类的meta-class
的isa
指针是指向它自己。这样就形成了一个完美的闭环。
通过上面的描述,再加上对objc_class
结构体中super_class
指针的分析,我们就可以描绘出类及相应meta-class
类的一个继承体系了,如下图所示:
对于NSObject
继承体系来说,其实例方法对体系中的所有实例、类和meta-class
都是有效的;而类方法对于体系内的所有类和meta-class
都是有效的。
二、类与对象操作函数
runtime
提供了大量的函数来操作类与对象。类的操作方法大部分是以class
为前缀的,而对象的操作方法大部分是以objc
或object_
为前缀。下面我们将根据这些方法的用途来分类讨论这些方法的使用。
1、类相关操作函数(class_系列函数)
objc_class
的定义,runtime
提供的操作类的方法主要就是针对这个结构体中的各个字段的。
(1)、父类(super_class)和元类(meta-class)
父类和元类操作的函数主要有:
//获取类的父类
Class class_getSuperclass(Class cls);
//判断给定的Class是否是一个元类
BOOL class_isMetaClass(Class cls);
- ①、class_getSuperclass:当
cls
为Nil
或者cls
为根类时,返回Nil
。不过通常我们可以使用NSObject
类的superclass
方法来达到同样的目的。 - ②、class_isMetaClass:如果是
class
是元类,则返回YES
;如果否或者传入的class
为Nil
,则返回NO
。
(2)、类名
类名操作的函数主要有:
// 获取类的类名
const char *class_getName(Class cls);
class_getName:如果传入的cls
为Nil
,则返回一个字字符串。
(3)、版本(version)
版本相关的操作包含以下函数:
// 获取版本号
int class_getVersion(Class cls);
// 设置版本号
void class_setVersion(Class cls, int version);
(4)、实例变量大小(instance_size)
实例变量大小操作的函数有:
// 获取实例大小
size_t class_getInstanceSize(Class cls);
(5)、成员变量(ivars)及属性
在objc_class
中,所有的成员变量、属性的信息是放在链表ivars
中的。ivars
是一个数组,数组中每个元素是指向Ivar(变量信息)的指针。runtime
提供了丰富的函数来操作这一字段。大体上可以分为以下几类:
- 成员变量操作函数,主要包含以下函数:
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable(Class cls, const char *name);
// 获取类成员变量的信息
Ivar class_getClassVariable(Class cls, const char *name);
// 添加成员变量
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types);
// 获取整个成员变量列表
Ivar *class_copyIvarList(Class cls, unsigned int *outCount);
①、class_getInstanceVariable:它返回一个指向包含name指定的成员变量信息的objc_ivar
结构体的指针(Ivar
)。
②、class_getClassVariable:一般认为Objective-C
不支持类变量。注意,返回的列表不包含父类的成员变量和属性。
③、class_addIvar:Objective-C
不支持往已存在的类中添加实例变量,因此不管是系统库提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?这时我们就可以使用class_addIvar
函数了。不过需要注意的是,这个方法只能在objc_allocateClassPair
函数与objc_registerClassPair
之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<< alignment。这取决于ivar的类型和机器的架构。如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))
。
④、class_copyIvarList:它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar
结构体的指针。这个数组不包含在父类中声明的变量。outCount
指针返回数组的大小。需要注意的是,我们必须使用free()
来释放这个数组。
- 属性操作函数,主要包含以下函数:
// 获取指定的属性
objc_property_t class_getProperty(Class cls, const char *name);
// 获取属性列表
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);
// 为类添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount);
// 替换类的属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount);
(6)、方法(methodLists)
方法操作主要有以下函数:
// 添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) ;
// 获取实例方法
Method class_getInstanceMethod(Class cls, SEL name);
// 获取类方法
Method class_getClassMethod(Class cls, SEL name);
// 获取所有方法的数组
Method *class_copyMethodList(Class cls, unsigned int *outCount);
// 替代方法的实现
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);
// 返回方法的具体实现
IMP class_getMethodImplementation(Class cls, SEL name);
IMP class_getMethodImplementation_stret(Class cls, SEL name);
// 类实例是否响应指定的selector
BOOL class_respondsToSelector(Class cls, SEL sel);
①、class_addMethod:该实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO
。如果要修改已存在实现,可以使用method_setImplementation
。一个Objective-C
方法是一个简单的C函数,它至少包含两个参数—self和_cmd。所以,我们的实现函数(IMP参数指向的函数)至少需要两个参数,如下所示
void myMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已存在。
另外,参数types是一个描述传递给方法的参数类型的字符数组;
②、class_getInstanceMethod、class_getClassMethod:与class_copyMethodList
不同的是,这两个函数都会去搜索父类的实现。
③、class_copyMethodList:返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)
(一个类类方法是定义在元类里面)。该列表不包含父类实现的方法。outCount参数返回方法的个数。在获取到列表后,我们需要使用free()方法来释放它。
④、class_replaceMethod:该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于class_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现。
⑤、getMethodImplementation:该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。
⑥、class_respondsToSelector:我们通常使用NSObject类的respondsToSelector:或instancesRespondToSelector:方法来达到相同目的。
(7)、协议(objc_protocol_list)
协议相关的操作包含以下函数:
// 添加协议
BOOL class_addProtocol(Class cls, Protocol *protocol);
// 返回类是否实现指定的协议
BOOL class_conformsToProtocol(Class cls, Protocol *protocol);
// 返回类实现的协议列表
Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount);
- ①、class_conformsToProtocol:可以使用NSObject类的conformsToProtocol:方法来替代。
- ②、class_copyProtocolList:返回的是一个数组,在使用后我们需要使用free()手动释放。
(8)、总结:class_xxx 系列函数
函数名称 | 函数作用 |
---|---|
class_addIvar | 为类添加实例变量 |
class_addProperty | 为类添加属性 |
class_addMethod | 为类添加方法 |
class_addProtocol | 为类遵循协议 |
class_replaceMethod | 替换类某方法的实现 |
class_getName | 获取类名 |
class_isMetaClass | 判断是否为元类 |
class_getSuperclass | 获取某类的父类 |
class_setSuperclass | 设置某类的父类 |
class_getProperty | 获取某类的属性 |
class_getInstanceVariable | 获取实例变量 |
class_getClassVariable | 获取类变量 |
class_getInstanceMethod | 获取实例方法 |
class_getClassMethod | 获取类方法 |
class_getMethodImplementation | 获取方法的实现 |
class_getInstanceSize | 获取类的实例的大小 |
class_respondsToSelector | 判断类是否实现某方法 |
class_conformsToProtocol | 判断类是否遵循某协议 |
class_createInstance | 创建类的实例(ARC下无效) |
class_copyIvarList | 拷贝类的实例变量列表 |
class_copyMethodList | 拷贝类的方法列表 |
class_copyProtocolList | 拷贝类遵循的协议列表 |
class_copyPropertyList | 拷贝类的属性列表 |
class_系列函数关注于类的内部,如实例变量,属性,方法,协议等相关问题
2、动态创建类和对象,获取类定义(objc_系列函数)
runtime
的强大之处在于它能在运行时创建类和对象。
(1)、动态创建类
动态创建类涉及到以下几个函数:
// 创建一个新类和元类
Class objc_allocateClassPair(Class superclass, const char *name,size_t extraBytes);
// 销毁一个类及其相关联的类
void objc_disposeClassPair(Class cls);
// 在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair(Class cls);
- ①、objc_allocateClassPair:如果我们要创建一个根类,则superclass指定为Nil。extraBytes通常指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。
为了创建一个新类,我们需要调用objc_allocateClassPair。然后使用诸如class_addMethod,class_addIvar等函数来为新创建的类添加方法、实例变量和属性等。完成这些后,我们需要调用objc_registerClassPair函数来注册类,之后这个新类就可以在程序中使用了。
实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。 - ②、objc_disposeClassPair:用于销毁一个类,不过需要注意的是,如果程序运行中还存在类或其子类的实例,则不能针对类调用该方法。
- ③、objc_registerClassPair:在应用中注册由objc_allocateClassPair创建的类,之后使用该函数来注册类,之后这个心累就可以在程序中使用了。
(2)、动态创建对象
动态创建对象的函数如下:
// 创建类实例
id class_createInstance(Class cls, size_t extraBytes);
// 在指定位置创建类实例
id objc_constructInstance(Class cls, void *bytes);
// 销毁类实例
void *objc_destructInstance(id obj);
- ①、class_createInstance:创建实例时,会在默认的内存区域为类分配内存。extraBytes参数表示分配的额外字节数。这些额外的字节可用于存储在类定义中所定义的实例变量之外的实例变量。该函数在ARC环境下无法使用。
调用class_createInstance的效果与+alloc方法类似。不过在使用class_createInstance时,我们需要确切的知道我们要用它来做什么。 - ②、objc_constructInstance:在指定的位置(bytes)创建类实例。ARC下无效。
- ③、objc_destructInstance:销毁一个类的实例,但不会释放并移除任何与其相关的引用。ARC下无效。
(3)、获取类定义
Objective-C动态运行库会自动注册我们代码中定义的所有的类。我们也可以在运行时创建类定义并使用objc_addClass函数来注册它们。runtime提供了一系列函数来获取类定义相关的信息,这些函数主要包括:
// 获取已注册的类定义的列表
int objc_getClassList(Class *buffer, int bufferCount);
// 创建并返回一个指向所有已注册类的指针列表
Class *objc_copyClassList(unsigned int *outCount);
// 返回指定类的类定义
Class objc_lookUpClass(const char *name);
Class objc_getClass(const char *name);
Class objc_getRequiredClass(const char *name);
// 返回指定类的元类
Class objc_getMetaClass(const char *name);
- ①、objc_getClassList:获取已注册的类定义的列表。我们不能假设从该函数中获取的类对象是继承自NSObject体系的,所以在这些类上调用方法是,都应该先检测一下这个方法是否在这个类中实现。
- ②、获取类定义的方法有三个:objc_lookUpClass, objc_getClass和objc_getRequiredClass。如果类在运行时未注册,则objc_lookUpClass会返回nil,而objc_getClass会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。而objc_getRequiredClass函数的操作与objc_getClass相同,只不过如果没有找到类,则会杀死进程。
- ③、objc_getMetaClass:如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。不过,每个类定义都必须有一个有效的元类定义,所以这个函数总是会返回一个元类定义,不管它是否有效。
(4)、总结:objc_xxx 系列函数
函数名称 | 函数作用 |
---|---|
objc_getClass | 获取Class对象 |
objc_getMetaClass | 获取MetaClass对象 |
objc_allocateClassPair | 分配空间,创建类(仅在 创建之后,注册之前 能够添加成员变量) |
objc_registerClassPair | 注册一个类(注册后方可使用该类创建对象) |
objc_disposeClassPair | 注销某个类 |
objc_getProtocol | 获取某个协议 |
objc_copyProtocolList | 拷贝在运行时中注册过的协议列表 |
objc_allocateProtocol | 开辟空间创建协议 |
objc_registerProtocol | 注册一个协议 |
objc_constructInstance | 构造一个实例对象(ARC下无效) |
objc_destructInstance | 析构一个实例对象(ARC下无效) |
objc_setAssociatedObject | 为实例对象关联对象 |
objc_getAssociatedObject | 获取实例对象的关联对象 |
objc_removeAssociatedObjects | 清空实例对象的所有关联对象 |
objc_msgSend | 发送ObjC消息 |
objc_系列函数关注于宏观使用,如类与协议的空间分配,注册,注销等操作
3、实例操作函数(object_xxx 系列函数)
实例操作函数主要是针对我们创建的实例对象的一系列操作函数,我们可以使用这组函数来从实例对象中获取我们想要的一些信息,如实例对象中变量的值。这组函数可以分为三小类:
(1)、针对整个对象进行操作的函数
这类函数包含
// 返回指定对象的一份拷贝
id object_copy(id obj, size_t size);
// 释放指定对象占用的内存
id object_dispose(id obj);
(2)、针对对象实例变量进行操作的函数
这类函数包含:
// 修改类实例的实例变量的值
Ivar object_setInstanceVariable(id obj, const char *name, void *value);
// 获取对象实例变量的值
Ivar object_getInstanceVariable(id obj, const char *name, void **outValue);
// 返回指向给定对象分配的任何额外字节的指针
void *object_getIndexedIvars(id obj);
// 返回对象中实例变量的值
id object_getIvar(id obj, Ivar ivar);
// 设置对象中实例变量的值
void object_setIvar(id obj, Ivar ivar, id value);
如果实例变量的Ivar已经知道,那么调用object_getIvar会比object_getInstanceVariable函数快,相同情况下,object_setIvar也比object_setInstanceVariable快。
(3)、针对对象的类进行操作的函数
这类函数包含:
// 返回给定对象的类名
const char *object_getClassName(id obj);
// 返回对象的元类
Class object_getClass(id obj);
// 设置对象的元类
Class object_setClass(id obj, Class cls);
(4)、总结:object_xxx 系列函数
函数名称 | 函数作用 |
---|---|
object_copy | 对象copy(ARC无效) |
object_dispose | 对象释放(ARC无效) |
object_getClassName | 获取对象的类名 |
object_getClass | 获取对象的MetaClass |
object_setClass | 设置对象的MetaClass |
object_getIvar | 获取对象中实例变量的值 |
object_setIvar | 设置对象中实例变量的值 |
object_getInstanceVariable | 获取对象中实例变量的值 (ARC中无效,使用object_getIvar) |
object_setInstanceVariable | 设置对象中实例变量的值 (ARC中无效,使用object_setIvar) |
objcet_系列函数关注于对象的角度,如实例变量。
4、方法操作函数(method_xxx 系列函数)
//判断类中是否包含某个方法的实现
BOOL class_respondsToSelector(Class cls, SEL sel);
//为类添加新的方法,如果方法该方法已存在则返回NO
BOOL class_addMethod(Class cls, SEL name, IMP imp,const char *types);
//替换类中已有方法的实现,如果该方法不存在添加该方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types);
//获取类中的某个实例方法(减号方法)
Method class_getInstanceMethod(Class cls, SEL name);
//获取类中的某个类方法(加号方法)
Method class_getClassMethod(Class cls, SEL name);
//获取类中的方法实现
IMP class_getMethodImplementation(Class cls, SEL name);
//获取类中的方法的实现,该方法的返回值类型为struct
IMP class_getMethodImplementation_stret(Class cls, SEL name);
//获取Method中的SEL
SEL method_getName(Method m);
//获取Method中的IMP
IMP method_getImplementation(Method m);
//获取方法的Type字符串(包含参数类型和返回值类型)
const char *method_getTypeEncoding(Method m);
//获取参数个数
unsigned int method_getNumberOfArguments(Method m);
//获取返回值类型字符串
char *method_copyReturnType(Method m);
//获取方法中第n个参数的Type
char *method_copyArgumentType(Method m, unsigned int index);
//获取Method的描述
struct objc_method_description *method_getDescription(Method m);
//设置Method的IMP
IMP method_setImplementation(Method m, IMP imp);
//替换Method
void method_exchangeImplementations(Method m1, Method m2);
//调用target对象的sel方法
id objc_msgSend(id target, SEL sel, 参数列表...)
函数名称 | 函数作用 |
---|---|
method_getName | 获取方法名 |
method_getImplementation | 获取方法的实现 |
method_getTypeEncoding | 获取方法的类型编码 |
method_getNumberOfArguments | 获取方法的参数个数 |
method_copyReturnType | 拷贝方法的返回类型 |
method_getReturnType | 获取方法的返回类型 |
method_copyArgumentType | 拷贝方法的参数类型 |
method_getArgumentType | 获取方法的参数类型 |
method_getDescription | 获取方法的描述 |
method_setImplementation | 设置方法的实现 |
method_exchangeImplementations | 替换方法的实现 |
method_系列函数关注于方法内部,如果方法的参数及返回值类型和方法的实现
5、属性操作函数(property_xxx 系列函数)
//替换类中的属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
//获取类中的属性
objc_property_t class_getProperty(Class cls, const char *name)
//拷贝类中的属性列表
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
//获取属性名称
const char *property_getName(objc_property_t property)
//获取属性的特性
const char *property_getAttributes(objc_property_t property)
//拷贝属性的特性列表
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
//拷贝属性的特性的值
char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
函数名称 | 函数作用 |
---|---|
property_getName | 获取属性名 |
property_getAttributes | 获取属性的特性列表 |
property_copyAttributeList | 拷贝属性的特性列表 |
property_copyAttributeValue | 拷贝属性中某特性的值 |
property_系类函数关注与属性*内部,如属性的特性等
6、协议操作函数(protocol_xxx 系列函数)
函数名称 | 函数作用 |
---|---|
protocol_conformsToProtocol | 判断一个协议是否遵循另一个协议 |
protocol_isEqual | 判断两个协议是否一致 |
protocol_getName | 获取协议名称 |
protocol_copyPropertyList | 拷贝协议的属性列表 |
protocol_copyProtocolList | 拷贝某协议所遵循的协议列表 |
protocol_copyMethodDescriptionList | 拷贝协议的方法列表 |
protocol_addProtocol | 为一个协议遵循另一协议 |
protocol_addProperty | 为协议添加属性 |
protocol_getProperty | 获取协议中的某个属性 |
protocol_addMethodDescription | 为协议添加方法描述 |
protocol_getMethodDescription | 获取协议中某方法的描述 |
7、变量操作函数(ivar_xxx 系列函数)
//获取Ivar的名称
const char *ivar_getName(Ivar v);
//获取Ivar的类型编码,
const char *ivar_getTypeEncoding(Ivar v);
//通过变量名称获取类中的实例成员变量
Ivar class_getInstanceVariable(Class cls, const char *name);
//通过变量名称获取类中的类成员变量
Ivar class_getClassVariable(Class cls, const char *name);
//获取指定类的Ivar列表及Ivar个数
Ivar *class_copyIvarList(Class cls, unsigned int *outCount);
//获取实例对象中Ivar的值
id object_getIvar(id obj, Ivar ivar);
//设置实例对象中Ivar的值
void object_setIvar(id obj, Ivar ivar, id value);
函数名称 | 函数作用 |
---|---|
ivar_getName | 获取Ivar名称 |
ivar_getTypeEncoding | 获取类型编码 |
ivar_getOffset | 获取偏移量 |
8、方法选择器操作函数(sel_xxx 系列函数)
//获取SEL的名称
const char *sel_getName(SEL sel);
//注册一个SEL
SEL sel_registerName(const char *str);
//判断两个SEL对象是否相同
BOOL sel_isEqual(SEL lhs, SEL rhs);
函数名称 | 函数作用 |
---|---|
sel_getName | 获取名称 |
sel_getUid | 注册方法 |
sel_registerName | 注册方法 |
sel_isEqual | 判断方法是否相等 |
9、IMP操作函数(imp_xxx 系列函数)
//通过块创建函数指针,block的形式为^ReturnType(id self,参数,...)
IMP imp_implementationWithBlock(id block);
//获取IMP中的block
id imp_getBlock(IMP anImp);
//移出IMP中的block
BOOL imp_removeBlock(IMP anImp);
函数名称 | 函数作用 |
---|---|
imp_implementationWithBlock | 通过代码块创建IMP |
imp_getBlock | 获取函数指针中的代码块 |
imp_removeBlock | 移除IMP中的代码块 |
三、关联对象
关联对象是Runtime中一个非常实用的特性,不过可能很容易被忽视。关联对象类似于成员变量,不过是在运行时添加的。我们通常会把成员变量(Ivar)放在类声明的头文件中,或者放在类实现的@implementation后面。但这有一个缺点,我们不能在分类中添加成员变量。如果我们尝试在分类中添加新的成员变量,编译器会报错。我们可能希望通过使用(甚至是滥用)全局变量来解决这个问题。但这些都不是Ivar,因为他们不会连接到一个单独的实例。因此,这种方法很少使用。
Objective-C针对这一问题,提供了一个解决方案:即关联对象(Associated Object)。
我们可以把关联对象想象成一个Objective-C对象(如字典),这个对象通过给定的key连接到类的一个实例上。不过由于使用的是C接口,所以key是一个void指针(const void *)。我们还需要指定一个内存管理策略,以告诉Runtime如何管理这个对象的内存。这个内存管理的策略可以由以下值指定:
OBJC_ASSOCIATION_ASSIGN
OBJC_ASSOCIATION_RETAIN_NONATOMIC
OBJC_ASSOCIATION_COPY_NONATOMIC
OBJC_ASSOCIATION_RETAIN
OBJC_ASSOCIATION_COPY
当宿主对象被释放时,会根据指定的内存管理策略来处理关联对象。如果指定的策略是assign,则宿主释放时,关联对象不会被释放;而如果指定的是retain或者是copy,则宿主释放时,关联对象会被释放。我们甚至可以选择是否是自动retain/copy。当我们需要在多个线程中处理访问关联对象的多线程代码时,这就非常有用了。
我们将一个对象连接到其它对象所需要做的就是下面两行代码:
static char myKey;
objc_setAssociatedObject(self, &myKey, anObject, OBJC_ASSOCIATION_RETAIN);
在这种情况下,self对象将获取一个新的关联的对象anObject,且内存管理策略是自动retain关联对象,当self对象释放时,会自动release关联对象。另外,如果我们使用同一个key来关联另外一个对象时,也会自动释放之前关联的对象,这种情况下,先前的关联对象会被妥善地处理掉,并且新的对象会使用它的内存。
id anObject = objc_getAssociatedObject(self, &myKey);
我们可以使用objc_removeAssociatedObjects函数来移除一个关联对象,或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil。
#四、实践操作
添加头文件:
#import <objc/runtime.h>
#import <objc/message.h>
1、元类实践(Meta Class)
void TestMetaClass(id self, SEL _cmd) {
Class currentClass = objc_getMetaClass(class_getName([self class]));
NSLog(@"Class is %@, super class is %@", [self class], [self superclass]);
NSLog(@"This objcet is %p meta-class = %p", [self class] ,currentClass);
//获取对象的meta-class
for (int i = 0; i < 4; i++) {
NSLog(@"Following the isa pointer %d meta-class is %@ times gives %p ", i, currentClass,currentClass);
currentClass = objc_getMetaClass(class_getName([currentClass superclass]));
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", objc_getMetaClass(class_getName([NSObject class])));
}
- (void)viewDidLoad {
[super viewDidLoad];
Class newClass = objc_allocateClassPair([NSError class], "TestClass", 0);
class_addMethod(newClass, @selector(testMetaClass), (IMP)TestMetaClass, "v@:");
objc_registerClassPair(newClass);
id instance = [[newClass alloc] initWithDomain:@"some domain" code:0 userInfo:nil];
[instance performSelector:@selector(testMetaClass)];
}
打印结果:
Class is TestClass, super class is NSError
This objcet is 0x7ffb3b63c1f0 meta-class = 0x7ffb3b63e380
Following the isa pointer 0 meta-class is TestClass times gives 0x7ffb3b63e380
Following the isa pointer 1 meta-class is NSError times gives 0x109613f00
Following the isa pointer 2 meta-class is NSObject times gives 0x109959d50
Following the isa pointer 3 meta-class is NSObject times gives 0x109959d50
NSObject's class is 0x109959d28
NSObject's meta class is 0x109959d50
在for循环外,我们通过实例对象的class方法来获取对象的isa,然后又通过objc_getMetaClass方法来获取isa的元类,在for循环中,我们打印isa的元类以及他的指针,然后依次回溯到NSObject的meta-class。分析打印结果,可以看到最后指针指点的地址是0x109f15d50,即NSObject的meta-class的类地址。这就充分的解释的下图:
- (1)、class方法
- class
{
return object_getClass(self);
}
+ class
{
return self;
}
实例方法返回的是isa指针指向的Class, 类方法返回的是本身
- (2)、superclass方法
+ superclass
{
return class_getSuperclass((Class)self);
}
- superclass
{
return class_getSuperclass(object_getClass(self););
}
调用的是runtime中的class_getSuperclass方法, 跟踪到最后实例方法返回的是isa->superclass,类方法返回的是self->superclass
- (3)、class和meta-class
调用isa或者Class的superclass会返回至其基类NSObject(或者其他基类),如果继续调用NSObject的superclass会返回null;如果先获取isa或者class的meta-class,然后调用superclass会返回至基类NSObject(或者其他基类),如果继续获取NSObject的meta-class,调用NSObject的superclass会返回其自身。
PS:我们在一个类对象调用class方法是无法获取meta-class,它只是返回类而已。
- (4)、获取class和meta-class
MyClass.h
#import <Foundation/Foundation.h>
@interface MyClass : NSObject
- (void)test;
+ (void)test;
@end
MyClass.m
#import "MyClass.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation MyClass
- (void)test{
NSLog(@"%@ -- %p",self,self);
Class aClass = [self class];
Class bClass = object_getClass(self);
Class cClass = objc_getClass(class_getName([self class]));
Class dClass = objc_getMetaClass(class_getName([self class]));
NSLog(@"%@--%@--%@--%@ %p--%p--%p--%p",aClass,bClass,cClass,dClass, aClass,bClass,cClass,dClass);
NSLog(@"%d",class_isMetaClass(aClass));
NSLog(@"%d",class_isMetaClass(bClass));
NSLog(@"%d",class_isMetaClass(cClass));
NSLog(@"%d",class_isMetaClass(dClass));
}
+ (void)test{
NSLog(@"%@ -- %p",self,self);
Class aClass = [self class];
Class bClass = object_getClass(self);
Class cClass = objc_getClass(class_getName([self class]));
Class dClass = objc_getMetaClass(class_getName([self class]));
NSLog(@"%@--%@--%@--%@ %p--%p--%p--%p",aClass,bClass,cClass,dClass, aClass,bClass,cClass,dClass);
NSLog(@"%d",class_isMetaClass(aClass));
NSLog(@"%d",class_isMetaClass(bClass));
NSLog(@"%d",class_isMetaClass(cClass));
NSLog(@"%d",class_isMetaClass(dClass));
}
@end
mian.h
MyClass *myClass = [[MyClass alloc] init];
[myClass test];
[MyClass test];
打印:
<MyClass: 0x7fcc51529b60> -- 0x7fcc51529b60
MyClass--MyClass--MyClass--MyClass 0x107492ea8--0x107492ea8--0x107492ea8--0x107492e80
0
0
0
1
MyClass -- 0x107492ea8
MyClass--MyClass--MyClass--MyClass 0x107492ea8--0x107492e80--0x107492ea8--0x107492e80
0
1
0
1
由上可见,- (Class)class;方法中的self是实例对象中Class的isa指针;+ (Class)class;方法中的self是类对象Class;当使用Class object_getClass(id obj);方法获取对象的class时,如果obj为实例对象,则返回实例对象的Classl;如果obj为类对象,则返回类对象的meta-class。
2、类实践(Class)
MyClass.h
#import <Foundation/Foundation.h>
@interface MyClass : NSObject<NSCoding>
@property (nonatomic, strong) NSArray *array;
@property (nonatomic, copy) NSString *string;
- (void)method1;
- (void)method2;
+ (void)classMethod1;
@end
MyClass.m
#import "MyClass.h"
@interface MyClass()<NSCopying>
{
NSInteger _instance1;
NSString * _instance2;
}
@property (nonatomic, assign) NSUInteger integer;
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
@end
@implementation MyClass
+ (void)classMethod1 {
}
- (void)method1 {
NSLog(@"call method method1");
}
- (void)method2 {
NSLog(@"call method method2");
}
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}
@end
main.h
- (void)viewDidLoad {
[super viewDidLoad];
MyClass *myClass = [[MyClass alloc] init];
unsigned int outCount = 0;
Class cls = myClass.class;
// 类名
NSLog(@"class name: %s", class_getName(cls));
// 父类
NSLog(@"super class name: %s", class_getName(class_getSuperclass(cls)));
NSLog(@"==========================================================");
// 是否是元类
NSLog(@"MyClass is %@ a meta-class", (class_isMetaClass(cls) ? @"" : @"not"));
NSLog(@"==========================================================");
Class meta_class = objc_getMetaClass(class_getName(cls));
NSLog(@"%@ is %@ a meta-class", meta_class,(class_isMetaClass(meta_class) ? @"" : @"not"));
NSLog(@"%s's meta-class is %s", class_getName(cls), class_getName(meta_class));
NSLog(@"==========================================================");
// 变量实例大小
NSLog(@"instance size: %zu", class_getInstanceSize(cls));
NSLog(@"==========================================================");
// 成员变量
Ivar *ivars = class_copyIvarList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSLog(@"instance variable's name: %s at index: %d", ivar_getName(ivar), i);
}
free(ivars);
Ivar string = class_getInstanceVariable(cls, "_string");
if (string != NULL) {
NSLog(@"instance variable %s", ivar_getName(string));
}
NSLog(@"==========================================================");
// 属性操作
objc_property_t * properties = class_copyPropertyList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSLog(@"property's name: %s", property_getName(property));
}
free(properties);
objc_property_t array = class_getProperty(cls, "array");
if (array != NULL) {
NSLog(@"property %s", property_getName(array));
}
NSLog(@"==========================================================");
// 方法操作
Method *methods = class_copyMethodList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[i];
SEL select = method_getName(method);
NSLog(@"Instance method's signature: %s",sel_getName(select));
}
free(methods);
Method method1 = class_getInstanceMethod(cls, @selector(method1));
if (method1 != NULL) {
SEL select = method_getName(method1);
NSLog(@"Instance method signature: %s",sel_getName(select));
}
Method classMethod = class_getClassMethod(cls, @selector(classMethod1));
if (classMethod != NULL) {
SEL select = method_getName(classMethod);
NSLog(@"Class method's signature: %s",sel_getName(select));
}
NSLog(@"MyClass is%@ responsd to selector: method3WithArg1:arg2:", class_respondsToSelector(cls, @selector(method3WithArg1:arg2:)) ? @"" : @" not");
IMP imp = class_getMethodImplementation(cls, @selector(method1));
imp();
NSLog(@"==========================================================");
// 协议
Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &outCount);
Protocol * protocol;
for (int i = 0; i < outCount; i++) {
protocol = protocols[i];
NSLog(@"protocol name: %s", protocol_getName(protocol));
}
NSLog(@"MyClass is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"" : @" not", protocol_getName(protocol));
NSLog(@"==========================================================");
}
打印结果:
class name: MyClass
super class name: NSObject
==========================================================
MyClass is not a meta-class
==========================================================
MyClass is a meta-class
MyClass is meta-class is MyClass
==========================================================
instance size: 48
==========================================================
instance variable is name: _instance1 at index: 0
instance variable is name: _instance2 at index: 1
instance variable is name: _array at index: 2
instance variable is name: _string at index: 3
instance variable is name: _integer at index: 4
instance variable _string
==========================================================
property is name: array
property is name: string
property is name: integer
property array
==========================================================
Instance method is signature: method1
Instance method is signature: method2
Instance method is signature: method3WithArg1:arg2:
Instance method is signature: integer
Instance method is signature: setInteger:
Instance method is signature: setArray:
Instance method is signature: .cxx_destruct
Instance method is signature: string
Instance method is signature: setString:
Instance method is signature: array
Instance method signature: method1
method is signature: classMethod1
MyClass is responsd to selector: method3WithArg1:arg2:
call method method1
==========================================================
protocol name: NSCopying
protocol name: NSCoding
MyClass is responsed to protocol NSCoding
==========================================================
3、动态创建类
#import "ViewController.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation ViewController
void sayFunction(id self, SEL _cmd, id some) {
NSLog(@"%@岁的%@说:%@", object_getIvar(self, class_getInstanceVariable([self class], "_age")),[self valueForKey:@"name"],some);
}
- (void)viewDidLoad {
[super viewDidLoad];
// 动态创建对象 创建一个Person 继承自 NSObject类
Class People = objc_allocateClassPair([NSObject class], "Person", 0);
// 为该类添加NSString *_name成员变量
class_addIvar(People, "_name", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*));
// 为该类添加int _age成员变量
class_addIvar(People, "_age", sizeof(int), sizeof(int), @encode(int));
// 注册方法名为say的方法
SEL s = sel_registerName("say:");
// 为该类增加名为say的方法
class_addMethod(People, s, (IMP)sayFunction, "v@:@");
// 注册该类
objc_registerClassPair(People);
// 创建一个类的实例
id peopleInstance = [[People alloc] init];
// KVC 动态改变 对象peopleInstance 中的实例变量
[peopleInstance setValue:@"苍老师" forKey:@"name"];
// 从类中获取成员变量Ivar
Ivar ageIvar = class_getInstanceVariable(People, "_age");
// 为peopleInstance的成员变量赋值
object_setIvar(peopleInstance, ageIvar, @18);
// 调用 peopleInstance 对象中的 s 方法选择器对于的方法
// objc_msgSend(peopleInstance, s, @"大家好!");
((void (*)(id, SEL, id))objc_msgSend)(peopleInstance, s, @"大家好");
peopleInstance = nil; //当People类或者它的子类的实例还存在,则不能调用objc_disposeClassPair这个方法;因此这里要先销毁实例对象后才能销毁类;
// 销毁类
objc_disposeClassPair(People);
}
打印:
18岁的苍老师说:大家好
PS:
在使用
objc_msgSend(peopleInstance, s, @"大家好!");
默认会出现以下错误:
objc_msgSend()报错
Too many arguments to function call ,expected 0,have3
直接通过objc_msgSend(self, setter, value)是报错,说参数过多。
请这样解决:
Build Setting–> Apple LLVM 7.0 – Preprocessing–> Enable Strict Checking of objc_msgSend Calls 改为 NO
当然你也可以这样写(推荐):
((void (*)(id, SEL, id))objc_msgSend)(peopleInstance, s, @"大家好");
强制转换objc_msgSend函数类型为带三个参数且返回值为void函数,然后才能传三个参数。此实战内容是,动态创建一个类,并创建成员变量和方法,最后赋值成员变量并发送消息。
4、关联对象
NSObject+Associated.h
#import <Foundation/Foundation.h>
typedef void (^CodingCallBack)();
@interface NSObject (Associated)
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) CodingCallBack associatedCallBack;
@end
NSObject+Associated.m
#import "NSObject+Associated.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation NSObject (Associated)
static const char XYAssociateNameKey = '\0';
- (void)setName:(NSString *)name{
// 设置关联对象
objc_setAssociatedObject(self, &XYAssociateNameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, &XYAssociateNameKey);
}
static const char XYAssociateCallBackKey = '\1';
- (void)setAssociatedCallBack:(CodingCallBack)callback {
objc_setAssociatedObject(self, &XYAssociateCallBackKey, callback, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (CodingCallBack)associatedCallBack {
return objc_getAssociatedObject(self, &XYAssociateCallBackKey);
}
@end
ViewController.h
#import "ViewController.h"
#import "NSObject+Associated.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *obj = [[NSObject alloc] init];
obj.name = @"xy";
obj.associatedCallBack = ^(){
NSLog(@"要写代码了!");
};
obj.associatedCallBack();
}
打印:
要写代码了!
5、归档
People.h
#import <Foundation/Foundation.h>
@interface People : NSObject<NSCoding>
@property (nonatomic, copy) NSString *name; // 姓名
@property (nonatomic, strong) NSNumber *age; // 年龄
@property (nonatomic, copy) NSString *occupation; // 职业
@property (nonatomic, copy) NSString *nationality; // 国籍
@end
People.m
#import "People.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation People
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([People class], &count);
for (NSUInteger i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(ivars);
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([People class], &count);
for (NSUInteger i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
@end