我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Swift如何优雅的进行解包

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Swift如何优雅的进行解包

前言

对于Swift学习而言,可选类型Optional是永远绕不过的坎,特别是从OC刚刚转Swift的时候,可能就会被代码行间的?与!,有的时候甚至是??搞得稀里糊涂的.

这篇文章会给各位带来我对于可选类型的一些认识以及如何进行解包,其中会涉及到Swift中if let以及guard let的使用以及思考,还有涉及OC部分的nullablenonnull两个关键字,以及一点点对两种语言的思考.

var num: Int?它是什么类型?

在进行解包前,我们先来理解一个概念,这样可能更有利于对于解包.

首先我们来看看这样一段代码:


var num: Int?
num = 10
if num is Optional<Int> {
	print("它是可选类型")
}else {
	print("它是Int类型")
}

请先暂时不要把这段代码复制到Xcode中,先自问自答,num是什么类型,是Int类型吗?

好了,你可以将这段代码复制到Xcode里去了,然后在IDE中的if上一定会出现这样一段话:'is' test is always true

不是Int类,它是Optiona类型

那么Optional类型是啥呢--可选类型,具体Optional是啥,点进去看看你就知道了.在这里我就不多做解释了.

var num: Int?这是Optional的声明,意思不是"我声明了一个Optional的Int值",而是”我声明了一个Optional类型值,它可能包含一个Int值,也可能什么都不包含”,也就是说实际上我们声明的是Optional类型,而不是声明了一个Int类型!

以此类推String?是什么类型,T?是什么类型,答案各位心中已经明了吧.

正是因为num是一个可选类型,所以它才能赋值为nil, var num: Int = nil,这样是不可能赋值成功的,因为Int类型中没有nil这个概念!

这就是Swift与OC一个很大区别,在OC中我们的对象都可以赋值为nil,而在Swift中,能赋值为nil只有Optional类型!

解包的基本思路

使用if let或者guard let,而非强制解包

我们先来看一个简单的需求,虽然这个需求在实际开发中意义不太大:

我们需要从网络请求获取到的一个人的身高(cm为单位)以除以100倍,以获取m为单位的结果然后将其结果进行返回.

设计思路:

由于实际网络请求中,后台可能会返回我们的身高为空(即nil),所以在转模型的时候我们不能定义Float类型,而是定义Float?便于接受数据

如果身高为nil,那么nil除以100是没有意义的,在编译器中Float?除以100会直接报错,那么其返回值也应该为nil,所以函数的返回值也是Float?类型

那么函数应该设计成为这个样子是这样的:


func getHeight(_ height: Float?) -> Float?

如果一般解包的话,我们的函数实现大概会写成这样:


func getHeight(_ height: Float?) -> Float? {
	if height != nil {
		return height! / 100
	}
	return nil
}

使用!进行强制解包,然后进行运算

我想说的是使用强制解包固然没有错,不过如果在实际开发中这个height参数可能还要其他用途,那么是不是每使用一次都要进行强制解包?

强制解包是一种很危险的行为,一旦解包失败,就有崩溃的可能,也许你会说这不是有if判断,然而实际开发中,情况往往比想的复杂的多,所以安全的解包行为应该是通过if let 或者guard let来进行


func getHeight(_ height: Float?) -> Float? {
	if let unwrapedHeight = height {
		return unwrapedHeight / 100
	}
	return nil
}

或者


func getHeight(_ height: Float?) -> Float? {
	guard let unwrapedHeight = height else {
		return nil
	}
	return unwrapedHeight / 100
}

那么if let和guard let 你更倾向使用哪个呢?

在本例子中,其实感觉二者的差别不大,不过我个人更倾向于使用guard let.

原因如下:

在使用if let的时候其大括号类中的情况才是正常情况,而外部主体是非正常情况的返回的nil;

而在使用guard let的时候,guard let else中的大括号是异常情况,而外部主体返回的是正常情况.

对于一个以返回结果为目的的函数,函数主体展示正常返回值,而将异常抛出在判断中,这样不仅逻辑更清晰,而且更加易于代码阅读

解包深入

有这么一个需求,从本地路径获取一个json文件,最终将其转为字典,准备进行转模型操作

在这个过程中我们大概有这么几个步骤:

1.获取本地路径

func path(forResource name: String?, ofType ext: String?) -> String?

2. 将本地路径读取转为Data

init(contentsOf url: URL, options: Data.ReadingOptions = default) throws

3. JSON序列化

class func jsonObject(with data: Data, options opt: JSONSerialization.ReadingOptions = []) throws -> Any

4. 是否可以转为字典类型

我们可以看到以上几个函数中,获取路径获取返回的路径结果是一个可选类型,而转Data的方法是抛出异常,JSON序列化也是抛出异常,至于最后一步的类型强转是使用as! [Sting: Any]这样的操作

这个函数我是这来进行设计与步骤分解的:

函数的返回类型为可选类型,因为下面的4步中都有可能失败进而返回nil

虽然有人会说第一步获取本地路径,一定是本地有的才会进行读取操作,但是作为一个严谨操作,凡事和字符串打交道的书写都是有隐患的,所以我这里还是用了guard let进行守护

这个函数看起来很不简洁,每一个guard let 后面都跟着一个异常返回,甚至不如使用if let看着简洁

但是这么写的好处是:在调试过程中你可以明确的知道自己哪一步出错


func getDictFromLocal() -> [String: Any]? {
	//1 获取路径
	guard let path = Bundle.main.path(forResource: "test", ofType:"json") else {
		return nil
	}
	//2 获取json文件里面的内容
	guard let jsonData = try? Data.init(contentsOf: URL.init(fileURLWithPath: path)) else {
		return nil
	}
	//3 解析json内容
	guard let json = try? JSONSerialization.jsonObject(with: jsonData, options:[]) else {
		return nil
	}
	//4 将Any转为Dict
	guard let dict = json as? [String: Any] else {
		return nil
	}
	return dict
}

当然,如果你要追求简洁,这么写也未尝不可,一波流带走


func getDictFromLocal() -> [String: Any]? {
	guard let path = Bundle.main.path(forResource: "test", ofType:"json"),
		let jsonData = try? Data.init(contentsOf: URL.init(fileURLWithPath: path)),
		let json = try? JSONSerialization.jsonObject(with: jsonData, options:[]),
		let dict = json as? [String: Any] else {
		return nil
	}
	return dict
}

guard let与if let不仅可以判断一个值的解包,而是可以进行连续操作

像下面这种写法,更加最求的是结果,对于一般的调试与学习,多几个guard let进行拆分,也未尝不可

至于哪种用法更适合,因人而异

可选链的解包

至于可选链的解包是完全可以一步到位,假设我们有以下这个模型


class Person {
	var phone: Phone?
}
class Phone {
	var number: String?
}

Person类中有一个手机对象属性,手机类中有个手机号属性,现在我们有位小明同学,我们想知道他的手机号

小明他不一定有手机,可能有手机而手机并没有上手机号码


let xiaoming = Person()
guard let number = xiaoming.phone?.number else {
	return
}

这里只是抛砖引玉,更长的可选链也可以一步到位,而不必一层层进行判断,因为可选链中一旦有某个链为nil,那么就会返回nil

nullable和nonnull

我们先来看这两个函数,PHImageManager在OC与Swift中通过PHAsset实例获取图片的例子


[[PHImageManager defaultManager] requestImageForAsset:asset 
targetSize:size 
contentMode:PHImageContentModeDefault 
options:options 
resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
// 非空才进行操作 注意_Nullable,Swift中即为nil,注意判断
if (result) {
}
}];

PHImageManager.default().requestImage(for: asset, 
targetSize: size, 
contentMode: .default, 
options: options, 
resultHandler: { (result: UIImage?, info: [AnyHashable : Any]?) in
 guard let image = result else { return }
})

在Swift中闭包返回的是两个可选类型result: UIImage?与info: [AnyHashable : Any]?

而在OC中返回的类型是 UIImage * _Nullable result, NSDictionary * _Nullable info

注意观察OC中返回的类型UIImage * 后面使用了_Nullable来修饰,至于Nullable这个单词是什么意思,我想稍微有点英文基础的应该一看就懂--"可能为空",这不恰恰和Swift的可选类型呼应吗?

另外还有PHFetchResult遍历这个函数,我们再来看看在OC与Swift中的表达


PHFetchResult *fetchResult;

[fetchResult enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

}];

let fetchResult: PHFetchResult
fetchResult.enumerateObjects({ (obj, index, stop) in
})

看见OC中Block中的回调使用了Nonnull来修饰,即不可能为空,不能为nil,一定有值,对于使用这样的字符修饰的对象,我们就不必为其做健壮性判断了.

这也就是nullable与nonnull两个关键字出现的原因吧--与Swift做桥接使用以及显式的提醒对象的状态

一点点Swift与OC的语言思考

OC函数是这样的


- (NSString *)stringByAppendingString:(NSString *)aString;

Swift中函数是这样的


public mutating func append(_ other: String)

仅从API来看,OC的入参是很危险的,因为类型是NSString *

那么nil也可以传入其中,而传入nil的后果就是崩掉,我觉得对于这种传入参数为nil会崩掉的函数需要特别提醒一下,应该写成这样:


- (NSString *)stringByAppendingString:(NSString * _Nonnull)aString;

或者这样


- (NSString *)stringByAppendingString:(nonnull NSString *)aString;

以便告诉程序员,入参不能为空,不能为空,不能为空.重要的事情说三遍!!!

反观Swift就不会出现这种情况,other后面的类型为String,而不是String?,说明入参是一个非可选类型.

基于以上对于代码的严谨性,所以我才更喜欢使用Swift进行编程.

当然,Swift的严谨使得它失去部分的灵活性,OC在灵活性上比Swift卓越,但是从安全角度和编码的长远意义看Swift才是现在与未来.

最后想说的是Swift在国内的使用并不是很受拥戴,这点很无奈,因为和整个的大环境有关.

以上就是Swift如何优雅的进行解包的详细内容,更多关于Swift解包的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Swift如何优雅的进行解包

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何优雅的在手机上进行Python编程

关注「实验楼」,每天分享一个项目教程   大家都知道有安卓上有一个QPython,可以写Python,但是QPython已经好久没有更新了,官方也貌似没有更新的打算了。今天,给大家推荐另一款安卓工具。正文共:1071 字 预计阅读时间:3
2023-01-31

如何进行C++代码的优雅异常处理?

如何进行C++代码的优雅异常处理?在编程过程中,异常处理是一个重要的方面。异常处理可以帮助我们在代码执行过程中处理意外情况,保证程序的稳定性和可靠性。在C++中,异常处理的机制能有效地捕获和处理错误,从而帮助我们优雅地处理异常情况。本文将介
如何进行C++代码的优雅异常处理?
2023-11-02

如何进行更加优雅地Docker部署项目

本篇文章给大家分享的是有关如何进行更加优雅地Docker部署项目,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。如果你需要经常性需要多处部署同样的项目,如果你曾经也遇到过"明明在
2023-06-19

如何进行Python序列进行解包教程

如何进行Python序列进行解包教程,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Python序列具有很广泛的应用范围,在实际的应用中还是有不少的问题需要我们大
2023-06-17

如何优雅、安全的关闭MySQL进程

前言 本文分析了 mysqld 进程关闭的过程,以及如何安全、缓和地关闭 MySQL 实例,对这个过程不甚清楚的同学可以参考下。 关闭过程 1、发起 shutdown,发出 SIGTERM 信号 2、有必要的话,新建一个关闭线程(shutd
2022-05-12

C++开发建议:如何进行C++代码优雅的错误处理

C++开发建议:如何进行C++代码优雅的错误处理引言:在C++开发中,错误处理是一项重要的技术,它涉及如何识别错误、处理异常情况以及保证程序的正常运行。良好的错误处理机制可以提高代码的可维护性和可靠性,而糟糕的错误处理可能导致程序崩溃、资源
C++开发建议:如何进行C++代码优雅的错误处理
2023-11-22

linux如何优雅的计算程序运行时间

小编给大家分享一下linux如何优雅的计算程序运行时间,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!优雅的计算程序运行时间在 Linux 下,可以通过 time
2023-06-27

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录