Effective objective C 2.0笔记(一)

第一章 熟悉Objective-C

之前看了《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》这本五星好书,受益颇多,在一定程度上提高了编写的代码的质量。现在就对这52个有效方法做简单笔记。

第1条:了解OC语言的起源

OC为C语言添加了面向对象特性,是其超集。OC与其他面向对象的语言在很多方面都有差别。OC语言由Smalltalk(消息型语言鼻祖)演化而来。OC语言使用动态绑定的“消息结构”而非“函数调用”,也就是说在运行时才会检查对象类型,接收一条消息后,究竟执行何种代码,由运行时环境而非编译器来决定。

第2条:在类的头文件中尽量少引入其他头文件

在类的头文件中引入其他头文件可能会有如下问题:

  1. A类头文件引入了B类的头文件,B类头文件又引入了A类头文件,这两个类就相互引用了,编译不过。
  2. A类头文件引入了C类的头文件,当B类引入A类头文件时,不需要的C类也被引入了B类。而另一个类又引入B类时,没有用到的A类C类也被引入了。如此持续下去,则要引入许多根本用不到的内容,会增加编译时间。

取而代之,我们应尽量使用向前声明@class声明,而在.m文件中引入头文件。@class让编译器知道有这样一个类,而不需要知道类的全部细节。这样将引入头文件的时机尽量的延后,只在需要的时候才引入。 但是有时候必须要在头文件中引入其他头文件,有如下情况:

  1. 继承至父类的子类,必须在头文件中引入父类的头文件。
  2. 类遵循某个协议时,该协议必须有完整的定义,编译器要知道该协议的方法,而不能使用@class。(其实也可以在.m文件中的扩展中遵循协议,那么就不用在头文件中去引入协议头文件了)

总的说来,每次在头文件中引入其他头文件之前,都要先问问自己这样做是否有必要。除非确有必要,否则不要引入,而应该使用向前声明@class。

第3条:多用字面量语法,少用与之等价的方法

Foundation框架中NSString,NSNumber,NSArray,NSDictionary都有对应的字面量语法:@””,@1,@[],@{}。字面量语法特点:

  1. 语法精简,缩减代码长度更为易读。
  2. 字面量语法有利于数组,字典的操作:可以通过下标的形式进行操作,简明扼要。
  3. 使用字面量创建数组/字典时,若集合中元素对象有nil,则会抛出异常,而非字面量创建时会到nil对象为止。这种差别表明,字面量语法更加安全,抛出异常总比创建的集合元素莫名的少了要好。
  4. 字面量创建的对象是不可变的,若想要可变的则需复制一份,类似@[].mutableCopy。

    第4条:多用类型常量,少用#define预处理指令

  5. 预处理指令定义的常量不含类型信息,编译器只是会在编译前据此执行查找与替换操作。在我们用错类型时,编译时并不能及时发现错误。如果不小心重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序的常量值不一致。
  6. 类型常量包含类型消息,清楚地描述了常量的含义,可以让代码更易被理解。使用时更好了利用编译器的某些特性。
  7. 常量命名规则:若常量局限于实现文件之内,则在前面加字母k;若常量在类之外可见,则通常以类名为前缀。
  8. 常量一般使用static const声明,const声明的变量可以防止被修改,如果试图修改会得到编译错误。而static修饰的变量意味着该变量仅在定义此变量的编译单元(及实现文件)中可见。
  9. 有时需要公开某个常量,以便可以在定义该变量的编译单元之外使用。此类常量需要放在“全局符号表”中,这就需要在头文件用extern关键字声明,而在实现文件中定义(不能使用static)。使用extern关键字,编译器无须查看其定义,即允许代码使用此常量。因为它知道,当链接成二进制文件后,肯定能找到这个常量。
  10. 预处理指令定义的字符串,编译器可以对相同的字符串进行优化,只保存一份到 .rodata 段。甚至有相同后缀的字符串也可以优化,”Hello world” 与 “world” 两个字符串,只存储前面一个。取的时候只需要给前面和中间的地址,如果是整形、浮点型会有多份拷贝,但这些数写在指令中。占的只是代码段而已,大量用预处理指令会导致二进制文件变大。

    第5条:用枚举表示状态 选项 状态码

    对于状态 选项等,使用枚举写出来的代码更易读懂。编译器为枚举分配一个独有的编号,从0开始,每个枚举递增1。也可以自己设值,不使用编译器分配的。设置的枚举值可以用二进制表示,这样通过“按位或操作符”可组合多个选项。 也可以使用typedef关键字重新定义枚举类型,使定义枚举变量的方式更简洁。Foundation框架中定义了一些辅助的宏,可以很方便的定义枚举类型,也可以指定用于保存枚举值的底层数据类型。相关的宏有两种:

    #define NS_ENUM(...) CF_ENUM(__VA_ARGS__)
    #define NS_OPTIONS(_type, _name) CF_OPTIONS(_type, _name)
    

    两种宏的区别:作为选项的枚举值经常使用按位或运算组合,在运算两个枚举值时,C++认为运算的结果应该是枚举的底层数据类型也就是NSUInteger。C++不允许将底层类型“隐式转换”为枚举类型本身。若编译器按C++模式编译,而按位运算的类型与枚举底层数据类型不一致,这时使用NS_ENUM会报错。而NS_OPTIONS很好的兼容了这个问题,若非C++模式编译其和NS_ENUM一样,若是C++模式编译,NS_OPTIONS会将按位操作的结果显式转换,为我们省去类型转换的操作。鉴于此,凡是需要按位运算组合的枚举都应使用NS_OPTIONS,若不需要组合,则应使用NS_ENUM。

    typedef NS_OPTIONS(NSUInteger, UIControlState) {
     UIControlStateNormal       = 0,
     UIControlStateHighlighted  = 1 << 0,                  // used when UIControl isHighlighted is set
     UIControlStateDisabled     = 1 << 1,
     UIControlStateSelected     = 1 << 2,                  // flag usable by app (see below)
     UIControlStateFocused NS_ENUM_AVAILABLE_IOS(9_0) = 1 << 3, // Applicable only when the screen supports focus
     UIControlStateApplication  = 0x00FF0000,              // additional flags available for application use
     UIControlStateReserved     = 0xFF000000               // flags reserved for internal framework use
    };
    

    还有就是,在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举后,编译器会提示我们有未处理的枚举,以免遗漏。

第一章的5条总结完毕,持续更新中…

Written on March 24, 2019