变量和常量
Swift中,使用关键字let来声明常量,使用关键字var来声明变量。
而在Objective-C中,如果没有特殊的指明,我们所声明的都是变量。可以通过如下几种方式来声明常量:
- 使用宏定义来模拟常量来使用。
- 使用const关键字来声明类型常量。
- 声明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,这样就能够很方便地兼容所有类型的字符串。
以上。
对线上代码一定要有敬畏感,不随意改动或者删除任何一句线上代码。如果需要改动,比如将老接口替换成新接口,那么一定要将影响点详尽罗列出,并跟新老接口的后端负责人、其他使用该接口的前端客户端,产品等确认好,改好之后还要同步到测试,一定要做全量回归。千万不要觉得没问题就改了,一定要养成严谨的工作习惯。