1、柯里化
把接受多個參數的方法進行一些變形,使其更加靈活的方法。函數式特點的重要表現。
舉個例子,下面的函數簡單地將輸入的數字加 1:
func addOne(num: Int) -> Int {
return num + 1
}
這個函數所表達的內容非常有限,如果我們之后還需要一個將輸入數字加 2,或者加 3 的函數,可能不得不類似地去定義返回為 num + 2 或者 num + 3 的版本。有沒有更通用的方法呢?我們其實可以定義一個通用的函數,它將接受需要與輸入數字相加的數,并返回一個函數。返回的函數將接受輸入數字本身,然后進行操作:
func addTo(adder: Int) -> Int -> Int {
return {
num in
return num + adder
}
}
有了 addTo,我們現在就能輕易寫出像是 addOne 或者 addTwo 這樣的函數了:
let addTwo = addTo(2) // addTwo: Int -> Int
let result = addTwo(6) // result = 8
柯里化是一種量產相似方法的好辦法,可以通過柯里化一個方法模板來避免寫出很多重復代碼,也方便了今后維護。
2、將 PROTOCOL 的方法聲明為 MUTATING
protocol Vehicle
{
var numberOfWheels: Int {get}
var color: UIColor {get set}
mutating func changeColor()
}
萬一協議使用者需要在對應方法中修改屬性
3、sequence
實現一個反向序列
Swift 的 for...in
可以用在所有實現了 SequenceType
的類型上,而為了實現 SequenceType
你首先需要實現一個 GeneratorType
。比如一個實現了反向的 generator
和 sequence
可以這么寫:
Swift4.0里面 GeneratorType 改成 Iterator, SequenceType 改成 Sequence
class ReverseGenerator<T>: IteratorProtocol {
typealias Element = T
var array: [Element]
var currentIndex = 0
init(array: [Element]) {
self.array = array
currentIndex = array.count - 1 //反向
}
func next() -> Element? {
if currentIndex < 0 {
return nil
}
else {
let element = array[currentIndex]
currentIndex -= 1
return element
}
}
}
class ReverseSequence<T>: Sequence {
typealias Element = T
var array: [Element]
init(array: [Element]) {
self.array = array
}
typealias Iterator = ReverseGenerator<Element>
func makeIterator() -> ReverseSequence<Element>.Iterator {
return ReverseGenerator(array: array)
}
}
let arr = [1,2,3,4,5,6]
for item in ReverseSequence(array: arr) {
print(item)
}
4、 ?? 的實現、&& 、||的實現
/// ?? 的實現邏輯
infix operator ???
func ???<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
switch optional {
case .some(let value):
print(value)
return value
case .none:
return defaultValue()
}
}
var demo: Demo?
let result = demo ??? Demo()
/// || 和 && 的實現邏輯
func &&&(leftValue: @autoclosure () -> Bool, rightValue: @autoclosure () -> Bool) -> Bool {
// print(leftValue)
if !leftValue() {
return false
} else if rightValue() {
return true
}
return false
}
var demo: Demo?
let result = (demo == nil) &&& (3 > 2)
5、Optional Chaining , 可選鏈
()? 就是 Void?
如果遇到沒有返回值的閉包如何判斷執行成功與否呢
let playClosure = {(child: Child) -> ()? in child.pet?.toy?.play()}
if let result: () = playClosure(xiaoming) {
print("好開心~")
} else {
print("沒有玩具可以玩")
}
6、重載操作符、自定義操作符
重載已有操作符 : + -
struct Vector2D {
var x = 0.0
var y = 2.0
}
func +(left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
let v1 = Vector2D(x: 2, y: 3)
let v2 = Vector2D(x: 1, y: 3)
let v3 = v1 + v2
print(v3)
直接重載已有操作符,不會出現編譯錯誤,因為系統已經聲明,但是自定義操作符需要聲明否則編譯錯誤(把上面的 + 換成 +* 自定義操作符就編譯出錯)
聲明定義 自定義操作符, 操作符設置在Swift3做了變更:常用于設置操作符計算優先級。
https://github.com/apple/swift-evolution/blob/master/proposals/0077-operator-precedence.md
precedencegroup MyPrecedence {
associativity: none
higherThan: LogicalConjunctionPrecedence
}
infix operator +* : MyPrecedence
運算的優先級,越高的話越優先進行運算。Swift 中乘法和除法的優先級是 150,加法和減法是 140,
higerThan用于設置優先級高低,
LogicalConjunctionPrecedence 常用邏輯運算優先級
還有 lowerThan
7、Swift REPL 交互式解釋環境
也就是說每輸入一句語句就立即執行和輸出。這在很多解釋型的語言中是很常見的,非常適合用來對語言的特性進行學習。
https://swifter.tips/swift-cli/
8、方法中嵌套方法
避免一些可能極少使用到的方法因為方法體太長而拆出去展開。
10、swift單例用 let是最簡便有效的
11、Any、AnyObject
一個針對對象 一個針對所有 包括函數
12、隨機數的生成
arc4random_uniform
創建一個 Range 的隨機數的方法
func randomInRange(range: Range<Int>) -> Int {
let count = range.endIndex - range.startIndex
return Int(arc4random_uniform(UInt32(count)) + UInt32(range.startIndex))
}
for i in 0...100 {
print(randomInRange(range: 0..<6))
}
13、typealias 和泛型
protocols do not allow generic parameters; use associated types instead
協議中是不可以使用泛型的,但是我們還可以在協議中約定一個typealias 要求必須實現,這樣也就可以在一定范圍內,對協議進行約束
protocol GeneratorType {
associatedtype Generator
func doSth(demo: String) -> Generator
}
class SomeBody: GeneratorType {
typealias Generator = String
func doSth(demo: String) -> String {
print("do Sth")
return "sth"
}
}
14、條件編譯
- armv7|armv7s|arm64都是ARM處理器的指令集
- i386|x86_64 是Mac處理器的指令集
i386是針對intel通用微處理器32位處理器 (模擬器)
x86_64是針對x86架構的64位處理器 (模擬器)
模擬器32位處理器測試需要i386架構,
模擬器64位處理器測試需要x86_64架構,
真機32位處理器需要armv7,或者armv7s架構,
真機64位處理器需要arm64架構。
以下判斷依然可以使用,根據不同環境做判斷
#if FREE_VERSION
print("do Sth")
#else
print("do Sth2")
#endif
在這里我們用 FREE_VERSION 這個編譯符號來代表免費版本。為了使之有效,我們需要在項目的編譯選項中進行設置,在項目的 Build Settings 中,找到 Swift Compiler - Custom Flags,并在其中的 Other Swift Flags 加上 -D FREE_VERSION 就可以了。
15、可變參數函數
可變參數函數寫NSString format初始化
oc里面:
NSString *string = [NSString stringWithFormat:
@"Hello %@. Date: %@", name, date];
swift:
extension NSString {
convenience init(format: NSString, _ args: CVarArg...) {
self.init()
}
}
NSString(format: "%@%@", "on", Demo())
16、reduce
reduce: 縮減操作,參數 一個變化的初始值 A、一個閉包函數,array中的對象每一個都會與變化的初始值 A一起執行一遍閉包方法,每次的閉包返回值作為變化后的 A ,進入下一個array對象的遍歷,直到全部完成遍歷
let numbers = [1, 2, 3, 4]
let numberSum = numbers.reduce(0, { x, y in
x + y
})
// numberSum == 10
func sum(num: Int...) -> Int {
return num.reduce(2, {$0 * $1})
}
sum(num: 3,4) // 24
func combinator(accumulator: Int, current: Int) -> Int {
return accumulator + current
}
[1, 2, 3].reduce(0, combine: combinator)
// 執行步驟如下
combinator(0, 1) { return 0 + 1 } = 1
combinator(1, 2) { return 1 + 2 } = 3
combinator(3, 3) { return 3 + 3 } = 6
= 6
17、map
map 方法接受一個閉包作為參數, 然后它會遍歷整個 numbers 數組,并對數組中每一個元素執行閉包中定義的操作。 相當于對數組中的所有元素做了一個映射。
不同類型都分別實現了對應的map方法,下面是Optional中的實現
public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
let numbers = [1, 2, 3, 4]
let number_2 = numbers.map { (num) -> Int in
num * 2
}
flatMap
相對于map,會自動過濾nil
對于二維數組,flatMap 依然會遍歷數組的元素,并對這些元素執行閉包中定義的操作。 但唯一不同的是,它對最終的結果進行了所謂的 “降維” 操作。 本來原始數組是一個二維的, 但經過 flatMap 之后,它變成一維的了。
public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
public func flatMap(transform: (Self.Generator.Element) -> S) -> [S.Generator.Element]
let numbersCompound = [[1,2,3],[4,5,6]];
var res = numbersCompound.map { $0.map{ $0 + 2 } }
// [[3, 4, 5], [6, 7, 8]]
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 2 } }
// [3, 4, 5, 6, 7, 8]
源碼
extension Sequence {
//...
public func flatMap(
@noescape transform: (${GElement}) throws -> S
) rethrows -> [S.${GElement}] {
var result: [S.${GElement}] = []
for element in self {
result.append(contentsOf: try transform(element))
}
return result
}
//...
}
flatMap 首先會遍歷這個數組的兩個元素 [1,2,3] 和 [4,5,6], 因為這兩個元素依然是數組, 所以我們可以對他們再進行 map 操作: $0.map{ $0 + 2 }。
這樣, 內部的 $0.map{ $0 + 2 } 調用返回值類型還是數組, 它會返回 [3,4,5] 和 [6,7,8]。
然后, flatMap 接收到內部閉包的這兩個返回結果, 進而調用 result.append(contentsOf:) 將它們的數組中的內容添加到結果集中,而不是數組本身。
那么我們最終的調用結果理所當然就應該是 [3, 4, 5, 6, 7, 8] 了。
filter
public func filter(_ isIncluded: (Substring.Element) throws -> Bool) rethrows -> String
過濾
18、找數組最大最小
let array = [10,-22,753,55,137,-1,-279,1034,77]
array.sorted().first
array.sorted().last
也可以用reduce計算
array.reduce(Int.max,min)
19、Swift中的app入口
swift工程中并沒有 main.m文件提供明顯的入口,但是在Appdelegate 有 @UIApplicationMain
在一般情況下,我們并不需要對這個標簽做任何修改,但是當我們如果想要使用 UIApplication 的子類而不是它本身的話,我們就需要對這部分內容 “做點手腳” 了。
剛才說到,其實 Swift 的 app 也是需要 main 函數的,只不過默認情況下是 @UIApplicationMain 幫助我們自動生成了而已。和 C 系語言的 main.c 或者 main.m 文件一樣,Swift 項目也可以有一個名為 main.swift 特殊的文件。在這個文件中,我們不需要定義作用域,而可以直接書寫代碼。這個文件中的代碼將作為 main 函數來執行。比如我們在刪除 @UIApplicationMain 后,在項目中添加一個 main.swift 文件,然后加上這樣的代碼:
UIApplicationMain(CommandLine.argc, UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)), NSStringFromClass(UIApplication.self), NSStringFromClass(AppDelegate.self))
現在編譯運行,就不會再出現錯誤了。當然,我們還可以通過將第三個參數替換成自己的 UIApplication 子類,這樣我們就可以輕易地做一些控制整個應用行為的事情了。比如將 main.swift 的內容換成:
import UIKit
class MyApplication: UIApplication {
override func sendEvent(event: UIEvent!) {
super.sendEvent(event)
print("Event sent: \(event)");
}
}
//可以替換@UIApplicationMain
UIApplicationMain(CommandLine.argc, UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)), NSStringFromClass(UIApplication.self), NSStringFromClass(AppDelegate.self))
這樣每次發送事件 (比如點擊按鈕) 時,我們都可以監聽到這個事件了。
21、DESIGNATED,CONVENIENCE 和 REQUIRED
DESIGNATED designated 指定初始化器
CONVENIENCE
REQUIRED
Swift 中不加修飾的 init 方法都需要在方法中保證所有非 Optional 的實例變量被賦值初始化
而在子類中也強制 (顯式或者隱式地) 調用 super 版本的 designated 初始化,所以無論如何走何種路徑,被初始化的對象總是可以完成完整的初始化的。
class ClassA {
let numA: Int
init(num: Int) {
numA = num
}
}
class ClassB: ClassA {
let numB: Int
override init(num: Int) {
numB = num + 1
super.init(num: num)
}
}
在上面的示例代碼中,注意在 init 里我們可以對 let 的實例常量進行賦值,這是初始化方法的重要特點。在 Swift 中 let 聲明的值是常量,無法被寫入賦值,這對于構建線程安全的 API 十分有用。而因為 Swift 的 init 只可能被調用一次,因此在 init 中我們可以為常量進行賦值,而不會引起任何線程安全的問題。
與 designated 初始化方法對應的是在 init 前加上 convenience 關鍵字的初始化方法。這類方法是 Swift 初始化方法中的 “二等公民”,只作為補充和提供使用上的方便。所有的 convenience 初始化方法都必須調用同一個類中的 designated 初始化完成設置,另外 convenience 的初始化方法是不能被子類重寫或者是從子類中以 super 的方式被調用的。
1、初始化路徑必須保證對象完全初始化,這可以通過調用本類型的 designated 初始化方法來得到保證;
2、子類的 designated 初始化方法必須調用父類的 designated 方法,以保證父類也完成初始化。
對于某些我們希望子類中一定實現的 designated 初始化方法,我們可以通過添加 required 關鍵字進行限制,強制子類對這個方法重寫實現。這樣做的最大的好處是可以保證依賴于某個 designated 初始化方法的 convenience 一直可以被使用。一個現成的例子就是上面的 init(bigNum: Bool):如果我們希望這個初始化方法對于子類一定可用,那么應當將 init(num: Int) 聲明為必須,這樣我們在子類中調用 init(bigNum: Bool) 時就始終能夠找到一條完全初始化的路徑了:
class ClassA {
let numA: Int
required init(num: Int) {
numA = num
}
convenience init(bigNum: Bool) {
self.init(num: bigNum ? 10000 : 1)
}
}
class ClassB: ClassA {
let numB: Int
required init(num: Int) {
numB = num + 1
super.init(num: num)
}
}
另外需要說明的是,其實不僅僅是對 designated 初始化方法,對于 convenience 的初始化方法,我們也可以加上 required 以確保子類對其進行實現。這在要求子類不直接使用父類中的 convenience 初始化方法時會非常有幫助。
22、Swift初始化返回nil
swift默認初始化器是沒有返回值的
public init?(string: String)
可以使用這種初始化器
23、protocol組合
Any 相當于 protocol<> 無類型的協議
在swift 后續版本 如果我們需要定義一個協議僅僅用于整合多個其他協議
protocol A { }
protocol B { }
protocol C: A, B {
}
可以直接用 typealias D = A & B
25、對于一個項目來說,外界框架是由 Swift 寫的還是 Objective-C 寫的,兩者并沒有太大區別
26、DYNAMIC
swift中的動態分發
27、protocol optional 可選接口
@objc
protocol C: A, B {
@objc optional func dosth()
}
用 @objc 修飾的 protocol 就只能被 class 實現了,也就是說,對于 struct 和 enum 類型,我們是無法令它們所實現的接口中含有可選方法或者屬性的。
28、autoreleasepool 自動釋放池
這是一種必要的延遲釋放的方式,因為我們有時候需要確保在方法內部初始化的生成的對象在被返回后別人還能使用,而不是立即被釋放掉。
什么情況下需要使用到:
一般情況ARC管理內存不需要我們再進行內存管理操作,但是在個別特殊情況:
func loadBigData() {
if let path = NSBundle.mainBundle()
.pathForResource("big", ofType: "jpg") {
for i in 1...10000 {
autoreleasepool {
let data = NSData.dataWithContentsOfFile(
path, options: nil, error: nil)
NSThread.sleepForTimeInterval(0.5)
}
}
}
}
短時間內的內存暴增,但是又并沒有離開方法,內存并未釋放,容易導致內存不足而崩潰,這個時候上面添加的 autoreleasepool 的作用就體現出來了,每次循環后都會將需要釋放的內容主動放入了自動釋放池,但是也不需要太頻繁,可以間隔10次之類的進行。
29、正則表達式
//MARK: - 正則匹配
struct MyRegex {
let regex: NSRegularExpression?
init(_ pattern: String) {
regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)
}
func match(input: String) -> Bool {
if let matches = regex?.matches(in: input, options: [], range: NSRange(location: 0, length: input.length)) {
return matches.count > 0
}
return false
}[]
}
infix operator =~
func =~ (str: String, pattern: String) -> Bool {
return MyRegex(pattern).match(input: str)
}
//
string =~ "^[0-9]$"
8個常用正則表達式
https://code.tutsplus.com/tutorials/8-regular-expressions-you-should-know--net-6149
/^[a-z0-9_-]{3,16}$/ 用戶名
/^[a-z0-9_-]{6,18}$/ 密碼
/^#?([a-f0-9]{6}|[a-f0-9]{3})$/ 十六進制 比如色號
/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/ 郵箱
/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/ url
30、Swift中的字典、數組變成值類型為什么不會帶來更大的消耗
當這些值類型屬性內容不發生變更的時候,進行賦值、傳遞操作只會移動指針,并不會重新分配內存地址,沒有堆內存的分配和釋放的問題,這樣的運行效率可以說極高。只有當值發生變更的時候才會重新分配地址。復制會將存儲在其中的值類型一并進行復制,而對于其中的引用類型的話,則只復制一份引用。(比如struct中的類對象),struct復制的時候 內屬性都發生地址復制,類對象這種引用類型則復制一份引用。
但以上都是針對小數據量 變更發生較少的時候。
在需要處理大量數據并且頻繁操作 (增減) 其中元素時,選擇 NSMutableArray 和 NSMutableDictionary 會更好
31、AnyClass
public typealias AnyClass = AnyObject.Type
(NSClassFromString("demo2.ViewController") as! UIViewController.Type).init()
32、Self 到底是什么
我們在看一些接口的定義時,可能會注意到出現了首字母大寫的 Self 出現在類型的位置上:
protocol IntervalType {
//...
/// Return `rhs` clamped to `self`. The bounds of the result, even
/// if it is empty, are always within the bounds of `self`
func clamp(intervalToClamp: Self) -> Self
//...
}
比如上面這個 IntervalType 的接口定義了一個方法,接受實現該接口的自身的類型,并返回一個同樣的類型。
這么定義是因為接口其實本身是沒有自己的上下文類型信息的,在聲明接口的時候,我們并不知道最后究竟會是什么樣的類型來實現這個接口,Swift 中也不能在接口中定義泛型進行限制。而在聲明接口時,我們希望在接口中使用的類型就是實現這個接口本身的類型的話,就需要使用 Self 進行指代。
import Foundation
protocol Copyable {
func mycopy() -> Self
}
class MyClass: Copyable {
var num = 1
func mycopy() -> Self {
let result = type(of: self).init()
(result as! MyClass).num = 3
return result
}
//通過動態類型初始化對象必須有required init 方法
required init() {
}
}
33、swift中的dynamicType 被廢除 替代方案為type(of:)
34、屬性觀察
willSet 和 didSet。newValue oldValue
35、Swift中的KVO
Swift 中我們也是可以使用 KVO 的,但是僅限于在 NSObject 的子類中。這是可以理解的,因為 KVO 是基于 KVC (Key-Value Coding) 以及動態派發技術實現的,而這些東西都是 Objective-C 運行時的概念。另外由于 Swift 為了效率,默認禁用了動態派發,因此想用 Swift 來實現 KVO,我們還需要做額外的工作,那就是將想要觀測的對象標記為 dynamic。
如果遇到需要監聽沒有標記dynamic的屬性時,只能開子類復寫屬性
self.addObserver(self, forKeyPath: "str", options: .new, context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print(keyPath)
}
36、局部 scope
讓代碼更清晰,模塊化
do {
//do sth
}
do {
//do sth
}
37、判等
判斷兩個對象是否相等,內容上的相等 而不是內存地址上的相等
swift在內存地址上的相等用 ===
swift中用的是 == ,通過重寫 == ,基本的類型都實現了對應的 ==
對象實現 Equatable 協議,同時在外部重寫對應的 == 方法,因為我們需要在任意地方都可以使用對應的比較
class TodoItem {
let uuid: String
var title: String
init(uuid: String, title: String) {
self.uuid = uuid
self.title = title
}
}
extension TodoItem: Equatable {
}
func ==(lhs: TodoItem, rhs: TodoItem) -> Bool {
return lhs.uuid == rhs.uuid
}
oc中用的是isEqual,通過重寫isEqual的方式來進行對象的比較
38、判等還需要考慮的一點,hash值,比較兩個對象是否內存地址也一樣
所以在重寫哈希方法時候所采用的策略,與判等的時候是類似的:對于非 NSObject 的類,我們需要遵守 Hashable 并根據 == 操作符的內容給出哈希算法;而對于 NSObject 子類,需要根據是否需要在 Objective-C 中訪問而選擇合適的重寫方式,去實現 Hashable 的 hashValue 或者直接重寫 NSObject 的 -hash 方法。
也就是說,在 Objective-C 中,對于 NSObject 的子類來說,其實 NSDictionary 的安全性是通過人為來保障的。對于那些重寫了判等但是沒有重寫對應的哈希方法的子類,編譯器并不能給出實質性的幫助。而在 Swift 中,如果你使用非 NSObject 的類型和原生的 Dictionary,并試圖將這個類型作為字典的 key 的話,編譯器將直接拋出錯誤。從這方面來說,如果我們盡量使用 Swift 的話,安全性將得到大大增加。
39、避免多重Optional
// Never do this!
func methodThrowsWhenPassingNegative1(number: Int) throws -> Int? {
if number < 0 {
throw Error.Negative
}
if number == 0 {
return nil
}
return number
}
if let num = try? methodThrowsWhenPassingNegative1(0) {
print(type(of:num))
} else {
print("failed")
}
// 輸出:
// Optional<Int>
// 其實里面包裝的是一個 nil
40、斷言
斷言的另一個優點是它是一個開發時的特性,只有在 Debug 編譯的時候有效,而在運行時是不被編譯執行的,因此斷言并不會消耗運行時的性能。這些特點使得斷言成為面向程序員的在調試開發階段非常合適的調試判斷,而在代碼發布的時候,我們也不需要刻意去將這些斷言手動清理掉,非常方便。
雖然默認情況下只在 Release 的情況下斷言才會被禁用,但是有時候我們可能出于某些目的希望斷言在調試開發時也暫時停止工作,或者是在發布版本中也繼續有效。我們可以通過顯式地添加編譯標記達到這個目的。在對應 target 的 Build Settings 中,我們在 Swift Compiler - Custom Flags 中的 Other Swift Flags 中添加 -assert-config Debug 來強制啟用斷言,或者 -assert-config Release 來強制禁用斷言。當然,除非有充足的理由,否則并不建議做這樣的改動。如果我們需要在 Release 發布時在無法繼續時將程序強行終止的話,應該選擇使用 fatalError。
41、playground 延時執行
https://swifter.tips/playground-delay/
42、swift中的 swizzle
盡量還是少在swift中使用runtime
initialize()
load()
這倆個方法都不可以在swift中使用了
所以如果后續需要執行swizzle交換 需要把初始化方法在Appdelegate didFinishLaunchingWithOptions 中手動調用
最后 swizzle如下
extension UIViewController {
static func initializeMethod() {
DispatchQueue.once(token: "initializeMethod") {
let originalSEL = #selector(UIViewController.viewWillAppear(_:))
let newSEL = #selector(UIViewController.MyViewWillAppear(_:))
let originalMethod = class_getInstanceMethod(self, originalSEL)
let newMethod = class_getInstanceMethod(self, newSEL)
let addResult = class_addMethod(self, originalSEL, method_getImplementation(newMethod!), method_getTypeEncoding(newMethod!))
if addResult {
class_replaceMethod(self, newSEL, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, newMethod!)
}
}
}
@objc func MyViewWillAppear(_ animated: Bool) {
print("測試swizzle")
MyViewWillAppear(animated)
}
}
43、Swift失去了 dispatch_once
在swift中不再支持 dispatch_once
給Dispatch 添加 once的擴展
extension DispatchQueue {
private static var _tokens = [String]()
public class func once(token: String, completion: () -> ()) {
objc_sync_enter(self)
defer {
objc_sync_exit(self)
}
if _tokens.contains(token) {
return
} else {
_tokens.append(token)
completion()
}
}
}
44、lazy 和map filtter等方法合用
let data = 1...3
lazy var result = data.lazy.map { $0 + 1 }
45、swift中的math
46、swift調用 C 動態庫
暫時只能通過OC來調用,bridge文件橋接
47、Mirror swift中的反射
盡量減少生產環境對Mirror的使用,畢竟本身他在Apple的實現里不是很豐富,也可能會被后期swift改版給改動
48、輸出格式化
String(format: "%.2f", 1.2222) 輸出:1.22
49、swift中的options
oc中的多個option直接用 | 符號鏈接:
UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAllowUserInteraction
為空則 nil
swift中options為數組
[.CurveEaseIn, .AllowUserInteraction],為空的時候即 [] 空數組
50、判斷數據類型
比如我們存儲一些數據后希望可以精確還原對應的數據類型,可以通過下面方式獲取數據類型
print(String(cString: NSNumber(value: 1.22).objCType))
存儲時使用 objCType 獲取類型,然后將數字本身和類型的字符串一起存儲。在讀取時就可以通過匹配類型字符串和類型的編碼,確定數字本來所屬的類型,從而直接得到像 Int 或者 Double 這樣的類型明確的量
OC中有@encode
51、特殊編譯符號
在 Swift 中,編譯器為我們準備了幾個很有用的編譯符號,用來處理類似這樣的需求,它們分別是:
符號 類型 描述
#file String 包含這個符號的文件的路徑
#line Int 符號出現處的行號
#column Int 符號出現處的列
#function String 包含這個符號的方法名字
printLog(message: "測試")
func printLog<T>(message: T, file: String = #file, method: String = #function, line: Int = #line) {
print("\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)")
}
52、將C函數映射為Swift函數
@_silgen_name("test") func test_swift(a: Int32) {
print("")
}
53、swift中的sizeOf
print(MemoryLayout.size(ofValue: data))
54、swift中的合理便捷安全的資源管理方式
例如圖片的管理,當圖片變更或者發生圖片名稱替換的時候,全局去找對應的圖片 很是惡心
把圖片資源的字符串名稱通過 有rawvalue的enum管理起來
三方庫 R.swift
55、playground 如何與項目協作
56、swift中的鎖
func myMethod(anObj: AnyObject!) {
objc_sync_enter(anObj)
// 在 enter 和 exit 之間 anObj 不會被其他線程改變
objc_sync_exit(anObj)
}
57、Apple 為了 iOS 平臺的安全性考慮,是不允許動態鏈接非系統的框架的。
雖然和 Apple 的框架的后綴名一樣是 .framework,使用方式也類似,但是這些第三方框架都是實實在在的靜態庫,每個 app 需要在編譯的時候進行獨立地鏈接。
生成framework
https://swifter.tips/code-framework/
58、枚舉寫鏈表,嵌套枚舉
indirect enum LinkedList<Element: Comparable> {
case Empty
case Node(Element, LinkedList<Element>)
func value() {
switch self {
case .Node(let element, let node):
print(element)
print("-")
print(node.value())
case .Empty:
print("end")
default:
print("-")
}
}
}
print(LinkedList.Node(1, LinkedList.Node(2, LinkedList.Node(3, LinkedList.Empty))).value())
59、運行時關聯屬性
var music: String? {
get {
return objc_getAssociatedObject(self, &key) as? String
}
set {
objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
60、尾遞歸 經過實驗好像并沒有用 依然棧溢出
普通遞歸:
func sum(n: UInt) -> UInt {
if n == 0 {
return 0
}
return n + sum(n: n - 1)
}
print(sum(n: 1000000))
運行崩潰,原因是棧溢出
這是因為每次對于 sum 的遞歸調用都需要在調用棧上保存當前狀態,否則我們就無法計算最后的 n + sum(n - 1)。當 n 足夠大,調用棧足夠深的時候,棧空間將被耗盡而導致錯誤,也就是我們常說的棧溢出了。
尾遞歸就是讓函數里的最后一個動作是一個函數調用的形式,這個調用的返回值將直接被當前函數返回,從而避免在棧上保存狀態。這樣一來程序就可以更新最后的棧幀,而不是新建一個,來避免棧溢出的發生。在 Swift 2.0 中,編譯器現在支持嵌套方法的遞歸調用了 (Swift 1.x 中如果你嘗試遞歸調用一個嵌套函數的話會出現編譯錯誤),因此 sum 函數的尾遞歸版本可以寫為:
func tailSum(n: UInt) -> UInt {
func sumInternal(n: UInt, current: UInt) -> UInt {
if n == 0 {
return current
} else {
return sumInternal(n - 1, current: current + n)
}
}
return sumInternal(n, current: 0)
}
tailSum(1000000)
但是如果你在項目中直接嘗試運行這段代碼的話還是會報錯,因為在 Debug 模式下 Swift 編譯器并不會對尾遞歸進行優化。我們可以在 scheme 設置中將 Run 的配置從 Debug 改為 Release,這段代碼就能正確運行了。
61、枚舉遍歷
枚舉實現 CaseIterable 協議
print(Test.allCases)
62、Swift判斷安全區域(用于適配所有的無Home鍵屏幕)
static func safeAreaTop() -> CGFloat {
if #available(iOS 11.0, *) {
//iOS 12.0以后的非劉海手機top為 20.0
if (UIApplication.shared.delegate as? AppDelegate)?.window?.safeAreaInsets.bottom == 0 {
return 20.0
}
return (UIApplication.shared.delegate as? AppDelegate)?.window?.safeAreaInsets.top ?? 20.0
}
return 20.0
}
static func safeAreaBottom() -> CGFloat {
if #available(iOS 11.0, *) {
return (UIApplication.shared.delegate as? AppDelegate)?.window?.safeAreaInsets.bottom ?? 0
}
return 0
}
static func hasSafeArea() -> Bool {
if #available(iOS 11.0, *) {
return (UIApplication.shared.delegate as? AppDelegate)?.window?.safeAreaInsets.bottom ?? CGFloat(0) > CGFloat(0)
} else { return false }
}
static func toolBarHeight() -> CGFloat {
return 49 + safeAreaBottom()
}
static func navigationHeight() -> CGFloat {
return 44 + safeAreaTop()
}
63、單元測試 target 的訪問級別
當你的應用程序包含單元測試 target 時,為了測試,測試模塊需要訪問應用程序模塊中的代碼。默認情況下只有 open 或 public 級別的實體才可以被其他模塊訪問。然而,如果在導入應用程序模塊的語句前使用 @testable特性,然后在允許測試的編譯設置(Build Options -> Enable Testability)下編譯這個應用程序模塊,單元測試目標就可以訪問應用程序模塊中所有內部級別的實體。