《Effective Objective-C》概念篇
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》概念篇的更多相关文章
- Linux Capabilities 入门教程:概念篇
原文链接:Linux Capabilities 入门教程:概念篇 Linux 是一种安全的操作系统,它把所有的系统权限都赋予了一个单一的 root 用户,只给普通用户保留有限的权限.root 用户拥有 ...
- 【转】android 电容屏(二):驱动调试之基本概念篇
关键词:android 电容屏 tp 工作队列 中断 多点触摸协议平台信息:内核:linux2.6/linux3.0系统:android/android4.0 平台:S5PV310(samsung ...
- 我的TDD实践---TDD概念篇
“我的TDD实践”系列之TDD概念篇 写在前面: 我的TDD实践这几篇文章主要是围绕测试驱动开发所展开的,其中涵盖了一小部分测试理论,更多的则是关注工具的使用及环境的搭建,做到简单实践先行,后理论专精 ...
- DNA拷贝数变异CNV检测——基础概念篇
DNA拷贝数变异CNV检测——基础概念篇 一.CNV 简介 拷贝数异常(copy number variations, CNVs)是属于基因组结构变异(structural variation), ...
- 【黑金原创教程】 FPGA那些事儿《概念篇》
简介一本讲述非软硬片上系统的书,另外还是低级建模的使用手册. 目录[黑金原创教程] FPGA那些事儿<概念篇>:File01 - 结构的玩笑[黑金原创教程] FPGA那些事儿<概念篇 ...
- 常见面试题整理--Python概念篇
希望此文可以长期更新并作为一篇Python的面试宝典.每一道题目都附有详细解答,以及更加详细的回答链接.此篇是概念篇,下一篇会更新面试题代码篇. (一).这两个参数是什么意思:*args,**kwar ...
- 【转帖】H5 手机 App 开发入门:概念篇
H5 手机 App 开发入门:概念篇 http://www.ruanyifeng.com/blog/2019/12/hybrid-app-concepts.html 作者: 阮一峰 日期: 2019年 ...
- 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 百篇博客分析OpenHarmony源码 | v62.01
百篇博客系列篇.本篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 51.c.h.o 本篇开始说文件系统,它是内核五大模块之一,甚至有Linux的设计哲学是" ...
- 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 百篇博客分析OpenHarmony源码 | v43.02
百篇博客系列篇.本篇为: v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里 ...
随机推荐
- 中心极限定理(Central Limit Theorem)
中心极限定理:每次从总体中抽取容量为n的简单随机样本,这样抽取很多次后,如果样本容量很大,样本均值的抽样分布近似服从正态分布(期望为 ,标准差为 ). (注:总体数据需独立同分布) 那么样本容量n应 ...
- Python中的异步任务队列 arq
引言 最近在用 sanic 写东西,所有涉及到IO阻塞的代码都需要用 aio 的模块,好在近年来 asyncio 生态圈发展的还算不错,该有的都有 ~ 近期业务中 登录/注册 业务涉及的很复杂(涉及到 ...
- uni-app 获取网络状态
uni.getNetworkType(OBJECT) 获取网络类型. OBJECT 参数说明 参数名 类型 必填 说明 success Function 是 接口调用成功,返回网络类型 network ...
- python 类的倒入
test.pyclass sss: def ddd(self): print("hello") test2.pyfrom testone import sss
- #Ubuntu 14.04 系统下载
http://mirrors.aliyun.com/ubuntu-releases/14.04/
- Android Studio 3.4 修改 .android 和.gradle缺省目录-windows7x64专业版环境。
说明:缺省会在用户目录建立.android和.gradle目录.会挤满C盘.可以改变缺省目录. 改变.gradle目录路径示例,修改到D:\android目录,步骤: 1.建立d:\android目录 ...
- 我的node-webkit笔记
话不多说,直接上码: index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8&q ...
- 解决StrToDateTime()不是有效日期类型的问题
方法一: function GetDateFormat():string; var SysFrset: TFormatSettings; begin Result:=''; GetLocaleForm ...
- element-ui 上传图片 后清空 图片 显示
使用element-ui组件,用el-upload上传图片,上传图片后再次打开还是会有原来的图片,想要清空原来上传的图片,只需要在组件上绑定ref,在提交成功后的方法里调用this.$refs.upl ...
- 【GMT43智能液晶模块】例程十八:LAN_HTTP实验——网页服务器
源代码下载链接: 链接:https://pan.baidu.com/s/1sr4a7TBPyvs18jTfCfVj8Q 提取码:jwfv 复制这段内容后打开百度网盘手机App,操作更方便哦 GMT43 ...