美文网首页iOS程序员
iOS中的NSObject*、id和instancetype

iOS中的NSObject*、id和instancetype

作者: ChinaChong | 来源:发表于2018-10-17 10:04 被阅读111次

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

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

    • 动态类型
      动态类型指的是对象指针类型的动态性,具体是指使用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方法。

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

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

    • 使用id修饰的对象是动态类型,编译器在编译期不会去判断其真实类型,因此id指向的对象不管向其发送任何消息,编译器在编译期都不会有任何报错

    • 使用NSObject *修饰的对象是静态类型,在编译期就已经明确该对象是NSObject对象,因此当我们对该对象发送NSObject没有声明的方法时,编译器就会果断报错

    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)newTestObject
      new开头的类方法,按照结论应该为关联返回类型,即返回值是TestObject类型
    • + (id)allocTestObject
      alloc开头的类方法,按照结论应该为关联返回类型,即返回值是TestObject类型
    • + (id)fuckTestObject
      不以alloc等开头的类方法,按照结论应该为非关联返回类型,即返回值是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后,编译就通过了,也就是说返回值是关联返回类型了。

    总结一下区别与联系:

    • idinstancetype都可以做方法的返回值。

    • id类型的返回值在编译期不能判断对象的真实类型,即非关联返回类型,instancetype类型的返回值在编译期可以判断对象的真实类型,即关联返回类型。

    • id可以用来定义变量, 可以作为返回值, 可以作为形参,instancetype只能用于作为返回值。

    参考资料

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

    相关文章

      网友评论

        本文标题:iOS中的NSObject*、id和instancetype

        本文链接:https://www.haomeiwen.com/subject/bhawgftx.html