Swift进阶黄金之路
Posted zhangferry
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift进阶黄金之路相关的知识,希望对你有一定的参考价值。
swift
这篇是对一文鉴定是Swift的王者,还是青铜文章中问题的解答。这些问题仅仅是表层概念,属于知识点,在我看来即使都很清楚也并不能代表上了王者,如果非要用段位类比的话,黄金还是合理的????。
Swift是一门上手容易,但是精通较难的语言。即使下面这些内容都不清楚也不妨碍你开发业务需求,但是了解之后它能够帮助我们写出更加Swifty的代码。
一、 协议 Protocol
ExpressibleByDictionaryLiteral
ExpressibleByDictionaryLiteral
是字典的字面量协议,该协议的完整写法为:
public protocol ExpressibleByDictionaryLiteral
/// The key type of a dictionary literal.
associatedtype Key
/// The value type of a dictionary literal.
associatedtype Value
/// Creates an instance initialized with the given key-value pairs.
init(dictionaryLiteral elements: (Self.Key, Self.Value)...)
首先字面量(Literal)的意思是:用于表达源代码中一个固定值的表示法(notation)。
举个例子,构造字典我们可以通过以下两种方式进行:
// 方法一:
var countryCodes = Dictionary<String, Any>()
countryCodes["BR"] = "Brazil"
countryCodes["GH"] = "Ghana"
// 方法二:
let countryCodes = ["BR": "Brazil", "GH": "Ghana"]
第二种构造方式就是通过字面量方式进行构造的。
其实基础类型基本都是通过字面量进行构造的:
let num: Int = 10
let flag: Bool = true
let str: String = "Brazil"
let array: [String] = ["Brazil", "Ghana"]
而这些都有对应的字面量协议:
ExpressibleByNilLiteral // nil字面量协议
ExpressibleByIntegerLiteral // 整数字面量协议
ExpressibleByFloatLiteral // 浮点数字面量协议
ExpressibleByBooleanLiteral // 布尔值字面量协议
ExpressibleByStringLiteral // 字符串字面量协议
ExpressibleByArrayLiteral // 数组字面量协议
Sequence
Sequence翻译过来就是序列,该协议的目的是一系列相同类型的值的集合,并且提供对这些值的迭代能力,这里的迭代可以理解为遍历,也即for-in
的能力。可以看下该协议的定义:
protocol Sequence
associatedtype Iterator: IteratorProtocol
func makeIterator() -> Iterator
Sequence
又引入了另一个协议IteratorProtocol
,该协议就是为了提供序列的迭代能力。
public protocol IteratorProtocol
associatedtype Element
public mutating func next() -> Self.Element?
我们通常用for-in
实现数组的迭代:
let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
for animal in animals
print(animal)
这里的for-in
会被编译器翻译成:
var animalIterator = animals.makeIterator()
while let animal = animalIterator.next()
print(animal)
Collection
Collection译为集合,其继承于Sequence。
public protocol Collection : Sequence
associatedtype Index : Comparable
var startIndex: Index get
var endIndex: Index get
var isEmpty: Bool get
var count: Int get
subscript(position: Index) -> Element get
subscript(bounds: Range<Index>) -> SubSequence get
是一个元素可以反复遍历并且可以通过索引的下标访问的有限集合,注意Sequence
可以是无限的,Collection
必须是有限的。
Collection
在Sequence
的基础上扩展了下标访问、元素个数能特性。我们常用的集合类型Array
,Dictionary
,Set
都遵循该协议。
CustomStringConvertible
这个协议表示自定义类型输出的样式。先来看下它的定义:
public protocol CustomStringConvertible
var description: String get
只有一个description
的属性。它的使用很简单:
struct Point: CustomStringConvertible
let x: Int, y: Int
var description: String
return "(\\(x), \\(y))"
let p = Point(x: 21, y: 30)
print(p) // (21, 30)
//String(describing: <#T##CustomStringConvertible#>)
let s = String(describing: p)
print(s) // (21, 30)
如果不实现CustomStringConvertible
,直接打印对象,系统会根据默认设置进行输出。我们可以通过CustomStringConvertible
对这一输出行为进行设置,还有一个协议是CustomDebugStringConvertible
:
public protocol CustomDebugStringConvertible
var debugDescription: String get
跟CustomStringConvertible
用法一样,对应debugPrint
的输出。
Hashable
我们常用的Dictionary
,Set
均实现了Hashable
协议。Hash的目的是为了将查找集合某一元素的时间复杂度降低到O(1),为了实现这一目的需要将集合元素与存储地址之间建议一种尽可能一一对应的关系。
我们再看Hashable`协议的定义:
public protocol Hashable : Equatable
var hashValue: Int get
func hash(into hasher: inout Hasher)
public protocol Equatable
static func == (lhs: Self, rhs: Self) -> Bool
注意到func hash(into hasher: inout Hasher)
,Swift 4.2 通过引入 Hasher
类型并采用新的通用哈希函数进一步优化 Hashable
。
如果你要自定义类型实现 Hashable
的方式,可以重写 hash(into:)
方法而不是 hashValue
。hash(into:)
通过传递了一个 Hasher
引用对象,然后通过这个对象调用 combine(_:)
来添加类型的必要状态信息。
// Swift >= 4.2
struct Color: Hashable
let red: UInt8
let green: UInt8
let blue: UInt8
// Synthesized by compiler
func hash(into hasher: inout Hasher)
hasher.combine(self.red)
hasher.combine(self.green)
hasher.combine(self.blue)
// Default implementation from protocol extension
var hashValue: Int
var hasher = Hasher()
self.hash(into: &hasher)
return hasher.finalize()
参考:Hashable / Hasher
Codable
Codable
是可Decodable
和Encodable
的类型别名。它能够将程序内部的数据结构序列化成可交换数据,也能够将通用数据格式反序列化为内部使用的数据结构,大大提升对象和其表示之间互相转换的体验。处理的问题就是我们经常遇到的JSON转模型,和模型转JSON。
public typealias Codable = Decodable & Encodable
public protocol Decodable
init(from decoder: Decoder) throws
public protocol Encodable
func encode(to encoder: Encoder) throws
这里只举一个简单的解码过程:
//json数据
"id": "1283984",
"name": "Mike",
"age": 18
// 定义对象
struct Person: Codable
var id: String
var name: String
var age: Int
// json为网络接口返回的Data类型数据
let mike = try! JSONDecoder().decode(Person.self, from: json)
print(mike)
//输出:Student(id: "1283984", name: "Mike", age: 18)
是不是非常简单,Codable还支持各种自定义解编码过程,完全可以取代SwiftyJSON
,HandyJSON
等编解码库。
Comparable
这个是用于实现比较功能的协议,它的定义如下:
public protocol Comparable : Equatable
static func < (lhs: Self, rhs: Self) -> Bool
static func <= (lhs: Self, rhs: Self) -> Bool
static func >= (lhs: Self, rhs: Self) -> Bool
static func > (lhs: Self, rhs: Self) -> Bool
其继承于Equatable
,即判等的协议。可以很清楚的理解实现了各种比较的定义就具有了比较的功能。这个不做比较。
RangeReplaceableCollection
RangeReplaceableCollection
支持用另一个集合的元素替换元素的任意子范围的集合。
看下它的定义:
public protocol RangeReplaceableCollection : Collection where Self.SubSequence : RangeReplaceableCollection
associatedtype SubSequence
mutating func append(_ newElement: Self.Element)
mutating func insert<S>(contentsOf newElements: S, at i: Self.Index) where S : Collection, Self.Element == S.Element
/* 拼接、插入、删除、替换的方法,他们都具有对组元素的操作能力 */
override subscript(bounds: Self.Index) -> Self.Element get
override subscript(bounds: Range<Self.Index>) -> Self.SubSequence get
举个例子,Array支持该协议,我们可以进行如下操作:
var bugs = ["Aphid", "Damselfly"]
bugs.append("Earwig")
bugs.insert(contentsOf: ["Bumblebee", "Cicada"], at: 1)
print(bugs)
// Prints "["Aphid", "Bumblebee", "Cicada", "Damselfly", "Earwig"]"
这里附一张Swift中Array遵循的协议关系图,有助于大家理解上面讲解的几个协议之间的关系:
图像来源:https://swiftdoc.org/v3.1/type/array/hierarchy/
二、@propertyWrapper
阅读以下代码,print 输出什么
@propertyWrapper
struct Wrapper<T>
var wrappedValue: T
var projectedValue: Wrapper<T> return self
func foo() print("Foo")
struct HasWrapper
@Wrapper var x = 0
func foo()
print(x) // 0
print(_x) // Wrapper<Int>(wrappedValue: 0)
print($x) // Wrapper<Int>(wrappedValue: 0)
这段代码看似要考察对@propertyWrapper
的理解,但是有很多无用内容,导致代码很奇怪。
@propertyWrapper
的意思就是属性包装,它可以将一系列相似的属性方法进行统一处理。举个例子,如果我们需要在UserDefaults
中加一个是否首次启动的值,正常可以这样处理:
extension UserDefaults
enum Keys
static let isFirstLaunch = "isFirstLaunch"
var isFirstLaunch: Bool
get
return bool(forKey: Keys.isFirstLaunch)
set
set(newValue, forKey: Keys.isFirstLaunch)
如果我们需要加入很多这样属性的话,就需要写大量的get
、set
方法。而@propertyWrapper
的作用就是为属性的这种设置提供一个模板写法,以下是使用属性包装的写法。
@propertyWrapper
struct UserDefaultWrapper<T>
private let key: String
private let defaultValue: T
init(key: String, defaultValue: T)
self.key = key
self.defaultValue = defaultValue
var wrappedValue: T
get
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
set
UserDefaults.standard.set(newValue, forKey: key)
extension UserDefaults
@UserDefaultWrapper(key: Keys.isFirstLaunch, defaultValue: false)
var isFirstLaunch: Bool
@propertyWrapper
约束的对象必须要定义wrappedValue
属性,因为该对象包裹的属性会走到wrappedValue
的实现。
回到实例代码,定义了wrappedValue
却并没有添加任何实现,这是允许的。所以访问x的时候其实是访问Wrapper
的wrappedValue
,因为没有给出任何实现所以直接打印出0
。而_x
和$x
对应的就是Wrapper
自身。
参考:Swift Property Wrappers
三、关键字
public open
public
open
为权限关键词。对于一个严格的项目来说,精确的最小化访问控制级别对于代码的维护来说相当重要的。完整的权限关键词,按权限大小排序如下:
open > public > internal > fileprivate > private
-
open
权限最大,允许外部module访问,继承,重写。 -
public
允许外部module访问,但不允许继承,重写。 -
internal
为默认关键词,在同一个module内可以共用。 -
fileprivate
表示代码可以在当前文件中被访问,而不做类型限定。 -
private
表示代码只能在当前作用域或者同一文件中同一类型的作用域中被使用。
这些权限关键词可以修饰,属性,方法和类型。需要注意:当一个类型的某一属性要用public修饰时,该类型至少要用public(或者open)权限的关键词修复。可以理解为数据访问是分层的,我们为了获取某一属性或方法需要先获取该类型,所以外层(类型)的访问权限要满足大于等于内层(类型、方法、属性)权限。
参考:Swift AccessControl
static class final
原文中final
跟权限关键词放在一起了,其实是不合理的,就将其放到这里来讨论。
static
静态变量关键词,来源于C语言。
在Swift中常用于以下场景:
// 仅用于类名前,表示该类不能被继承。仅支持class类型
final class Manager
// 单例的声明
static let shared = Manager()
// 实例属性,可被重写
var name: String = "Ferry"
// 实例属性,不可被重写
final var lastName: String = "Zhang"
// 类属性,不可被重写
static var address: String = "Beijing"
// 类属性,可被重写。注意只能作为计算属性,而不能作为存储属性
class var code: String
return "0122"
// 实例函数,可被重写
func download()
/* code... */
// 实例函数,不可被重写
final func download()
/* code... */
// 类函数,可被重写
class func removeCache()
/* code... */
// 类函数,不可被重写
static func download()
/* code... */
struct Manager
// 单例的声明
static let shared = Manager()
// 类属性
static var name: String = "Ferry"
// 类函数
static func download()
/* code... */
struct
和enum
因为不能被继承,所以也就无法使用class
和final
关键词,仅能通过static
关键词进行限定
mutating inout
mutating用于修饰会改变该类型的函数之前,基本都用于struct
对象的修改。看下面例子:
struct Point
var x: CGFloat
var y: CGFloat
// 因为该方法改变了struct的属性值(x),所以必须要加上mutating
mutating func moveRight(offset: CGFloat)
x += offset
func normalSwap(a: CGFloat, b: CGFloat)
let temp = a
a = b
b = temp
// 将两个值交换,需传入对象地址。注意inout需要加载类型名前
func inoutSwap(a: inout CGFloat, b: inout CGFloat)
let temp = a
a = b
b = temp
var location1: CGFloat = 10
var location2: CGFloat = -10
var point = Point.init(x: 0, y: 0)
point.moveRight(offset: location1)
print(point) //Point(x: 10.0, y: 0.0)
point.normalSwap(a: location1, b: location2)
print(location1) //10
print(location2) //-10
// 注意需带取址符&
point.inoutSwap(a: &location1, b: &location2)
print(location1) //-10
print(location2) //10
inout
需要传入取值符,所以它的改变会导致该对象跟着变动。可以再回看上面说的Hashable
的一个协议实现:
func hash(into hasher: inout Hasher)
hasher.combine(self.red)
hasher.combine(self.green)
hasher.combine(self.blue)
只有使用inout
才能修改传入的hasher的值。
infix operator
infix operator
即为中缀操作符,还有prefix、postfix后缀操作符。
它的作用是自定义操作符。比如Python里可以用**
进行幂运算,但是Swift里面,我们就可以利用自定义操作符来定义一个用**
实现的幂运算。
// 定义中缀操作符
infix operator **
// 实现该操作符的逻辑,中缀需要两个参数
func ** (left: Double, right: Double) -> Double
return pow(left, right)
let number = 2 ** 3
print(value) //8
同理我们还可以定义前缀和后缀操作符:
//定义阶乘操作,后缀操作符
postfix operator ~!
postfix func ~! (value: Int) -> Int
func factorial(_ value: Int) -> Int
if value <= 1
return 1
return value * factorial(value - 1)
return factorial(value)
//定义输出操作,前缀操作符
prefix operator <<
prefix func << (value: Any)
print(value)
let number1 = 4~!
print(number1) // 24
<<number1 // 24
<<"zhangferry" // zhangferry
前缀和后缀仅需要一个操作数,所以只有一个参数即可。
关于操作符的更多内容可以查看这里:Swift Operators。
注意,因为该文章较早,其中对于操作符的一些定义已经改变。
@dynamicMemberLookup,@dynamicCallable
这两个关键词我确实没有用过,看到dynamic
可以知道这两个特性是为了让Swift具有动态性。
@dynamicMemberLookup
中文叫动态查找成员。在使用@dynamicMemberLookup
标记了对象后(对象、结构体、枚举、protocol),实现了subscript(dynamicMember member: String)
方法后我们就可以访问到对象不存在的属性。如果访问到的属性不存在,就会调用到实现的 subscript(dynamicMember member: String)
方法,key 作为 member 传入这个方法。举个例子:
@dynamicMemberLookup
struct Person
subscript(dynamicMember member: String) -> String
let properties = ["nickname": "Zhuo", "city": "Hangzhou"]
return properties[member, default: "undefined"]
//执行以下代码
let p = Person()
print(p.city) //Hangzhou
print(p.nickname) //Zhuo
print(p.name) //undefined
我们没有定义Person的city
、nickname
,name
属性,却可以用点语法去尝试访问它。如果没有@dynamicMemberLookup
这种写法会被编译器检查出来并报错,但是加了该关键词编译器就不会管它是不是存在都予以通过。
@dynamicCallable
struct Person
// 实现方法一
func dynamicallyCall(withArguments: [String])
for item in withArguments
print(item)
// 实现方法二
func dynamicallyCall(withKeywordArguments: KeyValuePairs<String, String>)
for (key, value) in withKeywordArguments
print("\\(key) --- \\(value)")
let p = Person()
p("zhangsan")
// 等于 p.dynamicallyCall(withArguments: ["zhangsan"])
p("zhangsan", "20", "男")
// 等于 p.dynamicallyCal以上是关于Swift进阶黄金之路的主要内容,如果未能解决你的问题,请参考以下文章
(保姆级)自学网络安全超详细学习路线,从青铜到王者的进阶之路