Effective Objective-C 2.0(52个有效方法) —— 对象、消息、运行期 读书笔记

这里写图片描述

用Objective-C等面向对象语言编程时,“对象”就是“基本构造单元”,开发者可以通过对象来存储并传递数据,在对象之间传递数据并执行任务的过程就叫做“消息传递”;
应用程序运行起来以后,为其提供相关支持的代码叫做“Objective-C运行期环境”,它提供了一些使得对象之间能够传递消息的重要函数,并且包含创建类实例所用的全部逻辑

6、理解“属性”这一概念
可以用“@property”语法来定义对象中所封装的数据;
通过“特质”来指定存储数据所需的正确语义;
原子性(atomic,nonatomic)
读/写权限 (readwrite,readonly)
内存管理语义
assign:“设置方法”只会执行针对“纯量类型”的简单赋值操作;
Strong:表明该属性定义了一种“拥有关系”,为这种属性设置新值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去;
Weak:表明该属性定义了一种“非拥有关系”,为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似,然而在属性所指的对象遭到摧毁时,属性值也会清空
unsafe_unretained:这与assign相同,但是它适用于“对象类型”,该特质表达一种“非拥有关系”,当目标对象遭到摧毁时,属性值不会自动清空,这一点与weak有区别
Copy:表达的所属关系与strong类似,然而设置方法并不保留新值,而是将其“拷贝”(copy)

在设置属性所对应的实例变量时,一定要遵从该属性所声明的语义;
开发iOS程序时应该使用nonatomic属性,因为atomic属性会严重影响性能;

@dynamic关键字,它会告诉编译器:不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。

7、在对象内部尽量直接访问实例变量
在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应通过属性来写。
在初始化方法及dealloc方法中,总是应该直接通过实例变量来读写数据;
有时会使用惰性初始化技术配置某份数据,这种情况下,需要通过属性来读取数据

8、理解“对象等同性”这一概念
若想检测对象的等同性,请提供“isEqual:” 与hash方法
相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同
不要盲目的逐个检测每条属性,而是应该依照具体需求来制定检测方案
编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法;

#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSUInteger age;
@end
#import "Person.h"
@implementation Person
- (BOOL)isEqualToPerson:(Person *)otherPerson {
    if (self == otherPerson) return YES;
    if (![_name isEqualToString:otherPerson.name]) return NO;
    if (_age != otherPerson.age) return NO;
    return YES;
}
- (BOOL)isEqual:(id)object {
    if ([self class] == [object class]) {
        return [self isEqualToPerson:(Person *)object];
    } else {
        return [super isEqual:object];
    }
}
/*
- (BOOL)isEqual:(id)object {
    if (self == object) return YES;
    if ([self class] != [object class]) return NO;
    Person *otherPerson = (Person *)object;
    if (![_name isEqualToString:otherPerson.name])return NO;
    if (_age != otherPerson.age) return NO;
    return YES;
}
*/
- (NSUInteger)hash {
//    return 1222;
//    NSString *stringToHash = [NSString stringWithFormat:@"%@:%i",_name,_age];
//    return [stringToHash hash];
    NSUInteger nameHash = [_name hash];
    NSUInteger ageHash = _age;
    return nameHash ^ ageHash;
}

首先,直接判断两个指针是否相等,若相等,则其均指向同一对象,受测对象也必定相等,接下来,比较两对象所属的类,若不属于同一个类,则两对象不等。最后,检测每个属性是否相等。只要其中有不相等的属性,就判定两对象不等,否则两对象相等

9、以“类族模式”隐藏实现细节
类族模式可以把实现细节隐藏在一套简单的公共接口后面;
系统框架中经常使用类族
从类族的公共抽象基类中继承子类时要当心,若有开发文档,则应首先阅读;

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

#import "ViewController.h"
#import <objc/runtime.h>
static void *MyAlertViewKey = "MyAlertViewKey";
@interface ViewController ()<UIAlertViewDelegate>
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"信息" delegate:self cancelButtonTitle:@"NO" otherButtonTitles:@"YES", nil];
    void (^block)(NSInteger) = ^(NSInteger buttonIndex){
        if (buttonIndex == 0) {
            NSLog(@"NO");
        } else {
            NSLog(@"YES");
        }
    };
    objc_setAssociatedObject(alert, MyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
    [alert show];
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    void(^block)(NSInteger) = objc_getAssociatedObject(alertView, MyAlertViewKey);
    block(buttonIndex);
}

此方法根据给定的键从某对象中获取相应的关联对象值
objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>);
此方法以给定的键和策略为弄对象设置关联对象值
objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>);
此方法移除指定对象的全部关联对象
objc_removeAssociatedObjects(<#id _Nonnull object#>)
可以通过“关联对象”机制来把两个对象连起来;
定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系(保留)”与“非拥有关系(不保留)”
只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于查找的bug;

11、理解objc_msgSend的作用
“静态绑定”(在编译期就能决定运行时所应调用的函数)

#import <stdio.h>
void printHello() {
printf(“Hello, world!\n”);
}

void printGoodbye() {
printf(“Goodbye, world!\n”);
}

void doTheThing(int type) {
if(type == 0) {
printHello();
}else {
printGoodbye();
}
}

“动态绑定”(所要调用的函数直到运行期才能确定)

#import <stdio.h>
void printHello() {
printf(“Hello, world!\n”);
}

void printGoodbye() {
printf(“Goodbye, world!\n”);
}

void doTheThing(int type) {
void (*func)();
if(type == 0) {
func = printHello;
}else {
func =printGoodbye;
}
func();
return 0;
}

消息由接收者,选择子及参数构成,给某对象“发送消息”(invoke a message)也就相当于在该对象上“调用方法”(call a method);
发给某对象的全部消息都要有“动态消息派发系统”(dynamic message dispatch system)来处理,该系统会查出对应的方法,并执行其代码;
objc_msgSend()
objc_msgSend 函数会依据接收者与选择子的类型来调用适当的方法,为了完成此操作,该方法需要接收者所属的类中搜寻其“方法列表”(list of methods),如果能找到与选择子名称相符的方法,就跳至其实现代码。若是找不到,那就沿着继承体系继续向上查找,等找到合适的方法之后再跳转,如果最终还是找不到相符的方法。那就执行“消息转发”(message forwarding)操作
这么说来。想调用一个方法似乎需要很多步骤,所幸objc_msgSend会将匹配结果缓存在“快速映射表”(fast map)里面。每个类都有这样一块缓存,若是稍后还向该类发送与选择子相同的消息,那么执行起来就很快了
objc_msgSend_stret
objc_msgSend_fpret
objc_msgSendSuper

12、理解消息转发机制
若想令类能理解某条消息,我们必须以程序码实现出对应的方法才行,但是,在编译期向类发送了其无法解读的消息并不会报错。因为在运行期可以继续向类中添加方法。所以编译器在编译时还无法确知类中到底会不会有某个方法实现。当对象接收到无法解读的消息后,就会启动“消息转发”(message forwording)机制,程序员可经由此过程告诉对象应该如何处理未知消息

动态方法解析

  • (BOOL)resolveInstanceMethod:(SEL)selector;

备援接收者

  • (id)forwordingTargetForSelector:(SEL)selector;

完整的消息转发

  • (void)forwordInvocation:(NSInvocation *)invocation;

若对象 无法响应某个选择子,则进入消息转发流程;
通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中
对象可以把其无法解读的某些选择子转交给其他对象来处理
经过上述两步之后,如果还是没有处理选择子,那就启动完整的消息转发机制;

这里写图片描述

13、用“方法调配技术” 调试“黑盒方法”
类的方法列表会把选择子的名称映射到相关的方法实现之上,使得“动态消息派发系统”能根据此找到应该调用的方法。这些方法均匀函数指针的形式来表示,这种指针叫做IMP,原型如下:
id (* IMP)(id, SEL, …)

void method_exchangeImplementations(Method m1, Method m2)
此函数的两个参数表示待交换的两个方法实现,

Method class_getInstanceMethod(Class aClass, SEL aSelector)
此函数根据给定选择从类中取出与之相关的方法

在运行期,可以向类中新增或替换选择子所对应的方法实现
使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”, 开发者常用此技术向原有实现中添加新功能

一般来说,只有调试程序的时候才需要运行期修改方法实现,这种做法不滥用
这里写图片描述

14、理解“类对象”的用意
每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系
如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知
尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象。因为某些对象可能实现了信息转发功能;

typedef struct objc_object {
Class isa;
} id;
每个对象结构体的首个成员是Class类的变量。该变量定义了对象所属的类,通常称为
*“isa”指针**。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/heqiang2015/article/details/82591921