用id修饰和NSObject *修饰有何不同?
OC中的动态类型和静态类型
- 动态类型
动态类型指的是对象指针类型的动态性,具体指使用id修饰后将对象的类型确定推迟到运行时,
由赋给它的对象类型决定对象指针的类型,也就是说id修饰的对象为动态类型对象,
其他在编译器指明类型的为静态类型对象,通常如果不需要涉及到多态的话,还是尽量使用静态类型(原因:
错误可以在编译器提前查出,可读性好).
// 动态类型
id obj = [[TestObject alloc] init]
- 静态类型
一个指针变量指向特定类的对象时,使用的是静态类型,在编译的时候就知道这个
指针变量所属的类, 使用静态类型时, 编译器在编译期间,会做许多的类型检测
因为编译器需要知道哪个对象该如何使用.
// 静态类型
TestObject obj = [[TestObject alloc] init];
苹果官方objc.h文件里有关于Id的定义
// A pointer to an instance of a class
typedef struct objc_object *id;
id可以 用于指向所有的Objective-C对象,是一种万能指针,类似于C语言中的void *
与id类似 NSObject *可以指向所有继承自NSObject的对象,因为在Objective-C中
NSObject是基类, 绝大多数的类继承自NSObject, 因此根据面向对象编程的多态特性
NSObject (可以指向Objective-C中绝大多数的类实例对象
下面测试TestObject类的源码:
// TestObject.h
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
- (void)run;
@end
// TestObject.m
#import "TestObject.h"
@implementation TestObject
- (void)run {
NSLog(@"%s",__func__);
}
@end
接下来我们在ViewController中运行以下测试id修饰时的代码:
- (void)viewDidLoad {
[super viewDidLoad];
id p1 = [[TestObject alloc] init];
[p1 run];
}
运行情况是,编译通过,并且p1成功调用了run方法
如果将id改为NSObject *修饰的代码如下:
编译无法通过,报错:NSObject中没有声明run方法。
-
使用id修饰的对象是动态类型,编译器在编译期不会去判断其真实类型,因此id指向的对象不管向其发送任何消息,编译器在编译期都不会有任何报错
-
使用NSObject *修饰的对象是静态类型,在编译期就已经明确该对象是NSObject对象,因此当我们对该对象发送NSObject没有声明的方法时,编译器就会果断报错
id和instancetype的区别与联系
id和instancetype的区别要从关联返回类型和非关联返回类型说起
- 关联返回类型
1 类方法中,以alloc或new开头
2 实例方法中,以autorelease, init,retain或self开头
当方法会话值为id类型时,编译器不会返回一个类型不明的对象,会返回一个方法所在类类型的对象
这些方法就被称为是关联对象返回类型的方法,换句话说,这些方法的返回结果以方法所在的
类为类型 - 非关联返回类型
与关联返回类型相反
1 类方法中,不以alloc或new开头
2 实例方法中,不以autorelease, init, retain或self开头
当方法返回值为id类型时, 编译器会返回一个类型不明的对象,即id类型的对象
// TestObject.h
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
+ (id)newTestObject;
+ (id)allocTestObject;
+ (id)fuckTestObject;
- (void)smallMethod;
@property (nonatomic,copy) NSString *name;
@end
// TestObject.m
#import "TestObject.h"
@implementation TestObject
+ (id)newTestObject {
return [[TestObject alloc] init];
}
+ (id)allocTestObject {
return [[TestObject alloc] init];
}
+ (id)fuckTestObject {
return [[TestObject alloc] init];
}
- (void)smallMethod {
NSLog(@"%s",__func__);
}
@end
如图所示,所有返回值调用方法都可以通过编译, 因为返回的都是id类型,但是使用点语法时,fuckTestObject
的返回值报错, 因为此方法的返回值不是关联返回类型,返回的不是TestObject类型,而是id类型,
instancetype其实就是为了扩大关联返回类型的范围,让不是以上述关键字开头的方法的返回值也是关联返回类型
+ (instancetype)fuckTestObject {
return [[TestObject alloc] init];
}
当我们把fuckTestObject方法的返回值类型从id换成instancetype后,编译就通过了,也就是说返回值是关联返回类型了。
总结一下区别与联系:
id和instancetype都可以做方法的返回值。
id类型的返回值在编译期不能判断对象的真实类型,即非关联返回类型,instancetype类型的返回值在编译期可以判断对象的真实类型,即关联返回类型。
id可以用来定义变量, 可以作为返回值, 可以作为形参,instancetype只能用于作为返回值。