《Effective Objective-C》阅读笔记(上)

目录

高质量iOS之熟悉OC

了解OC语言的起源

在类的头文件中尽量少引入其他头文件

多用字面语法,少用与之等价的方法

字面数值

字面量数组

字面量字典

局限性

多用类型常量,少用#define预处理指令

用枚举表示状态、选项、状态码

高质量iOS之对象、消息、运行期

理解”属性“这一概念

属性特质

原子性

读/写权限

内存管理语义

方法名

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

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

特定类的等同性判定方法

等同性判定的执行深度

容器中可变类的等同性

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

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

理解objc_msgSend的作用

理解消息转发机制

动态方法解析

备援接收者

完整的消息转发

消息转发全流程

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

理解“类对象”的用意


高质量iOS之熟悉OC

了解OC语言的起源

OC语言由Smalltalk演化而来,后者是消息型语言的鼻祖,使用“消息结构”而非“函数调用”。

两者之间的区别看上去就像上面这样。

关键区别在于:使用消息结构的语言,其运行时所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定。

OC是C的“超集”,所以C语言中所有功能在编写OC代码时依然适用。理解C语言的内存模型,有助于理解OC的内存模型及其“引用计数”机制的工作原理。

OC语言中的指针是用来指示对象的,要声明某个变量,令其指代某个对象,可用如下语法:

这种语法就是照搬C语言的,声明了一个指向NSString的指针,所有OC对象都必须这样声明,因为对象所占内存总是分配在“堆空间”中,而不会分配在“栈”上

分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存则会在其栈帧弹出时自动清理。OC将对内存管理抽象出来不再需要用malloc及free来分配1或释放内存,而是在运行期把这部分工作抽象为一套内存管理架构,名叫“引用计数”。

在OC代码中,有时遇到定义里不含*的变量,它们可能使用“栈空间”,所保存的不是OC对象。

在类的头文件中尽量少引入其他头文件

OC与C和C++一样也使用”头文件“与”实现文件“来区隔代码。用OC语言编写任何类几乎都需要引入Foudation.h。如果不引入这个文件,就要引入与其超类所属框架相对应的“基本头文件”。比如UIViewController的子类的头文件需要引入UIKit.h

#import "EOCEmployer.h"

当某个类需要声明其他类为属性时,以前我们通常会使用上述代码来引入某个类的头文件。

但其实在编译当前类时,我们并不需要知道它属性中的类也就是EOCEmployer类的全部细节,只要知道有一个类叫做EOCEmployer就好,可以采用以下办法:

@class EOCEmployer;

这叫做“向前声明”该类。

而当前类的实现文件则需引入EOCEmployer类的头文件,因为如果要使用EOCEmployer,就必须知道其所有接口细节。

这样向前声明不仅可以节约编译时间,也解决了两个类互相引用的问题。

但是有时候必须引入其他头文件:如果类继承某个超类以及遵从某个协议时,必须要引入定义那个超类或协议的头文件

因为这时,协议必须有完整定义,要知道该协议中定义的方法。

除了例如“委托协议”的有些协议,最好把协议单独放在一个头文件中

委托协议只有协议和接受协议委托类放在一起定义才有意义,此时最好在实现文件中声明此类实现了该委托协议,并把这段实现的代码放在分类中。这样只要在实现文件引入包含委托协议的头文件即可

多用字面语法,少用与之等价的方法

字面语法是一种更精简声明NSString、NSNumber、NSArray、NSDictionary类的实例的语法,使用这种语法可以缩减源代码长度,使其更为易读。

字面数值

以下是使用字面量来创建NSNumber的语法:

NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.14159;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
//等号右边还可以是表达式
int x = 5;
float y = 6.23f;
NSNumber *expressionNumber = @(x * y);

字面量数组

使用字面量创建数组:

NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];
//还可以使用字面量取下标
NSString* dog = animals[1];

使用字面量语法,如果元素对象中有nil,则会抛出异常。

字面量字典

NSDictionary *personData = @{@"firstName":@"Matt",@"lastName":@"Galloway",@"age":@23};
//取键值
NSString *lastName = personData[@"lastName"];

与字面量数组相同,如果有值为nil,便会抛出异常

如果数组和字典可变,那么也可以用字面语法来修改元素值。

局限性

字面量不适用于以上类的自定义子类。

并且字面语法创建出来的对象都是不可变的

多用类型常量,少用#define预处理指令

编写代码时经常要定义常量,有一种方法是使用#define预处理命令

#define ANIMATION_DURATION 0.3

这种方法一般可以实现想要的效果,但是这样定义的常量没有类型信息,并且如果使用这种方法,预处理会把碰到的所有ANIMATION_DURATION都替换成0.3,这样的话,假设此指令声明在某个头文件中,那么所有引入了这个头文件的代码,ANIMATION_DURATION都会被替换。

因此定义常量是更好的选择,比如:

static const NSTimeInterval kAnimationDuration = 0.3

这行代码定义了一个类型为NSTimeInterval的常量

要注意常量名称,常用的命名法是:若常量局限于某”编译单元“(“实现文件”)之内,则在前面加字母k,若在类之外可见,则也类名为前缀。

定义的位置也很重要,不应定义在头文件中,如果常量定义在头文件中,相当于声明了一个全局变量,应该加上前缀来表明所属的类。

如果不打算公开某个常量,则应将其定义在使用该常量的实现文件里。

变量一定要同时用static与const来声明。const确保变量不会被修改,而static将变量限制在当前编译单元。

如果常量需要公开,定义方式有所不同:

//header file
extern NSString *const EOCStringConstant
//implementation file
NSString *const EOCStringConstant = "VALUE";

extern这个关键字可以告诉编译器在全局符号表中有一个名叫EOCStringConstant的符号。

由于要放在全局符号表里,所以命名要谨慎。为避免名称冲突,最好用与之相关的类名作前缀。

用枚举表示状态、选项、状态码

在以一系列常量来表示错误状态码或可组合的选项时,宜使用枚举为其命名。枚举是一种常量命名方式,某对象经历的各种状态可以定义为一个简单的枚举集。比如:

编译器为每个枚举分配一个独有的编号,从0开始,每个枚举递增1。

定义枚举的语法如下:

enum EOCConnectionState state = EOCConnectionStateDisconnected;
//可用typedef关键字定义,这样就不用每次敲入enum了
enum EOCConnectionState {EOCConectionStateDisconnected,EOCConectionStateConnecting,EOCConectionStateConnected,
};
typedef enum EOCConnectionState EOCConectionState;

可以指定枚举使用哪种底层数据类型,语法如下:

enum EOCConnectionStateConnectionState : NSInteger;
//指定底层数据类型为NSInteger
//还可以不使用编译器分配的序号,手工指定某个枚举成员对应的值
enum EOCConnectionStateConnectionState {EOCConnectionStateDisconnected = 1,EOCConnectionStateConnecting,EOCConnectionStateConnected,
};

定义选项也应使用枚举类型,若选项可以彼此组合,更应如此。只要枚举定义得对,各选项可通过“按位或操作符”来组合。比如:

enum UIViewAutoresizing {UIViewAutoresizingNone = 0,UIViewAutoresizingFlexibleLeftMargin = 1 << 0,UIViewAutoresizingFlexibleWidth = 1 << 1,UIViewAutoresizingFlexbleRightMargin = 1 << 2,UIViewAutoresizingFlexibleTopMargin = 1 << 3,UIViewAutoresizingFlexibleHeight = 1 << 4,UIViewAutoresizingFlexibleBottomMargin = 1 << 5,
}

这样每个选项都可启用或禁用,因为每个枚举值对应的二进制表示中只有一位是1,可以用“按位与操作符”判断是否已启用某个选项。

Foundation框架中有一些宏可以用来定义这些枚举类型并指定底层数据类型,用法如下:

typedef NS_ENUM(NSUInteger, EOCConnectionState) {EOCConnectionStateDisconnected,EOCConectionStateConnecting,EOCConectionStateConnected,
};
typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection) {EOCPermittedDirectionUp  = 1 << 0,EOCPermittedDirectionDown = 1 << 1,EOCPermittedDirectionLeft = 1 << 2,EOCPermittedDirectionRight = 1 << 3,
};

需要注意,凡是需要以按位或操作来组合的枚举都应使用NS_OPTIONS定义,若枚举不需要互相组合,则应使用NS_ENUM来定义。

枚举还可以放在switch语句里,要注意在switch语句中,如果用枚举来定义状态集,则最好不要有default分支,这样如果加入新的状态,会有警告信息。

高质量iOS之对象、消息、运行期

理解”属性“这一概念

属性是一种用@property语法来定义的用来封装对象中的数据的变量。

属性特质

属性特质分为四类:

原子性

如果具备nonatomic特质,则不使用同步锁,如不声明nonatomic特质,默认为atomic。

读/写权限

readwrite和readonly两个特质分别表示拥有存取方法和只有获取方法

内存管理语义

编译器合成存取方法时,要根据此特质来决定生成的代码。

assing “设置方法”只会执行针对“纯量类型”的简单赋值

strong 先保留新值,再释放旧值,再将新值设置上去

weak 不保留新值,不释放旧值,与assign类似。属性所指对象摧毁时,属性值清空

unsafe_unretained 与assign相同,但适用于对象类型,摧毁时不清空,与weak相反

copy 与strong类似,但不保留新值,而是将其“拷贝”

方法名

即存取方法

在实现初始化方法时,一定遵循属性定义中的语义

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

在对象之外访问实例变量时总是应该通过属性来做,而在对象内部,推荐的做法是:读取实例变量时采取直接访问的形式,而设置实例变量时通过属性来做。

有一些特殊情况:

1.在初始化方法中,总是应该直接访问实例变量。如果待初始化的实例变量声明在超类中,子类中无法直接访问,则调用设置方法。

2.惰性初始化必须通过“获取方法”来访问属性,否则永远不会初始化。

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

对象等同性判定方法与==操作符不同,==比较的是两个指针本身。"isEqual"方法可以判断对象的等同性,某些对象有特殊的“等同性判定方法”。

NSObject协议中有两个用于判断等同性的关键方法:

- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

默认实现是:当且仅当指针值相等时,对象才相等。

如果想覆写方法,那就要理解其约定

如果"isEqual"方法判定相等,那么hash方法也必须返回同一个值,如果hash返回同一个值,isEqual方法未必判定相等。

编写hash方法时,应该用当前对象做做实验,以便减少碰撞频度与降低运算复杂程度之间取舍。

特定类的等同性判定方法

在编写判定方法时,应一并覆写"isEqual"方法,实现方式为:如果受测的参数与接受该消息的对象都属于一个类,就调用自己编写的判定方法,否则交给超类来判断。

等同性判定的执行深度

创建等同性判定方法时,需要决定是整个对象还是根据其中几个字段。NSArray对比方法是,先看对象个数,再对比每个对象,这叫做“深度等同性判定”。可以创建标识符来帮助判定等同性。

容器中可变类的等同性

在容器中放入可变类对象时,放入collection之后,就不应再改变其哈希码了。

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

“类族”是一种很有用的模式,可以隐藏“抽象基类”背后的实现细节。类族中的所有类基于一个基类,并且一般不允许直接创建。

判断是否为类族中的类,不要检测两个类对象是否等同,而是应该采用isKindOfClass:方法。

从公共抽象基类中继承子类时应先阅读开发文档。

如NSArray的类族要新增子类,要遵守几条规则:

1.子类应继承自类族中抽象基类

2.子类应定义自己的数据存储方式

3.子类应覆写超类文档中指明需要覆写的方法

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

关联对象相当于把对象变成字典,不同的键对应不同的值。设置关联对象值时,通常使用静态全局变量做键。

存储关联对象时,可以指明存储策略,来维护内存管理语义。

下列方法管理关联对象:

理解objc_msgSend的作用

在对象上调用方法用OC术语来说叫“传递信息”。OC中对象收到消息后,调用哪个方法于运行期决定,因此是一门动态语言。

OC中将方法转变为消息调用的是objc_msgSend这个函数

这个函数会依据接收者与选择子的类型来调用适当的方法。先搜寻方法列表,若能找到相符合的,就跳至实现代码,若找不到,就沿着继承体系向上查找,找到合适的方法之后再跳转。如果最后还找不到,就执行“消息转发”。

理解消息转发机制

消息转发分为两大阶段,第一阶段先征询接收者,看是否能动态添加方法,这叫做“动态方法解析”。第二阶段分为两小步:首先查看有没有其他对象可以处理该消息,有的话就会转给那个对象。如果没有,就启动完整的转发机制,吧消息封装到NSInvocation对象中,令接收者设法解决当前未处理的这条信息。

动态方法解析

对象收到无法解决的消息后,首先调用类方法:

使用这种方法的前提是:相关方法的实现代码已经写好,只等着运行时动态插在类里面。

备援接收者

当前接收者可以处理未知的选择子,这一步系统会问接收者是否可以把这条消息装给其他接收者来处理:

若找到备援对象,就将其返回;若找不到,就返回nil。

我们无法操作这一步转发的消息,若想先修改消息内容再发送,就得启用完整的消息转发机制。

完整的消息转发

这里将消息封装在NSInvacation对象中,并调用方法来转发信息:

消息转发全流程

每一步均有机会处理消息,步骤越往后,处理消息的代价就越大。

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

可以在运行期改变对象给定的选择子名称对应的方法,这被称为“方法调配”。

比如可以交换两个方法实现,可以通过下列方法:

通过此方案,可以为那些完全不知道具体实现的黑盒方法增加日志记录功能,有助于程序调试。

理解“类对象”的用意

描述OC对象所用的数据结构定义在运行期程序库的头文件里,id类型也定义在这里:

该结构体首个成员是Class类变量,定义了对象的类,称为"isa"指针。Class对象也定义在这个头文件中:

说明Class本身也是OC对象,类对象所属的类型是”元类“。每个类仅有一个类对象

在查询类型信息时,尽量使用类型信息查询方法,而不要直接比较两个类对象是否等同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/25432.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql --- 相关基础知识整理

目录 一、基本数据结构1、聚簇索引和非聚簇索引1.1 数据存储方式1.2 查询效率1.3 插入和更新性能1.4 适用场景 2、InnoDB 存储引擎2.1 B树2.2 行格式2.3 缓冲池2.4 日志文件 3、MyISAM存储引擎3.1 表文件结构3.2 B树3.3 数据存储特点 4、InnoDB和MyISAM的区别 二、索引1、索引类…

JSX 实现列表渲染

const list [{ id: 1001, name: Vue },{ id: 1002, name: React },{ id: 1003, name: Angular },{ id: 1004, name: Node }, ] function App() {return (<div className"App">this is App{/* 渲染列表 */}<ul>{list.map(item > <li key{item.id}&…

ue5.2.1 quixel brideg显示asset not available in uAsset format

我从未见过如此傻x的bug&#xff0c;在ue5.2.1上通过内置quixel下载资源显示 asset not available in uAsset format 解决办法&#xff1a;将ue更新到最新版本&#xff0c;通过fab进入商场选择资源后add to my library 点击view in launcher打开epic launcher&#xff0c;就可…

Excel大文件拆分

import pandas as pddef split_excel_file(input_file, output_prefix, num_parts10):# 读取Excel文件df pd.read_excel(input_file)# 计算每部分的行数total_rows len(df)rows_per_part total_rows // num_partsremaining_rows total_rows % num_partsstart_row 0for i i…

微信小程序开发TABBAR及第三方接口调用程序

最终样式&#xff1a; 1、在微信小程序管理页面增加第三方调用接口 注意事项&#xff1a;必须是htts安全协议的接口 配置完成后在微信开发工具中可以看到配置的第三方接口URL 2、项目目录文件结构 3、程序代码 app.json {"pages": ["pages/home/home",&…

git -学习笔记

目录 基本操作语法 设置用户和邮箱 版本回退 工作区和暂存区 撤销修改 删除与恢复 一工作区删除了&#xff0c;但是暂存区没删除 二工作区误删了&#xff0c;暂存区还有 github-Git 连接 报错解决-push远程仓库被拒绝 远程库 分支 分支冲突 储藏分支 回到当前分…

谷云科技iPaaS×DeepSeek:构建企业智能集成的核心底座

2025年&#xff0c;DeepSeek大模型的爆发式普及&#xff0c;正引领软件行业实现 “智能跃迁”。从代码生成到系统集成&#xff0c;从企业级应用到消费级产品&#xff0c;自然语言交互能力已成为新一代软件的核心竞争力。据行业分析&#xff0c;超60%的软件企业已启动大模型适配…

UE Python笔记

插件 官方 商城 Python Editorhttps://www.fab.com/listings/f4c99ba0-1a86-4f6a-b19d-2fd13f15961b GitHUB 好像只更新到了2020年4.2x的版本。可能有大佬改了5.x的版本。也希望分享给我一份。谢谢 https://github.com/20tab/UnrealEnginePython 学习笔记 网上教程一大堆。…

PXE批量网络装机与Kickstart自动化安装工具

目录 一、系统装机的原理 1.1、系统装机方式 1.2、系统安装过程 二、PXE批量网络装机 2.1、PXE实现原理 2.2、搭建PXE实际案例 2.2.1、安装必要软件 2.2.2、搭建DHCP服务器 2.2.3、搭建TFTP服务器 2.2.4、挂载镜像并拷贝引导文件到tftp服务启动引导文件夹下 2.2.5、编…

【C语言】第八期——指针、二维数组与字符串

目录 1 初始指针 2 获取变量的地址 3 定义指针变量、取地址、取值 3.1 定义指针变量 3.2 取地址、取值 4 对指针变量进行读写操作 5 指针变量作为函数参数 6 数组与指针 6.1 指针元素指向数组 6.2 指针加减运算&#xff08;了解&#xff09; 6.2.1 指针加减具体数字…

Linux系统管理(十七)——配置英伟达驱动、Cuda、cudnn、Conda、Pytorch、Pycharm等Python深度学习环境

文章目录 前言安装驱动下载安装Cuda编辑环境变量安装Cudnn安装conda验证安装成功配置conda镜像退出conda环境创建python环境查看当前conda环境激活环境安装python包安装pytorch 安装pycharm安装jupyter notebook 前言 深度学习和大语言模型的部署不免会用到Linux系统&#xff…

C++蓝桥杯基础篇(六)

片头 嗨~小伙伴们&#xff0c;大家好&#xff01;今天我们来一起学习蓝桥杯基础篇&#xff08;六&#xff09;&#xff0c;练习相关的数组习题&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 第1题 数组的左方区域 这道题&#xff0c;实质上是找规律&#xff0c;…

计算机毕业设计Python+DeepSeek-R1大模型期货价格预测分析 期货价格数据分析可视化预测系 统 量化交易大数据 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

webstorm的Live Edit插件配合chrome扩展程序JetBrains IDE Support实现实时预览html效果

前言 我们平时在前端网页修改好代码要点击刷新再去看修改的效果&#xff0c;这样比较麻烦&#xff0c;那么很多软件都提供了实时预览的功能&#xff0c;我们一边编辑代码一边可以看到效果。下面说的是webstorm。 1 Live Edit 首先我们需要在webstorm的settings里安装插件Live …

可以免费无限次下载PPT的网站

前言 最近发现了一个超实用的网站&#xff0c;想分享给大家。 在学习和工作的过程中&#xff0c;想必做PPT是一件让大家都很头疼的一件事。 想下载一些PPT模板减少做PPT的工作量&#xff0c;但网上大多精美的PPT都是需要付费才能下载使用。 即使免费也有次数限制&#xff0…

九、数据治理架构流程

一、总体结构 《数据治理架构流程图》&#xff08;Data Governance Architecture Flowchart&#xff09; 水平结构&#xff1a;流程图采用水平组织&#xff0c;显示从数据源到数据应用的进程。 垂直结构&#xff1a;每个水平部分进一步划分为垂直列&#xff0c;代表数据治理的…

vue3中ref和reactive响应式数据、ref模板引用(组合式和选项式区别)、组件ref的使用

目录 Ⅰ.ref 1.基本用法&#xff1a;ref响应式数据 2.ref模板引用 3.ref在v-for中的模板引用 ​4.ref在组件上使用 ​5.TS中ref数据标注类型 Ⅱ.reactive 1.基本用法&#xff1a;reactive响应式数据 2.TS中reactive标注类型 Ⅲ.ref和reactive的使用场景和区别 Ⅳ.小结…

SpringBoot集成easy-captcha图片验证码框架

SpringBoot集成easy-captcha图片验证码框架 此项目已经很久未维护&#xff0c;如有更好的选择&#xff0c;建议使用更好的选择!!! 一、引言 验证码&#xff08;CAPTCHA&#xff09;是现代应用中防止机器人攻击、保护接口安全的核心手段之一。然而&#xff0c;从零开发验证码…

算法与数据结构(二叉树中的最大路径和)

题目 思路 这道题我们可以考虑用递归来解决。 首先设计一个maxPath函数用来递归计算二叉树中一个节点的最大贡献值&#xff0c;具体来说&#xff0c;就是以该节点为根节点的子树中寻找以该节点为起点的一条路径&#xff0c;使得该路径上的节点值之和最大。 如果该节点为空&a…

7-Zip命令行复制

先下载一个&#xff1a; 找到压缩exe目录&#xff1a; 写脚本 set EXE_PATH"C:/Program Files/7-Zip/7z.exe"%EXE_PATH% a -t7z OutputName.7z "D:/工程/前端工作"pause