《Effective Objective-C 2.0》读书笔记.md

[TOC]

多用类型常量,少用 #define

  1. 对于局部常量(.m文件中),
  • 使用 static 声明表明变量只在本文件中可见,所以无需前缀
  • 同时使用 static const 关键字与#define 效果相同,好处在于带有类型信息。
static const CGFloat kTopViewHeight = 40;
  1. 对于全局常量
  • 由于全局使用,使用类名做前缀
  • 在 .h 文件中使用 extern 声明
extern NSString * const KKSlideTabBarBgColor;
  • 在 .m 文件中定义
NSString * const KKSlideTabBarBgColor = @"name";

NS_ENUM 与 NS_OPTIONS

  • 使用 NS_ENUMNS_OPTIONS 可以指定底层数据类型,而且可以保证系统兼容
  • 当多种状态可以互相组合时,使用 NS_OPTIONS,否则使用 NS_ENUM
  • 命名规则:前缀+状态
typedef NS_ENUM(NSUInteger, GDFConnectionState) {
    GDFConnectionStateDisconnected,
    GDFConnectionStateConnecting,
    GDFConnectionStateConnected,
};

    GDFConnectionStateDisconnected,
    GDFConnectionStateConnecting,
    GDFConnectionStateConnected,
};

typedef NS_OPTIONS(NSUInteger, GDFDirection) {
    GDFDirectionUp    = 1 << 0,
    GDFDirectionDown  = 1 << 1,
    GDFDirectionLeft  = 1 << 2,
    GDFDirectionRight = 1 << 3,
};

消息转发 message forwarding

《Effective Objective-C 2.0》读书笔记.md_第1张图片
e28856e7825cc2b7113288d11b2f1be6.png

动态方法解析 resolve method

  • 动态方法解析是消息转发的第一步,在这里处理,效率最高
    @dynamic 属性 使编译器不自动生成实例变量及存储方法
    调用的方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector

代码参见:Runtime.md 动态方法解析 resolve method

方法调配 method swizzling

  1. 作用1:在运行死交换两个方法的实现
// 根据方法名找到方法的实现
class_getInstanceMethod(__unsafe_unretained Class cls, SEL name)
// 交换两个方法的实现
method_exchangeImplementations(Method m1, Method m2)
  1. 作用2:为既有的方法实现添加新功能——调试黑盒方法,为完全不知道具体实现的方法添加日志功能


    《Effective Objective-C 2.0》读书笔记.md_第2张图片
    fed427a97a343ee16f2c5edc37065689.png
@implementation NSString (EOC)

+ (void)load {
    Method originalMethod = class_getInstanceMethod([NSString class],
                                                    @selector(lowercaseString));
    Method swappedMethod = class_getInstanceMethod([NSString class],
                                                   @selector(eoc_myLowercaseString));
    method_exchangeImplementations(originalMethod, swappedMethod);
}

- (NSString *)eoc_myLowercaseString {
    NSString *lowercase = [self eoc_myLowercaseString];
    NSLog(@"%@ => %@", self, lowercase);
    return lowercase;
}

类对象

// 对象结构体
// isa 指针指向类对象
struct objc_object {
    Class isa;
};

// 类结构体
// 1. 这个结构存放类的元数据,实例中的方法,变量等信息就存储在类对象中
// 2. isa 指针指向元类(metaclass),元类描述类对象本身所具有的元数据,类方法就存储在元类中
// 3. 每个类只有一个类对象,每个类对象只有一个元类
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;
};
《Effective Objective-C 2.0》读书笔记.md_第3张图片
f81c1ab1af0d8cee47e96db2568859d0.png

前缀

  1. 苹果保留了两字符前缀,我们应该使用三字符前缀
  2. C函数名应该加上前缀
  3. 头文件中的全局变量需要加上前缀
  4. 为私有方法名添加前缀(例如p_),用于区分哪些方法是私有的,哪些是公有的,私有方法可以随意改动,公有方法则要三思而后行。
  5. 为第三方类添加分类时,分类中的方法要增加前缀,可以避免覆盖原有方法。

使用段位缓存代理能否响应某个方法

  • 段位:结构体可以设置其成员所占的二进制位数
struct {
    unsigned int fieldA : 8; // fieldA 占 8 个二进制位,即 0 ~ 255
    unsigned int fieldB : 4;
    unsigned int fieldC : 2;
    unsigned int fieldD : 1;}_delegateFlag;
  • 实例:
@protocol KKSlideTabBarViewDelegate 
@optional;
- (void)slideTabBarView:(KKSlideTabBarView *)tabBar pageChangedFromIndex:(NSUInteger)from toIndex:(NSUInteger)to;
- (void)slideTabBarView:(KKSlideTabBarView *)tabBar itemMoreClicked:(UIButton *)itemMore;
@end
@interface KKSlideTabBarView : UIView
@property (nonatomic,weak) id  delegate;
@end
@implementation KKSlideTabBarView {   
    struct {
        unsigned int didPageChangedHandle    : 1;
        unsigned int didItemMoreClickdHandle : 1;
    }_delegateFlags;
}
- (void)setDelegate:(id)delegate {
    _delegate = delegate;
    _delegateFlags.didPageChangedHandle = [self.delegate respondsToSelector:@selector(slideTabBarView:pageChangedFromIndex:toIndex:)];
    _delegateFlags.didItemMoreClickdHandle = [self.delegate respondsToSelector:@selector(itemMoreClicked:)];

}
- (void)itemMoreClicked:(UIButton *)sender
{
    if (_delegateFlags.didItemMoreClickdHandle) {
        [self.delegate slideTabBarView:self itemMoreClicked:sender];
    }}
}

NSOperation 与 GCD 优缺点

  • GCD 优点:纯 C api,更加轻量级。而operation 是对象,更加重量级
  • NSOperation 优点:
  1. 操作加入队列后可以取消(已经启动的任务无法取消)
  2. 可以自动操作之间的依赖关系
  3. 可以使用 KVO 监控 NSOperation 对象的属性,比如通过 isCancelled 判断任务是否取消, isFinished 属性判断任务是否完成
  4. 可以指定每个操作的优先级,而 GCD 只能指定队列的优先级
  5. 可以自定义 operation 对象

使用 NSCache 和 NSPurgeableData 缓存数据

  • 只有费时操作才值得放入缓存,比如需要从网络获取的数据、从磁盘读取的数据
@interface KKSlideTabBarViewController ()
{
    NSCache *_cache;
}
@end

_cache = [NSCache new];
_cache.countLimit = 100;
_cache.totalCostLimit = 5 * 1024 * 1024;

NSPurgeableData *cacheData = [_cacheobjectForKey:@"url..."];
if (cacheData) {
    // stop the data being purged
    [cacheData beginContentAccess];
   
    // 使用数据
    // ......
   
    // Mark that the data may be purged again
    [cacheData endContentAccess];
} else {
    NSData *data = [NSData dataWithContentsOfURL:@"xxx"];
    NSPurgeableData *purgeableData = [NSPurgeableData dataWithData:data];
    [_cache setObject:purgeableData
               forKey:@"url..."
                 cost:data.length];
   
    // With access already maked
   
    // user data
    // .....
   
    // Mark that the data may be purged now
    [purgeableData endContentAccess];
}

精简 + load 与 + initialize 方法

它们都是在类载入系统时由运行时系统调用,不能手动调用。

+ load 方法:
在类加载时调用。
系统会先调用父类的 load 再调用子类的 load,先调用类本身 load,再调用 categery load。
在 load 方法中,运行时系统处于脆弱状态,不能确定其他类是否加载完毕。不能在 load 方法中使用其他类对象,因为无法知道这个类是否加载了。
+ initialize 方法
+ initialize 方法是惰性加载,使用到类时才会调用。
+ initialize 方法调用时,系统处于正常状态,可以在 + initialize 方法中使用其他类对象。
+ initialize 方法一定会在线程安全的环境中执行,那么执行 + initialize 方法时会阻塞其他线程
如果子类没有实现 + initialize 方法,而父类实现了,那么会调用夫类的。所以应该这么实现:

+ (void)initialize
{
    if (self == [KKSlideTabBarView class]) {
        // 只有当 KKSlideTabBarView 类载入系统时才执行这里的代码
        // 不然的话 KKSlideTabBarView 的父类载入系统也会调用 initialize
    }
}

你可能感兴趣的:(《Effective Objective-C 2.0》读书笔记.md)