Book ISBN
编写高质量 iOS 与 OS X 代码的 52 个有效方法 9787111451297
Date Chapters
2019-05-01

熟悉 Objective-C

了解 Objective-C 语言的起源

  • 消息结构(Message Structure)的语言,运行时执行的代码由运行环境决定;函数调用(Function Calling)的语言,运行时所执行的代码由编译器决定,对于多态,则按照虚函数表(Virtual Method Table)寻找。
  • 运行时组件(Runtime Component) 本质上是一种与开发者所编写代码相链接的「动态库(Dynamic Library),这样的好处是只需要更新运行时组件(无需重新编译)即可提升程序性能。
  • Obj-C 是 C 的超集。
  • Obj-C 中的对象所占内存总是分配在堆空间(Heap Stack);而指向对象的指针所占内存总是分配在栈帧(Stack Frame)中。
  • 堆中的内存需要程序员自己管理,栈中的内存会在其栈帧弹出(Pop)时自动清理。
  • 创建对象相比创建结构体(C 结构体)需要额外开销,例如分配和释放堆内存等。

// ⚠️ 由于 Obj-C 中的字符串(NSString)略有特殊,此处并未使用书中的 NSString 作为范例
// 对象本身被分配在堆上;obj1 & obj2 被分配在栈上
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = obj1;

// (lldb) p obj1
// (NSObject *) $0 = 0x00000001005092b0
// (lldb) p obj2
// (NSObject *) $1 = 0x00000001005092b0
// (lldb) p &obj1
// (NSObject **) $2 = 0x00007ffeefbff508
// (lldb) p &obj2
// (NSObject **) $3 = 0x00007ffeefbff500

在类的头文件中尽量少引入其他头文件

  • 当需要知道有一个类的存在时,而不关心其内部细节时,可以使用向前声明(Forward Declaring)告知编译器,即可以在 .h(头文件)中 @class SomeClass;而在 .m(实现文件)中引入实际的 .h。
  • 当在两个头文件中互相引入对方,则会导致「循环引用(Chicken-Egg Situation)」,无法通过编译。
  • 将引入头文件的时机尽量延后,只有在确定有需要时才引入,否则会增加耦合度、拉长编译时间、产生相互依赖等问题。
  • 继承父类和遵循协议则不能使用向前声明,必须引入相应的头文件,因此协议最好声明在单独的头文件中。
  • 由于代理协议(Delegate Protocol)和遵守协议代理的类声明在一起时才有意义,最好在实现文件中声明类遵守了该代理协议,并将实现代码放在 Class-Continuation 分类(Class-Continuation Category)中;因此只需要在实现文件中引入包含代理协议的头文件即可,而不需要将其放在公共头文件(Public Header File)中。

多用字面量语法,少用与之等价的方法

  • 字面量(Literal)语法简化了 Obj-C 的部分 API:
NSString *strValue = @"str";
NSNumber *intValue = @1;
NSNumber *doubleValue = @3.14;
NSArray *arrValue = @[@"a", @"b", @"c"];
NSString *firstValueForArr = arrValue[0];
// ⚠️ 字面量创建的数组、字典都是不可变的
NSMutableArray *mutableArrValue = [@[@"a", @"b", @"c"] mutableCopy];
mutableArrValue[0] = @"maimieng.com";
NSDictionary *dictValue = @{@"key" : @100};
NSNumber *valueForDictByKey  = dictValue[@"key"];
NSMutableDictionary *mutableDictValue = [@{@"key" : @100} mutableCopy];
mutableDictValue[@"key"] = @200;
  • 字面量语法在 NSArrayNSDictionary 等类中插入 nil 对象时会直接崩溃,而直接使用 API 则会发生「截断」,对于这两个用法差异务必要注意:
id nilObj = nil;
// *** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[3]
NSArray *arrWithNilObject1 = @[@0, nilObj, @2];
// (0)
NSArray *arrWithNilObject2 = [NSArray arrayWithObjects:@0, nil, @2, nil];
// *** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]
NSDictionary *dictWithNilObject1 = @{@"a" : @"A", @"b" : nilObj, @"c" : @"C"};
// { a = A; }
NSDictionary *dictWithNilObject2 = [NSDictionary dictionaryWithObjectsAndKeys:@"A", @"a", nilObj, @"b", @"C", @"c", nil];
  • 除了字符串,字面量语法仅适用于 Foundation 框架中,即我们自定义继承自上述支持字面量的类时,将不再支持使用字面量。

多用类型常量,少用 #define 预处理指令