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

2020-07-20 09:40:06 浏览数 (1)

变量和常量

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,这样就能够很方便地兼容所有类型的字符串。

以上。


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

0 人点赞