美文网首页
对象、消息、运行期

对象、消息、运行期

作者: 晨阳Xia | 来源:发表于2020-11-06 16:47 被阅读0次

[toc]

第 6 条 对象、消息、运行期

消息传递

在对象之间传递数据并执行任务的过程就叫做消息传递

属性

  1. 属性是Objective-C的一项特性,用于封装对象中的数据。
  2. 也可以把属性当作一种简称,其意思是说:编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。

作用域

public private protect

Objective-C 为什么很少定义变量的作用域

  1. 定义变量的作用域会导致对象布局在编译器就已经固定了(查看22页)
  2. 如果代码使用了编译器计算出来的偏移量,那么在修改类定义之后必须重新编译,否则就会出错。

dynamic

使用@dynamic关键字,他会告诉编译器:不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。而且,在编译访问属性的代码时,即使编译器发现没有定义存取方法,也不会报错,他相信这些方法能在运行期找到。

property

  1. 生成属性
  2. 如果使用了属性的话,那么编译器就会自动编写访问这些属性所需的方法,此过程称为“自动合成”。需要强调的是,这个过程有编译器在编译期执行,所以编辑器看不到这些“合成方法“的源代码。除了生成方法之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前加下划线,以此作为实例变量的名字。

synthesize

  1. 使用@synthesize语法来指定实例变量的名字。
  2. 可以解决setter getter方法不能同时存在的问题。

属性的特质:

原子性

  1. 只作用域setter 和 getter方法

读写权限

  1. 具备readwrite特质的属性拥有“获取方法”与“设置方法”。若属性由@synthesize实现,则编译器会自动生成这两个方法
  2. 具备readonly特质的属性仅拥有获取方法,只有该属性由@synthesize实现时,编译器才会为其生成获取方法。

内存管理的语义 (需要详细查看)

  1. assign:针对纯量类型
  2. strong:此特质表明该属性定义了一种“拥有关系”,为这种属性设置新值时,设置方法会先保留新值,并释放旧值,然后在将新值设置上去。
  3. weak:此特质表明了一种”非拥有关系“,为这种属性设置新值时,设置方法既不保留新值也不释放旧值,此特质同assign蕾丝,然而在属性所指的对象遭到摧毁时,属性值也会清空。
  4. copy:此特质所表达的所属关系与strong相似。然而设置方法并不保留新值,而是将其“拷贝”。

方法名:

  1. getter == name 指定“获取方法”的方法名
  2. setter = name 指定设置的方法名

为什么iOS开发中设置属性不用nonatomic

在iOS中使用同步锁的开销较大,这会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”,若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。

要点

  • 可以用@property语法来定义对象中锁封装的数据
  • 通过“特质“来指定存储数据所需的正确语义
  • 在设置属性所对应的实例变量时,一定要遵从该属性所声明的语义
  • 开发iOS程序时应该使用nonatomic属性,因为atomic属性会严重影响性能

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

直接访问_firstname的好处

  1. 由于不经过Objective-C的方法派发(第11条)步骤,所以直接访问实例变量的速度当然比较快。在这种情况下,编译器所生成的代码会直接访问保存对象实例变量的那块内存。如果调用属性,则会走类的方法列表
  2. 直接访问实例变量时,不会调用其“设置方法”,这就饶过了为相关属性所定义的“内存管理语义”

直接访问_firstname的坏处

  1. 如果直接访问实例变量,那么不会触发“键值观测” (kvo)通知
  2. 通过属性来访问,有助于排查与之相关的错误

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

isEqualString 比 isEqual快

因为后者要执行额外的步骤,因为他不知道受测对象的类型。

使用isEqual来判断对象等同性:

  1. 直接判断两个指针是否相同,相同直接返回yes
  2. 比较对象所属的类,不同则直接返回no
  3. 检测每个属性值是否相同
  4. 实现hash方法
    注意:有时候我们可能认为:一个EOCPerson实例可以与其子类(比如EOCSmithPerson)实例相等。在继承体系中判断等同性时,经常遭遇此类问题。所以实现 isEqual方法时需要考虑到这种情况

特定类所具有的等同性判定方法

详见 33页

等同性判定的执行深度

NSArry的检测方式为先看两个数组所含对象个数是否相等,若相等,则在每个对应位置的脸哥哥对象身上调用其“isEqual"方法。如果对应位置上的对象均相等,那么这两个数组就相等,这叫做“深度等同性判定”。

容器中可变类的等同性

把某个对象放入collection之后,就不应再改变其哈希码了。collection会把各个对象按照其哈希码分装到不同的“箱子数组”中。如果某对象在放入“箱子”之后哈希码又变了,那么现在所处的箱子对他来说是错误的

要点

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

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

  • 类族可以灵活应对多个类,经他们的实现细节隐藏在抽象积累后面,以保持接口整洁
  • 工厂模式是创建类族的办法之一
  • 系统框架中许多类族。大部分collection类都是类族。例如NSArry与其可变版本NAMutableArray。所以检测两个一个对象是不是数组的时候使用 [maybeAbArray class] == [NSArray class]是错误的。应该使用[maybeAnArray isKindOfClass:[NSArray class]]
  • NSArray 本身只不过是包在其他隐藏对象外面的壳,他仅仅定义了多有数组都需要具备的一些接口

要点

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

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

- (UIButton *)btn {
    if (_btn == nil) {
        _btn = [UIButton buttonWithType:UIButtonTypeCustom];
        _btn.backgroundColor = [UIColor redColor];
        _btn.frame = CGRectMake(100, 100, (self.view.frame.size.width - 100) / 2, 50);
        [_btn addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchUpInside];
        _btn.tag = 101;
    }
    void (^block)(NSInteger) = ^(NSInteger btnIndex){
        NSLog(@"%ld",(long)btnIndex);
    };
    
    objc_setAssociatedObject(_btn, XSYBtnKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
    
    return _btn;
}

- (void)clickBtn:(UIButton *)btn {
    void (^ block)(NSInteger) = objc_getAssociatedObject(btn, XSYBtnKey);
    block(btn.tag);
}

第 11 条 理解objc_msgSend的作用

消息传递

在对象上调用方法时Objective-C中经常使用的功能。用Objective-C的术语来说,这叫做“消息传递”。消息有“名称 或 “选择子”,可以接受参数,而且可能还有返回值
for example
id returnValue = [someObject messageName:parameter];
someObject 叫做“接受者”。messageName叫做“选择子”。选择子与参数合起来称为“消息”。
编译器看到此消息后,将其转换成一条标准的c语言函数调用,所调用的函数乃是消息传递机制中的核心函数,叫做objc_magSend,其“原型”如下:
void objc_msgSend(id self, SEL cmd, ..)

查找类中的方法列表

objc_msgSend函数会依据接受者与选择子的类型来调用适当的方法。 为了完成完成此操作,该方法需要在接受者所属的类中搜索其“方法列表”,如果能找到与选择子名称相符的方法,就跳至其实现代码。如是找不到就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。如果最终还是找不到相符的方法,那就执行“消息转发”操作。这么说来,香调用一个方法似乎需要很多的步骤。所幸的是objc_msgSend会将匹配结果缓存再“快速映射表”里面,每个类都有这样一块缓存,若是稍后还向该类发送与选择子相同的消息,那么执行起来就很快了。
选择子就是查表时所用的“键”

要点

  • 消息由接受者、选择子及参数构成。给某对象发送消息,也就相当于在该对象上“调用方法”
  • 发送给某对象的全部消息都要由“动态消息派发系统”(dynamic message dispatch system)来处理,该系统会查出对应的方法,并执行其代码

第 12 条 理解消息转发机制

当提示"unrecognized selector sent to instance" 时要执行消息转发的步骤

  1. 消息转发分为两个阶段:第一阶段先征询接受者所属的类,看其是否能动态添加方法,以处理当前这个“未知选择子”,这叫做“动态方法解析”(dynamic method resolution)
  2. 第二阶段涉及完整的消息转发机制(图 2-2)

动态方法解析

image.png
  1. 第一步:类中的方法类表中含有此选择子
  2. 如果没有则执行以下消息转发流程
  3. 第二步:进行动态方法解析,调用 +(BOOL)resolveInstanceMethod:(SEL)selector 动态添加方法。使用这种方法的前提是:相关方法的实现代码已经写好,只等着运行的时候动态插在类里面就可以了。比如实现的那个 XSYAutoDictionary
  4. 第三步:备援接受者,这一步无法操作转发的消息 。在这一步中,运行期系统会问它:能不能把这条消息转给其他接受者来处理。与该步骤对应的处理方法如下:-(id)forwardingTargetForSelector:(SEL)selector.若能找到备援对象,则将其返回,若找不到就返回nil,通过此方案,我们可以用“组合”来模拟出“多重继承”的某些特性。在一个对象内部可能还有一系列其他对象,该对象可经由此方法将能够处理某选择子的相关内部对象返回,这样的话,在外界看来好像是该对象亲自处理了这些消息似的。比如 nstimer的弱引用
  5. 第四步 完成整的消息转发 调用 -(void)forwardInvocation:(NSInvocation*)invocation 。首先创建NSInvocation对象,把与尚未处理的那条消息有关的全部细节都封于其中,此对象包括选择子、目标及参数。在出发NSInvocation对象是,“消息派发系统”将亲自出马,吧消息指派给目标对象。实现此方法时若发现某调用操作不应有本类处理,则需调用超类的同名方法。这样的话继承体系中的每个类都有机会处理此调用请求,直至NSObject。
  6. 如果最后调用了NSObject类的方法,那么该方法还会继而调用“doesNotRecognizeSelector:"以抛出异常,此异常表明选择子最终未能得到处理

要点

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

第 13 条 用方法调配技术调试黑盒方法

与给定的选择子相对应的方法实现也是可以改变的原因

类的方法列表会把选择子的名称映射到相应的方法实现上,使得“动态消息派发系统”能够据此找到应该调用的方法。这些方法均以函数指针的形式来表示,这种指针叫做 IMP,原型 id (*IMP)(id, SEL, ...)

要点

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

第 14 条 理解类对象的用意

消息接受者究竟是何物?

运行期系统是如何赵浩到某个对象的类型的呢?

“在运行期检视对象类型”这一操作也叫做“类型信息查询(内省)”这个强大而有用的特性内置于Fundation框架的NSObject协议里。

编译器无法确定某类型对象到底能解读多少种选择子,因为运行期还可向其中动态新增

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

“isMemberOfClass"能够判断出对象是否为某个特定类的实例,而“isKindOfClass"则能够判断出对象是否为某个派生类的实例

判断类对象是否相等

id object = /.../
if ([object class] == [EOCSomeClass class]) {
}
原因在于:类对象是“单利”,在应用程序范围内,每个类的Class仅有一个实例。

要点

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

相关文章

  • 对象、消息、运行期

    Objective-C作为面向对象编程,“对象”(object)就是“基本构造单元”(building block...

  • 对象、消息、运行期

    对象 --- 在Objective-C等面向对象语言编程时,“对象”(object)就是“基本构造单元”,开发...

  • 对象、消息、运行期

    [toc] 第 6 条 对象、消息、运行期 消息传递 在对象之间传递数据并执行任务的过程就叫做消息传递 属性 属性...

  • iOS 对象,消息,运行期

    Effective Objective C 2.0:编写高质量iOS与OS X代码的52个有效方法 第 6 条: ...

  • 2.对象、消息、运行期

    第6条 理解属性这一概念 __unsafe_unretained 跟 assign 类似,只不过它是修饰对象的,而...

  • Effective Objective-C 2.0读书笔记(二)

    对象、消息、运行期 “对象”就是“基本构造单元”(building block),开发者可以通过对象来存储并传递数...

  • 第2章 对象/消息/运行期

    用Objective-C等面向对象语言编程时"对象"(object)就是"基本构造单元"(building blo...

  • 第2章 对象、消息、运行期

    第6条:理解属性这一概念 可以使用@property语法来定义对象中所封装的数据。 通过“特质”来指定存取数据所需...

  • Effective Objective-C 2.0 总结与笔记(

    第二章:对象、消息、运行期 ​ “对象”就是“基本构造单元”,开发者可以通过对象来存储并传递数据。对象之间传递...

  • EOC 笔记 二(1)

    二、对象、消息、运行期(1) 『对象 object』是使用面向对象语言编程时的『基本构造单元』。 开发者可以通过对...

网友评论

      本文标题:对象、消息、运行期

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