[Objective-C] 从NSInteger说开去

在iOS开发过程中,我一直习惯于使用C语法里的基本类型,而很少用(除非必须使用)Foundation的数据类型。最近看了一些资料,发现自己这样写可能有风险,虽然目前没遇到过相关的问题,但这是非常需要注意的一点。

新博客wossoneri.com

坏习惯的开端

初写iOS时,我做的是把原项目从Android端移植到iOS端。因为涉及到不同语言,又因为不熟悉iOS,加上还要与用C写的网络库进行纠缠,我小心翼翼的用了基本数据类型完成大多数编码。能用int就坚决不用NSInteger,能用float就坚决不用CGFloat。你可能会问,虽然这个过程用到的语言很杂,写Objective-C的时候就大胆的用Foundation的数据类型呗。想法很好,只是当时我看不懂我们网络库实现原因不敢乱改代码,怕伤着哪个地方的逻辑,最后代码移着移着,我的.m文件就变成了.mm文件——对,变成Objective-CC++混编代码了。当时就索性一股脑的用C语言的基本数据类型去做了。现在想想我入门iOS的过程真是坎坷呢,留下了一堆烂毛病。

不过好在我现在开始拼命的多看资料,一点点把走的弯路走回来。

一次突然的疑问

平时也会看Github上面的一些代码,发现代码里用到int的比较少,用NSInteger的比较多,于是就考虑了一下这个问题:这两种类型有啥区别?

这里不得不吐槽一下<Objectice-C程序设计>这本书,当初就是看这本书了解O-C语法的,可书上的介绍与代码全是int的,完全没见NSInteger的影子。可见学习这事不能只靠一本书,毕竟书的内容覆盖面有限。

关于NSInteger和int的优劣

从查看头文件可以看到其实这样定义的:

#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

这段定义是说如果程序是在64位系统下运行的,NSInteger就代表long,如果是32位的就代表int

也就是说,如果你不知道程序将要在什么系统环境下运行时,最好使用NSInteger,这样可以保证数据很大时不出现问题。简而言之,就是如果你并不考虑位数对程序的影响或者说你觉得并不能游刃有余的操作intlong,那么NSInteger是一个很安全的选择。

那么是不是说有了NSInteger就可以不用int了呢?

当然不,以上的前提都是基于一个很大范围数字变量所要考虑的。当你的变量值的范围在一个你可控的范围内,使用int反而更合适,比如说,你要保存一张手机照片的尺寸,那么几千的值是绝对够了的,于是用int也不会产生任何问题,而用NSInteger反而增加代码阅读难度。毕竟int作为通用的基本类型很直观,相反的是NSInteger的代码往其他地方移植也会对别人产生困扰(是不是想太多了: D),而且,从效率上来讲,直接用int的效率还是略微要高一些的。

什么时候用NSInteger

其实在Apple的文档或者示例代码里,intNSInteger都会存在,大多数出现NSInteger是在函数的返回值上。因为函数的返回值是根据参数而不断变化的,范围是不可控的,所以就会使用NSInteger作为返回值。

所以,简单说来,凡是API用到了NSInteger,那就别用int了。一般情况代码计数不会出现很大的值,毕竟32位的int范围可达到-2147483648~2147483647。

然后说开去,说什么

NSInteger类似,Foundation数据类型里也有类似于与float对应的CGFloat

/* Definition of `CGFLOAT_TYPE', `CGFLOAT_IS_DOUBLE', `CGFLOAT_MIN', and
   `CGFLOAT_MAX'. */

#if defined(__LP64__) && __LP64__
# define CGFLOAT_TYPE double
# define CGFLOAT_IS_DOUBLE 1
# define CGFLOAT_MIN DBL_MIN
# define CGFLOAT_MAX DBL_MAX
#else
# define CGFLOAT_TYPE float
# define CGFLOAT_IS_DOUBLE 0
# define CGFLOAT_MIN FLT_MIN
# define CGFLOAT_MAX FLT_MAX
#endif

/* Definition of the `CGFloat' type and `CGFLOAT_DEFINED'. */

typedef CGFLOAT_TYPE CGFloat;
#define CGFLOAT_DEFINED 1

看完定义就明白,CGFloat也是对于不同位的系统的容错。

最后至于NSStringstd::string那区别就没那么简单了。

对于常用的数据类型,就这些了。

对于这些数据类型的格式化,可以参考这部分文档:formatSpecifiers

你觉得我这就说完了

起初遇到这个问题时,我就简单的上网查了一下,感觉没什么区别。不过最近看博客,发现有个大神这样讲

应避免使用基本类型,建议使用 Foundation 数据类型

当时我就一惊,我的代码用的都是int,压根就没打算用NSInteger,我突然觉得自己养成了一个很差的编码习惯,而且我的代码可能会对我以后求职有影响。

惊诈之余我就上网找起资料,认真阅读不同人的不同说法,然后得到一个我自己的结论:写int并无大碍,而且用基本数据类型并没什么明显缺陷,毕竟我也是知道什么时候用int啊,long啊,'float'啊这些的。虚惊一场。

但这事还是有教训的,那就是了解一个新东西一定要多了解一些,像我当初就对这个问题不以为意,突然提起来了心就虚了。

另外有一个收获就是多看别人写的博客多关注一些细微的知识点总是能学到新东西的。像今天这个问题一样,对我已经写好的代码影响虽然不大,但我再写这块代码时的确是更安心了对吧。

reference

When to use NSInteger vs. int