iOS程序员

iOS中的NSObject*、id和instancetype

2018-10-17  本文已影响111人  ChinaChong

id修饰和NSObject *修饰有何不同?

要详细了解两者的不同,需要先说一说Objective-C中的动态类型和静态类型。

// 动态类型
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;

下面是我们用来测试的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方法。

接下来我们在ViewController中运行以下测试NSObject *修饰时的代码:

编译无法通过,报错:NSObject中没有声明run方法。

idinstancetype的区别与联系

idinstancetype的区别要从关联返回类型和非关联返回类型说起:

根据Cocoa的命名规则,满足下述规则的方法:
 1. 类方法中,以allocnew开头
 2. 实例方法中,以autoreleaseinitretainself开头
当方法返回值为id类型时,编译器不会返回一个类型不明的对象,会返回一个方法所在类类型的对象,这些方法就被称为是关联返回类型的方法。换句话说,这些方法的返回结果以方法所在的类为类型。

与关联返回类型相反
 1. 类方法中,不以allocnew开头
 2. 实例方法中,不以autoreleaseinitretainself开头
当方法返回值为id类型时,编译器会返回一个类型不明的对象,即id类型的对象。

此处划重点!!!此处划重点!!!此处划重点!!!

 许多网上的资料并没有拿出证据来证明上述结论的正确性。
一、比如当我们自己的类方法以alloc等开头后,怎么证明它是关联返回类型,也就是说怎么证明返回值是方法所在类的类型
二、我们的类方法不以alloc等开头,怎么证明它是非关联返回类型,也就是说怎么证明返回值不是方法所在类的类型

以下是我给出的证明:

// 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类型,按照结论:



我证明结论的思路是,既然返回值都是id类型,那么如果我们利用调用实例方法来证明的话,在编译期调用类所属的实例方法是不会报错的,因为id是动态类型,在编译期不会判断其真实类型,程序启动运行后,由于返回的的确是TestObject对象,所以我们不论是在编译期还是运行时都看不出其返回值到底是不是如结论所说的TestObject对象。因此,我们可以通过调用实例对象的属性来证明,id类型可以接收任何消息,但是却无法使用点语法获取属性。若返回值是TestObject对象,那么使用点语法便不会报错,如果不是TestObject对象,使用点语法获取属性就一定会报错。


代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [[TestObject allocTestObject] smallMethod];
    [[TestObject newTestObject] smallMethod];
    [[TestObject fuckTestObject] smallMethod];
    
    
    NSString *temp;
    
    temp = [TestObject allocTestObject].name;
    temp = [TestObject newTestObject].name;
    temp = [TestObject fuckTestObject].name;
    
}

如图所示,所有返回值调用方法都可以通过编译,因为返回的都是id类型。但是使用点语法时fuckTestObject的返回值报错,因为此方法的返回值不是关联返回类型,返回的不是TestObject类型,而是id类型。因此证明结论是正确的。

说完了关联返回类型和非关联返回类型,该说说instancetype

instancetype其实就是为了扩大关联返回类型的范围,让不是以上述关键字开头的方法的返回值也是关联返回类型。

我们将代码稍作调整:

// TestObject.h
+ (instancetype)fuckTestObject;
// TestObject.m
+ (instancetype)fuckTestObject {
    return [[TestObject alloc] init];
}

当我们把fuckTestObject方法的返回值类型从id换成instancetype后,编译就通过了,也就是说返回值是关联返回类型了。

总结一下区别与联系:

参考资料

卡布达巨人--iOS开发笔记(六):Objective-C动态特性
imy博--OC中的id类型
Mattt--instancetype

上一篇 下一篇

猜你喜欢

热点阅读