编写高质量iOS有效方法总结(一)

1.Objective-C 起源

  • Objective-C 是 C 的 “超集”,所以 C 语言中的所有功能在编写 Objective-C 代码时依然适用。
  • Objective-C 使用“消息结构”而非“函数调用”。
//Messaging (Objective-C) //消息结构其运行时执行的的代码由运行环境来决定 Object *obj = [Object new]; [ob performWith:parameter1 and: parameter2]; //Function calling (C++) //函数调用则由编译器来决定 Object *obj = new Object; obj->perform(parameter1, parameter2)

  • 在运行时编译器不关心接收消息的对象是何种类型,接收消息的对象问题也要在运行时处理,这个过程叫做“动态绑定”。
  • 所有 Objective-C 语言的对象都必须用 NSString * 这种类型声明,实际是一个指向 NSString 的指针。对象所占内存总是分配在堆空间(heap space)而不会在栈(stack)上。
    不能在栈上分配 Objective-C 对象:
NSString stackString; //error: interface type cannot be statically allocated

  • 分配在堆中的内存必须直接管理,而栈上的内存则会在其栈帧弹出时自动清理。有时遇到定义里面不含有 * 的变量,可能会被分配到栈内存:
CGRect frame; frame.origin,x = 10.0f;

  • 创建对象要比创建结构体有额外的开销(分配及释放堆内存),如果只需要保存非对象类型(int,float,double,char 等)使用结构体更高效。
2.在类的头文件中尽量少引入其他头文件
  • 向前声明(forward declaring)该类:在编译一个使用了 SomeClass 类的文件时,不需要知道 SomeClass 的全部细节,只需知道一个类名就好。
    @class SomeClass
    在 .m 实现文件则需要引入 SomeClass 类的头文件,因为要使用后者,则必须知道其所有接口的细节。
  • 将引入头文件的时机尽量延后,只有这确有需要时才引入,因为只要引入了 .h 文件就会一并引入这个类的所有内容,引入了许多用不到的内容会增加编译时间。
  • “循环引用”:在各自的头文件中引入对方的头文件。
    使用 #import 代替 #include 虽然不会导致死循环,但这却意味着两个类里有一个无法被正确编译。使用向前声明可以解决此问题。
  • 如果你写的类继承自某个超类,则必须引入定义那个超类的头文件。同理,如果你写的类尊循某个协议,那么该协议必须有完整的定义,不能使用向前声明,向前声明只能告诉编译器有某个协议,却不知道协议中的方法。尽量把该类遵循的协议声明到“分类”中。如果不行,就把该协议单独放在一个头文件中,然后将其引入。
3.多用字面量语法,少用与之等价的方法
  • 使用字面量语法可以缩减源代码长度,更易读,更整洁。
NSArray *arrayA = [NSArray arrayWithObjects:@"dog", @"cat", @"badger", nil]; NSString *cat = [arrayA objectAtIndex:1]; NSArray *arrayB = @[@"dog", @"cat", @"badger"]; NSString *cat = arrayA[1]; NSDictionary *dicA = [NSDictionary dictionaryWithObjectsAndKeys:@"Matt", @"firstName", @"Galloway", @"lastname", [NSNumber numberWithInt:28], @"age", nil]; NSDictionary *dicB = @{@"firstName":@"Matt", @"lastname":@"Galloway", @"age":[NSNumber numberWithInt:28]};

【编写高质量iOS有效方法总结(一)】若 "cat" 变成 nil ,arrayA虽然能创建出来,但却只有 "dog" 一个对象。因为 arrayWithObjects 会依次处理各个参数,直到发现 nil 为止。同理 dictionaryWithObjectsAndKeys 方法也是一样。
  • 字典中的对象和键必须都是 Objective-C 对象,不能把整数直接放进去,要封装成NSNumber 实例才行。
  • 使用字面量语法创建出来的字符串,数组,字典对象都是不可变的,若要可变版本则需要复制一份:
NSMutableArray *mutable = [@[@1, @2, @3, @4] mutableCopy];

4.多用类型常量,少使用 #define 预处理指令
  • 很多时候定义一个动画执行时间会有这种写法:
    #define ANIMATION_DURATION 0.3
    假设这个指令声明在某个头文件中,那么所有引入了这个头文件的代码,其 ANIMATION_DURATION 都会被替换。也就是无意间可能替换了我们并不想替换的 ANIMATION_DURATION 。所以我们应该用编译器的特性来定义一个常量:
static const NSTimeInterval kAnimationDuration = 0.3;

  1. 常用命名法:若常量局限于某个 .m 文件之内,则在前面加字母 k ;若常量在类之外可见,则通常以类名为前缀。
  2. 这种命名法清楚地描述了常量的含义(包含类型信息)。
  3. 一定要同时使用 static 和 const 来声明:试图修改 const 声明的变量编译器会报错;static 修饰符意味着该变量仅在 .m 文件中可见。
  4. 如果不加 static ,编译器会创建一个外部符号,此时若其他 .m 中也声明了同名变量,编译器就会报错。
  • 对外公开的常量(比如通知中用到的 NSNotificationName )。其实注册者无须知道实际字符串的值,只需以常值比变量来注册自己想要接收的通知即可。此类常量应该放在全局符号表,以便可以定义该常量在 .m 文件之外。
//In the header file extern NSString *const SomeClassNSNotificationName; //In the implementation file NSString *const SomeClassNSNotificationName = @"VALUE";

这是一个由常量指针修饰的 NSString 对象,不能够被修改。extern 关键字告诉编译器,在全局符号表中会有一个叫 SomeClassNSNotificationName 的符号,编译器无需查看其定义就允许代码使用,因为这个常量链接成二进制文件之后肯定能找到。此类常量只能定义一次,编译器会在数据段(全局区中的data区)为字符串分配存储空间。

    推荐阅读