Objective-C中的 id, isa,和 instancetype
Posted __Sunshine_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Objective-C中的 id, isa,和 instancetype相关的知识,希望对你有一定的参考价值。
(1)id在动态类型的应用
在Objective-C Runtime Reference中,对id是这么定义和声明的:
id
A pointer to an instance of a class.
Declaration
typedef struct objc_object *id;
从定义来看,id是一个指向某个类的实例的指针。从声明来看,id是一个类型的别名,这个类型是“struct objc_object *”,也就是代表了指向一个objc_object结构体的指针。
id是一种通用的对象类型,可以用来存储任何类型的对象。也可以理解为“万能指针”。下面是《Programming in Objective-C》对id的作用的描述:
“That is, id can be used for storing objects that belong to any class. That real power of this data type is exploited when it’s used this way to store different types of objects in a variable during the execution of a program”
也就是,id可用于动态类型(dynamic typing)和动态绑定(dynamic binding)。例子如下:
// Animal.h
#import <Foundation/Foundation.h>
@interface Animal : NSObject
-(void)eat;
@end
// Animal.m
#import "Animal.h"
@implementation Animal
-(void)eat
NSLog(@"animal eat!");
@end
// Person.h
#import "Animal.h"
@interface Person : Animal // Person类继承了Animal类
@end
// Person.m
#import "Person.h"
-(void)eat
NSLog(@"person eat!"); // 重写了Animal类中的eat方法
@end
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[])
@autoreleasepool
id anInstance; //此时 anInstance 对象为 id类型
Animal *animal = [[Animal alloc] init];
Person *person = [[Person alloc] init];
anInstance = animal; //此时 anInstance 对象存储了 Animal类型的实例 animal
[anInstance eat]; //调用的是 Animal类的对象方法
anInstance = person; //此时 anInstance 对象存储了 Person类型的实例 person
[anInstance eat]; //调用的是 Person类的对象方法
return 0;
//运行结果为:
要注意的是,虽然id的动态类型和动态绑定功能(与动态类型相对的是静态类型(static typing))比较强大,但不要滥用,因为:
1)用静态类型可以让编译器尽量在编译阶段检测错误。
2)动态类型的错误只能在运行时(runtime)检测出来,因为编译器不检测id所存储的对象的类型,即使调用了该对象没有的方法也不会在编译阶段检测出来,这对于产品的制造是不利的。
3)静态类型可让代码更好理解。如 Person *p 比 id *p 更能让人明白 p的意义。
在C语言中,对于typedef有个这样的例子:
typedef struct tnode *Treeptr; //此时定义Treeptre为指向tnode结构体的指针
typedef struct tnode
char *word;
int count;
Treeptr left;
Treeptr right;
Treenode; //定义Treenode为tnode结构体类型
Treeptr talloc(void)
return (Treeptr) malloc(sizeof(Treenode)); //申请一块内存,大小如Treenode结构体总大小,返回为指向它的Treeptr型指针
如上Treeptr为指向结构体的指针,所以定义变量或者强制类型转换的时候不用再在其后加星号”“。同理,id在定义对象的时候不需要在变量名之前加”“。
(2)isa
再回头看id的声明:
Declaration
typedef struct objc_object *id;
那这里的objc_object又是什么东西?在Objective-C Runtime Reference中,对objc_object也有定义和声明如下:
objc_object
Represents an instance of a class.
Declaration
struct objc_object Class isa; ;
Fields
isa
A pointer to the class definition of which this object is an instance.
从定义上看objc_object就是某个类的实例,亦即OC中的对象。从声明上看,它属于C语言中的结构体,并且第一个成员是Class型的isa。
那什么是Class?
在objc.h中:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
所以Class又是一个指向objc_class结构体的指针。
而objc_class结构体在 runtime.h 定义如下:
struct objc_class
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
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;
因为OC中一切对象,类也是Class的对象,所以objc_object对象中的isa指针指向的是自身所属的类objc_class,而objc_class中的isa指针又指向自身所属的类,所以可以按以下示意图这么理解OC中的类、对象、指针,但是这个类的isa指向的类是什么类?
原来Class对象所属的类,称为Meta Class,Class中的isa指针指向就是它的Meta Class。而这个Meta Class本身也是个Class,也有自己的isa和super_class指针,所以有更为复杂的示意图:
(上图及其解释可见:http://chun.tips/blog/2014/11/05/bao-gen-wen-di-objective%5Bnil%5Dc-runtime-(2)%5Bnil%5D-object-and-class-and-meta-class/,黑白示意图可见:http://blog.devtang.com/blog/2013/10/15/objective-c-object-model/)
下面修改自一道题:
Person *person = [Person new]; //用Person类创建对象
BOOL is0 = [person isKindOfClass:[Person class]];
BOOL is1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL is2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL is3 = [[Person class] isKindOfClass:[Person class]];
BOOL is4 = [[Person class] isMemberOfClass:[Person class]];
NSLog(@"\\n%d \\n%d \\n%d \\n%d \\n%d", is0, is1, is2, is3, is4); //输出以上判断结果
NSLog(@"%@, %@, %@", [NSObject class], [Person class], [person class]);
//运行结果是:
结果显示,is0为1,表示person 对象是Person类或其子类(这里Person类没有子类)的实例,is2、is3、is4为0分别表示NSOject类对象、Person类对象、Person类对象不属于NSOject类、Person类或其子类、Person类的对象的实例,也还好理解。但is1表示NSObject类对象是NSObject类或其子类的实例这又该怎么理解呢?还有个小问题是最后的NSLog输出显示[Person class]和[person class]的结果为什么都是Person呢?
下载objc源代码,在Object.m中:
- class
return (id)isa;
//对于对象来说,发送class消息,即调用对象方法class,返回的是isa指针
+ class
return self;
//对于类来说,发送class消息,即调用类方法class,只是返回自身
所以也就是位什么[Person class]与[person class]的结果相等了。
- (BOOL)isKindOf:aClass
register Class cls;
for (cls = isa; cls; cls = class_getSuperclass(cls))
if (cls == (Class)aClass)
return YES;
return NO;
//将该对象的isa指针与参数aClass对比,否则取isa的父类再与aClass对比,直到两者相等或者cls为nil就退出循环
- (BOOL)isMemberOf:aClass
return isa == (Class)aClass;
//只将该对象的isa指针与参数aClass对比
理解这几种方法的原理,上题就可以理解了:
BOOL is0 = [person isKindOfClass:[Person class]];
person中isa指针指向的是Person类,[Person class]的结果也是Person类,所以用isKindOfClass可以在第一次比较就得到YES。is0为1。
BOOL is1 = [[NSObject class] isKindOfClass:[NSObject class]];
[NSObject class]的结果是NSObject类,NSObject中的isa指针指向的是NSObject的Meta Class,所以在第一次比较中,cls作为NSObject的Meta Class,与NSObject类相比较,结果是不相等。所以取cls的父类,也就是NSObject类,再与[NSObject class]得到的NSObject类相比较,结果是相等,所以返回YES,退出循环。Is1为1.
BOOL is2 = [[NSObject class] isMemberOfClass:[NSObject class]];
只将NSObject中的isa指针指向的NSObject的Meta Class,与[NSObject class]得到的NSObject类比较一次,结果是不相等,返回NO。is2为0.
BOOL is3 = [[Person class] isKindOfClass:[Person class]];
[Person class]的结果是Person类,Person中的isa指针指向的是Person的Meta Class,所以在第一次比较中,cls作为Person的Meta Class,与Person类相比较,结果是不相等。所以取cls的父类,也就是NSObject的Meta Class,再与[Person class]得到的Person类相比较,结果仍不相等,再取NSObject的Meta Class的父类,也就是NSObject类,与Person类相比较,仍不相等,再取NSObject类的父类,也就是nil,此时cls已经不符合循环的条件了,退出循环,返回NO。Is3为0.
BOOL is4 = [[Person class] isMemberOfClass:[Person class]];
只将Person中的isa指针指向的Person的Meta Class,与[Person class]得到的Person类比较一次,结果是不相等,返回NO。is2为0.
(3)instancetype :
对于初始化方法:
-(instancetype) init;
“The special type instancetype indicates that the return type from the init method will be the same class as the type of object it is initializing (that is, the receiver of the init message) “
也就是instancetype 关键字的使用,会让编译器默认该init 函数的返回值与调用该函数的对象的类型一致。
// Person.h
@interface Person : NSObject
NSString *name;
@property int age;
@end
// Person.m
#import "Person.h"
@implementation Person
-(instancetype) init // 重写 init 方法
self = [super init];
if (self)
name = @"Jack";
return self; // 返回自身
@end
返回类型若跟其 init 消息的接收者的类型不同,则报警告!
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[])
@autoreleasepool
Person *p = [[Person alloc] init];
return 0;
对Person类对象发送alloc消息,生成一个Person型的实例对象,再对该实例对象发送init消息,编译器会认为返回值的类型也应该是Person型,所以赋值表达式左边的指针类型也应该是Person型的。如果指针p不是Person型的,比如是NSString型的,系统就会报警告(warning),但直到要用该指针去调用Person的实例变量或者方法时才会报出错(error):
ios5之前的版本是用id做返回值类型,之后很多函数的返回值类型改为instancetype,这样有利于编译器检查类型错误,使得不能随意改写函数的返回值类型。
(PS.不推荐重写alloc方法,因为涉及内存的物理分配)
下面小小的总结下 id 与 instancetype 的异同:
以上是关于Objective-C中的 id, isa,和 instancetype的主要内容,如果未能解决你的问题,请参考以下文章