Effective Objective-C 2.0学习记录(四)

news2024/11/16 1:32:05

学习记录

  • 15.用前缀避免命名空间冲突
  • 16.提供“全能初始化方法”
  • 17.实现description方法
    • debugDescription:
  • 18.尽量使用不可变对象
  • 19.使用清晰而协调的命名方式
    • 类与协议的命名
  • 20.为私有方法名加前缀
  • 21.理解OC错误模型
  • 22.理解NSCopying协议
    • 深拷贝和浅拷贝

15.用前缀避免命名空间冲突

由于OC没有像其他语言那样内置的命名空间,所以我们需要避免潜在的命名冲突。如果发生命名冲突,那么应用程序的链接过程就会出错。因为其中出现了重复符号:

在这里插入图片描述
错误在于,应用程序中的两份代码都各自实现了名为EOCTheClass的类。这导致EOCTheClass所对应的类符号和“元类”符号各定义了两次。

甚至可能出现更糟糕的情况:在运行期载入了含有重名类的程序库,此时,“动态加载器”就遭遇了“重名符号错误”,很可能导致整个程序崩溃。

避免此情况的唯一办法就是变相实现命名空间:为所有的名称都加上适当前缀。所选前缀可以是与公司、应用程序、或二者皆有关联之名

使用Cocoa创建应用程序时一定要注意,Apple宣称其保留使用所有“两字母前缀”的权利,所以你自己选用的前缀应该是三个字母的。

不仅是类名,应用程序中的所有名称都应加前缀。如果要为既有的类新增分类,那么一定要给“分类”及“分类”中的方法加上前缀,第25条解释了这么做的原因。

  • 选择与你的公司、应用程序、或二者皆有关联之名称作为类名前缀,并在所有代码中使用这一前缀。
  • 若自己开发的程序库中用到了第三方库,则应为其中的名称加上前缀。

16.提供“全能初始化方法”

所有的对象都得初始化。初始化时一般要提供一些额外信息,UITableViewCell初始化时就要知名样式及标识符,标识符能够区分不同类型的单元格。这种对象的创建成本较高,我们在创建的时候可以依照标识符来复用,以提升程序效率。我们把这种可以为对象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”

创建类实例的方式不止一种,那么这个类就会有多个初始化方法,不过仍然要在其中选一个作为全能初始化方法,令其他初始化方法最终都调用它。NSDate是一个例子,其初始化方法如下:
请添加图片描述

正如该类的文档描述的那样,在上面几个初始化方法中,“initWithTimeIntervalSinceReferenceDate:”是初始化方法,也就是说,其余的初始化方法都调用它。,于是,只有在全能初始化方法中,才会存储内部数据。这样的话,当底层存储机制改变时,只需修改此方法的代码就好,不需要改其他方法的代码。

比如说,要写一个表示矩形的类,其接口可以这样写:

#import<Foundation/Foundation.h>

@interface EOCRectangle : NSObject
@property (nonatomic, assign, readonly) float width;
@property (nonatomic, assign, readonly) float height;
@end

根据18条中的建议,我们把属性声明为只读。不过这样一来,外界就无法设置EOCRectangle的属性了。开发者可能会提供初始化方法以设置这两个属性:

- (id) initWithWidth: (float) width andHeight: (float) height {
	if ((self = [super init])) {
		_width = width;
		_height = height;
	}
	return self;
}

可是,如果有人用[[EOCRectangle alloc]init]来创建矩形会如何呢?这么做是合乎规则的,因为EOCRectangle的超类NSObject实现了这个名为init的方法,调用完该方法后,全部实例变量都将设为0(或设置成符合其数据类型且与0等价的值)。如果把alloc方法分配好的EOCRectangle交由此方法来初始化,那么矩形的宽度与高度就是0,因为全部实例变量都设为0了。这也可能正是你想要的效果,不过此时我们一般希望能自己设定默认的宽度与高度值,或是抛出异常,指明本类实例必须用“全能初始化方法”来初始化。也就是说,在EOCRectangle这个例子中,应该像下面这样,参照其中一种版本来覆写init方法:

 - (id)init{
    return [self initWithWidth:5.0f andHeight:10.0f];
}
 - (id)init{
    @throw  [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Must use initWithWidth:andHeight: instead." userInfo:nil]
}

每个子类的全能初始化方法都应该调用其超类的对应方法,并逐层向上,实现“initWithCoder:”方法时也要这样,应该先调用超类的相关方法,然后再执行与本类有关的任务。这样编写出来的EOCSquare类就完全遵守NSCoding协议了。如果编写“initWithCoder:”方法时没有调用超类的同名方法,而是调用了自制的初始化方法,或是超类的其他初始化方法,那么EOCRectangle类的“initWithCoder:”方法就没机会执行,于是,也就无法将_width和_height两个实例变量解码了。

  • 在类中提供一个全能初始化方法,并与文档里指明。其他初始化方法均应调用此方法。
  • 若全能初始化方法与超类不同,则需覆写超类中的对应方法。
  • 如果超类的初始化方法不适用于子类,那么应该覆写这个超类方法,并在其中抛出异常。

17.实现description方法

调试程序时,经常需要打印并查看对象信息。一种办法是编写代码把对象的全部属性都输出到日志中。不过最常用的办法还是如下:

NSLog(@"object = %@", object);

在构建需要打印到日志的字符串时,object对象会收到description消息,该方法所返回的描述信息将取代“格式字符串”里的%@。比方说,object是个数组,若用下列代码打印其信息:

NSArray *object = @[@"A string", @(123)];
NSLog(@"object = %@", object);

输出:

object = {
	"A string"
	123
}

如果是一个自定义的类,则输出:

object = <EOCPerson: 0x7fd9a1600600>

如果我们想让其输出其他信息,就需要重写description方法:

- (NSString *) description {
	return [NSString stringWithFormat:@"<%@: %p, %@>",
			[self class],
			self,
			@{@"latitude":_title,
			@"latitude":@(_latitude),
			@"longitude":@(_longitude)}
		];
} 

输出结果:

location = <EOCLocation: 0x7f98f2e01d20, {
	latitude = "51.506"
	longitude = 0;
	title = London;
}>

debugDescription:

这个也是一种描述方法,和description差不多,就是描述的位置不一样,description是在函数调用类的时候触发方法才输出的,而debugDescription是在控制台中使用命令打印该对象时才调用的。当然加断点查看时也可以看到debugDescription的描述。

如果你在description不想将一些内容输出的话,你就可以将那些数据写在debugDescription中,让程序员自己调试时可以方便的看到这些数据,而description方法就输出你想要让用户看到的信息就行了。

  • 实现description方法返回一个有意义的字符串,用以描述该实例。
  • 若想在调试时打印出更详尽的对象描述信息,则应实现debugDescription方法。

18.尽量使用不可变对象

不可变对象,我们第一时间想到的肯定是不可变数组那种不可变对象,但是这里的不可变不是这样的,它指的是这个类里边的属性是不能直接被修改的,要实现这种功能,我们就需要用到我们的readonly(只读)修饰符。默认情况下,属性是readwrite(即可读又可写)的,这样修饰出来的类都是“可变的”。所以需要设置为readonly,就像这样:

@property (nonatomic, copy, readonly) NSString *identifier; 
@property (nonatomic, copy, readonly) NSString *title; 
@property (nonatomic, assign, readonly) float latitude;

现在,这个属性就只能用在实现代码内部设置这些属性了,但其实,在对象外部还可以通过“键值编码”技术来设置这些属性,就像“setValue:forKey:”方法。“点语法”也可以,因为点语法就是调用set方法的。这样做虽说可以改动,但是却违背了本心,还会导致数据不同而出现问题,所以不建议更改。

[pointOfInterest setValue:@"abc" forKey:@"identifier"];

这样子可以改动属性值,因为KVC会在类里查找“setIdentifier:”方法,并借此修改此属性。即使没有于公共接口中公布此方法,它也依然包含在类中。不过,这样做等于违规地绕过了本类所提供的API,要是开发者使用这种“杂技代码”的话,那么得自己开应对可能出现的问题。

还有一种可以修改数据的方法就是直接用类型信息查询功能查出属性所对应的实例变量在内存布局中的偏移量,以此来人为设置这个实例变量的值。这样做比绕过本类的公共API还要不合规范。所以不应该因为这个原因而忽视所提的建议,大家还是要尽量编写不可变的对象。

  • 尽量创建不可变的对象
  • 若某属性进可于对象内部修改,则在“分类”中将其由属性扩展为readwrite属性
  • 不要把可变的collection作为属性公开,而应提供相关方法,以此修改对象中的可变collection。

19.使用清晰而协调的命名方式

类,方法,变量的命名是Objective-C编程的重要环节。名称中一般都带有“in”,“for”,“with”等介词
方法与变量名应使用“驼峰命名法”,以小字母开头,其后每个单词首字母大写。类名也使用驼峰命名法,只是首字母大写,前面通常有两三个前缀字母。

类与协议的命名

应该为类与协议的名称加上前缀,以避免命名空间冲突,而且应该像给方法起名时那样把词句组织好,使其从左至右读起来较为通顺。基本命名规则就是:命名方式应该一致,如果要从其他的类中继承子类,那么就要遵守其原本的命名惯例。 例如:UIView它的子类就应该是***View,表明其来历。

总结规则:

  • 如果方法的返回值是新创建的,那么方法名的首个词应是返回值的类型,除非前面还 有修饰语,例如localized String。属性的存取方法 不遵循这种命名方式,因为一般认 为这些方法不会创建新对象,即便有时返回内部对象的一份拷贝, 我们也认为那相当 于原有的对象。这些存取方法应该按照其所对应的属性来命名。
  • 应该把表示参数类型的名词放在参数前面。
  • 如果方法要在当前对象上执行操作,那么就应该包含动词;若执行操作时还需要参数, 则应该在动词后面加 上一个或多个名词。
  • 不要使用str 这种简称,应该用string 这样的全称。
  • Boolean 属性应加is 前缀。如果某方法返回非属性的Boolean 值,那么应该根据其功 能,选用has 或is 当前缀。
  • 将get 这个前缀留给那些借由“输出参数〞来保存返回值的方法,比如说,把返回值填充到〝C语言式数组” ( C - style array ) 里的那种方法就可以使用这个词做前缀 。
  • 起名时应遵从标准的OC命名规范,这样创建出来的接口更容易为开发者所理解。
  • 方法名要言简意赅,从左至右读起来要像个日常用语中的句子才好。
    方法名里不要使用缩略后的类型名称。
  • 给方法名起名时的第一要务就是确保其风格与你自己的代码或所要集成的框架相符。

20.为私有方法名加前缀

通常我们在写方法时,并没有对其进行私有共有分类,导致调试时可能很麻烦,现在为私有方法加上前缀,这样便于修改方法或方法签名。唯一注意的是:一定不要只使用_作为前缀,因为苹果公司使用的就是_作为私有方法的前缀的,你自己定义的私有方法名有可能就会和人家自带的冲突。

  • 给私有方法的名称加上前缀,这样可以很容易地将其同公共方法区分开。
  • 不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司用的。

21.理解OC错误模型

对于ARC,在默认情况下不是“异常安全的”。具体来说意味着如果抛出异常,本应该在作用区域未必释放的对象现在却不会自动释放了。如果想生成“异常安全”的代码,可以通过设置编译器的标志来实现。即使不用ARC,也很难写出在抛出异常时不会导致内存泄漏的代码。如果有段代码先创建好了某个资源,使用完了之后在将其释放,可是在释放资源之前如果抛出异常了,该资源就不会被释放。

OC采用的办法是:只在极其罕见的情况下抛出异常,异常抛出之后,无需考虑恢复问题,而且程序此时应该退出。

异常值一个应用于极其严重的错误。出现不那么严重的错误时,OC语言所采用的变成范式为:令方法返回nil/0,或者使用NSError,表示其中有错误发生

  • 只有发生了可使整个应用程序都崩溃的严重错误时,才应使用异常。
  • 在错误不那么严重的情况下,可以指派“委托方法”来处理错误,也可以把错误信息放在NSError对象里面,经由“输出参数”返回给调用者。

22.理解NSCopying协议

使用对象时经常需要拷贝它,在OC中,此操作通过copy方法来完成。如果想令自己的类支持拷贝操作,那就要实现NSCopying协议,该协议只有一个方法:

- (id) copyWithZone : (NSZone*) zone;

为什么会出现NSZone呢?因为在以前开发程序时,会据此把内存分成不同的“区”(Zone),而对象会创建在某个区里面,现在已经不需要了,每个程序只有一个区:“默认区”(default zone)。所以说,尽管必须实现这个方法,但是你不必担心其中的Zone参数。

copy方法由NSObject实现,该方法只是以“默认区”为参数来调用上面的方法。我们总是想覆写copy方法,其实真正需要实现的却是“copyWithZone:”方法。

如果想使某个类支持拷贝,只需声明该类遵循NSCopying协议,并实现其中的那个方法即可。比方说有个表示个人信息的类,可以在其接口定义中声明此类遵从NSCopying协议:

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString* firstName;
@property (nonatomic, copy, readonly) NSString* lastName;

- (id) initWithFirstName: (NSString*) firstName
             andLastName: (NSString*) lastName;
@end

然后实现协议中规定的方法(copyWithZone:)。

- (id) copyWithZone:(NSZone *)zone {
    EOCPerson* copy = [[[self class] allocWithZone:zone]
                       initWithFirstName:_firstName andLastName:_lastName];
    return copy;
}

在本例实现的“copyWithZone:”中,我们直接把待拷贝的对象交给全能初始化方法,令其执行所有初始化工作。

深拷贝和浅拷贝

深拷贝:在拷贝对象自身时,将其底层数据也一并复制过去。
浅拷贝:只拷贝容器对象本身,不复制其中的数据。

请添加图片描述

  • 若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。
  • 如果自定义的对象分为可变版本和不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议。
  • 复制对象时需决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝。
  • 如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【1-神经网络计算】北京大学TensorFlow2.0

课程地址&#xff1a;【北京大学】Tensorflow2.0_哔哩哔哩_bilibiliPython3.7和TensorFlow2.1六讲&#xff1a;神经网络计算&#xff1a;神经网络的计算过程&#xff0c;搭建第一个神经网络模型神经网络优化&#xff1a;神经网络的优化方法&#xff0c;掌握学习率、激活函数、损…

ArcGIS基础实验操作100例--实验99三维爆炸分析

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 空间分析篇--实验99 三维爆炸分析 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

Open3D ICP精配准(使用鲁棒性核函数,Python版本)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 标准的ICP算法(点到平面)是使得下面这个目标函数最小化: 其中 p 、 q p、q p、q是相对应的匹配点,

RocketMQ5.0.0路由中心NameServer

一、NameServer概览NameServer是RocketMQ的注册中心&#xff0c;是消息存储Broker、生产者、消费者沟通的桥梁。NameServer集群之间是相互独立的&#xff0c;Broker启动时向所有NameServer注册中心注册。通过DLedger构建NameServer集群&#xff0c;实现如主从切换等功能。启动N…

【笔记】大话设计模式24-28

【笔记】大话设计模式24-28 文章目录【笔记】大话设计模式24-2824 职责链模式24.1 Example24.2 定义24.3 Show me the code24.4 总结25 中介者模式25.1 Example25.2 定义25.3 Show me the code25.4 总结26 享元模式26.1 Example26.2 定义26.3 Show me the code26.4 总结27 解释…

aws s3 参与s3game寻找宝藏游戏挑战学习s3对象存储

参考资料 Pirates S3game workshop http://s3game-level1.s3-website.us-east-2.amazonaws.com/level1.html https://blog.benclmnt.com/notes/s3-game/ https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/index.html 强烈推荐这种寓教于乐的方式学…

【ROS2 入门】ROS 2 actions 概述

大家好&#xff0c;我是虎哥&#xff0c;从今天开始&#xff0c;我将花一段时间&#xff0c;开始将自己从ROS1切换到ROS2&#xff0c;在上一篇中&#xff0c;我们一起了解ROS 2中Parameters&#xff0c; 这一篇&#xff0c;我们主要会围绕ROS中另外一个重要的概念“Actions ”来…

Linux 系统调用的实现(x86_64)

目录 1、系统调用的定义 1.1 SYSCALL_METADATA宏 1.2 __SYSCALL_DEFINEx定义 2、系统调用表-sys_call_table数组的定义 3、用户态系统调用流程 kernel 5.10 1、系统调用的定义 系统调用的定义我们其实都不陌生&#xff0c;类似这样的函数SYSCALL_DEFINE0&#xff0c; SYSC…

C语言常见错误汇总

1 数组遍历时使用sizeof(a) 任务&#xff1a;有个数组&#xff0c;找出第二大的数&#xff0c;并且打印出来&#xff08;使用*操作数组元素个数&#xff0c;不要使用[]&#xff09; #include<stdio.h> int main01() {int a[] { 100,100,100,234,123,500,32,68,41,99,1…

code.org免费的少儿编程入门平台

现在市面上的少儿编程课&#xff0c;都是先花9.9就能体验几节课&#xff0c;然后要花几千块才能继续学习后面的课程。这些钱大可不必花。 现在给大家推荐一个免费的网站&#xff0c;code.org&#xff0c;它是一个非营利组织创办的网站&#xff0c;目标是让每个学生都能像生物、…

高并发系统设计 --多级缓存

为了提高系统的性能&#xff0c;一般会引入“缓存机制”&#xff0c;将部分热点数据存入缓存中&#xff0c;用空间换取时间&#xff0c;以达到快速响应的目的。 我们对缓存的认知停留在redis&#xff0c;但其实缓存远远不是只有redis的&#xff0c;从客户端发起请求开始&#…

MySQL整体使用》导入数据、约束、多表查询、事务、变量类型、资源占用

我发的MySQL相关内容&#xff1a; C#基础知识体系框架图&#xff0c;及起对应我发过的博客 linux安装mysql8配置使用&#xff0c;并解决常见问题 MySQL常用命令&#xff08;DQL&#xff09; 执行脚本命令&#xff0c;本地生成SQL文件后在服务器执行 // 进入mysql命令控制 m…

svg绘(viewBox viewport preserveAspectRatio)代替png图片等

当我们的代码中需要一个小图标的时候没必要去iconfont进行下载图标使用 要是下载的png格式那么容量还很大 远不如svg 直接自己代码写 记住svg的坐标朝向和数学坐标轴不一样 实现下图添加的小图标 <svg width"20px" height"20px" style"border: …

2023java面试之Zookeeper基础

一、说说 Zookeeper 是什么&#xff1f;直译&#xff1a;从名字上直译就是动物管理员&#xff0c;动物指的是 Hadoop 一类的分布式软件&#xff0c;管理员三个字体现了 ZooKeeper 的特点&#xff1a;维护、协调、管理、监控。简述&#xff1a;有些软件你想做成集群或者分布式&a…

冯诺依曼体系结构

冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 截至目前&#xff0c;我们所认识的计算机&#xff0c;都是有一个个的硬件组件组成&#xff1a; 输入单元&#xff1a;包括键盘, 鼠…

netbeans中配置maven

deploy-发布到远程maven库本节默认maven库为nexusnetbeans中按ctrl1&#xff0c;打开Project窗口&#xff1b;在Project窗口中找到相关的project或module,在项目名上点击鼠标右键&#xff1b;在弹出菜单中找到菜单“Run Maven”的子菜单“Goals”&#xff0c;并点击&#xff0c…

PCB封装创建(IC类+USB)

目录 一&#xff1a;IC类 封装原理图 规格参数选最大。创建过程 1.放置焊盘 2.我们需要八个上图焊盘&#xff0c;可以用特殊粘贴 3.丝印层设置 封装向导 右击0805R&#xff0c;选择footprint 输入焊盘尺寸 二&#xff1a;USB封装 原理图 创建过程 1.放置焊盘&#x…

SSM 03_SpringMVC REST风格 Postman SSM整合 拦截器

01-SpringMVC简介SpringMVC是隶属于Spring框架的一部分&#xff0c;主要是用来进行Web开发&#xff0c;是对Servlet进行了封装。SpringMVC是处于Web层的框架&#xff0c;所以其主要的作用就是用来接收前端发过来的请求和数据然后经过处理并将处理的结果响应给前端&#xff0c;所…

元宇宙时代业务扩张,专精特新小巨人找到了增长“神器”

进入2023年&#xff0c;元宇宙时代正扑面而来。自从脸书公司更名为Meta以来&#xff0c;元宇宙就在全球迅速走红。《福布斯》认为&#xff0c;2030年全球元宇宙的市场规模有望高达5万亿美元。更为重要的是&#xff0c;元宇宙正在成为数实融合的新界面、未来商业的新型基础设施。…

如何在浏览器中安装使用Vue开发者工具?Vue开发者工具的安装使用?可直接提取插件安装使用

一个混迹于Github、Stack Overflow、开源中国、CSDN、博客园、稀土掘金、51CTO等 的野生程序员。 目标&#xff1a;分享更多的知识&#xff0c;充实自己&#xff0c;帮助他人 GitHub公共仓库&#xff1a;https://github.com/zhengyuzh 以github为主&#xff1a; 1、分享前端后端…