【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.o
	EOCOtherView.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 语句并未处理所有枚举。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AntDesignBlazor示例——暗黑模式

本示例是AntDesign Blazor的入门示例&#xff0c;在学习的同时分享出来&#xff0c;以供新手参考。 示例代码仓库&#xff1a;https://gitee.com/known/BlazorDemo 1. 学习目标 暗黑模式切换查找组件样式覆写组件样式 2. 添加暗黑模式切换组件 1&#xff09;双击打开MainL…

在CMake中自定义宏 add_definitions(-DDEBUG)

hehedalinux:~/Linux/loveDBTeacher-v6$ tree . ├── CMakeLists.txt └── test.c0 directories, 2 files hehedalinux:~/Linux/loveDBTeacher-v6$ test.c #include <stdio.h> #define NUMBER 3int main() {int a 10; #ifdef DEBUGprintf("我是一个程序猿,我…

驾驭未来:从传统运维到智能化运维的转型之路

随着科技的飞速发展&#xff0c;企业的业务需求也在不断变化。为了满足这些需求&#xff0c;企业的IT架构逐渐向云原生、容器化和微服务化演进。作为支撑企业业务发展的运维人员&#xff0c;我们需要紧跟时代步伐&#xff0c;不断提升自己的技能和认知水平。 在2023年全球运维大…

评估LLM在细胞数据上的实用性(3)-基因层面的评估

目录 定义基因功能预测扰动预测基因网络分析 基因层面的评估基因功能预测扰动预测基因网络分析 定义 基因功能预测 基因功能预测对于识别基因在不同条件下的特性非常重要。因为人类大约有20,000个蛋白质编码基因&#xff0c;只有一些被标注了功能。对基因功能的准确预测可以帮…

CMake在静态库中链接静态库

hehedalinux:~/Linux/multi-v2$ tree . ├── calc │ ├── add.cpp │ ├── CMakeLists.txt │ ├── div.cpp │ ├── mult.cpp │ └── sub.cpp ├── CMakeLists.txt ├── include │ ├── calc.h │ └── sort.h ├── lib │ ├── l…

【WEB API自动化测试】接口文档与在线测试

这一篇我们主要介绍如何做API帮助文档&#xff0c;给API的调用人员介绍各个 API的功能, 输入参数&#xff0c;输出参数, 以及在线测试 API功能(这个也是方便我们自己开发调试) 我们先来看看我们的API最终帮助文档及在线测试最终达到的效果: 概要图 GET API 添加产品API: 删除…

flutter使用get库管理路由,并设页面跳转动画和常见动画

get库还是非常强大的一个仓库&#xff0c;里面包含了非常常用的一些方法&#xff0c;比如路由管理&#xff0c;这是最常见和最常用的一个功能了&#xff0c;我们可以先配置一个路由对象&#xff0c;然后在里面配置路由列表&#xff0c;并且设置路由跳转方式。 第一种方式&…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷⑦

2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷7 目录 需要竞赛软件包环境以及备赛资源可私信博主&#xff01;&#xff01;&#xff01; 2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷7 模块一 …

jetson orin nano 使用yolov8导出engine

1. 导出onnx 经过前面训练&#xff0c;得到了best.pt模型&#xff0c;现在想要使用tensorrt进行推理&#xff0c;需要先导出为onnx格式&#xff0c;再转化为engine格式。 yolo export modelbest.pt formatonnx opset12 simplifyTrue2.解决错误 在导出过程中&#xff0c;可能…

postman 之 接口请求

一、前言 1. 安装 2. 主界面 3. 请求区域 Body下主要包含以下4中格式 form-data&#xff1a;混合表单&#xff0c;支持上传文件x-www-form-urlencoded&#xff1a;文本表单raw&#xff1a;原始格式&#xff0c;支持JSON/XML格式&#xff08;后面可选择&#xff09;binary&am…

Linux进阶课:目录(文件夹)与文件操作

1、ls与cat的区别是是什么&#xff1f; 答&#xff1a;ls命令的含义是list&#xff0c;显示当前目录中内容。不加参数时它显示当前目录中除隐藏文件外的所有文件及目录的名字。 cat命令是linux下的一个文本输出命令&#xff0c;通常是用于查看某个文件的内容的。 2、[abc]这个…

只不过孤岛罢了:我的2023年总结

2023已悄然过去&#xff0c;还记得跨年夜那天&#xff0c;我突然接到一星期要期末考的消息&#xff0c;我的内心是多么奔溃&#xff0c;先不说一天一门强度如此之高&#xff0c;重要的是矩阵论&#xff0c;工程优化等等科目&#xff0c;还要速成&#xff0c;于是麻木得预习一日…

Servlet-体系结构

一、思考 读者阅读完上一篇关于Servlet基本概念的文章后&#xff0c;我们知道每次实现一个Servlet&#xff0c;都需要覆盖五个接口&#xff0c;我们对除service接口外的其它四个接口&#xff0c;我们通常不会做什么处理。那么&#xff0c;这种实现方式是否有些繁琐呢&#xff…

MT36291 2.5A 高效的1.2MHz电流模式升压转换器 DCDC管理芯片 航天民芯

描述 MT36291是一个恒定频率、6引脚SOT23电流模式升压转换器&#xff0c;旨在用于小型、低功耗的应用。MT36291的开关频率为1.2MHz&#xff0c;并允许使用2mm或更低高度的微小、低成本的电容器和电感器。内部软启动导致注入电流小&#xff0c;延长电池寿命。MT36291的特点是在光…

【数据结构Java版】对象的比较之Comparable与Comparator比较器

目录 一、基本类型的比较 二、对象类型的比较 &#xff08;1&#xff09;对象类型比较出现的问题 &#xff08;2&#xff09;重写基类equals方法 &#xff08;3&#xff09;基于Comparable接口的比较 1.实现Comparable接口&#xff0c;重写compareTo方法 &#xff08;4&a…

C++力扣题目501--二叉搜索树中的众数

给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root &#xff0c;找出并返回 BST 中的所有 众数&#xff08;即&#xff0c;出现频率最高的元素&#xff09;。 如果树中有不止一个众数&#xff0c;可以按 任意顺序 返回。 假定 BST 满足如下定义&#xf…

激光雷达lidar

LIDAR 101 What is lidar? Lidar (light detection and ranging) uses eye-safe laser beams to “see” the world in 3D, providing machines and computers an accurate representation of the surveyed environment. How Does Lidar Work? A typical lidar sensor emi…

数据结构与算法之美学习笔记:46 | 概率统计:如何利用朴素贝叶斯算法过滤垃圾短信?

目录 前言算法解析总结引申 前言 本节课程思维导图&#xff1a; 上一节我们讲到&#xff0c;如何用位图、布隆过滤器&#xff0c;来过滤重复的数据。今天&#xff0c;我们再讲一个跟过滤相关的问题&#xff0c;如何过滤垃圾短信&#xff1f; 垃圾短信和骚扰电话&#xff0c;我…

牛客(JZ36 二叉搜索树与双向链表)

题目链接 思路1&#xff1a;使用中序遍历&#xff0c; 创建一个cur记录当前结点&#xff0c;prev记录上一个结点&#xff0c;这样cur->left prev&#xff0c;prev->right cur&#xff0c; 这样就链接 成功了。 难点&#xff1a;需要使用引用来控制prev。 /* struct T…

软件测试|深入理解Python中的re.search()和re.findall()区别

前言 在Python中&#xff0c;正则表达式是一种强大的工具&#xff0c;用于在文本中查找、匹配和处理模式。re 模块提供了许多函数来处理正则表达式&#xff0c;其中 re.search()和 re.findall() 是常用的两个函数&#xff0c;用于在字符串中查找匹配的模式。本文将深入介绍这两…