[Objective-C] id类型和instancetype类型
前些时间在源码里看到instancetype返回类型,一脸惊异,表示接触iOS不久没见过这东西,但发现跟id功能差不多。故查了一些资料,了解了两者之间的区别,故将资料简单翻译整理了一下,为博客充一个数 : )
转载保留原链接哦原文地址
id类型
id
数据类型可以存储任何类型的对象。可以说,它是一般对象类型。
例如可以声明一个为id
类型的变量:
id graphicObject
也可声明方法使其具有id类型的返回值:
- (id)newObject:(int)type;
id
类型是Objective-C中十分重要的特性,它是多态和动态绑定的基础。
instancetype类型
instancetype
是clang3.5开始提供的一个关键字,表示一个未知的Objective-C对象,类似于id
按照Cocoa的惯例,Objective-C里所有使用init
,alloc
等名称的方法都会返回一个接受类类型的实例。这些方法被称为“有一个关联的返回类型”的方法,也就是说发给这些方法中的任意一个的消息都会返回一个以相同的静态类型代替接收类类型的一个实例,例如:
@interface NSObject
+ (id)alloc;
- (id)init;
@end
@interface NSArray : NSObject
@end
和下面的通用初始化代码:
NSArray *array = [[NSArray alloc] init];
该表达式[NSArray alloc]
是NSArray *
类型,因为alloc
拥有一个隐式的关联的返回类型。类似的,表达式[[NSArray alloc] init]
也是NSArray *
类型,因为init
的返回类型也是一个关联的返回类型,同时也知道它的接收器有一个NSArray *
的类型。如果alloc
和init
都没有一个关联的返回类型,表达式就会返回一个id
类型,如同方法签名里声明的一样。
iOS 8 里很多以前返回
id
的方法现在都改为了instancetype
,甚至init
和alloc
。另外考虑兼容swift,还是用instancetype
好
可以通过声明instancetype
类型作为一个拥有关联类型的方法的返回类型。instancetype
这个上下文关键字只允许用在Objective-C方法的返回类型中。例如:
注意只能用在Objective-C的方法中,变量不行的哦。常见于构造方法。
@implementation oneObject
+ (instancetype)initOneObject {
oneObject *obj = [oneObject new];
return oneObject;
}
@end
一个关联返回类型也可以通过一些方法推断出来。要确定一个方法是否有一个可以被推断出的关联的返回类型,首先要参考驼峰命名法命名的selector
中的第一个单词(如initWithObjects
中的init
),其次要看其返回类型与自己的类的类型是否兼容,并且:
- 第一个单词是
alloc
或new
,并且方法是一个类方法(+
开头) - 第一个单词是
autorelease
,init
,retain
或者self
,且方法是一个实例方法(-
开头)
如果一个拥有关联返回类型的方法被子类方法复写了,那么子类方法必须返回一个与子类类型兼容的类型。比如:
@interface NSString : NSObject
- (NSUnrelated *)init; // incorrect usage: NSUnrelated is not NSString or a superclass of NSString
@end
关联的返回类型只会影响发送的消息的类型或者通过指定方法访问属性的类型。在其他方面,拥有关联返回类型的方法与返回id
类型的方法是一致的。
用instancetype代替id有什么好处?
用instancetype
可以给自定义方法一个类似alloc/init
的行为,这个主要方便于构造函数
当使用id
时,本质上不会有任何类型检查。使用instancetype
,编译器和IDE知道返回的是什么类型的东西,并且更好地检查你的代码和自动补全代码。举个例子:
//Class A
@interface ClassA : NSObject
- (id)methodA;
- (instancetype)methodB;
@end
//Class B
@interface ClassB : NSObject
- (id)methodX;
@end
//Main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
//这一行编译器不会产生报错,因为methodA方法返回的是id。但在运行时会出现异常
[[[[ClassA alloc] init] methodA] methodX];
//这一行不会通过编译器的检查,错误为"No visible @interface ClassA declares selector methodX",因为methodB返回instancetype,即接收器的类型。
[[[[ClassA alloc] init] methodB] methodX];
}
return 0;
}
也可以说,在所有可以使用instancetype
的情形中都有其好处。在详细解释之前,先声明:在一个类返回一个与自己类型一致的实例时,就适合使用instancetype
。
实际上,Apple对于这个主题是这么解释的:
在你的代码中,在合适的地方用返回类型
instancetype
代替id
类型。这通常出现在init
方法和类的工厂方法。即使编译器会自动的把以init
,alloc
和new
开头和返回类型为id
的方法转换成返回instancetype
类型,除此之外它并不会转换其他方法。Objectice-C 明确约定对所有方法都写instancetype
。来源Adopting Modern Objective-C
下面继续,首先看几个定义:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; //initializer
+ (id)fooWithBar:(NSInteger)bar; //class factory
@end
对于一个类工厂方法,你应该总是使用instancetype
类型。编译器不会自动将id
转换为instancetype
。这个id
是一个通用对象。不过你一旦将其改为instancetype
,编译器就知道这个方法返回的是一个什么类型的对象。
这并不是一个学术问题。举例来说,以前在Mac OS下[[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]
会产生如下错误:Multiple methods named 'writeData:' found with mismatched result, parameter type or attributes。原因就在于NSFileHandle
和NSURLHandle
都提供一个writeData:
方法。由于[NSFileHandle fileHandleWithStandardOutput]
返回的是id
,编译器就不确定writeData:
是哪个类调用的。
解决这个问题就需要做下列方法中的一个:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
或者
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
当然,更好的方法就是声明fileHandleWithStandardOutput
的返回类型为instancetype
。这样,就不需要声明类型或赋值了。
现在版本的O-C源码里对上述例子有了修改:
+ (NSFileHandle *)fileHandleWithStandardOutput;
,所以不会有报错。不过,还是有其他例子存在,比如length
方法,在UILayoutSupport
中返回CGFloat
,在NSString
里返回NSUInteger
对于初始化器,这个就更加复杂了。当你写出如下代码:
- (id)initWithBar:(NSInteger)bar
编译器就会假设你输入的是这样的代码
- (instancetype)initWithBar:(NSInteger)bar
这对于ARC来说很重要。见前面instancetype的定义。
这也就是为什么很多人会说使用instancetype
不是必须的。当然我认为你还是应该去这么写。下面会解释为什么:
这有三个好处:
- 明确性。你的代码的行为如同你写的那样,而不是其他行为。
- 模式化。你为此养成了一个好的代码习惯,这有时的确很重要。
- 一致性。你写的代码前后会保持一致,增加其可读性。
明确性:
不得不承认,一个初始化方法init
返回instancetype
并不会带来明显的技术优势。但是这只是因为编译器会自动地将id
转换为instancetype
。你若让init
方法返回id
类型,编译器还要再解释这个方法好像是要返回instancetype
,这样总会显得很奇怪。
下面两句代码对于编译器来说是等价的:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
而对你来说,看这两行代码应该并不一样。在最好的情况下而言,你会学会忽略这两行的差别。但这并不是你应该学会忽略的,对你来说这两句应该是不一样的
模式化:
当然init
方法和其他方法没有区别,但一旦你定义一个类工厂,那就有差别了。
下面两行代码并不等价:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
你需要第二个代码样式。如果你习惯去写instancetype
作为返回类型的话,你每次都会得到正确的类型。
一致性:
最后,想象你把这些东西都放在一起:你想要一个init
方法和一个类工厂。
如果你习惯于对init
使用id
类型,你的代码看起来是这样:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
但是,如果你使用instancetype
,你的代码就好看的多
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
这样更明确也更可读。而且更清楚的看到他们返回同样的东西。
结论:
除非你是故意为旧的编译器写代码,否则在合适的地方你应该用instancetype
。
以后当你写id
之前应该三思:这个方法返回的是否是这个类的实例,如果是,就用instancetype
。
当然,还是会有很多需要写id
类型的情形,但你可能用instancetype
会更多一些。
参考
http://clang.llvm.org/docs/LanguageExtensions.html#related-result-types
http://stackoverflow.com/questions/8972221/would-it-be-beneficial-to-begin-using-instancetype-instead-of-id
http://nshipster.com/instancetype/
Written with StackEdit.
[Objective-C] id类型和instancetype类型的更多相关文章
- MIME类型和Java类型
MIME类型和Java类型 类型转换Spring Cloud Stream提供的开箱即用如下表所示:“源有效载荷”是指转换前的有效载荷,“目标有效载荷”是指转换后的“有效载荷”.类型转换可以在“生产者 ...
- DATETIME类型和BIGINT 类型互相转换
项目中使用BIGINT来存放时间,以下代码用来转换时间类型和BIGINT类型 SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ========= ...
- Timestame类型和String 类型的转化
Timestame类型和String 类型的转化 String转化为Timestamp: SimpleDateFormat df = new SimpleDateFormat("yyyy-M ...
- Python3.x中bytes类型和str类型深入分析
Python 3最重要的新特性之一是对字符串和二进制数据流做了明确的区分.文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示.Python 3不会以任意隐式的方式混用str和b ...
- Date类型和Long类型的相互转换
Date类型和Long类型的相互转换: import java.text.SimpleDateFormat; import java.util.Date; public class T { publi ...
- java中XMLGregorianCalendar类型和Date类型之间的相互转换
import java.text.SimpleDateFormat;import java.util.Date;import java.util.GregorianCalendar;import ja ...
- 关于 Go 中 Map 类型和 Slice 类型的传递
关于 Go 中 Map 类型和 Slice 类型的传递 Map 类型 先看例子 m1: func main() { m := make(map[int]int) mdMap(m) fmt.Printl ...
- C#枚举类型和int类型相互转换
C#枚举类型和int类型相互转换 C# 枚举(Enum) 枚举是一组命名整型常量.枚举类型是使用 enum 关键字声明的. C# 枚举是值数据类型.换句话说,枚举包含自己的值,且不能继承或传递继承. ...
- 着重基础之—MySql Blob类型和Text类型
着重基础之—MySql Blob类型和Text类型 在经历了几个Java项目后,遇到了一些问题,在解决问题中体会到基础需要不断的回顾与巩固. 最近做的项目中,提供给接口调用方数据同步接口,传输的数据格 ...
随机推荐
- Redis笔记(1)数据结构与对象
1.前言 此系列博客记录redis设计与实现一书的笔记,提取书本中的知识点,省略相关说明,方便查阅. 2.基本数据结构 2.1 简单动态字符串SDS(simple dynamic string) 结构 ...
- SQL语句:Mac 下 处理myql 不能远程登录和本地登录问题
mac下,mysql5.7.18连接出错,错误信息为:Access denied for user 'root'@'localhost' (using password: YES) ()里面的为she ...
- django中url,静态文件,POST请求的配置 分类: Python 2015-06-01 17:00 789人阅读 评论(0) 收藏
平时使用的是pycharm,所以这篇文章主要也是使用pycharm默认创建的django项目为基础进行讲解.项目目录如下图: 1.URL的配置 当创建好项目后,运行项目就可以看到django默认的页面 ...
- javascript实现代码高亮-wangHighLighter.js
1. 引言 (先贴出wangHighLighter.js的github地址:https://github.com/wangfupeng1988/wangHighLighter注意,程序和使用说明的更新 ...
- screen 实战后台命令执行备份
一.安装 [root@vmware ~]# yum install -y screen 二.直接在命令行键入 screen 命令 [root@vmware ~]# screen 三.暂时终端会话 那么 ...
- GitHub多人协作简明教程
本文面向已经了解/熟悉git基本命令但是并不熟悉如何使用GitHub进行多人协作开发项目的同学. 为了简单起见,这里假设只有两个开发人员,HuanianLi 和 DaxiangLi.他们在GitHub ...
- Spring Security 之 Remember-Me (记住我)
效果:在用户的session(会话)过期或者浏览器关闭后,应用程序仍能记住它.用户可选择是否被记住.(在登录界面选择) “记住”是什么意思? 就是下次你再访问的时候,直接进入系统,而不需要 ...
- Tomcat学习总结(9)——Apache Tomcat 8新特性
一.Apache Tomcat 8介绍 Apache Tomcat 8RC1版于2013年8月份发布.它 经过了2年的开发,引入了很多新特征,由于目前还只是Alpha版,故不推荐在产品中使用.但是我们 ...
- go程序性能测量和分析
性能测量 在很多情况之下,通过分析代码是很难确定某个模块性能好坏的.请看下面的例子,你觉得哪一个函数性能最优? //斐波那契数 package fib import "math" ...
- 计算文章作品发布时间的php代码
/* 计算发布时间据当前时间 如1秒前 1分钟前 1小时 1天 1个星期 1个人月 1年 */ function format_dates($time) { if($time <= 0) ret ...