读《Effective Objective-C 2.0:编写高质

2018-04-02  本文已影响23人  HelloiWorld

读《Effective Objective-C 2.0:编写高质量iOS与OS X代码的52个有效方法》有感

1. Objective-C使用动态绑定的消息结构,在运行时才会检查对象类型。

这种动态消息工作方式决定了其不可能实现真正的私有方法或私有实例变量。

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

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

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

5. 用枚举表示状态、选项、状态码

6. 理解“属性特质”

7. 在对象内部尽量访问实例变量

  • 由于不经过Objective-C的方法派发,直接访问实例变量的的速度更快
  • 直接访问实例变量不会调用其设置方法,故绕过了内存管理语义,也不会触发KVO通知

8. 理解“对象等同性”

  • ==比较的是指针本身
  • isEqual:默认比较的是指针地址
    1. 先判self == object,指针相等则返回YES
    2. 再判[self class] != [object class],若所属类不相同则直接返回NO(注:需要考虑继承情况,可使用isKindOfClass:
    3. 依次分别对每个属性做值判断,一旦有一个不等则返回NO
    4. 最后默认返回YES
  • 等同性约定:若isEqual:判定两对象相等,则hash一定相等;但hash相同的两对象未必相等

9. 以“类簇模式”隐藏实现细节

类簇模式可以把实现细节隐藏在公共接口之后,在系统框架中经常使用。

Objective-C语言无法指明某个基类是“抽象的”,所以子类是不知道必须要实现父类的某个方法的。开发中常常是在超类的开发文档中注明。
个人的做法是在父类抽象方法实现中加

#define MustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]

宏,这样一旦子类实例将消息发送到了父类该方法就会抛出异常

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

void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy)
id objc_getAssociatedObject(id object, void *key)
void objc_removeAssociatedObjects(id object)

关联对象涉及到runtime的一些用法,需要引入#import <objc/runtime.h>,常常用于给分类(category)增加属性

11. 理解消息发送(objc_msgSend)的作用

关于消息发送的相关知识,具体可查阅《招聘一个靠谱的iOS》面试题参考答案(上):objc中向一个对象发送消息obj-foo和objc_msgsend函数之间有什么关系

12. 理解消息转发(objc_msgForward)机制

关于消息发送的相关知识,具体可查阅《招聘一个靠谱的iOS》面试题参考答案(下):objc_msgforward函数是做什么的直接调用它将会发生什么

13. “方法调配技术”(method swizzling)

实质就是交换两个方法的实现,可用来在运行期为已有类新增或替换选择子(@selector)所对应的方法。比如在分类的load方法中,执行方法交换,以修改替换原有的方法实现。

14. 理解“类对象”结构

typedef struct objc_class *Class;
struct objc_class {
    Class isa; //isa指针,指向metaclass(该类的元类)
    Class super_class; //指向objc_class(该类)的super_class(父类)
    const char *name; //objc_class(该类)的类名
    long version; //objc_class(该类)的版本信息,初始化为0,可以通过runtime函数class_setVersion和class_getVersion进行修改和读取
    long info; //一些标识信息,如CLS_CLASS表示objc_class(该类)为普通类。ClS_CLASS表示objc_class(该类)为metaclass(元类)
    long instance_size; //objc_class(该类)的实例变量的大小
    struct objc_ivar_list *ivars; //用于存储每个成员变量的地址
    struct objc_method_list **methodLists; //方法列表,与info标识关联
    struct objc_cache *cache; //指向最近使用的方法的指针,用于提升效率
    struct objc_protocol_list *protocols; //存储objc_class(该类)的一些协议
};

15. 用前缀避免命名空间冲突

Objective-C没有内置命名空间(namespace)机制。

16. 提供“全能”(指定,designated)初始化方法

指定初始化方法应覆盖所有其他初始化方法,并覆写超类对应方法。

17. 实现description方法

#import <objc/runtime.h> //导入runtime头文件

//重写description方法同下,NSLog打印model详细信息
//重写debugDescription, lldb打印model详细信息
- (NSString *)debugDescription {
    //声明一个字典
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    
    //得到当前class的所有属性
    uint count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    //循环并用KVC得到每个属性的值
    for (int i = 0; i < count; i++) {
        objc_property_t property = properties[i];
        NSString *name = @(property_getName(property));
        id value = [self valueForKey:name]?:@"nil";//默认值为nil字符串
        [dictionary setObject:value forKey:name];//装载到字典里
    }
    
    //释放
    free(properties);
    
    return [NSString stringWithFormat:@"<%@: %p> -- %@",[self class],self,dictionary];
}

18. 尽量使用不可变对象

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

代码洁癖的自我修养

20. 为私有方法名加前缀

有助于区分于公开方法,一目了然

21. 理解Objectiove-C错误模型

22. 理解NSCopying协议

这是一个严谨的单例模式宏,支持ARC和MRC

#if __has_feature(objc_arc) // ARC

#define DEF_SINGLETON(name) \
static id _instance; \
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
\
+ (instancetype)sharedInstance \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
});\
return _instance; \
} \
+ (id)copyWithZone:(struct _NSZone *)zone \
{ \
return _instance; \
}

#else // 非ARC

#define DEF_SINGLETON(name) \
static id _instance; \
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
\
+ (instancetype)sharedInstance \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
}); \
return _instance; \
} \
\
- (oneway void)release \
{ \
\
} \
\
- (id)autorelease \
{ \
return _instance; \
} \
\
- (id)retain \
{ \
return _instance; \
} \
\
- (NSUInteger)retainCount \
{ \
return 1; \
} \
\
+ (id)copyWithZone:(struct _NSZone *)zone \
{ \
return _instance; \
}

#endif

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

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

现在已采用的方法,为大文件"减负",如AppDelegate+Configuration

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

分类名和方法名都宜添加前缀,防冲突

26. 勿在分类中声明属性

有必要的时候还是用关联对象实现吧,虽然作者不推荐

27. 使用"class-continuation分类"隐藏实现细节

其实也就是在.m文件中声明私有属性,但并不是真的私有,仍然可以用runtime遍历属性列表找到该属性。

28. 通过协议提供匿名对象

通过id<xxxDelegate>这种方式声明匿名对象,这样使用者无须关心这种对象的具体类型,只专注于它能去做的事即可。

架构设计

比如要同时支持百度地图,高德地图,谷歌地图,这时由于这些第三方库来自不同的类,所以无法继承自同一基类,但它们都具有类似的特征,故可以采用这种协议方法将类似功能的方法提取公开,结合类簇模式思想调取对应第三方库的实现。

29. 理解引用计数

  • retain: 递增保留计数
  • release: 递减保留计数
  • autorelease: 待稍后(通常是下一次"事件循环",event loop)清理自动释放池(autorelease pool)时,再递减保留计数

保留计数至少为1。若保留计数为正,则对象继续存活;若保留计数降为0,对象就被销毁。

30. 以ARC简化引用计数

  • ARC->MRC:-fno-objc-arc
  • MRC->ARC:-fobjc-arc

allocnewcopymutableCopy作为前缀的方法(这里要注意的是,后面接的词语首位必须是大写字母,即符合驼峰命名规则,详见《Objective-C高级编程:iOS与OS X多线程和内存管理》此书),其返回的对象"归调用者所有"(调用这四种方法的那段代码要负责释放方法所返回的对象,即autorelease不生效,需要手动额外抵消一次保留操作)。

31. 在dealloc方法中只释放引用并解除监听

ARC下dealloc方法中应该做的

32. 编写“异常安全代码”时留意内存管理问题

33. 以弱引用避免保留环

__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        // use strongSelf
    }
};

Weak-Strong-Dance防止block和对象间的循环引用不用多说,偷懒的写法是使用宏:

#ifndef weakify
#if __has_feature(objc_arc)

#define weakify( x ) \\
_Pragma("clang diagnostic push") \\
_Pragma("clang diagnostic ignored \\"-Wshadow\\"") \\
autoreleasepool{} __weak __typeof__(x) __weak_##x##__ = x; \\
_Pragma("clang diagnostic pop")

#else

#define weakify( x ) \\
_Pragma("clang diagnostic push") \\
_Pragma("clang diagnostic ignored \\"-Wshadow\\"") \\
autoreleasepool{} __block __typeof__(x) __block_##x##__ = x; \\
_Pragma("clang diagnostic pop")

#endif
#endif

#ifndef strongify
#if __has_feature(objc_arc)

#define strongify( x ) \\
_Pragma("clang diagnostic push") \\
_Pragma("clang diagnostic ignored \\"-Wshadow\\"") \\
try{} @finally{} __typeof__(x) x = __weak_##x##__; \\
_Pragma("clang diagnostic pop")

#else

#define strongify( x ) \\
_Pragma("clang diagnostic push") \\
_Pragma("clang diagnostic ignored \\"-Wshadow\\"") \\
try{} @finally{} __typeof__(x) x = __block_##x##__; \\
_Pragma("clang diagnostic pop")

#endif
#endif

RAC里的高级写法:

#define weakify(...) \\
    autoreleasepool {} \\
    metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)

#define strongify(...) \\
    try {} @finally {} \\
    _Pragma("clang diagnostic push") \\
    _Pragma("clang diagnostic ignored \\"-Wshadow\\"") \\
    metamacro_foreach(rac_strongify_,, __VA_ARGS__) \\
    _Pragma("clang diagnostic pop")

34. 以“自动释放池块”降低内存峰值

常用于for循环中,将循环体代码用@autoreleasepool{...}包裹起来,可以及时在每次循环后降低内存峰值,而不用等到线程执行下一次事件循环时

35. 用“僵尸对象”调试内存管理问题

  • 开启方式:Xcode -> Edit Scheme -> Run -> Diagnostics -> Enable Zombie Objects<br/
  • 原理:修改对象的isa指针,令其指向特殊的僵尸类,此僵尸类能够响应所有的选择子(通过“完整的消息转发机制”)。
  • 响应方式:如果消息接收者是僵尸对象(名称前缀为_NSZombie_),此时打印一条包含消息内容及其接收者的消息,然后终止程序。

调试时通过环境变量NSZombieEnabled开启此功能,系统将不再回收对象,而是将其转化为僵尸对象。

36. 不要使用retainCount

retainCount可能永远不返回0。因为有时系统会优化对象的释放行为,当保留计数为1且执行了递减操作,这时为了节省对象引用计数-1的开销,会在稍后的某个时间点直接回收。

  • 系统会尽可能把NSString实现成单例对象。如果是编译期常量,编译器会把NSString对象所表示的数据放到应用程序的二进制文件里,这样运行时无需再创建NSString对象。
  • NSNumber类似使用了“标签指针”(把与数值有关的全部消息都放在指针值里面)来标注特定类型(不包括浮点数对象)的数值,同样在运行时就无需再创建NSNumber对象。
  • 进行了以上两种方式优化的单例对象,其保留计数绝对不会变(很大的值)。这种对象的保留及释放都是空操作。

37. 理解“块”

语法结构:return_type (^block_name)(parameters)

38. 为常用的块类型创建typedef

使用简单,作为属性时应使用copy修饰

39. 用handler块降低代码分散程度

建议用同一个handler块来处理网络成功与失败情况,比如虽然网络请求成功了,但数据不符合预期,这时方便将错误信息用NSError一并返回

40. 用块引用其所属对象时不要出现保留环

41. 多用派发队列,少用同步锁

42. 多用GCD,少用performSelector方法

43. 掌握GCD及操作队列的使用时机

NSOperation是对GCD更高层次的封装,也提供了GCD无法实现(很难实现)的特性。关于它及NSOperationQueue的用法可参照 AFNetworking

44. 通过Dispatch Group机制,根据系统资源状况来执行任务

通过dispatch group可以在并发队列同时执行多项任务,并在这组任务执行完毕时获得通知。

45. 使用dispatch_once来执行只需运行一次的线程安全代码

+ (instancetype)sharedInstance { 
    static ManageClass *sharedInstance = nil; //每次执行会复用变量
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
        sharedInstance = [[self alloc] init]; 
    });
    return sharedInstance; 
} 

编写“只需执行一次的线程安全代码”,dispatch_once方式实现单例性能显然要高于同步锁机制@synchronized

46. 不要使用dispatch_get_current_queue

47. 熟悉系统框架

FoundationCoreFoundation提供的API多了解一下

48. 多用块枚举,少用for循环

49. 对自定义其内存管理语义的collection使用无缝桥接

50. 构建缓存时使用NSCache而非NSDictionary

NSCache优点在于当系统资源将要耗尽时,它会自动删减“最久未使用的”(LRU)缓存。

所以我选择用 YYCache

51. 精简initialize与load的实现代码

52. 别忘了NSTimer会保留其目标对象

NSTimer会保留目标,反复执行的计时器会造成保留环是个老生常谈的问题了,常常用的方法时创建一个分类,使用“块”来打破保留环

+ (NSTimer *)weak_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
                                     repeats:(BOOL)repeats
                                handlerBlock:(void(^)(void))handler {
    return [self scheduledTimerWithTimeInterval:timeInterval
                                         target:self
                                       selector:@selector(handlerBlockInvoke:)
                                       userInfo:[handler copy]
                                        repeats:repeats];
}

+ (NSTimer *)weak_timerWithTimeInterval:(NSTimeInterval)timeInterval
                                repeats:(BOOL)repeats
                           handlerBlock:(void (^)(NSTimer *timer))handler {
    return [NSTimer timerWithTimeInterval:timeInterval
                                   target:self
                                 selector:@selector(handlerBlockInvoke:)
                                 userInfo:[handler copy]
                                  repeats:repeats];
}

+ (void)handlerBlockInvoke:(NSTimer *)timer {
    void (^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}

笔记📒GitHub链接

读《Effective Objective-C 2.0:编写高质量iOS与OS X代码的52个有效方法》有感

上一篇下一篇

猜你喜欢

热点阅读