1 基础篇 类与对象
Class
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:
typedef struct objc_class *Class;
查看objc/runtime.h中objc_class结构体的定义如下:
struct 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;
isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类),
super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。
cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。
NSArray *array = [[NSArray alloc] init];
其流程是:
-
[NSArray alloc]
先被执行。因为NSArray没有+alloc
方法,于是去父类NSObject去查找。 - 检测NSObject是否响应
+alloc
方法,发现响应,于是检测NSArray类,并根据其所需的内存空间大小开始分配内存空间,然后把isa
指针指向NSArray类。同时,+alloc
也被加进cache列表里面。 - 接着,执行
-init
方法,如果NSArray响应该方法,则直接将其加入cache
;如果不响应,则去父类查找。 - 在后期的操作中,如果再以
[[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数据结构。
另外还有我们常见的id,它是一个objc_object结构类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void *指针类型的作用。
alloc和init可以看做是是这样的,alloc可以看做是做了一个初稿,init按照这个初稿完成了一个可以使用的产品(对象)。
alloc分配内存 内部存放定义的属性
init 初始化实例变量 将初始化完成以后的空间首地址返回去
// 创建一个新类和元类
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。然后使用诸如class_addMethod,class_addIvar等函数来为新创建的类添加方法、实例变量和属性等。完成这些后,我们需要调用objc_registerClassPair函数来注册类,之后这个新类就可以在程序中使用了。
实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。
// 创建类实例
id class_createInstance ( Class cls, size_t extraBytes );
// 在指定位置创建类实例
id objc_constructInstance ( Class cls, void *bytes );
// 销毁类实例
void * objc_destructInstance ( id obj );
运行时
运行时在项目中实用的方法不多,我一一列举.
1 对象关联,给UIView分类写一个方法,动态给UIView增加一个点击手势
- (void)setTapActionWithBlock:(void (^)(void))block
{
UITapGestureRecognizer *gesture = objc_getAssociatedObject(self, kDTActionHandlerTapGestureKey);
if (!gesture)
{
gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(__handleActionForTapGesture:)];
[self addGestureRecognizer:gesture];
objc_setAssociatedObject(self, kDTActionHandlerTapGestureKey, gesture, OBJC_ASSOCIATION_RETAIN);
}
objc_setAssociatedObject(self, kDTActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY);
}
//这段代码检测了手势识别的关联对象。如果没有,则创建并建立关联关系。同时,将传入的块对象连接到指定的key上。注意`block`对象的关联内存管理策略。
//手势识别对象需要一个`target`和`action`,所以接下来我们定义处理方法:
//objc
- (void)__handleActionForTapGesture:(UITapGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateRecognized)
{
void(^action)(void) = objc_getAssociatedObject(self, kDTActionHandlerTapBlockKey);
if (action)
{
action();
}
}
}
2 方法交换
/在类被加载到运行时的时候,就会执行,用运行时替换系统的方法.load在编译前执行,别在里面写太多方法,会影响启动速度
+ (void)load {
Method originalMethod = class_getInstanceMethod([self class], @selector(pushViewController:animated:));
Method swizzledMethod = class_getInstanceMethod([self class], @selector(jd_pushViewController:animated:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
3 我用有时候想用系统类时候,想知道系统类有什么东东,可以用下面三个方法,把类的属性方法都打印出来.
//获取一个类所有属性
id LenderClass = objc_getClass("UISwipeActionPullView");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
//获取一个类所有ivar
unsigned int count = 0;
Ivar *members = class_copyIvarList(LenderClass, &count);
for (int i = 0; i < count; i++)
{
Ivar var = members[i];
const char *memberAddress = ivar_getName(var);
const char *memberType = ivar_getTypeEncoding(var);
NSLog(@"address = %s ; type = %s",memberAddress,memberType);
}
//获取一个类所有对象方法
unsigned int count = 0;
Method *memberFuncs = class_copyMethodList(LenderClass, &count);
for (int i = 0; i < count; i++)
{
SEL address = method_getName(memberFuncs[i]);
NSString *methodName = [NSString stringWithCString:sel_getName(address) encoding:NSUTF8StringEncoding];
NSLog(@"member method : %@",methodName);
}