类与对象
Class
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class
结构体的指针。
//定义为:
typedef struct objc_class *Class;
在objc/runtime.h
中objc_class结构体定义如下:
Class 是指向类结构体的指针,NSObject 的class 方法就返回这样一个指向其类结构的指针。
truct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
/**
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:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它以让我们
识别出不同类定义版本中实例变量布局的改变。
5.方法链表里面存储的是Method 类型的,Method中含有SEL和IMP.一个方法 Method,其包含一个方
法选标 SEL – 表示该方法的名称,一个types – 表示该方法参数的类型,一个 IMP - 指向该方法
的具体实现的函数指针。
*/
NSArray *array = [[NSArray alloc] init];
其流程是:
1. `[NSArray alloc]`先被执行。因为NSArray没有`+alloc`方法,于是去父类NSObject去查找。
2. 检测NSObject是否响应`+alloc`方法,发现响应,于是检测NSArray类,并根据其所需的内存空间大小开始分配内存空间,然后把`isa`指针指向NSArray类。同时,`+alloc`也被加进cache列表里面。
3. 接着,执行`-init`方法,如果NSArray响应该方法,则直接将其加入`cache`;如果不响应,则去父类查找。
4. 在后期的操作中,如果再以`[[NSArray alloc] init]`这种方式来创建数组,则会直接从cache中取出相应的方法,直接调用。
//objc_object与id
`objc_object`是表示一个类的实例的结构体,它的定义如下(`objc/objc.h`):
//objc
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数据结构。
*/
objc_cache
此处为上面所说的cache的demo:为一个objc_cache结构体的指针
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
/*
mask:一个整数,指定分配的缓存bucket的总数。在方法查找过程中,Objective-C runtime使用这
个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作
(index = (mask & selector))。这可以作为一个简单的hash散列算法。
*/
unsigned int occupied OBJC2_UNAVAILABLE;
//occupied:一个整数,指定实际占用的缓存bucket的总数
Method buckets[1] OBJC2_UNAVAILABLE;
/**
buckets:指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的
是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。
这个数组可能会随着时间而增长。
*/
};
元类(Meta Class)
所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)
NSArray *array = [NSArray array];
+array消息发送给了NSArray类,而这个NSArray也是一个对象。既然是对象,那么它也是一个objc_object指针,它包含一个指向其类的一个isa指针。那么这些就有一个问题了,这个isa指针指向什么呢?为了调用+array方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念
meta-class是一个类对象的类
meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。
因此:objc_class
的isa指向的是元类,objc_object
的isa指向的是实例对象所属的类.当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class
的方法列表中查找。
补充:
meta-class
也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class
的isa指向基类的meta-class
,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class
都使用NSObject的meta-class
作为自己的所属类,而基类的meta-class
的isa指针是指向它自己。这样就形成了一个完美的闭环。因此:
对NSObject继承体系来说,其实例方法对体系中的所有实例、类和meta-class都是有效的;而类方法对于体系内的所有类和meta-class都是有效的
Demo:
void TestMetaClass(id self, SEL _cmd) {
NSLog(@"This objcet is %p", self);
NSLog(@"Class is %@, super class is %@", [self class], [self superclass]);
Class currentClass = [self class];
for (int i = 0; i < 4; i++) {
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = objc_getClass((__bridge void *)currentClass);
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", objc_getClass((__bridge void *)[NSObject class]));
}
#pragma mark -
@implementation Test
- (void)ex_registerClassPair {
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)];
}
@end
//运行结果
2014-10-20 22:57:07.352 mountain[1303:41490] This objcet is 0x7a6e22b0
2014-10-20 22:57:07.353 mountain[1303:41490] Class is TestStringClass, super class is NSError
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 0 times gives 0x7a6e21b0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 1 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 2 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] Following the isa pointer 3 times gives 0x0
2014-10-20 22:57:07.353 mountain[1303:41490] NSObject's class is 0xe10000
2014-10-20 22:57:07.354 mountain[1303:41490] NSObject's meta class is 0x0
//注意:我们在一个类对象调用class方法是无法获取meta-class,它只是返回类而已。
类与对象操作函数
runtime提供的操作类的方法主要就是针对objc_class
结构体中的各个字段的
类名
// 获取类的类名
const char * class_getName ( Class cls );
//如果传入的cls为Nil,则返回一个字字符串。
父类(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
函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。
实例变量大小(instance_size)
// 获取实例大小
size_t class_getInstanceSize ( Class cls );
成员变量(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 );
// 获取整个成员变量列表(所有成员变量包括 属性创建的_成员变量 和直接_成员变量 创建;包括.h和.m中创建的)
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
class_getInstanceVariable
函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。
class_getInstanceVariable
函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。
Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?这时我们就可以使用class_addIvar
函数了。不过需要注意的是,这个方法只能在objc_allocateClassPai
函数与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 );
// 获取属性列表(获取所有属性包括.h和.m中的)
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 );
方法
// 添加方法
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 );
// 获取所有方法的数组(传入类获取所有实例方法包括.h和.m注意还有属性创建的方法以及.cxx_destruct(与dealloc有关))
//(传入元类获取类方法)
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 );
IMP imp;
imp();//即可执行这个方法
我们也可以将IMP转换为函数
void (* myMethodIMP)(id self, SEL _cmd) = (void *)imp;
然后就可以通过执行函数来执行imp
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
一样替代原方法的实现。
class_getMethodImplementation
函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))
更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分
class_respondsToSelector
函数,我们通常使用NSObject类的respondsToSelector:
或instancesRespondToSelector:
方法来达到相同目的。
协议(objc_protocol_list)
// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
class_conformsToProtocol
函数可以使用NSObject类的conformsToProtocol:
方法来替代。
class_copyProtocolList
函数返回的是一个数组,在使用后我们需要使用free()
手动释放。
版本(version)
// 获取版本号
int class_getVersion ( Class cls );
// 设置版本号
void class_setVersion ( Class cls, int version );
其它
Class objc_getFutureClass ( const char *name );
void objc_setFutureClass ( Class cls, const char *name );
//通常我们不直接使用这两个函数
注意:
SEL :Defines an opaque type that represents a method selector 消息
Method:An opaque type that represents a method in a class definition 方法
IMP:A pointer to the start of a method implementation. 实现
实例对象执行方法
动态创建类和对象
动态创建类
// 创建一个新类和元类 我们在创建类时候已经创建了元类
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
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时,我们需要确切的知道我们要用它来做什么。
id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
NSLog(@"%@", [str1 class]);
id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);
//输出结果:
2014-10-23 12:46:50.781 RuntimeTest[4039:89088] NSString
2014-10-23 12:46:50.781 RuntimeTest[4039:89088] __NSCFConstantString
/**
使用class_createInstance函数获取的是NSString实例,而不是类簇中的默认占位符类__NSCFConstantString。
*/
objc_constructInstance
函数:在指定的位置(bytes)创建类实例。
objc_destructInstance
函数:销毁一个类的实例,但不会释放并移除任何与其相关的引用。
实例操作函数
实例操作函数主要是针对我们创建的实例对象的一系列操作函数,我们可以使用这组函数来从实例对象中获取我们想要的一些信息,如实例对象中变量的值。
- 针对整个对象进行操作的函数
// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size );
// 释放指定对象占用的内存
id object_dispose ( id obj );
举个栗子:
//把对象a转换为其子类的对象b
NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
//设置对象所属类
object_setClass(newB, MyClass.class);
object_dispose(a);
- 针对对象实例变量进行操作的函数
// 修改类实例的实例变量的值
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
快
- 针对对象的类进行操作的函数
// 返回给定对象的类名
const char * object_getClassName ( id obj );
// 返回对象的类
Class object_getClass ( id obj );
// 设置对象的类
Class object_setClass ( id obj, Class cls );
获取类定义
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体系的,所以在这些类上调用方法时,都应该先检测一下这个方法是否在这个类中实现。
举个栗子:
int numClasses;
Class * classes = NULL;
numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
classes = malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
NSLog(@"number of classes: %d", numClasses);
for (int i = 0; i < numClasses; i++) {
Class cls = classes[i];
NSLog(@"class name: %s", class_getName(cls));
}
free(classes);
}
//输出结果:
获取类定义的方法有三个:objc_lookUpClass
, objc_getClass
和objc_getRequiredClass
。如果类在运行时未注册,则objc_lookUpClass
会返回nil,而objc_getClass
会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。而objc_getRequiredClass
函数的操作与objc_getClass
相同,只不过如果没有找到类,则会杀死进程。
objc_getMetaClass
函数:如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil。不过,每个类定义都必须有一个有效的元类定义,所以这个函数总是会返回一个元类定义,不管它是否有效。