前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Swift进阶二:基本数据类型相关

Swift进阶二:基本数据类型相关

作者头像
拉维
发布2020-07-20 09:40:06
8030
发布2020-07-20 09:40:06
举报
文章被收录于专栏:iOS小生活iOS小生活

变量和常量

Swift中,使用关键字let来声明常量,使用关键字var来声明变量

在Objective-C中,如果没有特殊的指明,我们所声明的都是变量。可以通过如下几种方式来声明常量:

  1. 使用宏定义来模拟常量来使用。
  2. 使用const关键字来声明类型常量。
  3. 声明readonly只读属性。但是这只是一种伪常量,因为我们可以在其子类中通过复写getter方法来修改其值。

数值类型

整型

有符号整型

Int8:有符号8位整型,1字节

Int16:有符号16位整型,2字节

Int32:有符号32位整型,4字节

Int64:有符号64位整型,8字节

Int:默认,和平台相关(拥有与当前平台的原生字相同的长度),相当于OC中的NSInteger。

无符号整型

UInt8:无符号8位整型,1字节

UInt16:无符号16位整型,2字节

UInt32:无符号32位整型,4字节

UInt64:无符号64位整型,8字节

UInt:默认,和平台相关(拥有与当前平台的原生字相同的长度),相当于OC中的NSUInteger

浮点型

Float:32位浮点型,,4字节,至少有6位数字的精度

Double:64位浮点型(默认),8字节,至少有15位数字的精度

鉴于Double的精度更高,所以在二者均可的情况下,优先使用Double类型。

各个类型的取值区间如下:

类型别名

类型别名是一个为已存在类型定义的一个可选择的名字,可以使用typealias关键字来定义一个类型的别名。

有的时候,一个既有类型的名字可能会比较晦涩,在某些业务场景下,联系上下文,如果你想使用一个更合适、更具有表达性的名字来代替这个晦涩的既有类型名,那么就可以使用别名。

比如说,在做音频编码的时候,音频的采样率就是一个8位无符号整数,此时就可以给UInt8起一个别名:

代码语言:javascript
复制
typealias AudioSample = UInt8
let sample: AudioSample = 32

Optional可选型

可选型的使用:

代码语言:javascript
复制
let str: String? = "norman"

//判断展开
if str != nil {
    let count = str!.count
    print(count) // 6
}

//可选绑定
if let actualStr = str {
    let count = actualStr.count
    print(count) // 6
}

//强制展开
//⚠️使用!进行强制展开之前必须确保可选项中包含一个非nil的值
//let count = str!.count
//print(count) // 6

//隐式展开
//⚠️有些可选项一旦被设定值之后,就会一直拥有值,此时就不必每次访问的时候都进行展开
//通过在声明的类型后面添加一个叹号来隐式展开可选项
let string: String! = "lavie"
print(string.count) // 5

//可选链
//⚠️使用可选链的返回结果也是一个可选项
let count = str?.count
if let count = count {
    print(count) // 6
}

可选型Optional的实现原理探究

Optional实际上是标准库里面的一个enum类型,Optional在标准库中的定义如下:

代码语言:javascript
复制
public enum Optional<Wrapped> : ExpressibleByNilLiteral {

    /// The absence of a value.
    ///
    /// In code, the absence of a value is typically written using the `nil`
    /// literal rather than the explicit `.none` enumeration case.
    case none

    /// The presence of a value, stored as `Wrapped`.
    case some(Wrapped)

    /// Creates an instance that stores the given value.
    public init(_ some: Wrapped)

    @inlinable public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?

    @inlinable public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?

    public init(nilLiteral: ())

    @inlinable public var unsafelyUnwrapped: Wrapped { get }

    public static func ~= (lhs: _OptionalNilComparisonType, rhs: Wrapped?) -> Bool

    public static func == (lhs: Wrapped?, rhs: _OptionalNilComparisonType) -> Bool

    public static func != (lhs: Wrapped?, rhs: _OptionalNilComparisonType) -> Bool

    public static func == (lhs: _OptionalNilComparisonType, rhs: Wrapped?) -> Bool

    public static func != (lhs: _OptionalNilComparisonType, rhs: Wrapped?) -> Bool
}

通过上面的定义我们可知,Optional是一个enum,它有两个值:none和some,其中some是泛型的(使用Wrapped来表示泛型)。

Optional.none就是nil;

Optional.some则包装了实际的值。

在枚举Optional中,还有一个泛型属性unsafelyUnwrapped,其定义如下:

代码语言:javascript
复制
@inlinable public var unsafelyUnwrapped: Wrapped { get }

理论上,我们可以直接调用unsafelyUnwrapped属性来获取可选项的值:

代码语言:javascript
复制
let str: String? = "norman"

if str != nil {
    print(str.unsafelyUnwrapped.count)
}

实际上,str.unsafelyUnwrapped等同于str!。

字符串相关

Raw String中的字符串插值

代码语言:javascript
复制
let sum = 3 + 4
let result1 = "sum is \(sum)" // sum is 7
let result2 = #"sum is \(sum)"# // sum is \\(sum)
let result3 = #"sum is \#(sum)"# // sum is 7

使用索引访问和修改字符串

每一个String值都有相关的索引类型String.Index,它用于表示每个Character在字符串中的位置。

startIndex属性表示String中第一个Character的位置;endIndex表示String中最后一个字符后面的那个位置

endIndex属性并不是字符串下标脚本的合法实际参数。

如果String为空,则String和endIndex相等。

String.Index的定义如下:

代码语言:javascript
复制
    /// A position of a character or code unit in a string.
    public struct Index {
    }

我们可以看到,Index实际上是一个结构体

我们可以使用index(before:)和index(after:)方法来访问给定索引的前后

访问给定索引更远的索引,你可以使用index(_, offsetBy:);

代码语言:javascript
复制
        let name = "norman"
        print(name[name.startIndex]) // n
        print(name[name.index(before: name.endIndex)]) // n
        print(name[name.index(after: name.startIndex)]) // 0
        
        let index = name.index(name.startIndex, offsetBy: 3)
        print(name[index]) // m

可以使用insert来插入字符或者字符串

代码语言:javascript
复制
        var name = "norman"
        //插入字符
        name.insert("~", at: name.endIndex)
        print(name) // norman~
        //插入字符串
        name.insert(contentsOf: "Hello, ", at: name.startIndex)
        print(name) // Hello, norman~

可以使用remove和removeSubrange方法移除字符或者字符串

代码语言:javascript
复制
        var name = "Hello, norman~"
        //移除字符
        name.remove(at: name.index(before: name.endIndex))
        print(name)
        
        //移除字符串
        let range = name.startIndex...name.index(name.startIndex, offsetBy: 5)
        name.removeSubrange(range)
        print(name)

为什么Swift字符串的索引是String.Index结构体,而不是数字下标

在Unicode中, 一个我们可以看得见的单一字符,有可能并不是一个Unicdoe标量。例如【é】这个字符。它可以是一个Unicode标量【\u{e9}】, 也有可能是二个Unicode标量【\u{65}】和【\u{301}】组合的结果。上述2个标量的情况在Swift计算中,仍然会被认为是1个字符。 我们可以认为Swift中的charactor类型是一种扩展的类型。其由1个或者多个Unicode标量组成。不再是我们认为的1对1的对应关系。character是一个Unicode标量序列。 正因如此。一个字符串的长度,或者随机访问,需要遍历这个字符串的char序列进行Combine计算后才能得到。所以Swift用String.Index这个结构体来抽象String中的char的index。Swift也就不能提供下标为数字的随机访问。而且仅提供Start和End2个默认的String.index。这是因为它只能告诉你最开始的和最后的, 因为其他的都需要去从前或者从后进行遍历。在需要的时候进行Unicode变量组合计算后才能真正获知。 那有没有方法使用数字字面量来进行切片呢? 答案是可以的。 如下: extension String { public subscript(intRange: Range<Int>) -> String { let start = self.index(startIndex, offsetBy: intRange.startIndex) let end = self.index(startIndex, offsetBy: intRange.endIndex) let range = start..<end return String(self[range]) } } 我们使用扩展。来扩展String 类型的一个下标操作。传入的Range是Int类型的。

子字符串——Substring

Swift中的子字符串的概念和Objective-C中子字符串的概念相当不同

Swift中,使用下标或者类似prefix等方法得到的子字符串是Substring类型。Substring拥有String的大部分方法。Substring也可以转成String。

而Objective-C中,无论是原字符串还是原字符串的子字符串,都是NSString类型。

代码语言:javascript
复制
        let greeting = "hello, swift"
        let endIndex = greeting.index(greeting.startIndex, offsetBy: 4) ?? greeting.endIndex
        let begining = greeting[...endIndex] // Substring
        let newString = String(begining) // 将Substring转成String
        print(newString) // hello

下面我们来简单介绍下Swift中的Substring。

Swift中为什么要单独拉一个SubString出来呢?很大程度上是出于性能的考量

在Swift中,子字符串会重用一部分原字符串的内存。如上图,子字符串“Hello”所指向的内存地址,还是原字符串“Hello,world!”的内存中存储“Hello”的那部分内存地址。

我们在修改原始字符串,或者修改子字符串之前,都是不需要花费拷贝内存的代价的。但是一旦修改了一旦修改了原字符串,或者修改子字符串,或者要将子字符串转成String类型,那么子字符串就会单独拷贝出来,在新的内存区域存储该子字符串的内容

String和Substring都会遵循StringProtocol协议。如果我们在平时的工作中需要定义一些字符串操作函数,那么所接受的参数优先遵循StringProtocol协议,而不是继承自String,这样就能够很方便地兼容所有类型的字符串。

以上。


对线上代码一定要有敬畏感,不随意改动或者删除任何一句线上代码。如果需要改动,比如将老接口替换成新接口,那么一定要将影响点详尽罗列出,并跟新老接口的后端负责人、其他使用该接口的前端客户端,产品等确认好,改好之后还要同步到测试,一定要做全量回归。千万不要觉得没问题就改了,一定要养成严谨的工作习惯。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档