【学习iOS高质量开发】——熟悉Objective-C

news/2024/2/23 15:49:28/文章来源:https://blog.csdn.net/m0_73974920/article/details/135540550

文章目录

  • 一、Objective-C的起源
    • 1.OC和其它面向对象语言
    • 2.OC和C语言
    • 3.要点
  • 二、在类的头文件中尽量少引用其他头文件
    • 1.OC的文件
    • 2.向前声明的好处
    • 3.如何正确引入头文件
    • 4.要点
  • 三、多用字面量语法,少用与之等价的方法
    • 1.何为字面量语法
    • 2.字面数值
    • 3.字面量数组
    • 4.字面量字典
    • 5.可变数组和字典
    • 6.局限性
    • 7.要点
  • 四、多用类型常量,少用#define预处理指令
    • 1.类型常量与#define
    • 2.static和const
    • 3.外界可见的常值变量
    • 4.要点
  • 五、用枚举表示状态、选项、状态码
    • 1.枚举表示状态
    • 2.枚举表示选项
    • 3.辅助宏
    • 4.枚举表示状态码
    • 5.要点


一、Objective-C的起源

1.OC和其它面向对象语言

Objective-C和Java、C++都是面向对象语言但是语法上有些许不同。OC使用“消息结构”而不是“函数调用”,这二者的区别主要体现在:使用消息结构的语言,其运行所应执行的代码由运行环境来决定;使用函数调用的语言,则由编译器决定。OC的重要工作都是由运行期组件来完成而不是编译器。

消息与函数调用之间的区别:

//Messaging(Objevtive-C)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];//Function calling(C++)
Object *obj = new Object;
obj->perform(parameter1, parameter2);

运行期组件包含OC面向对象特性的全部数据结构和函数,它的本质就是一种动态库并且能够桥梁一样把我们所编写的代码连接起来。

2.OC和C语言

OC是C语言的超集,因此C语言中所有的功能在OC中依然可以使用,并且理解OC中的内存模型和引用计数的一个前提就是理解C语言的内存模型。OC的指针是指向对象的,想要声明一个变量并且指向某个对象可以使用如下语法:


NSString *someString = @"The string";

OC中所有的对象都必须这么声明,因为OC中的对象所占的内存是在堆区中而不会分配到栈区中,这样的好处有:

1.在堆区分配对象意味着对象的生命周期可以超出当前函数的作用域。如果对象被分配在栈区,当函数返回时,栈上的局部变量会被自动销毁,这将导致对象在栈上被销毁,进而引发悬垂指针的问题。
2.指针变量通常存储在栈中,对象在堆区分配可以被多个指针同时所指。
3.堆区可以容纳任意大小的对象,而栈区的大小通常是有限的。由于对象的大小不确定,将其分配到堆区可以更好地适应各种大小的对象需求。

在这里插入图片描述

3.要点

1.Objective-C为C语言添加了面向对象特性,是其超集。Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。接收一条消息之后,究竟应执行何种代码,由运行期环境而非编译器来决定。

2.理解C语言的核心概念有助于写好Objective-C程序。尤其是要掌握内存模型和指针。

二、在类的头文件中尽量少引用其他头文件

1.OC的文件

与C和C++一样,Objective-C也使用“头文件”(header file)与“实现文件”(implementation file)来区隔代码。用Objective-C编写“类”(class)的标准方式:以类名做文件名,分别创建两个文件,头文件后缀用.h,实现文件后缀用.m。

//EOCPerson.h
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@end//EOCPerson.m
#import "EOCPerson.h"
@implementation EOCPerson
//Implementation of methods
@end

如果创建一个新类例如EOCEmployer类然后将其设置为EOCPerson类的属性就需要在ECOPerson类的接口部分添加#import "EOCEmployer.h"。有时候我们使用EOCPerson类时不需要知道ECOEmployer类的全部细节只需要知道有这个类就行,此时就可以使用向前声明(forward declaring) 需要修改的就是在接口部分将#import "EOCEmployer.h"替换成@class EOCEmployer;

但是在ECOPerson类的实现部分要使用ECOEmpoyer类就必须知道它的全部细节因此就需要添加#import "EOCEmployer.h"。需要注意的是,将引入头文件的时机尽量延后,只在需要的时候才引入,这样就可以减少类的使用者所需引入头文件的数量。

2.向前声明的好处

向前声明解决了两个类相互引用的问题。例如要编译EOCEmployer类则编译器必须知道EOCPerson类,要编译EOCPerson类则编译器必须知道EOCEmployer类,所以需要在两个头文件中互相引用对方的头文件,但是这样就会导致循环引用从而引发两个类中有一个不能被正常编译。使用向前声明则不需要引入头文件也就不会通过头文件的循环引用导致出错。

3.如何正确引入头文件

1.如果你写的类继承于某个超类,则必须引入定义那个超类的头文件。

2.如果声明某个类遵循某个协议,那么这个协议就必须有完整的定义也就是要知道这个协议的全部细节,因此就不能使用向前声明。所以要实现属性、实例变量或者遵循协议而必须引入头文件的话,则应该将其移至“class-continuation分类中”。

4.要点

1.除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。

2.有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循的协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。


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

1.何为字面量语法

在OC中初始化NSString对象一般用alloc及init方法,这种方法有点繁杂,因此从Objective-C 1.0的时候出现了非常简单的方式来创建NSString对象,就是“字符串字面量”,语法如下:

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

这个格式跟C语言中字符串的初始化如出一辙。使用字面量语法可以缩减源代码长度,使其更为易读。

2.字面数值

传统的将数值包装成OC的NSNumber对象使用如下方法:

NSNumber *someNumber = [NSNumber numberWithInt:1];

使用字面量语法则是:

NSNumber *someNumber = @1;

并且能够以NSNumber实例表示的所有数据类型都可以使用字面量:

NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
//也适用下面的的表达式
int x = 5;
float y = 6.32f;
NSNumber *expressionNumber = @(x * y);

3.字面量数组

传统的创建数组和操作数组元素的方法如下:

NSArray *animals = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", @"badger", nil];NSString *dog = [animals objectAtIndex:1];

而使用字面量语法来创建和操作数组元素的方式如下:

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

可以看到使用字面量语法更加简单并且可读性更强。还有一点,当数组中某个元素为nil时,使用传统方法创建的数组会依次处理各个参数直到遇到nil才停止,也就是不会报错但是数组个数为nil元素之前的个数。使用字面量语法则会抛出异常并且程序终止,这样程序的健壮性会更好。

4.字面量字典

传统的创建字典和根据键来取值的方法如下:

NSDictionary *personData = [NSDictionary dictionaryWithObjectivesAndKeys:@"Mett", @"firstName", @"Galloway", @"lastName", [NSNumber numberWithInt:28], @"age", nil];NSString *lastName = [personData objectForKey:@:lastName"];

而使用字面量语法创建字典和根据键来取值的方法如下:

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

可以看到使用字面量语法更加简单并且更符合阅读习惯。和字面量创建数组一样,使用字面量语法创建字典遇到nil则会抛出异常并且程序终止。

5.可变数组和字典

通过取下标操作,可以访问数组中的某个元素或者字典中的某个键对应的元素,如果数组和字典是可变的(mutable),那么也能通过下标修改其中的元素值,例如:

mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";

6.局限性

字面量语法有个小小的限制,就是除了字符串以外,所创建出来的对象必须属于Foundation框架才行。如果自定义了这些类的子类,则无法用字面量语法创建其对象。并且使用字面量语法创建出来的字符串、数组、字典对象都是不可变的,要变成可变对象则需要拷贝一份:

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

7.要点

1.应该使用字面量语法来创建字符串、数值、字典。与创建此类对象的常规方法相比,这么做更加简明扼要。

2.应该通过取下标操作来访问数组下标或者字典中的键对应的元素。

3.用字面量语法创建数组或者字典时,若值中有nil,则会抛出异常。因此,务必确保值里不含nil。

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

1.类型常量与#define

通常在定义常量时会用到#define,例如#define ANIMATION_DURATION 0.3, 但是使用这种方法定义的常量不会包含类型信息并且还有可能在编译之后运行时被修改从而导致问题。因此还有更好的方法来进行替换,例如下面这个方法:

static const NSTimeInterval kAnimationDuration = 0.3;

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

常量命名规则:
如果常量局限在实现文件中则在常量前加k;如果常量在类之外可见,则通常将类名作为该常量的前缀。

常量定义的位置也非常重要,我们最好不要将常量定义在头文件中,若你定义在头文件中,又被其他的文件引用了,那么该这个文件中的这个常量都会被其替换掉,所以最好不要在头文件中定义常量。

2.static和const

变量一定要同时使用static和const来声明,如果试图修改由const修饰符所声明的变量,那么编译器就会报错。static修饰符则意味着该变量仅在定义此变量的编译单元中可见。假如声明此变量时不加static,则编译器会为它创建一个“外部符号”。此时若是另一个编译单元中也声明了同名变量,那么编译器就会抛出错误信息。

3.外界可见的常值变量

有时候我们需要对外公开某个常量,比如说,用一个对象派发通知,让其他想要接收通知的对象向该对象注册,派发通知的时候需要字符串表示通知名称,我们定义一个常量,外界就可以直接使用这个常值变量来注册自己想要接收的通知即可,而不用知道实际字符串的值。此类常量需放在“全局符号表”中,以便可以在定义该常量的编译单元之外使用。方式如下:

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

这个常量在头文件中声明并且在实现文件中定义。 注意const在常量类型中的位置。因为常量定义应该从右向左解读,所以它是一个不可变的指向NSString *类型的指针,这个指针的指向不允许被改变。extern就是告诉编译器,在全局符号表中将会有一个名叫EOCStringConstant的符号,也就是说,编译器无需查看其定义,即允许代码使用此常量。因为符号要放在全局符号表里,所以我们就得更加注意其命名,为避免冲突,最好是用与之相关的类名做前缀。

4.要点

1.不能用预处理指令定义常量。这样定义出来的常量不含类型信息,编译器只是会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。

2.在实现文件中使用static const来定义“只在编译单元内可见的常量”。由于此常量不在全局符号表中,所以无需为其名称加前缀。

3.在头文件中使用extern来声明全局变量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应该加以区隔,通常用与之相关的类名做前缀。

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

1.枚举表示状态

枚举是一种常量命名方式。某个对象所经历的各种状态就可以定义为一个简单的枚举集。例如用下列枚举表示“嵌套字连接”的状态:

enum EOCConnectionState {EOCConnectionStateDisConnected,EOCConnectionStateConnecting,EOCConnectionStateConnected,
};

编译器一般从0开始每个枚举加1来为枚举分配一个独有的编号,并且实现枚举所用的数据类型取决于编译器。定义枚举变量的方式通常如下:

enum EOCConnectionState = EOCConnectionStateDisConnected;

可以看到不是很简洁,因此为了操作起来更加方便可以使用typedef关键字重新定义枚举类型的方式:

typedef enum EOCConnectionState EOCConnectionState;

C++11标准修改后,可以指明用何种底层数据类型来保存枚举类型的变量,这样就可以向前声明枚举变量了。其语法如下:

enum EOCConnectionState : NSInteger {/*...*/};

上面这个枚举类型数据的底层数据类型就是NSInteger了,我们也可以使用向前声明的方法:

enum EOCConnectionState : NSInteger;

还可以收订指定某个枚举成员的所对应的值,那么在它之后的枚举类型的值就是在该值的基础上依次加一。

enum EOCConnectionState {EOCConnectionStateDisConnected = 1,EOCConnectionStateConnecting,EOCConnectionStateConnected,
};

2.枚举表示选项

使用枚举可以定义选项并且用“按位或操作符”可以组合多个选项,用“按位与操作符”可以判断是否启用某个选项。例如,iOS UI框架中有如下枚举类型,用来表示某个试图应该如何在水平或者垂直方向上调整方向:

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

上面操作代码如下:

enum UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;if (resizing & UIViewAutoresizingFlexibleWidth) {//UIViewAutoresizingFlexibleWidth is set
}

在这里插入图片描述

3.辅助宏

Foundation框架下定义了一些辅助宏来定义枚举类型从而指定用于保存枚举值的底层数据类型。这些宏在支持新语法的编译器上使用新式语法,否则是旧式语法。

4.枚举表示状态码

可以把逻辑含义相似的一组状态码放入一个枚举集中,而不需要用#define来定义。比如在创建UI元素的时候使用不同的样式,可以把这些样式声明为枚举类型。在switch语句里,枚举类型可以这样定义:

typedef NS_ENUM (NSUInteger, EOCConnectionState) {EOCConnectionStateDisConnected,EOCConnectionStateConnecting,EOCConnectionStateConnected,
};switch (_currentState) {EOCConnectionStateDisConnected://Handle disconnected statebreak;EOCConnectionStateConnecting://Handle connecting statebreak;EOCConnectionStateConnected://Handle connected statebreak;
}

通常我们喜欢在switch语句的最后加上default分支,但是定义状态码的话最好不要用。因为你本就是枚举变量,使用default分支如果用户没有使用枚举中的类型,那么这个switch语句也会进入default分支,会导致程序出现问题。

5.要点

1.应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。

2.如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。

3.用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。

4.在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。

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

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

相关文章

Apache DolphinScheduler 3.1.8 保姆级教程【安装、介绍、项目运用、邮箱预警设置】轻松拿捏!

概述 Apache DolphinScheduler 是一个分布式易扩展的可视化 DAG 工作流任务调度开源系统。适用于企业级场景&#xff0c;提供了一个可视化操作任务、工作流和全生命周期数据处理过程的解决方案。 Apache DolphinScheduler 旨在解决复杂的大数据任务依赖关系&#xff0c;并为应…

浏览器网页内嵌Qt-C++音视频播放器的实现,支持软硬解码,支持音频,支持录像截图,支持多路播放等,提供源码工程下载

一.前言 在浏览器中实现播放RTSP实时视频流&#xff0c;⼤体上有如下⼏个⽅案&#xff1a; ⽅案一&#xff1a;浏览器插件⽅案 ActiveX、NPAPI、PPAPI ActiveX插件适用于IE浏览器&#xff0c;NPAPI与PPAPI插件适用于谷歌浏览器&#xff0c;不过这些插件都已经不被浏览器所支持…

HashData湖仓一体方案:方案概览与Hive数据同步

随着云计算、大数据、AI的发展和普及&#xff0c;各行各业的业务场景日益复杂&#xff0c;数据呈现出大规模、多样性的特点&#xff0c;企业对数据仓库的需求也进一步拓展至对多元化数据实时处理的场景。 数据湖是多元数据存储与使用的便捷选择&#xff0c;而云原生具有数据资…

GBASE南大通用提问:如果程序检索到 NULL 值,该怎么办?

可在数据库中存储 NULL 值&#xff0c;但编程语言支持的数据类型不识别 NULL 状态。程序必须 采用某种方式来识别 NULL 项&#xff0c;以免将它作为数据来处理。 在 SQL API 中&#xff0c;指示符变量满足此需要。指示符变量是与可能收到 NULL 项的主变量相 关联的一个附加的变…

PHP面试小结(20240108)

PHP 部分 1. php的包管理工具是如何实现自动加载的 换句话问&#xff1a;composer 实现原理是什么&#xff1f;spl_autoload_register() 首先&#xff0c;Composer 是 PHP 的一个包管理和包依赖管理的工具 &#xff0c; 打开安装之后生成的 "vendor" 文件, 里面有个…

opencv_角点检测

文章内容 一个opencv检测角点的程序 运行效果 #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream>using namespace cv; using namespace std;void detectCorners(M…

uniapp中按钮点击跳转页面失效,纠正错误(亲测可用)

不知道伙伴你的错误和我是否一致&#xff1f; 我当时为了点击跳转按钮发现跳转不了&#xff0c;如下错误提示&#xff1a; worker.js?libNameWAAccelerateWorker.js:1 [Deprecation] SharedArrayBuffer will require cross-origin isolation as of M92, around July 2021. S…

Debian12 安装jenkins 公钥配置

jenkins公钥配置 参考&#xff1a;Debian Jenkins 软件包 这是 Jenkins 的 Debian 软件包存储库&#xff0c;用于自动安装和升级。 要使用此存储库&#xff0c;请先将密钥添加到您的系统&#xff08;对于每周发布行&#xff09;&#xff1a; sudo wget -O /usr/share/keyring…

36.有效的数独

36.有效的数独 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图…

企业微信开发:自建应用:access_token

access_token 过期后接口响应 access_token 已经过期&#xff08;2小时&#xff09;后&#xff0c;调用接口的响应&#xff1b;本文中以发送消息接口为例&#xff0c;说明接口响应的情况。 官方开发文档链接&#xff1a;获取access_token access_token 过期后调用接口 响应体 …

2024年腾讯云主机价格表,附报价明细

腾讯云服务器租用价格表&#xff1a;轻量应用服务器2核2G3M价格62元一年、2核2G4M价格118元一年&#xff0c;540元三年、2核4G5M带宽218元一年&#xff0c;2核4G5M带宽756元三年、轻量4核8G12M服务器446元一年、646元15个月&#xff0c;云服务器CVM S5实例2核2G配置280.8元一年…

机器学习小记——KNN(K近邻)

为了让绝大多数人都可以看懂&#xff0c;所以我就用简单的话语来讲解机器学习每一个算法 第一次写ML的博文&#xff0c;所以可能会有些地方出错&#xff0c;欢迎各位大佬提出意见或错误 祝大家开心进步每一天&#xff5e; 博文代码全部为python 简单的说一下什么是机器学习…

adb wifi 远程调试 安卓手机 命令

使用adb wifi 模式调试需要满足以下前提条件&#xff1a; 手机 和 PC 需要在同一局域网下。手机需要开启开发者模式&#xff0c;然后打开 USB 调试模式。 具体操作步骤如下&#xff1a; 将安卓手机通过 USB 线连接到 PC。&#xff08;连接的时候&#xff0c;会弹出请求&#x…

Android 系统启动过程纪要(基于Android 10)

前言 看过源码的都知道&#xff0c;Launcher系统启动都会经过这三个进程 init ->zygote -> system_server。今天我们就来讲解一下这三个进程以及Launcher系统启动。 init进程 准备Android虚拟机环境&#xff1a;创建和挂载系统文件目录&#xff1b;初始化属性服务&…

AI大模型预先学习笔记二:prompt提问大模型、langchain使用大模型框架、fine tune微调大模型

文章目录 一、Prompt Engineering&#xff08;怎么去提问大模型&#xff09;1&#xff09;环境准备2&#xff09;交互代码的参数备注3&#xff09;交互代码 二、LangChain&#xff08;一个框架去使用大模型&#xff09;1&#xff09;LangChain核心介绍&#xff1a;I/O模块、数据…

Java NIO (二)NIO Buffer类的重要方法

1 allocate()方法 在使用Buffer实例前&#xff0c;我们需要先获取Buffer子类的实例对象&#xff0c;并且分配内存空间。需要获取一个Buffer实例对象时&#xff0c;并不是使用子类的构造器来创建&#xff0c;而是调用子类的allocate()方法。 public class AllocateTest {static…

四、Sharding-JDBC系列04:分库分表后,如何不停机迁移数据?

目录 停机迁移方案 双写迁移方案 一般会有两种方案&#xff1a; 停机迁移方案 这种方案最简单也是最low的。 数据迁移前&#xff0c;在网站或者app挂个公告&#xff0c;说0点到早上6点系统进行维护&#xff0c;无法访问。 接着到0点停机&#xff0c;系统停掉&#xff0c;…

k8s---配置资源管理

目录 配置资源管理的方式 secret pod如何来引用secret&#xff1f;&#xff1f;&#xff1f; 陈述式创建&#xff1a; 声明式创建 Secret创建加密文件 使用token挂载 环境变量使用 docker-registry ConfigMap 陈述式 热更新 总结&#xff1a; 配置资源管理的方式 …

Go-gin-example 第二部分 jwt验证

文章目录 使用 JWT 进行身份校验jwt知识点补充认识JWTTOKEN是什么jwt的使用场景jwt的组成headerpayloadsignature 下载依赖包编写 jwt 工具包jwt中间件编写如何获取token 编写获取token的Apimodels逻辑编写路由逻辑编写修改路由逻辑 验证token将中间件接入Gin功能验证模块 续接…

【开源】基于JAVA语言的固始鹅块销售系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 鹅块类型模块2.3 固始鹅块模块2.4 鹅块订单模块2.5 评论管理模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 鹅块类型表3.2.2 鹅块表3.2.3 鹅块订单表3.2.4 鹅块评论表 四、系统展示五、核心代码5.…