《Effective Objective-C 2.0》读书笔记

第 1 章 熟悉Objective-C

第 1 条: 了解Objective-C语言的起源

  • Objective-C语言使用"消息结构"而非"函数调用",使用消息结构的语言,其运行是所应执行的代码是由运行环境来决定的;而使用函数调用的语言,则由编译器决定
  • 采用消息结构的语言不论是否多态,总是在运行时才会去查找所要执行的方法
  • Objective-C的重要工作都由"运行期组件"(runtime component)而非编译器来完成.使用Objective-C的面向对象特性所需的全部数据结构都在运行期组件里面
  • Objective-C是C的"超集"(superset),所以C语言中的所有功能在编写Objective-C代码时依然适用
  • 在Objective-C代码中,定义里不含*的变量,它们可能会使用"栈空间"(stack space),这些变量所保存的不是Objective-C对象

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

在创建的一个类中,需要引入一个新类,来作为其的一个属性,我们不应该引入这个新类的头文件,而是使用"向前声明"(forward declaring)使用class来处理,然后在实现文件中引入新类的头文件

//EOCPerson.h
#import <Foundation/Foundation.h>

@class EOCEmployer

@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;
@end

//EOCPerson.m
#import "EOCPerson.h"
#import "EOCEmployer.h"
复制代码

好处:

  • 减少编译时间
  • 降低彼此依赖程度
  • 解决两个类互相引用的问题

必须要在头文件中引入其他头文件的情况

  • 写的类继承自某个超类
  • 写的类遵从某个协议(protocol)

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

  • 尽量使用字面量语法来声明NSNumber, NSArray, NSDictionary
NSNumber *someNumBer = @1;

NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];

NSDictionary *personData = @{@"firstName": @"Matt",
                             @"lastName": @"Galloway",
                             @"age": @28};
复制代码
  • 在NSArray, NSMutableArray, NSDictionary, NSMutableDictionary取值操作, 尽量使用字面量语法
NSString *animal = animals[1];

NSString *firstName = personData[@"firstName"];
复制代码

使用字面量语法的好处

  • 使用字面量语法(literal syntax) 可以缩减源代码长度,使其更为易读
  • 用字面量语法创建数组或字典时, 若值中为nil,则会立马抛出异常, 方便处理

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

类型常量和预处理指令都能达到定义常量的效果, 但二者有什么区别呢

  • 预处理指令定义出来的常量没有类型信息,而且可被替换
  • 类型常量包含类型信息, 不可别替换

类型常量

static const NSTimeInterval kAnimationDuration = 0.3;

//const 修饰,表示不可更改
//static 修饰,表示该变量仅在定义此变量的编译单元中可见
复制代码

对外公开某个常量

// in the header file
extern NSString *const EOCStringConstant;

//in the implementation file
NSString *const EOCStringConstant = @"VALUE";
复制代码

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

在开发中,我们应该用枚举来管理一些我们定义的,能够组合在一起的状态,比如下面网络连接状态的枚举

typedef NS_ENUM(NSUInteger, EOCConnectionState) {
    EOCConnectionStateDisconnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConected,
};
复制代码

在处理枚举类型的switch语句中不要实现default分支. 这样的话, 加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举

第 2 章 对象, 消息, 运行期

第 6 条: 理解"属性"这一概念

"属性"(property) 是Objective-C的一项特性,用于封装对象中的数据

存取方法

@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

//等同于
@interface EOCPerson : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
复制代码

使用@dynamic关键字,将不会自动创建实现属性所用的实例变量,也不会为其创建存取方法

@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

@implementation EOCPerson
@dynamic firstName, lastName;
@end
复制代码

属性特质

原子性
  • nonatomic: 非原子性的, 不使用同步锁
  • atomic: 原子的, 加同步锁,确保原子性
atomic 与 nonatomic的区别
  • 具备atomic特质的获取方法会通过所定机制来确保其操作的原子性.也就是说,如果两个线程读写同一属性,那么不论何时,总能看到有效的属性值
  • 使用nonatomic, 那么当其中一个线程正在修改某个属性值时, 另外一个线程也许会突然闯入,把尚未修改好的属性值读取出来

在iOS开发中,所有的属性都声明为nonatomic, 这样做的原因是, 在iOS中使用同步锁的开销较大,这样会带来性能问题.但是在开发Mac OS X程序时, 使用atomic属性通常不会有性能瓶颈

读/写权限
  • readwrite : 拥有 getter/setter方法
  • readonly : 只拥有getter方法
内存管理语义
  • assign 纯量类型(CGFloat, NSInteger)的简答赋值
  • strong 拥有关系, 赋新值时, 先保留新值,并释放旧值,然后再将新值设置上去
  • weak 未拥有关系, 赋新值,既不保留新值,也不释放旧值,在属性所指向的对象遭到摧毁时, 属性值会被清空
  • unsafe_unretained 语义和assign相同, 适用于对象类型,非拥有关系,当目标对象遭到摧毁时, 属性值不会被清空
  • copy 不保留新值,而是直接拷贝

第 7 条: 在对象内部尽量直接访问实例变量

在对象之外访问实例变量时,笔者建议在读取实例变量的时候采用直接访问的形式,而在设置实例变量的时候通过属性来做

  • 直接访问实例变量的速度比较快,因为编译器所生成的代码会直接访问保存对象实例变量的那块内存
  • 直接访问实例变量时, 会跳过set方法
  • 直接访问实例变量,不会触发KVO

上述笔者说的方案, 要注意一下两个问题

  • 在初始化方法及dealloc方法中, 总是应该直接通过实例变量来读写数据
  • 有时会使用惰性初始化技术配置某份数据,这种情况下,需要通过属性来读取数据

第 8 条: 理解 "对象等同性" 这一概念

  • 使用==操作符比较的是两个指针本身,而不是其所指的对象,应该使用NSObject协议中声明的"isEqual:"方法来判断两个对象的等同性
//如果已知两个对象是属于同一类,那么就可以使用==操作符
NSString *foo = @"Badger 123";
NSString *bar = [NSString stringWithFormat:@"Badger %i", 123];
BOOL equalA = (foo == bar) // NO
BOOL equalB = [foo isEqual: bar] //YES
复制代码
  • 如果已知受测的两对象是NSSting类型, 请使用"isEqualToString: "方法比较,如果是NSArray,就使用"isEqualToArray: ",NSDictionary就使用"isEqualToDictionary: "
  • 如果仅仅只是想比较其指针所指向的内容是否相同, 可以使用"isEqual:"方法

第 9 条: 以 "类族模式" 隐藏实现细节

在我们iOS开发中,其实也有用到"类族"这种模式,比如下面这个类方法

+ (UIButton *)buttonWithType:(UIButtonType)type;
复制代码

我们只需输入类型, 就能得到不同类型的button, 无需关心其内部是如何实现的

创建类族

以一个处理雇员的例子来说, 每个雇员都有自己的名字和工资, 而每个雇员的工作内容不同,然后这些东西我们都可以一个员工类来完成

typedef NS_ENUM(NSUInerger, EOCEmployeeType) {
    EOCEmployeeTypeDeveloper,
    EOCEmployeeTypeDesigner,
    EOCEmployeeTypeFinance,
};
@interface EOCEmployee : NSObject
@property (copy) NSString *name;
@property NSUInerger salary;

+(EOCEmployee *)employeeWithType:(EOCEmployeeType)type;
-(void)doADaysWork;
@end;

@implementation EOCEmployee

+(EOCEmployee *)employeeWithType:(EOCEmployeeType)type
{
    switch(type) {
        case EOCEmployeeTypeDeveloper:
            return [EOCEmployeeTypeDeveloper new];
            break;
        case EOCEmployeeTypeDesigner:
            return [EOCEmployeeTypeDesigner new];
            break;
        case EOCEmployeeTypeFinance:
            return [EOCEmployeeTypeFinance new];
            break;
    }
}

-(void)doADaysWork{
}

@end;

//实体子类
@interface EOCEmployeeTypeDeveloper : EOCEmployee
@end

@implementation EOCEmployeeTypeDeveloper
-(void)doADaysWork{
    [self writeCode];
}
@end;
复制代码

第 10 条: 在既有类中使用关联对象存放自定义数据

通过关联对象的方式,我们可以给某个对象关联许多其他对象,这样的话, 我们就可以从某个对象中获取相应的关联对象的值

关联对象的相关语法

//为某个对象设置关联对象值
void objc_setAssociatedObject (id object, void *key, id value, objc_AssociationPolicy policy)

//从某个对象中获取相应的关联对象值
id objc_getAssociatedObject(id object, void *key)

// 移除指定对象的全部关联对象
void objc_removeAssociatedObjects(id object)
复制代码

关联对象用法举例

#import <objc/runtime.h>

static void *EOCMyAlertViewKey = "EOCMyAlertViewKey";

-(void)askUserAQuestion {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" 
                            message:@"What do you want to do?" 
                            delegate:self 
                            cancelButtonTitle:@"Cancel" 
                            otherButtonTitles:@"Continue", nil];
    
    void (^block)(NSInteger) = ^(NSInteger buttonIndex) {
        if (buttonIndex == 0) {
            [self doCancel];
        }else {
            [self doContinue];
        }
    };
    
    objc_setAssociatedObject(alert,EOCMyAlertViewKey,block, OBJC_ASSOCIATION_COPY);
    [alert show];
}

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    void (^block)(NSInteger) = objc_getAssociatedObject(alertView, EOCMyAlertViewKey)
    block(buttonIndex);
}
复制代码

第 11 条: 理解objc_msgSend的作用

在Objective-C中, 如果想某对象传递消息, 那就会使用动态绑定机制来决定需要调用的方法.在底层,所有方法都是普通的C语言函数,然后对象收到消息之后,究竟该调用哪个方法则完全于运行期决定, 甚至可以在程序运行是改变,这些特性使得Objective-C成为了一门真正的动态语言

给对象发消息可以这样写:

id returnValue = [someObject  messageName:parameter];
复制代码

someObject 叫做"接收者"(receiver), messageName叫做"选择子"(selector). 选择子与参数合起来称为"消息"(message). 编译器看到此消息后, 将其转换为一条标准的C语言函数调用, 所调用的函数乃是消息传递机制中的核心函数,叫做objc_msgSend, 其原型如下:

void objc_msgSend(id self, SEL cmd, ...)
复制代码

第一个参数代表接收者, 第二个参数代表选择子,后续参数就是消息中的那些参数,其顺序不变

通过编译器转换得到以下函数

id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
复制代码

objc_msgSend函数会一聚接收者与选择子的类型来调用适当的方法. 为了完成此操作,该方法需要在接收者所属的类中搜寻其"方法列表",如果能找到与选择子名称相符的方法,就跳至其实现代码.若是找不到, 那就沿着继承体继续向上查找,等找到合适的方法之后再跳转.如果最终还是找不到相符的方法, 那就执行"消息转发"操作

猜你喜欢

转载自blog.csdn.net/weixin_34311757/article/details/91398952