【Effective Objective - C】—— 熟悉Objective-C

【Effective Objective - C】—— 熟悉Objective-C

  • 熟悉Objective-C
  • 1.oc的起源
    • 消息和函数的区别
    • 运行期组件和内存管理
    • 要点:
  • 2.在类的头文件中尽量少引入其他头文件
    • 向前声明
    • 要点:
  • 3.多使用字面量语法,少用与之等价的方法
    • 字符串字面量
    • 字面数值
    • 字面数组
    • 字面字典
    • 局限性
    • 要点
  • 4.多用类型常量,少用#define预处理指令
    • 常量的名称与位置
      • 常量命名法
      • 位置
    • 使用static与const来声明
      • static修饰符
      • const修饰符
    • 使用extern声明全局变量
    • 要点
  • 5.用枚举表示状态,选项,状态码
    • 要点

熟悉Objective-C

Objective-C通过一套全新语法,在C语言基础上添加了面向对象特性。Objective-C的语法中频繁使用方括号,而且不吝于写出极长的方法名,这通常令许多人觉得此语言较为冗长。其实这样写出来的代码十分易读,只是C++或Java程序员不太能适应。
Objective-C语言学起来很快,但有很多微妙细节需注意,而且还有许多容易为人所忽视的特性。另一方面,有些开发者并未完全理解或是容易滥用某些特性,导致写出来的代码难于维护且不易调试。本章讲解基础知识,后续各章谈论语言及其相关框架中的各个特定话题。

1.oc的起源

和C++,Java一样,Objective-C也是面向对象语言,但是它们在许多方面都有差别。差别在于Objective-C使用的是消息结构而非函数调用,Objective-C语言由Smalltalk演化而来的,Smalltalk是消息型语言的鼻祖

消息和函数的区别

//Messaging
Object* obj = [Object new];
[obj performWith: parameter1 and: parameter2];
//Function
Object* obj = new Object;
obj->preform (parameter1, parameter2);

关键区别在于:使用消息结构的语言,其运行时所应执行的代码由环境来决定;使用函数调用的语言,则由编译器决定。对于函数来说,如果范例代码的调用函数是多态的,那么就在运行时按照虚方法表来查出来到底执行哪个函数,而采用消息结构的语言,不管是否为多态总是在运行时才回去查找所要执行的方法。

运行期组件和内存管理

  • Objective-C的重要工作都由“运行期组件”(runtime component)而非编译器来完成。使用Objective-C的面向对象特性所需的全部数据结构及函数都在运行期组件里面。举例来说,运行期组件中含有全部内存管理方法。运行期组件本质上就是一种与开发者所编代码相链接的“动态库”(dynamic library),其代码能把开发者编写的所有程序粘合起来。这样的话,只需更新运行期组件,即可提升应用程序性能。而那种许多工作都在“编译期”(compile time)完成的语言,若想获得类似的性能提升,则要重新编译应用程序代码。
  • Objective-C是C的“超集”(superset),所以C语言中的所有功能在编写Objective-C代码时依然适用。因此,必须同时掌握C与Objective-C这两门语言的核心概念,方能写出高效的Objective-C代码来。其中尤为重要的是要理解C语言的内存模型(memory model),这有助于理解Objective-C的内存模型及其“引用计数”(reference counting)机制的工作原理。若要理解内存模型,则需明白:Objective-C语言中的指针是用来指示对象的。想要声明一个变量,令其指代某个对象,可用如下语法:
NSString *someString = @"The string";

上述代码声明了一个someString的变量,类型为Nsstring*,也就是说,此变量为指向Nsstring的指针,所有oc的对象都必须这样声明,对象所占内存总是分配在堆空间上,而绝不能分配在栈空间上

如果再次创建一个对象Same,那么这两个对象队徽分配在堆中,它们同时指向了堆中的NSString实例:

NSString *someString = @"The string";
NSString *anotherString = someString;

如下图所示:
在这里插入图片描述
分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存则会在其栈桢弹出时自动清理。
Objective-C运行期环境把堆内存管理工作抽象为一套内存管理结构,名叫“引用计数”。

要点:

  • Objective-C为C语言添加了面向对象特性,是其超集。Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。接收一条消息之后,究竟应执行何种代码,由运行期环境而非编译器来决定。
  • 理解C语言的核心概念有助于写好Objective-C程序。尤其是要掌握内存模型和指针。

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

Objective-C采用的是头文件和实例文件区分代码,头文件.h ,实例文件.m,这里以EOCPerson为例格式如下:

// EOCPerson.h
#import <Foundation/Foundation.h>@interface EOCPerson : NSObject
@property (nonattomic, copy) NSString *firstName;
@property (nonattomic, copy) NSString *lastName;
@end//EOCPerson.m
#import "EOCPerson.h"@implementation EOCPerson
// Implementation of methods
@end

向前声明

如果又创建一个名为EOCEmployer的新类,然后为EOCPerson类添加这个属性。就会是这个样子。

// EOCPerson.h
#import <Foundation/Foundation.h>
#import "EOCEmployer.h"
@interface EOCPerson : NSObject
@property (nonattomic, copy) NSString *firstName;
@property (nonattomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;
@end

如果在EOCPerson类的头文件中,我们不需要知道这个新类的全部信息,就可以使用向前声明的方式。现在的头文件就变成这样了:

// EOCPerson.h
#import <Foundation/Foundation.h>@class EOCEmployer;@interface EOCPerson : NSObject
@property (nonattomic, copy) NSString *firstName;
@property (nonattomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;
@end

EOCPerson类的实现文件则需引入EOCEmployer类的头文件,因为若要使用后者,则必须知道其所有接口细节。于是,实现文件就是:

//EOCPerson.m
#import "EOCPerson.h"
#import "EOCEmployer.h"@implementation EOCPerson
// Implementation of methods
@end

尽量将引入头文件的时机延后,只在确有需要时才引入,这样就可以减少类的使用者所需引入头文件数量,减少编译时间。

向前上名声明的好处:

  • 解决了这两个类相互引用问题。
  • 相互引用:有两个类,它们都在头文件中引入了对方的头文件,两个类都进行各自的引用解析,这样就会导致“循环引用”(chicken-and-egg situation)。虽然我们使用#import而非#include不会导致死循环,但是这意味着两个类中有一个类无法被正确编译。
  • 但是,有时候就必须引入头文件,比如继承以及遵循的协议。

要点:

  • 除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。
  • 有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循的协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入,减少不必要的编译,提升性能。

3.多使用字面量语法,少用与之等价的方法

字符串字面量

不使用alloc及init方法来分配并初始化NSString对象,让语法更简洁。

NSString *someString = @"Effective Objective-C 2.0";

这种语法也可以来声明NSNumber、NSArray、NSDictionary类的实例。

字面数值

NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';

字面数组

字面量语法创建数组。

NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];

字面量语法操作数组

NSString *dog = animals[1];

字面字典

“字典”是一种映射型数据结构,可向其中添加键值对。

字面量字典创建

NSDictionary *personData = @{@"firstName" : @"Matt", @"lastName" : @"Galloway", @"age" : @28};

字面量语法访问

NSString *lastName = personData[@"lastName"];

这样写省去了沉赘的语法,令此行代码简单易读。

局限性

字面量语法除了字符串以外,所创建出来的对象必须属于Foundation框架才行。然而一般来说,标准的实现已经很好了,使用这些已经足够了。

此外,使用字面量语法创建出来的字符串、数组、字典对象都是不可变的(immutable)。若想要可变版本的对象,,则需复制一份:

NSMutableArray *mutable = [@[@1, @2, @3, @4] mutableCopy];

这样做会多调用一个方法,而且还要再创建一个对象,不过使用字面量语法所带来的好处还是多与上述缺点的。

要点

  • 应该使用字面量语法来创建字符串、数值、数组、字典。与创建此类对象的常规方法相比,这么做更加简明扼要。
  • 应该通过取下标操作来访问数组下标或字典中的键所对应的元素。
  • 用字面量语法创建数组或字典时,若值中有nil,则会抛出异常。因此,务必确保值里不含nil。

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

编写代码时经常要定义常量。如果我们使用预处理指令,如下。

#define ANIMATION_DURATTON 0.3;

那么源代码中的ANIMATION_DURATTON字符串都会被替换为0.3,不过这样定义出的常量没有类型信息。此外,假设此指令声明在某个头文件中,那么所有引入这个头文件的代码,其ANIMATION_DURATTON都会被替换。

所以我们最好使用类型常量,如下:

static const NSTimeInterval KAnimationDuration = 0.3;

用此方法定义的常量包含类型信息,其好处是清楚地描述了常量的含义。由此可知该常量类型为NSTimeInterval,这有助于为其编写开发文档。

常量的名称与位置

常量命名法

若常量局限于某“编译单元”(也就是“实现文件”)之内,则在前面加字面k;若常量在类之外可见,则通常以类名为前缀。
位置

位置

因为Objective-C没有“名称空间”(namespace)这一概念,所以在头文件使用static const定义常量,其实等于声明了一个名叫KAnimationDuration的全局变量。此名称应该加上前缀,以表明其所属的类,例如可改为EOCViewClassAnimationDuration。

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

// EOCAnimatedView.h
#import <UIKit/UIKit.h>@interface EOCAnimatedView : UIView
- (void)animate;
@end// EOCAnimatedView.m
#import "EOCAnimatedView.h"static const NSTimeInterval KAnimationDuration = 0.3;@implementation EOCAnimatedView
- (void)animate {[UIViewanimateWithDuration:KAnimationDuration animations:^(){// .......}];
}
@end

使用static与const来声明

static修饰符

该修饰符意味着变量仅在定义此变量的编译单元可见。假如声明此变量时不加static,则编译器会为它创建一个“外部符号”。此时若是另一个编译单元中也声明了同名变量,那么编译器就会抛出一条错误消息:

duplicate symbol _KAnimationDuration in:EOCAnimatedView.oEOCOtherView.o

const修饰符

该变量意味着变量不可修改,如果试图修改由const修饰符所声明的变量,那么编译器就会报错。

实际上,如果一个变量既声明为static,又声明为const,那么编译器就会像#define预处理指令一样,把所有遇到的变量都替换为常值。不过,用这种方式定义的常量带又类型信息。

使用extern声明全局变量

有时候需要对外公开某个常量。此时,我们需要声明一个外界可见的常值变量(constant variable)。此类常量需放在“全局符号表”(global symbol table)中,以便可以在编译单元之外使用。定义方法为:

// In the header file
extern NSString *const EOCStringConstant;// In the implementation file
NSString *const EOCtringCostant = @"VALUE";

这个常量在头文件中“声明”,且在实现文件中“定义”。在本例中,EOCtringCostant就是一个常量,这个常量是指针,指向NSString对象。

此类常量必须要定义,而且只能定义一次。通常将其定义在与声明该常量的头文件相关的实现文件里。

要点

  • 不要用预处理指令定义常量。这样定义出来的常量不含类型信息,编译器只是会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
  • 在实现文件中使用static const来定义“只在编译单元内可见的常量”。由于此类常量不在全局符号表中,所以无须为其名称加前缀。
  • 在头文件中使用extern来声明全局变量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应加以区隔,通常用与之相关的类名做前缀。

5.用枚举表示状态,选项,状态码

枚举只是一种常量命名方式,某个对象所经历的各个状态、定义选项或者把逻辑含义相似的一组状态码都可以放入一个枚举集里。

编译器会为枚举分配一个独有的编号,从0开始,每个枚举递增1,也可以手动设置某个枚举成员对应的值,后面的枚举值一次加1。

可以指明枚举用的何种底层数据类型,这样编译器清楚底层数据类型的大小,可以向前声明枚举类型。

UIButton的状态:

typedef NS_ENUM(NSInteger, UIButtonRole) {UIButtonRoleNormal,UIButtonRolePrimary,UIButtonRoleCancel,UIButtonRoleDestructive
} API_AVAILABLE(ios(14.0));

要点

  • 应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
  • 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
  • 用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
  • 在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch 语句并未处理所有枚举。

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

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

相关文章

Java--业务场景:SpringBoot 通过Redis进行IP封禁实现接口防刷

文章目录 前言具体实现步骤1. 定义自定义注解2. 编写拦截器类IpUrlLimitInterceptor3. 在WebConfig类中添加IpUrlLimitInterceptor4. 添加注解到接口上 测试效果参考文章 前言 在实际项目中&#xff0c;有些攻击者会使用自动化工具来频繁刷新接口&#xff0c;造成系统的瞬时吞…

单因素方差分析--R

任务说明 三个剂量水平的药物处理受试者&#xff0c;每个剂量水平十个受试者&#xff0c;现在收集到数据后&#xff0c;问&#xff1a; 药物剂量水平显著影响受试者的response&#xff1f; 或者不同剂量药物处理受试者有显著效果的差异吗&#xff1f; 数据 library(tidyvers…

css选择器在python中如何使用

css选择器整理&#xff1a;https://blog.csdn.net/qq_40910788/article/details/84842951 目标&#xff1a;爬取某文章网站列表&#xff1a; 基础代码如下&#xff1a; import random import time import urllib.request import redef reptileTest(url):try:my_headers [&q…

Self-Attention

前置知识&#xff1a;RNN&#xff0c;Attention机制 在一般任务的Encoder-Decoder框架中&#xff0c;输入Source和输出Target内容是不一样的&#xff0c;比如对于英-中机器翻译来说&#xff0c;Source是英文句子&#xff0c;Target是对应的翻译出的中文句子&#xff0c;Attent…

Danswer部署指南

Quickstart How to deploy Danswer on your local machine ​ Requirements gitdocker with compose (docker version > 1.13.0) ​ Setup This quickstart guide covers setting up Danswer for local execution Clone the Danswer repo: git clone https://github.com…

Mysql 数据库ERROR 1820 (HY000): You must reset your password using ALTER USER 解决办法

Mysql 5.7数据库原来一直都能正常访问&#xff0c;突然访问不了&#xff0c;查看日志提示数据库需要修改密码&#xff0c; 具体解决办法如下操作&#xff1a; Windows 下&#xff1a; mysql的bin目录下&#xff0c; mysql>use mysql; mysql>mysql -uroot -p密码; 判…

gem5学习(14):将gem5扩展到ARM——Extending gem5 for ARM

目录 一、Downloading ARM Binaries 二、Building gem5 to run ARM Binaries 三、Modifying simple.py to run ARM Binaries 四、Running gem5 五、ARM Full System Simulation An aside on FS simulations 这个是gem5-learning中Getting Started的最后一篇文章&#xff…

imgaug库指南(20):从入门到精通的【图像增强】之旅

引言 在深度学习和计算机视觉的世界里&#xff0c;数据是模型训练的基石&#xff0c;其质量与数量直接影响着模型的性能。然而&#xff0c;获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此&#xff0c;数据增强技术应运而生&#xff0c;成为了解决这一问题的…

各版本 操作系统 对 .NET Framework 与 .NET Core 支持

有两种类型的受支持版本&#xff1a;长期支持 (LTS) 版本和标准期限支持 (STS) 版本。 所有版本的质量都是一样的。 唯一的区别是支持的时间长短。 LTS 版本可获得为期三年的免费支持和补丁。 STS 版本可获得 18 个月的免费支持和修补程序。 有关详细信息&#xff0c;请参阅 .N…

Java重修第五天—面向对象2

通过学习本篇文章可以掌握如下知识 static&#xff1b;设计单例&#xff1b;继承。 之前文章我们已经对面向对象进行了入门学习&#xff0c;这篇文章我们就开始深入了解面向对象设计。 static 我们定义了一个 Student类&#xff0c;增加姓名属性&#xff1a;name &#xff1…

用通俗易懂的方式讲解:内容讲解+代码案例,轻松掌握大模型应用框架 LangChain

本文介绍了 LangChain 框架&#xff0c;它能够将大型语言模型与其他计算或知识来源相结合&#xff0c;从而实现功能更加强大的应用。 接着&#xff0c;对LangChain的关键概念进行了详细说明&#xff0c;并基于该框架进行了一些案例尝试&#xff0c;旨在帮助读者更轻松地理解 L…

最好的 8 个解锁 Android 手机的应用程序分析

如何解锁我的 Android 手机是一个困扰全球数百万人的问题。有多种Android解锁器可用于解锁手机。用户应确保选择最好的应用程序以轻松满意地完成工作。必须注意的是&#xff0c;数据在解锁手机的整个过程中都是安全可靠的。此类应用程序还应该能够在所有情况下检索数据。 锁屏移…

时间差异导致数据缺失,如何调整Grafana时间与Prometheus保持同步?

Grafana时间如何调快或调慢&#xff1f; 在k8s环境中&#xff0c;常使用prometheusgrafana做监控组件&#xff0c;prometheus负责采集、存储数据&#xff0c;grafana负责监控数据的可视化。 在实际的使用中&#xff0c;有时会遇到这样的问题&#xff0c;k8s集群中的时间比真实…

力扣:209.长度最小的子数组

1.题目分析&#xff1a; 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 示例 …

Vue的api接口封装以及使用说明、模块说明

在Api目录下面建立user.js&#xff0c;如果以后有不同的接口请求地址都可以单独创建不同的&#xff0c;目的是方便维护&#xff01; import request from /utils/request 这个代码是引入之前封装好的 request.js 文件&#xff0c;具体可以参考上门一篇文档 Vue的request.js模…

传奇手游详细图文架设教程

开始架设 1. 架设条件 传世手游架设需要准备&#xff1a; linux 服务器&#xff0c;建议 CentOs 7.6 版本&#xff0c;游戏源码&#xff0c; 游戏运行大约占 2.5G 左右内存。 2. 安装宝塔及环境 宝塔是一个服务器运维管理软件&#xff0c;安装命令&#xff1a; yum inst…

NVMe-oF 1.1规范:多路径、非对称命名空间和NVMe/TCP

提到NVMe over Fabric&#xff0c;我就会想到它的几种应用场景&#xff1a; 1、 存储阵列到主机的网络连接&#xff08;替代FC、iSCSI等&#xff09;&#xff1b; 2、 服务器、本地NVMe存储解耦&#xff08;跨机箱/JBOF&#xff09;&#xff0c;SSD存储资源池化共享&#xff…

序章 搭建环境篇—准备战士的剑和盾

第一步&#xff1a;安装node.js Node.js 内置了npm&#xff0c;只要安装了node.js&#xff0c;就可以直接使用 npm&#xff0c;官网地址&#xff1a; Download | Node.js 在这里不建议安装最新版本的node.js&#xff0c;可以选跟我一样的版本&#xff0c;node版本v16.13.2 链…

C++内存管理机制(侯捷)笔记3

C内存管理机制&#xff08;侯捷&#xff09; 本文是学习笔记&#xff0c;仅供个人学习使用。如有侵权&#xff0c;请联系删除。 参考链接 Youtube: 侯捷-C内存管理机制 Github课程视频、PPT和源代码: https://github.com/ZachL1/Bilibili-plus 第三讲&#xff1a;malloc和…

POI:对Excel的基本写操作 整理1

首先导入相关依赖 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --><!--xls(03)--><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.2</version></depend…