1.运行时
  • OC 语言由 Smalltalk(20世纪70年代出现的一种面向对象的语言) 演化而来,后者是消息型语言的鼻祖。
  • OC 使用动态绑定的消息结构,在运行时检查对象类型。
  • 使用消息结构的语言,其运行时执行的代码由运行环境来决定。而使用函数调用的语言,则由编译器决定。
  • OC 对象所占内存总是分配在“堆空间”,绝不会分配在“栈”上。分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存会在栈帧弹出时自动清理。
  • OC 运行期内存管理架构,名叫“引用计数”。
  • OC 中会遇到定义中不含 * 的变量,也是会使用“栈空间”。比如 CoreGraphics 框架的 CGRect,整个系统框架都在使用这种结构体,如果使用 OC 对象,性能会受影响。
2.属性
  • 用来封装对象中的数据。
  • 如果使用了属性的话,那么编译器就会主动编写访问这些属性所需的方法,此过程叫做“自动合成”。这个过程在编译期执行,点语法是编译时特性。
  • @synthesize 语法来指定实例变量的名字。
@synthesize testString = _testString;
  • @dynamic 关键字会告诉编译器不要创建实现属性所用的实例变量,也不要为其创建存取方法。而且,在编译访问属性的代码时,即使编译器发现没有定义存取方法也不会报错,它相信这些方法能在运行时找到。
@dynamic testString;
_testString //Use of undeclared identifier '_testString'
  • 属性特质的设定也会影响编译器所生成的存取方法。属性特质包括:原子性、读写属性、内存管理语义(assign、strong、weak、unsafe_unretained、copy)、方法名(getter==…)。
  • 如果想在其他方法里设置属性值,同样要遵守属性定义中所宣称的语义,因为“属性定义”就相当于“类”和“待设置的属性值”之间所达成的契约

    比如在指定构造器中对成员变量的赋值
@interface TestObject ()
//虽然这个属性已经是只读性质,也要写上具体的语义,以此表明初始化方法在设置这些值时所用方法
@property(copy,readonly) NSString *testString;
@end @implementation TestObject
- initWithString:(NSString *)string { self = [super init]; if (self) {
//用初始化方法设置好属性值之后,就不要再改变了,此时属性应设为“只读”
_testString = [string copy];
}
return self;
}
@end
3.对象等同性
  • “==”操作符比较的是两个指针本身,而不是所指的对象。应该使用 NSObject 协议中声明的“isEqual”方法来判断两个对象的等同性。
    NSString *textA = @"textA";
NSString *textAnother = [NSString stringWithFormat:@"textA"];
NSLog(@"%d",textA == textAnother);// 0
NSLog(@"%d",[textA isEqual:textAnother]);// 1
NSLog(@"%d",[textA isEqualToString:textAnother]);// 1
  • 在自定义的对象中正确复写“isEqual”"hash"方法,来判定两个方法是否相等。
  • 如果 “isEqual”方法判定两个对象相等,那么其 hash 方法也必须返回同一个值。

    比如下面这个类
@interface TestObject : NSObject
@property NSString *testString;
@end @implementation TestObject
- (BOOL)isEqual:(id)object {
if (self == object) return YES;
if ([self class] != [object class]) return NO; TestObject *otherObject = (TestObject *)object;
if (![self.testString isEqualToString:otherObject.testString]) {
return NO;
}
return YES;
}
-(NSUInteger)hash {
//在没有性能问题下,hash 方法可以直接返回一个数
return 1227;
} @end

在继承体系中判断等同性,还需判断是否是其子类

相同的对象必须具有相同的哈希码,但是相同哈希码的对象却未必相同

特定类型等同性判断
  • 自己创建等同性判断方法,无需检测参数类型,大大提升检测速度。就像“isEqualToString”一样。
- (BOOL)isEqualToTestObject:(TestObject *)testobject {

    if (self == testobject) {
return YES;
}
if (![self.testString isEqualToString:testobject.testString]) {
return NO;
}
return YES;
}
- (BOOL)isEqual:(id)object { if ([self class] == [object class]) {
return [self isEqualToTestObject:(TestObject *)object];
}else {
return [super isEqual:object];
}
}
  • 有时候无需将所有数据逐个比较,只根据其中部分数据即可判明二者是否相等。

比方说一个模型类的实例是根据数据库的数据创建而来,那么其中可能会含有一个唯一标识符(unique identifier),在数据库中用作主键。这时候,我们就可以根据标识符来判定等同性,尤其是此属性声明为 readonly 时更应该如此。只要标识符相等,就可以说明这两个对象是由相同数据源创建,据此断定,其他数据也相等。

当然,只有类的编写者才知道那个关键属性是什么。

要点:不要盲目的逐个检测每条属性,而是应该按照具体需求制定检测方案

4.理解 objc_msgSend
  • 在对象上调用方法是 OC 中经常使用的方法。专业术语叫做“传递消息”,消息有名称(或叫选择子),可以接受参数,或许还有返回值。
  • 在 OC 中,对象收到消息之后,究竟该调用哪个方法完全于运行期决定,甚至可以在运行时改变,这些特性使 OC 成为一门真正的动态语言。

    给对象发送消息可以这样写:

id value = [obj messageName:parameter]

obj 叫做接收者,messageName 叫做 selector,selector 和参数合起来称为消息

编译器看到此消息后,将其转换为一条标准的 C 语言函数调用

void objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

第一个参数代表接收者,第二个代表 selector(SEL是selector类型)

这是个“参数个数可变的函数”,”…“ 代表后续参数,就是消息中的参数

  • objc_msgSend 方法在接收者所属的类中搜寻其”方法列表“,如果能找到与 selector 名称相符的方法,就跳至其实现代码。若是找不到,就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。如果最终还是找不到相符的方法,就执行”消息转发“(在之后解释)。
  • 看起来,想调用一个方法似乎需要很多步骤。所幸 objc_msgSend 会将匹配结果缓存在”快速映射表“中,每个类都有这样一个缓存,若是后来还向该类发送相同的消息,那么执行起来就会很快了。
  • 其他消息发送函数
//Sends a message with a simple return value to the superclass of an instance of a class.
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
//Sends a message with a data-structure return value to an instance of a class.
objc_msgSend_stret(id _Nullable self, SEL _Nonnull op, ...)
//Sends a message with a data-structure return value to the superclass of an instance of a class.
objc_msgSendSuper_stret(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
  • 刚才提到 objc_msgSend 找到合适的方法之后,就会”跳转过去“。之所以可以这样做是因为 OC 对象的每个方法都可以视为简单的 C 函数,原型如下:
<return_type> Class_selector(id self, SEL _cmd, ...)

每个类都有一张表格,selector 的名称就是查表时所用的 key

  • 原型的样子和 objc_msgSend 很像,而且函数的最后一项操作是调用另一个函数而且不会将其返回值另作他用,就可以利用”尾调用优化“技术,令”跳至方法实现“变得简单。

尾调用技术:编译器会生成跳转至另一函数所需的指令码,而且不会向调用堆栈中推入新的”栈帧“

  • 要点:发给某对象的全部消息都要由“动态消息派发系统”来处理,该系统会查出对应的方法,并执行其代码
5.消息转发机制
  • 对象在收到无法解读的消息之后会发生什么情况?如果在控制台中看到上面这种提示信息,那就说明你给对象发送了一条其无法解读的消息,启动了消息转发机制
- 因为在运行期可以继续向类中添加方法,所以编译器在编译期还无法确知类中是否有某个方法的具体实现。
- 当对象接收到无法解读的消息,就会触发“消息转发机制”,程序员可以经由此过程告诉对象如何处理未知消息

消息转发分为两大阶段

  • 第一阶段:征询接收者,能否动态添加方法,处理当前这个“未知的 selector”
//当未知的 selector 是实例方法时的调用
+ (BOOL)resolveInstanceMethod:(SEL)sel ;
//当未知的 selector 是类方法时的调用
+ (BOOL)resolveClassMethod:(SEL)sel;

使用这种办法的前提是:相关方法的实现代码已经写好,只等着运行时加入类里面

  • 第二阶段:运行时系统会请求接收者以其他手段处理与消息相关的方法调用,可细分为两小步。
//第一步:询问能不能把未知的消息转给其他接收者处理
- (id)forwardingTargetForSelector:(SEL)aSelector ;

若当前接收者能找到备援接收者,则将其返回,若找不到,则返回 nil

-如果返回一个对象,则运行期系统把消息转给那个对象,于是消息转发结束

如果返回 nil,执行第二步

《Effective Objective-C》概念篇的更多相关文章

  1. Linux Capabilities 入门教程:概念篇

    原文链接:Linux Capabilities 入门教程:概念篇 Linux 是一种安全的操作系统,它把所有的系统权限都赋予了一个单一的 root 用户,只给普通用户保留有限的权限.root 用户拥有 ...

  2. 【转】android 电容屏(二):驱动调试之基本概念篇

    关键词:android  电容屏 tp 工作队列 中断 多点触摸协议平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台:S5PV310(samsung ...

  3. 我的TDD实践---TDD概念篇

    “我的TDD实践”系列之TDD概念篇 写在前面: 我的TDD实践这几篇文章主要是围绕测试驱动开发所展开的,其中涵盖了一小部分测试理论,更多的则是关注工具的使用及环境的搭建,做到简单实践先行,后理论专精 ...

  4. DNA拷贝数变异CNV检测——基础概念篇

    DNA拷贝数变异CNV检测——基础概念篇   一.CNV 简介 拷贝数异常(copy number variations, CNVs)是属于基因组结构变异(structural variation), ...

  5. 【黑金原创教程】 FPGA那些事儿《概念篇》

    简介一本讲述非软硬片上系统的书,另外还是低级建模的使用手册. 目录[黑金原创教程] FPGA那些事儿<概念篇>:File01 - 结构的玩笑[黑金原创教程] FPGA那些事儿<概念篇 ...

  6. 常见面试题整理--Python概念篇

    希望此文可以长期更新并作为一篇Python的面试宝典.每一道题目都附有详细解答,以及更加详细的回答链接.此篇是概念篇,下一篇会更新面试题代码篇. (一).这两个参数是什么意思:*args,**kwar ...

  7. 【转帖】H5 手机 App 开发入门:概念篇

    H5 手机 App 开发入门:概念篇 http://www.ruanyifeng.com/blog/2019/12/hybrid-app-concepts.html 作者: 阮一峰 日期: 2019年 ...

  8. 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 百篇博客分析OpenHarmony源码 | v62.01

    百篇博客系列篇.本篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 51.c.h.o 本篇开始说文件系统,它是内核五大模块之一,甚至有Linux的设计哲学是" ...

  9. 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 百篇博客分析OpenHarmony源码 | v43.02

    百篇博客系列篇.本篇为: v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里 ...

随机推荐

  1. Linux里使用rz和sz命令

    lrzsz是一个unix通信套件提供的X,Y,和ZModem文件传输协议,官网:http://freecode.com/projects/lrzsz/ windows 需要向centos服务器上传文件 ...

  2. Mongoose 预定义模式修饰符 Getters 与 Setters 自定义修饰符

    mongoose 预定义模式修饰符 mongoose 提供的预定义模式修饰符,可以对我们增加的数据进行一些格式化,主要有:lowercase.uppercase .trim,这里不一一演示,对trim ...

  3. Codeforces 828F Best Edge Weight - 随机堆 - 树差分 - Kruskal - 倍增算法

    You are given a connected weighted graph with n vertices and m edges. The graph doesn't contain loop ...

  4. .htaccess tricks总结

    目录 .htaccess tricks总结 一.什么是.htaccess 二.利用条件 三.利用方式 && tricks 1.将指定后缀名的文件当做php解析 2.php_value利 ...

  5. JAVA字符编码二:Unicode,ISO-8859,GBK,UTF-8编码及相互转换

    第二篇:JAVA字符编码系列二:Unicode,ISO-8859-1,GBK,UTF-8编码及相互转换   1.函数介绍 在Java中,字符串用统一的Unicode编码,每个字符占用两个字节,与编码有 ...

  6. Oracle系列十四 序列、索引和同义词

    序列 : 提供有规律的数值.索引  : 提高查询的效率同义词  :给对象起别名 序列: 可供多个用户用来产生唯一数值的数据库对象 自动提供唯一的数值 共享对象 主要用于提供主键值 将序列值装入内存可以 ...

  7. SSAS 项目部署失败的问题

    在创建SSAS项目过程中,创建数据源.数据源视图.多维数据集.纬度等一切都没有问题.但是在“进程”这一步的时候,发现总是报错,提示如下.OLE DB 错误: OLE DB 或 ODBC 错误 : 用户 ...

  8. Ubuntu14 配置开机自启动/关闭

    1.ubuntu默认运行级别为2(runlevel),所以在/etc/rc2.b中S开头的链接文件(连接到/etc/init.d)就是自启动项.不想开机自动启动可以把S开头的文件重命名或删除,重命名好 ...

  9. LeetCode_448. Find All Numbers Disappeared in an Array

    448. Find All Numbers Disappeared in an Array Easy Given an array of integers where 1 ≤ a[i] ≤ n (n  ...

  10. 〓经典文字MUD武侠游戏 我的江湖 〓

    〓经典文字MUD武侠游戏 我的江湖 〓 我的江湖(FFLIB)基于地狱内核扩展,目前已扩展了很多实用功能! 我的江湖玩法 和掌心西游.书剑.东方故事.侠缘.武林等玩法大同小异 但扩展了更多好玩的玩法, ...