1.对象模型
Objective-C是一门面向对象的语言,对象是我们编程的基本单元,所有的操作都是通过对象。对象其实是对 数据 和 行为 的封装。在OC中,数据的载体就是实例变量,我们可以通过属性便捷的访问到实例变量,行为其实就是对象的方法,也可以称为发消息,方法内部可以传递数据和操作数据。
平时我们声明一个对象,例如:NSString *string = @"zzy";
这条语句的意思是,创建了一个NSString
类型的对象实例,实例的内容是zzy
,并返回这个实例的内存地址给string
这个变量保存,之后我们就可以通过变量string
来操作这个实例。学过C语言的都知道,其实*string
就是指这是一个NSString
类型的指针,所以OC对象的本质其实就是指向某块内存地址的指针。
对象的结构
在OC中每个对象都是一个类的实例,对象的结构如下:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
可以看到对象是一个结构体,结构体当中有一个Class类型的成员变量isa。Class的定义为typedef struct objc_class *Class;,这是一个指向objc_class结构体的一个结构体指针。而objc_class其实就是对象所属类的原型:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
可以看到Class
中也存放了一个isa
指针,所以Class
也是一个对象,被称为类对象。同时Class
结构体当中还存着:指向父类的super_class
指针、类名、版本、实例对象的大小、实例变量列表、方法列表、缓存、协议等信息。通过类创建出来的实例对象所拥有的属性,方法,协议等都是存储在类结构体当中。
实例对象的isa
指针指向其所属的类,类结构体当中存放着对象的属性和方法列表。类对象的isa
指针指向其所属的元类,元类中存放着类对象的方法列表(类方法),而元类内部也有一个isa
指针,指向的是基类的元类,而基类的元类的isa
指针指向自身。用一个经典的图类表示整个关系链路:
不能向编译后得到的类中增加实例变量:因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量。
运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,否则类一旦注册到runtime中后就不能改变实例变量了。
2. 属性
对象中的数据是通过实例变量来保存的,OC提供了一种便捷的访问实例变量的方式:属性。属性的本质就是包括实例变量 + setter方法 + getter方法
。实例变量的值保存在对象内部,通过“偏移量(offset)”来保存,即:该变量距离对象内存区域的起始地址的距离,这个是通过硬编码来标识的。当我们声明一个属性之后,系统会自动帮我们生成一个成员变量和属性,这个成员变量和属性的描述(类型,名称等)分别存放在类的ivar_list
和property_list
当中,并且生成setter
方法和getter
方法追加到method_list
当中,然后计算属性在对象内部的偏移量,并实现setter
方法和getter
方法。
声明属性的方式:@property (nonatomic, readwrite, copy) NSString *name;
这个时候编译器会自动帮我们合成实例变量_name
。默认的实例变量名为属性名前面加下划线。如果不想用系统默认的实例变量名,也可以通过@synthesize
关键字手动指定实例变量名,例如@synthesize name = _myName;
那么我们的实例变量就变成了_myName
,不过一般为了规范没有人这么做。
如果不想让系统自动帮我们合成实例变量和setter
、getter
方法,也可以通过关键字@dynamic
声明(例如:@dynamic name;
),然后自己手动去合成实例变量和相关存取方法。如果没有实现这些合成方法的话,那么是无法正常使用属性的。
目前使用@synthesize
的一般场景:
当我们手动实现了
setter
和getter
方法的时候,系统默认我们自己管理属性,所以就不再帮我们合成实例变量了,这个时候需要用@synthesize
手动合成实例变量,不然编译器就会报错。当重写父类属性的时候,在子类中需要用
@synthesize
手动合成实例变量,否则无法使用。使用了
@dynamic
的时候。
属性包含的三种语义:
atomic && nonatomic :原子性 && 非原子性
在声明属性的时候,编译器默认的属性语义是atomic
原子性的。atomic
和nonatomic
的区别是前者在setter方法
赋值的时候会进行加锁保证赋值过程的完整性(安全性),而后者不会使用同步锁。在iOS中,由于频繁加锁会导致性能问题,而且即使采用了atomic
,在多线程操作的情况下,也并不能保证线程的安全性。如果要保证线程安全,需要专门进行其他加锁机制处理。所以一般情况下,我们平时声明属性用的都是nonatomic
。
readwrite && readonly :可读可写 && 只读
readwrite
表示属性可读可写。readonly
表示属性只能读取不能修改,一般用于在.h
中对外暴露的属性不想被别人修改时这么声明,然后在.m的extension
中再重新定义为可读可写。编译器默认的属性语义为readwrite
。
内存管理语义:strong、weak、copy、assign、unsafe_unretained
-
strong
表示对属性所指对象的一种强引用的关系。当声明为strong
时,在setter
方法中会先持有新值,再释放旧值,然后再把新值赋值给实例变量。
eg:
@property (nonatomic, strong) NSMutableArray *array;
- (void)setArray:(NSMutableArray *)array {
// [array retain];
// [_array release];
_array = array;
}
-
weak
表示对属性所指对象的一种弱引用的关系。当声明为weak
时,在setter
方法中即不会持有新值,也不会释放旧值,只是进行一次简单的赋值操作。但是当属性所指的对象被销毁时,属性值会自动置为nil 比较安全。
eg:
@property (nonatomic, weak) NSMutableArray *array;
- (void)setArray:(NSMutableArray *)array {
_array = array;
}
-
copy
类似于strong
,也表示对属性所指对象的一种强引用的关系,不同的是,copy
语义在setter
方法中并不是直接持有新值,而是会拷贝出一份不可变的副本持有,然后再赋值给实例变量。
eg:
@property (nonatomic, copy) NSString *name;
- (void)setName:(NSString *)name {
NSString *copyName = [name copy];
//[_name release];
_name = copyName;
//[copyName release]
}
copy
语义一般是用于那些具有可变子类的类型如:NSArray
、NSDictionary
、NSString
等。这些类族都有其对应的可变子类,如果声明一个不可变的NSString
类型的属性,由于父类指针可以指向子类对象,在给属性赋值的时候,传递给setter
方法的值有可能是一个可变子类NSMutableString
的实例对象,那么在该属性值赋值完成后,由于属性所指的对象其实是个可变的字符串,就有可能被外界所篡改。所以为了保证属性的安全,在赋值的时候需要先copy出一份不可变的对象,然后再赋值。
assign
表示在setter
方法赋值时只会进行简单的赋值操作,只用于修饰基本类型的数据(例如NSInteger、CGFloat等)。unsafe_unretained
语义类似于assign
,不同的是它用于修饰对象类型。如同它的字面意义一样,它不会持有属性所指的对象(类似于weak
),但是当属性所指的对象被销毁时,属性值不会自动置为nil,所以它并不安全,可能会导致野指针。
3. 方法
在OC中,方法又被称为发消息,对象需要调用方法来传递数据,而方法是什么呢?例如一个方法:[self doSomething:@"something"];
,编译器会将这个方法转为如下的C语言函数:
objc_msgSend(self, @selector(doSomething:), @"something");
objc_msgSend(id self, SEL cmd, ...)
这个函数就是OC消息传递的核心函数。我们平时调用的方法最终都会转为这个函数调用。这个函数接受两个及以上的参数,分别是:消息的接收者、消息的签名selector、消息的参数。消息的接收者就是该方法的调用者,消息的签名是一个SEL类型的数据,可以理解为方法名的包装,参数就是调用方法所传递的参数,按顺序传入。
当调用这个函数后,objc_msgSend
会根据当前消息接收者的isa
指针找到其所属的类,然后在类的方法列表中根据selector
的名称找到对应的方法并执行,同时会将查找的结果缓存起来以供下次查找时快速的执行。如果找不到,就会根据super_class
指针沿着继承体系一直往上查找,直到找到合适的方法之后跳转到方法的实现并执行。如果直到找到基类还是找不到对应方法的话,那就开始执行消息转发机制。如果消息转发的过程中也没有找到的话,那就抛出异常程序终止。异常信息是常见的unrecognized selector send to instace xxxx
消息转发
上面在介绍方法调用的实现流程时说到了消息转发,消息转发是发生在当给一个对象发送没有实现的方法时会启动消息转发。例如我们给一个对象发送没有实现的消息:
[obj performSelector:@selector(doWork:array:) withObject:@"somthing" withObject:@[@"work"]];
此时消息转发一共分为三个阶段:
第一个阶段:动态方法解析
runtime会先询问对象所属的类,看其能否动态的添加方法以处理当前未处理的消息。处理的方法有两个:
实例方法:+ (BOOL)resolveInstanceMethod:(SEL)sel
类方法:+ (BOOL)resolveClassMethod:(SEL)sel
方法的参数就是当前消息的selector
,返回值是来标识当前的类是否能动态的添加方法来处理这个selector
eg:
@interface MessageInvokeObj : NSObject
@end
@implementation MessageInvokeObj
void doWork(id self, SEL cmd, NSString *work, NSArray *array) {
NSLog(@"work = %@, array = %@", work, array);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"doWork:array:"]) {
class_addMethod(self, sel, (IMP)doWork, "v@:@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
通过class_addMethod
函数动态的添加一个方法来处理当前的消息。此时动态添加方法成功,消息转发结束。
第二个阶段:备援接收者
如果第一个阶段无法处理该消息的话,runtime会询问当前的类能否把这个消息转发给其他对象去处理,如果可以的话,就返回这个对象,否则就返回nil。
处理方法:- (id)forwardingTargetForSelector:(SEL)aSelector
eg:
@interface InvokeMethodObj : NSObject
@end
@implementation InvokeMethodObj
- (void)doWork:(NSString *)work array:(NSArray *)array {
NSLog(@"work = %@, array = %@", work, array);
}
@end
@interface MessageInvokeObj : NSObject
@end
@implementation MessageInvokeObj
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *selString = NSStringFromSelector(aSelector);
if ([selString isEqualToString:@"doWork:array:"]) {
InvokeMethodObj *obj = [[InvokeMethodObj alloc] init];
return obj;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
第三个阶段: forwardInvocation
最后一个阶段,runtime会将这个消息的所有信息封装到NSInvocation
对象当中,包括消息的接收者target
、消息的selector
以及消息的所有参数。最后一次询问接收者能否处理,如果不能将抛出异常。如果可以,直接把消息指派给目标对象。
先返回正确的方法签名:- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
然后执行消息转发:- (void)forwardInvocation:(NSInvocation *)anInvocation
eg:
@interface MessageInvokeObj : NSObject
@end
@implementation MessageInvokeObj
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSString *selString = NSStringFromSelector(aSelector);
if ([selString isEqualToString:@"doWork:array:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
InvokeMethodObj *obj = [[InvokeMethodObj alloc] init];
if ([obj respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:obj];
}
}
@end
OC对象模型大概就是这些,要想更深入的了解,需要读下runtime的源码。下一篇写下OC的内存管理...