Effective Objective-C 2.0 学习笔记

(一日两条,持续更新)

从Objective-C起源说起

  • OC 使用动态绑定消息结构,在运行时才会检查对象类型。接收一条消息之后,究竟该执行何种代码,由运行期环境而非编译器来决定。
  1. 好好学习C语言

  2. 类的头文件中(.h)尽量少引用其他头文件。可以用@class解除循环引用

  3. 多用字面量语法

NSString *string = @"字面量";
NSDictionary *dict = @{@"key":@"value",@"key":@"value"};
NSArray *array = @[@1,@2,@3];

 //可以使用表达式
int x = 1;
int y = 2;
NSNumber *z = @(x*y);
//可变的稍微麻烦一点点就是要copy一下
NSMutableArray *array2=@[@1,@2].mutableCopy;

多用类型常量,少用#define

  • 变量一定要同时用static 和const来声明

  • const 意味着无法修改他所修饰的右边内容。
    常量定义通常从右到左解读,
    NSString *const string = @"哈哈"
    这个含义就是说string是一个常量,而这个常量是一个指针,我们不希望有人改变此指针常量,让他指向别的NSString对象,这是符合要求的。
    如果放在NSString前面,就是说,指针指向的值是常量,字符串本来就是个常量啊。

  • static 指该变量仅定义(作用域)在这个.m文件中,不用它修饰,加入另一个编译单元(在OC环境下就是.m文件)中声明了同名变量,编译器报错

  • 通过extern 修饰可以对外公开某个常量,要在.h中公开声明,.m中去实现定义 这种常量还要注意一点:在命名的时候注意用类名做前缀来区分。

使用枚举表示状态、选项、状态码

表示状态:

typedef NS_ENUM(NSUInteger, <#MyEnum#>) {
    <#MyEnumValueA#>,
    <#MyEnumValueB#>,
    <#MyEnumValueC#>,
};

表示选项(可多选)

typedef NS_OPTIONS(NSUInteger, <#MyEnum#>) {
    <#MyEnumValueA#> = 0,
    <#MyEnumValueA#> = 1 << 0,
    <#MyEnumValueB#> = 1 << 1,
    <#MyEnumValueC#> = 1 << 2,
};

表示状态码的时候,就是和switch相结合。
记得不写default break; 这个选项。
如果写了,以后新增枚举元素,会被归为这里的,不会提示。

属性这一概念

  • property语法来定义对象中所封装的数据

  • 通过“特质”来制定存储数据所需要的正确语义

  • 在设置属性所对应的实例变量时,一定要遵从该属性所声明的语义。

    例如:

    copy  修饰的NSString 在initWithString的时候,内部创建方法要用[_string copy];
    
  • 使用nonatomic

在对象内部尽量直接访问实例变量

三个原则

  • 在对象内部读取数据时,通过实力变量来读。 写入数据时,通过属性来写
    这样既可以提高读取操作的速度,又能控制对属性的写入操作,贯彻“内存管理”语义

  • 初始化方法及dealloc方法中总是应该直接通过实例变量来读写数据
    有些情况必须通过设置方法来调用:如果待初始化的实例变量声明在超类中而我们又无法在自雷中去直接访问此实例变量。

  • 使用懒加载初始化数据的时候,需要通过属性来读取数据。

对象等同性(暂时了解)

  • 若想检测对象的等同性,请提供isEqual:和hash方法
  • 相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同
  • 不要盲目地诸葛检测每条属性,应该依照具体需求来制定检测方案
  • 编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法

以“类族模式”隐藏实现细节

  • 类族模式可以把实现细节隐藏在一套简单的公共接口后面
  • 系统框架中经常使用类族
  • 从类族的公共抽象基类中集成

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

  • 可以通过“关联对象机制”来把两个对象连起来
  • 定义关联对象时可制定内存管理语义,用以模仿定义属性时所采用的拥有关系和非拥有关系
  • 只有在其他做法不可行时才应选用关联对象,因为这种做法会引入难以查找bug。

例子:

objc_setAssociatedObject(alert,AlertViewKey,block,BJC_ASSOCIATION_COPY);
objc_getAssociatedbject(alertView,AlertViewKey);

理解objc_msgSend的作用

  • 消息由接收者、选择子及参数构成。给某对象发送消息也就相当于在该对象上调用方法。
  • 发给某对象的全部消息都要由“动态消息派发系统”来处理,该系统会查出对应的方法,并执行其代码。

理解消息转发机制

message forwarding "消息转发"

  • 消息转发两大阶段
    1. 先征询接收者,所属的类,看其能否动态添加方法处理这个selector——动态方法解析
    2. 首先,请接收者看看有没有其他对象能处理这条消息。若没有“备援的接收者”,就启动完整的消息转发机制,运行期系统会把消息有关的全部细节都封装到NSInvocation对象中,再给接收者最后一次机会,设法解决当前还未处理的这条消息。
  • 若对象无法响应某个选择子,则进入消息转发流程
  • 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时在将其添加入类中
  • 对象可以把无法解读的某些选择子转交给其他对象来处理。
  • 经过上述两步之后,还是无法处理选择子,就启动完整的消息转发机制。

用“方法调配技术”调试“黑盒方法”

  • 在运行期,可以向类中新增或替换选择子所对应的方法实现。
  • 使用另一份实现来代替原有的方法实现,叫“方法调配”,开发者常用此技术向原有实现中添加新功能
  • 一般来说,只有调试程序的时候需要在运行期修改方法实现,这种方法不宜滥用。

举例:

获得方法:
class_getInstanceMethod(Class aClass,SEL aSelector)
交换方法:
class_exchangeImplementations(originalMethod,swappedMethod)

理解“类对象”的用意

  • Class 本身也是一个ObjectIve-C对象 super_class 定义了本类的超类。
  • 类对象所属的类型(也就是isa指针所指向的类型)是另外一个类,叫做“元类”用来表述类对象本身所具备的元数据。“类方法”就定义在此处,类方法可以理解成类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。
  • 每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系。
  • 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法探知。
  • 尽量使用类型信息查询方法来确定对象类型。而不要直接比较对象,因为某些对象可以能实现了消息转发功能。

接口与API设计

  • 选择与你公司、应用程序或者二者皆有关联之名称作为类名的前缀,并在所有代码中都使用这一前缀
  • 自己开发的程序库中用到了三方库,应该为其中的名称加上前缀。

提供“全能初始化方法”

  • 在类中提供一个全能初始化方法,并于文档里指明。其他初始化方法均应调用此方法。
  • 若全能初始化方法与超类不同,则需覆写超类中的对应方法。
  • 如果超类的初始化方法不适用子类,那么应该覆写这个超类方法,并在其中抛出异常。

实现 description 方法

  • 实现description 方法返回一个有意义的字符串,用以描述该实例。其中可以以字典的方式来存储具体的内容更为合理。
  • 调试时打印,需要实现debugDescription 方法。

尽量使用不可变对象

  • 尽量创建不可变的对象
  • 若某属性仅可于对象内部修改,则在分类中将其由readonly属性扩展为readwrite属性
  • 不要把可变的collection作为属性公开,而应该提供相关方法,以此来修改对象中的可变collection。

使用清晰而协调的命名方式

  • 起名要遵从标准的OC命名规范
  • 方法名言简意赅,从左到右读起来像个日常用语中的句子
  • 方法名里不要使用缩略后的类型名称

为私有方法名前加前缀

  • 给私有方法的名称加上前缀,这样可以很容易地将其同公共方法区分开。
  • 不要单用一个下划线去做私有方法的前缀,因为这种做法是预留给苹果公司调用的

理解Objective-C错误类型

  • 只有发生了使整个应用程序崩溃的严重错误时,才应使用异常
  • 在错误不那么严重的情况下,可以指派委托方法来处理错误,也可以把错误信息放在NSError对象里,精油输出参数返回给调用者。

推荐阅读更多精彩内容