Swift如何优雅的进行解包

目录
  • 前言
  • var num: Int?它是什么类型?
  • 解包的基本思路
  • 解包深入
  • 可选链的解包
  • nullable和nonnull
  • 一点点Swift与OC的语言思考

前言

对于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解包的资料请关注我们其它相关文章!

(0)

相关推荐

  • swift4.2实现新闻首页导航

    对于仿照新闻首页的页面,已经有比较好用的OC版本,现在我们来写一个swift版本的. 设备:xcode 10.2     语言:swift 4.2 效果图: 我们先创建一个多控制器的导航栏,直接上代码: // // JHSBarItemView.swift // ScrollBarController // // Created by yaojinhai on 2019/4/15. // Copyright © 2019年 yaojinhai. All rights reserved. // i

  • SwiftUI中级List如何添加新内容(2020年教程)

    功能说明 如何使用List循环显示array内容 .self 作为id的使用 如何更新List内容 TextField基础使用 代码 import SwiftUI struct ListAddItemView: View { @State var products = ["手机","电脑","水杯"] @State var pName:String = "" var body: some View { VStack{ Text

  • Swift 去除 TableView 多余的空Cell中的横线的方法

    在使用 UITableViewController 的时候,多余的空 cell 会默认展示很多横线. 如何去除呢? 给 footerHeight 反一个极小的值就可以了 override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.001 } 结果 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们.

  • Swift无限循环控件开发

    无限循环控件是一个常常用到的一个控件,尤其是一些广告或者应用内容公告通知,或者新闻滚动的设计,都是必备的.这种控件网上也有很多,也有很多可以自定义的版本,功能非常强大. 但对于我们开发者来说,在具体的应用上风格和样式都是比较统一的,一般只需要自己特定的一种风格或样式即可,引入第三方显然有点大材小用.那么我们怎么能简单而且又快速的造一个无限循环的控件呢,只要我们知道无限循环的原理,那么我们就很自由的按照需求快速的完成.今天我们就讲讲这个'造轮'过程. 首先我们简单分析一下无限循环的原理.一个控件的

  • Swift 进阶 - map 和 flatMap的使用

    map 和 flatMap 主要分在集合上的使用和在可选类型上的使用,下面分别来看下. 集合上使用 map 和 flatMap 先看如下的代码: func getInfos(by name: String) -> [String] { if name == "Jack" { return ["Male", "25", "New York"] } else if name == "Lucy" { ret

  • IOS在SwiftUI中显示模态视图的实例代码

    简介 这里教大家如何弹出一个简单的模态视图.分别有两个页面,ContentView和GCPresentedView,以下对应简称为A和B.我们要做的是在A视图中点击按钮跳转到B视图,然后再从B视图点击按钮返回到A视图. 步骤 在A视图中创建按钮和模态视图代码 struct ContentView: View { @State var isPresented = false var body: some View { Button(action: { self.isPresented = true

  • Swift UIButton使用教程

    一.UIButton基本操作 1.创建按钮 let btn: UIButton = UIButton()//没有样式 let btns:UIButton =UIButton(type: UIButtonType)//有样式 let button = UIButton(frame:CGRect(x:10, y:150, width:100, height:30))//简化创建方式 UIButtonType有以下类型 public enum UIButtonType : Int { case cus

  • iOS SwiftUI 颜色渐变填充效果的实现

    SwiftUI 为我们提供了各种梯度选项,所有这些选项都可以通过多种方式使用. Gradient 渐变器 A color gradient represented as an array of color stops, each having a parametric location value. gradient是一组颜色的合集,每个颜色都忽略位置参数 LinearGradient 线性渐变器 线性渐变器拥有沿轴进行渐变函数,我们可以自定义设置颜色空间.起点和终点. 下面我们看看Linear

  • 深入探究Swift枚举关联值的内存

    enum Season { case Spring, Summer, Autumn, Winter } let s = Season.Spring 这是枚举最基础的用法,但是在swift中,对枚举的功能进行了加强,也就是关联值. 关联值可以将额外信息附加到 enum case中,像下面这样子. enum Test { case test1(v1: Int, v2: Int, v3: Int) case test2(v1: Int, v2: Int) case test3(v1: Int) cas

  • 详解Swift 之clipped是什么如何用

    clipped()函数介绍 Clips the view to its bounding rectangular frame. 将View裁剪成矩形 By default, a view's bounding frame is used only for layout, so any content that extends beyond the edges of the frame is still visible. Use the clipped(antialiased:)modifier

随机推荐