【原】Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法(Matt Galloway著)读书笔记(二)

第14条:理解 “类对象” 的用意

对象类型并不是在编译期就绑定好了,而是要在运行期查找。在运行期检视对象类型的操作,叫做 “类型信息查询(内省)”

元类

在运行期程序库的头文件中,id 类型的定义:

typedef struct objc_object {
    Class isa;
} *id;

每个对象结构体是首个成员是 Class 类的变量 isa,该变量定义了对象所属的类。

在运行期程序库的头文件中,Class 类型的定义:

typedef struct objc_class *Class;
struct objc_class {
    Class isa;
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
};

定义:isa 指针所指向的类。
作用:用来表述对象本身所具备的元数据。“类方法” 就定义在这里。

在类继承体系中查询类型信息

isMemberOfClass: 判断出对象是否为某个特定类的实例。
isKindOfClass: 判断出对象是否为某类或者其派生类的实例。

说明代码:

NSMutableDictionary *dict = [NSMutableDictionary dictionary]; // 底层对象为:__NSDictionaryM
    NSLog(@"%d", [dict isMemberOfClass:[NSDictionary class]]); // NO
    NSLog(@"%d", [dict isMemberOfClass:[NSMutableDictionary class]]); // NO
    NSLog(@"%d", [dict isMemberOfClass:NSClassFromString(@"__NSDictionaryM")]); // YES
    NSLog(@"%d", [dict isKindOfClass:[NSDictionary class]]); // YES
    NSLog(@"%d", [dict isKindOfClass:[NSArray class]]); // NO

像这样的类型信息查询方法使用 isa 指针获取对象所属的类,然后通过 super_class 指针在继承体系中游走。
系统对象创建后,其元类并未创建时使用的类,系统将其转为一些底层类,如上面的 __NSDictionaryM
自定义对象(继承NSObject),还是创建时的对象。

类对象是单例,在用于程序范围内,每个类的 Class 仅有一个实例。
自定义对象,通过 == 操作符也可判断出对象是否为某类的实例。

id object = [EOCSomeClass new];
if ([object class] == [EOCSomeClass class]) {
    ...
}

代理: 对象可能会把其受到的所有选择子转发给另一个对象,这个对象就是代理。这种对象均以 NSProxy 为根类。
代理对象上调用 class 方法返回的是代理对象本身,而非接受代理的对象所属的类。

总结: 尽量使用类型查询方法来确定对象类型,而不是直接比较对象,因为某些对象可能实现了消息转发功能。

第三章:接口与 API 设计

第15条:用前缀避免命名空间冲突

问题:Objective-C 没有其他语言那种内置的命名空间机制,容易产生命名冲突。

苹果宣称其保留使用所有 “两字母前缀” 的权利,所以我们在开发中最好使用三个字母的。

若自己所开关的程序库中用到了第三方库,则应为其中的名称加上前缀。

第16条:提供 “全能初始化方法”

这种可为对象提供必要信息以便其能完成工作的初始化方法叫做 “全能初始化方法”。

在类中提供一个全能初始化方法。其他初始化方法均应调用此方法。
若全能初始化方法与超类不同,则需要覆写超类中的对应方法。

第17条:实现 description 方法

NSLog(@"object = %@", object)

在构建需要打印到日志的字符串时,object 对象会收到 description 消息,该方法所返回的描述信息将取代 ”格式字符串“ 里的 ”%@“

自定义对象在打印时,只能打印出对象的类名及地址,这些内容一般没有什么用。
覆写 description 方法,可以打印自己所需要的内容。

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %p, %@>",
    [self class],
    self,
    @{@"参数1" : 参数1,
      @"参数2" : 参数2}
    ];
}

debugDescription 用于 debug 模式下 po 的打印。

第18条:尽量使用不可变(对外只读)对象

不可变:只读(read-only)
可变:即可读又可写(read-write)

尽量把对外公布出来的属性设置为只读,而且只在必要的时候才将属性对外公布。

第19条:使用清晰而协调的命名方式

起名时应遵从标准的 Objective-C 命名规范,这样创建出来的接口更容易为开发者所理解。

第20条:为私有方法名加前缀

好处:

  • 便于区分 公共方法私有方法
  • 便于修改 方法名方法签名

建议:
不要单用一个 _ 做私有方法的前缀,苹果公司就是这么用的,可能会覆写系统私有方法。建议使用 p_ 作为私有方法的前缀

第21条:理解 Objective-C 错误模型

第22条:理解 NSCopying 协议

  • copy:返回的拷贝对象与当前一致
  • immutableCopy:返回不可变的拷贝对象
  • mutableCopy:返回可变的拷贝对象

第23条:通过委托与数据源协议进行对象间通信

缓存方法响应能力缓存的最佳途径是使用 ”位段“ 数据类型。

位段:

struct data {
    unsigned int fieldA : 8; // 位段,占8个二进制位
    unsigned int fieldB : 4; // 位段,占4个二进制位
    unsigned int fieldC : 2; // 位段,占2个二进制位
    unsigned int fieldD : 1; // 位段,占1个二进制位
};

代理缓存:

// 用于缓存委托对象是否能响应特地的选择子
struct {
    unsigned int delegateMethdo1 : 1;
    unsigned int delegateMethdo2 : 1;
    unsigned int delegateMethdo3 : 1;
} _delegateFlags;

...

// 实现缓存功能所有的代码可以写在 delegate 属性所对应的设置方法里面
- (void)setDelegate:(id<delegate类名>)delegate {
    _delegate = delegate;
    _delegateFlags.delegateMethdo1 = [delegate respondsToSelector:@selector(delegateMethdo1:)];
    _delegateFlags.delegateMethdo2 = [delegate respondsToSelector:@selector(delegateMethdo2:)];
    _delegateFlags.delegateMethdo3 = [delegate respondsToSelector:@selector(delegateMethdo3:)];
}

在以后调用代理方法的时候,直接使用结构体里面的标志进行判断即可。

第24条:将类的实现代码分散到便于管理的数个分类之中

第25条:总是为第三方类的分类名称加前缀

在运行期系统加载分类时,会将分类中所实现的方法都加入到类的方法列表中,就好比这个类的固有方法。如果类中本来就有此方法,而分类中有实现了一次,那么分类中的方法会覆盖原来那一份实现代码。实际上可能会发生很多次覆盖,多次覆盖的结果以最后一个分类为准。

为防止覆盖,将分类的名称和其中的方法名称加上前缀

第26条:勿在分类中声明属性

posted @ 2020-01-13 12:35  墨隐于非  阅读(302)  评论(0编辑  收藏  举报