美文网首页
Effective Objective-C 2.0读书笔记(2)

Effective Objective-C 2.0读书笔记(2)

作者: _桃夭大人_ | 来源:发表于2019-02-01 14:00 被阅读13次

第二章 对象、消息、运行期

第六条 理解属性这一概念

属性用于封装对象中的数据。

(1)属性的特质:
  • 原子性
    atomic(nonatomic非原子性 不使用同步锁)
  • 读写权限
    readwrite、readonly
  • 内存管理语义
    assign:修饰基本数据类型
    strong:表明该属性定义了一种“拥有关系”。在设置新值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。
    weak:表明一种“非拥有关系”。在为属性设置新值时,设置方法既不保留新值,也不释放旧值。在属性所指的对象销毁时,属性值也会清空。
    copy:在设置新值时,设置方法并不保留新值,而是将其“拷贝”。一般修饰 不可变类型。
    unsafe_unretained:适用于“对象类型”,表明“非拥有关系”,当目标对象销毁时,属性值不会自动清空。
  • 方法名
    getter=<name> 指定“获取方法”的方法名
    @property (nonatomic, assign ,getter=isOn) BOOL on;
    setter=<name>指定“设置方法”的方法名。

(2)atomic和nonatomic的qubie

atomic 获取方法会通过锁定机制来确保其操作的原子性。但是不能保证“线程安全”
但是由于在iOS中使用同步锁开销较大,会带来性能问题,所以 一般情况下 我们都是用nonatomic。

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

在读取实例变量的时候采用直接访问,在设置实例变量的时候通过属性来做

在初始化方法及dealloc 方法中,总是应该直接通过实例变量来读写数据

用惰性初始化时,需要通过属性来读取数据

第八条 理解“对象等同性”这一概念

等同性约定:若两对象相等,则其哈希码也相等,但是哈希码相等的两对象却未必相等。

==:比较的是两个指针本身
isEqual:判断两个对象的等同性
isEqualToString:比isEqual快

判断两个对象相等:重写isEqual 和 hash方法

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

“类族模式”可以灵活应对 多个类,将它们的实现细节隐藏在抽象基类后面,以保持接口的简洁。

  NSArray * array = @[@"1"];
    if ([array class] == [NSArray class]) {
        NSLog(@"不会进到这里");
// 因为由NSArray初始化方法所返回的那个实例其类型是隐藏在类族公共接口后面的某个内部类型。但是isKindOfClass是可以的
    }

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

“关联对象”:可以给某对象关联许多其他对象,这些对象通过“键”来区分,存储对象值时,可以指明“存储策略”用以维护相应的内存管理语义。

对象关联类型

关联对象方法

  • void obj_setAssociatedObject() 此方法以给定的键和策略为某对象设置 关联对象值。
  • id objc_getAssociatedObject() 此方法根据给定的键从某对象中获取相应的关联对象值。
  • void objc_removeAssociatedObjects() 此方法移除指定对象的全部关联对象。

关联对象用法

- (void)showAlert{
    UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"标题" message:@"副标题" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
    void(^block)(NSInteger) = ^(NSInteger buttonIndex){
        if (buttonIndex == 0) {
            [self doCancel];
        }else{
            [self doContinue];
        }
    };
// 给alert添加一个 key为 EOCMyAlertViewKey 内存管理方式为copy的关联对象 block
    objc_setAssociatedObject(alert, EOCMyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
    [alert show];
    
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
// 取出 alert 中 key为EOCMyAlertViewKey 的关联对象
    void(^block)(NSInteger) = objc_getAssociatedObject(alertView, EOCMyAlertViewKey);
// 执行方法
    block(buttonIndex);
}

第十一条 理解objc_msgSend的作用

在对象上调用方法用Objective-C的术语来说叫,“传递消息”。消息有 名称、选择器(selector)可以接受参数 ,也可返回值。

id someObject = [someObject messageName:parameter];
编译器 将其转成标准的C语言函数调用==>
void objc_msgSend(id self,SEL cmd,...) //(接收者,选择器,参数...)
==>
id retrunValue = void objc_msgSend(someObject,messageName,parameter)

objc_msgSend 在接收者所属的类中搜索其 “方法列表”,如果能找到与选择器名称相符的方法,就跳转至实现方法,如果找不到就沿着继承体系继续向上查找,等找到了 再跳转,如果最终找不到相符的方法,就执行“消息转发”操作
objc_msgSend会将匹配结果缓存在“快速映射表”里
其他边界情况:

  • objc_msgSend_stret
    如果待发送的消息要返回结构体,那么可交由此函数处理。只有当CPU的寄存器能够容纳下消息返回类型时,才能处理。
  • objc_msgSend_fpret
    如果消息返回的是浮点数,那么可交由此函数处理。(需要对“浮点数寄存器”做特殊处理)
  • objc_msgSendSuper
    如果要给超类发消息,那么交由此函数处理。([super message:parameter])

尾调用优化/尾递归优化:
只有当某函数的最后一个操作仅仅是调用其他函数而不会将其 返回值另做他用时,才执行“尾调用优化”。可以防止“栈溢出”。

第十二条** 理解消息转发机制**

当对象接收到无法解读的消息后,就会启动“消息转发”机制。
消息转发 机制流程:

  • 第一个阶段 先征询接收者
    所属的类,看其是否能动态添加方法,以处理当前这个“未知的选择器”这叫做“动态方法解析”
    动态方法解析:
// 看其类中 是否能新增一个实例方法 用来处理此 选择器
+ (BOOL)resolveInstanceMethod:(SEL)selector;
// 看其类中 是否能新增一个类方法 用来处理此选择器
+ (BOOL)resolveClassMethod:(SEL)selector;

使用这种办法的前提是:相关方法的实现代码已经写好,只等运行时动态插在里面就可以了。
代码演示

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    // 先将 选择器化为字符串
    NSString * selectorString = NSStringFromSelector(sel);
    // 检测是否表示 设置方法/获取方法
    if ([selectorString hasPrefix:@"set"]) {
        // 将该 选择器的方法加到类里面 所添加的方法为纯C函数实现的
        class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
    }else{
        class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
    }
    
    return [super resolveInstanceMethod:sel];
}
void autoDictionarySetter(int num1){
    
}

int autoDictionaryGetter(){
    
    return 63;
}
  • 第二个阶段 完整的消息转发机制
    首先,请接收者看看有没有其他对象能够处理这条消息。
  • 若有 则运行期系统会把消息转给那个对象(备援接收者)。
    该步骤对应处理的方法:
// 方法参数:未知选择器
- (id)forwardingTargetForSelector:(SEL)selector;
// 若当前接收者 能找到备援对象,则返回备援接收者;若找不到 返回 nil。
  • 若没有,则启动*完整的消息转发机制,运行期系统会把消息有关的全部细节封装到NSInvocation对象中(包括选择器,目标(target),参数),再给接收者最后一次机会,领其设法解决当前这条消息。

在触发NSInvocation对象是,“消息派发系统”会把消息指派给目标对象。
此步骤会调用下列方法来转发消息:

// 改变调用目标,使消息在新的目标上调用
- (void)forwardInvocation:(NSInvocation *)invocation;

消息转发全流程

消息转发

完整代码演示

@interface EOCAutoDictionary ()
@property (nonatomic, strong) NSMutableDictionary * backKingStore;

@end
@implementation EOCAutoDictionary
// 将属性声明为@dynamic 编译器不会自动为其自动生成实例变量及存取方法了;
@dynamic string, number, date, opaqueObject;
- (id)init{
    if (self = [super init]) {
        _backKingStore = [[NSMutableDictionary alloc]init];
    }
    return self;
}
// 动态方法解析 查看是否能动态的添加一个实例方法 来处理此选择器
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    // 为所有属性 动态添加setter getter 方法
    NSString * selectorString = NSStringFromSelector(sel);
    if ([selectorString hasPrefix:@"set"]) {
        class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
    }else{
        class_addMethod(self, sel , (IMP)autoDictionaryGetter, "@@:");
    }
    return YES;
}
// setter函数实现
void autoDictionarySetter(id self, SEL _cmd, id value){
    EOCAutoDictionary * typedSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary * backingStore = typedSelf.backKingStore;
    NSString * selectorString = NSStringFromSelector(_cmd);
    NSMutableString * key = [selectorString mutableCopy];
    [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    NSString * lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    if (value) {
        [backingStore setObject:value forKey:key];
    }else{
        [backingStore removeObjectForKey:key];
    }
}
// getter函数实现
id autoDictionaryGetter(id self, SEL _cmd){
    EOCAutoDictionary * typedSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary * backingStore = typedSelf.backKingStore;
    NSString * key = NSStringFromSelector(_cmd);
    
    return [backingStore objectForKey:key];
}
@end

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

方法调配
新功能将在本类的所有实例中生效,而不是仅限于覆写了相关方法的那些子类实例。
IMP
类的方法列表会把选择器的名称 映射到 相关的方法实现上,使得“动态消息派发系统”能够根据找到应该调用的方法。这些方法均以函数指针的形式来表示,这种指针叫做“IMP”,原型如下:
id ( IMP )( id, SEL, ...)*

改变了映射关系
  • 交换两个方法实现 函数
void method_exchangeImplementations(Method m1, Method m2)
  • 获取方法实现 函数
Method class_getInstanceMethod(Class aClass, SEL aSelector)

代码实例
可用于 为既有方法实现 增添新功能

Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
// 获取 方法实现
Method swappedmethod = class_getInstanceMethod([NSString class],@selector(uppercaseString));
// 交换方法
method_exchangeImplementations(originalMethod, swappedmethod);

通过此方案,开发者在调试时,可以为那些“完全不知道其具体实现的”黑盒方法增加日志记录功能。

第十四条 理解“类对象”的用意

Objective-C 对象的本质是什么?
每个Objective-C 对象实例 都是指向某块内存数据的指针。所以在声明变量时,类型前面跟着 “*”。

id类型对象结构体:

typedef struct objc_object {
       Class isa;
} *id;
  • 每个对象结构体的首个成员是Class类的变量,
  • 该变量定义了对象所属的类,通常称为“isa”指针。
    “isa”指针就指向NSString。

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;
};

此结构体存放类的元数据(metadata),例如实例实现方法表、实例变量表
元类(metaclass)
类对象所属的类型(也就是isa指针所指向的类型)是另外一个类,叫做元类。用来表述 类对象本身所具有的元数据。

image.png

每个类仅有一个“类对象”,而每个“类对象”仅有一个与之相关的“元类”

super_class 指针确立了继承关系,isa指针描述了实例所属的类

在类继承体系中查询类型信息
isMemberOfClass:能够判断出对象是否 为某个特定类的实例。
isKindOfClass:能够判断出来对象是否 为某类或其派生类的实例。
isKindOfClass >> isMemberOfClass

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

相关文章

网友评论

      本文标题:Effective Objective-C 2.0读书笔记(2)

      本文链接:https://www.haomeiwen.com/subject/yhycsqtx.html