人呢,有時候真的會很懶,懶到吃飯都不想吃了...
-
lazy,有時候有些操作可以延遲處理,優(yōu)化性能
看看一下打印有什么區(qū)別
let data = 1...3
let result = data.lazy.map {
(i: Int) -> Int in
print("正在處理 \(i)")
return i * 2
}
print("準備訪問結果")
for i in result {
print("操作后結果為 \(i)")
}
print("操作完畢")
結果.png
let data = 1...3
let result = data.map {
(i: Int) -> Int in
print("正在處理 \(i)")
return i * 2
}
print("準備訪問結果")
for i in result {
print("操作后結果為 \(i)")
}
print("操作完畢")
結果.png
-
反射Mirror可以獲取模型的鍵值對
struct Person {
let name: String
let age: Int
}
let xiaoMing = Person(name: "XiaoMing", age: 26)
let r = Mirror(reflecting: xiaoMing) // r 是 MirrorType
xiaoMing.self
for child in r.children {
print("屬性名:\(String(describing: child.label)),值:\(child.value)")
}
// KVC獲取值
func valueFrom(_ object: Any, key: String) -> Any? {
let mirror = Mirror(reflecting: object)
for child in mirror.children {
let (targetKey, targetMirror) = (child.label, child.value)
if key == targetKey {
return targetMirror
}
}
return nil
}
dump(xiaoMing)
dump(valueFrom(xiaoMing, key: "name"))
結果png
-
獲取對象類型
-
“在 Objective-C 中,使用 -class 方法就可以拿到對象的類,我們甚至可以用 NSStringFromClass 將它轉換為一個能夠打印出來的字符串:
NSString *str = [[NSString alloc] init]; NSLog(@"%@",NSStringFromClass([str class]));” -
“在 Swift 中,為了獲取一個 NSObject 或其子類的對象的實際類型,對這個調用其實有一個好看一些的寫法,那就是 type(of:)?!?/p>
let date = NSDate() let name = type(of: date) print(name) // 輸出: __NSDate -
“當然,在Swift中我們可以求助于 Objective-C 的運行時,來獲取類并按照原來的方式轉換:
let date = NSDate() let name: AnyClass! = object_getClass(date) print(name) // 輸出: __NSDate
-
-
KVO
“KVO (Key-Value Observing) 是 Cocoa 中公認的最強大的特性之一,但是同時它也以爛到家的 API 和極其難用著稱。和屬性觀察不同,KVO 的目的并不是為當前類的屬性提供一個鉤子方法,而是為了其他不同實例對當前的某個屬性 (嚴格來說是 keypath) 進行監(jiān)聽時使用的。其他實例可以充當一個訂閱者的角色,當被監(jiān)聽的屬性發(fā)生變化時,訂閱者將得到通知。
這是一個很強大的屬性,通過 KVO 我們可以實現(xiàn)很多松耦合的結構,使代碼更加靈活和強大:像通過監(jiān)聽 model 的值來自動更新 UI 的綁定這樣的工作,基本都是基于 KVO 來完成的。
在 Swift 中我們也是可以使用 KVO 的,而且在 Swift 4 中,結合 KeyPath,Apple 為我們提供了非常漂亮的一套新的 API。不過 KVO 僅限于在 NSObject 的子類中,這是可以理解的,因為 KVO 是基于 KVC (Key-Value Coding) 以及動態(tài)派發(fā)技術實現(xiàn)的,而這些東西都是 Objective-C 運行時的概念。另外由于 Swift 為了效率,默認禁用了動態(tài)派發(fā),因此想用 Swift 來實現(xiàn) KVO,我們還需要做額外的工作,那就是將想要觀測的對象標記為dynamic和@objc。
在 Swift 4 之前的版本中,為一個 NSObject 的子類實現(xiàn) KVO 的最簡單的例子看起來是這樣的:
class MyClass: NSObject {
@objc dynamic var date = Date()
}
private var myContext = 0
class Class: NSObject {
var myObject: MyClass!
override init() {
super.init()
myObject = MyClass()
print("初始化 MyClass,當前日期: \(myObject.date)")
myObject.addObserver(self,
forKeyPath: "date",
options: .new,
context: &myContext)
delay(3) {
self.myObject.date = Date()
}
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,context: UnsafeMutableRawPointer?)
{
if let change = change, context == &myContext {
if let newDate = change[.newKey] as? Date {
print("MyClass 日期發(fā)生變化 \(newDate)")
}
}
}
}
let obj = Class()
/*輸出應該類似于:
初始化 Class,當前日期: 2017-10-26 08:50:24 +0000
Class 日期發(fā)生變化 2017-10-26 08:50:27 +0000*/
Swift 4 中 Apple 引入了新的 KeyPath 的表達方式,不需要傳Context去區(qū)分是那個變量發(fā)生了改變,監(jiān)聽方式改為閉包模式
var observation: NSKeyValueObservation?
observation = myObject.observe(\MyClass.date, options: [.new]) { (_, change) in
if let newDate = change.newValue {
print("AnotherClass 日期發(fā)生變化 \(newDate)")
}
}
// 值得注意的是:必須要用屬性observation賦值 否則不會調用閉包里面的代碼
-
Lock
無并發(fā),不編碼。而只要一說到多線程或者并發(fā)的代碼,我們可能就很難繞開對于鎖的討論。簡單來說,為了在不同線程中安全地訪問同一個資源,我們需要這些訪問順序進行。Cocoa 和 Objective-C 中加鎖的方式有很多,但是其中在日常開發(fā)中最常用的應該是 @synchronized,這個關鍵字可以用來修飾一個變量,并為其自動加上和解除互斥鎖。這樣,可以保證變量在作用范圍內不會被其他線程改變。舉個例子,如果我們有一個方法接受參數(shù),需要這個方法是線程安全的話,就需要在參數(shù)上加鎖:
-(void)myMethod:(id)anObj {
@synchronized(anObj) {
// 在括號內持有 anObj 鎖
}
}
如果沒有鎖的話,一旦 anObj 的內容被其他線程修改的話,這個方法的行為很可能就無法預測了。
但是加鎖和解鎖都是要消耗一定性能的,因此我們不太可能為所有的方法都加上鎖。另外其實在一個 app 中可能會涉及到多線程的部分是有限的,我們也沒有必要為所有東西加上鎖。過多的鎖不僅沒有意義,而且對于多線程編程來說,可能會產(chǎn)生很多像死鎖這樣的陷阱,也難以調試。因此在使用多線程時,我們應該盡量將保持簡單作為第一要務?!?br>
“雖然這個方法很簡單好用,但是很不幸的是在 Swift 中它已經(jīng) (或者是暫時) 不存在了。其實 @synchronized 在幕后做的事情是調用了 objc_sync 中的 objc_sync_enter 和 objc_sync_exit 方法,并且加入了一些異常判斷。因此,在 Swift 中,如果我們忽略掉那些異常的話,我們想要 lock 一個變量的話,可以這樣寫:
func myMethod(anObj: AnyObject!) {
objc_sync_enter(anObj)
// 在 enter 和 exit 之間持有 anObj 鎖
objc_sync_exit(anObj)
}
“如果我們喜歡以前的那種形式,甚至可以寫一個全局的方法,并接受一個閉包,來將 objc_sync_enter 和 objc_sync_exit 封裝起來:
func synchronized(_ lock: AnyObject, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
再結合 Swift 的尾隨閉包的語言特性,這樣,使用起來的時候就和 Objective-C 中很像了:
func myMethodLocked(anObj: AnyObject!) {
synchronized(anObj) {
// 在括號內持有 anObj 鎖
}
}
舉一個具體的使用例子,比如我們想要為某個類實現(xiàn)一個線程安全的 setter,可以這樣進行重寫:
// 一個實際的線程安全的 setter 例子
class Obj {
var _str = "123"
var str: String {
get {
return _str
}
set {
synchronized(self) {
_str = newValue
}
}
// 下略
}
}
-
UnsafePointer
Swift 本身從設計上來說是一門非常安全的語言,在 Swift 的思想中,所有的引用或者變量的類型都是確定并且正確對應它們的實際類型的,你應當無法進行任意的類型轉換,也不能直接通過指針做出一些出格的事情。這種安全性在日常的程序開發(fā)中對于避免不必要的 bug,以及迅速而且穩(wěn)定地找出代碼錯誤是非常有幫助的。但是凡事都有兩面性,在高安全的同時,Swift 也相應地喪失了部分的靈活性。
現(xiàn)階段想要完全拋棄 C 的一套東西還是相當困難的,特別是在很多上古級別的 C API 框架還在使用 (或者被間接使用)。開發(fā)者,尤其是偏向較底層的框架的開發(fā)者不得不面臨著與 C API 打交道的時候,還是無法繞開指針的概念,而指針在 Swift定義了一套對 C 語言指針的訪問和轉換方法,那就是 UnsafePointer 和它的一系列變體。對于使用 C API 時如果遇到接受內存地址作為參數(shù),或者返回是內存地址的情況,在 Swift 里會將它們轉為 UnsafePointer<Type> 的類型,比如說如果某個 API 在 C 中是這樣的話:
void method(const int *num) {
printf("%d",*num);
}
其對應的 Swift 方法應該是:
func method(_ num: UnsafePointer<CInt>) {
print(num.pointee)
}
我們這個 tip 所說的 UnsafePointer,就是 Swift 中專門針對指針的轉換。對于其他的 C 中基礎類型,在 Swift 中對應的類型都遵循統(tǒng)一的命名規(guī)則:在前面加上一個字母 C 并將原來的第一個字母大寫:比如 int,bool 和 char 的對應類型分別是CInt,CBool 和 CChar。在上面的 C 方法中,我們接受一個 int 的指針,轉換到 Swift 里所對應的就是一個 CInt 的 UnsafePointer 類型。這里原來的 C API 中已經(jīng)指明了輸入的 num 指針的不可變的 (const),因此在 Swift 中我們與之對應的是 UnsafePointer 這個不可變版本。如果只是一個普通的可變指針的話,我們可以使用 UnsafeMutablePointer 來對應:
| C API | Swift API |
|---|---|
| const Type * | UnsafePointer |
| Type * | UnsafeMutablePointer |
在 C 中,對某個指針進行取值使用的是 *,而在 Swift 中我們可以使用 memory 屬性來讀取相應內存中存儲的內容。通過傳入指針地址進行方法調用的時候就都比較相似了,都是在前面加上 & 符號,C 的版本和 Swift 的版本只在聲明變量的時候有所區(qū)別:
// C
int a = 123;
method(&a); // 輸出 123
// Swift
var a: CInt = 123
method(&a) // 輸出 123
遵守這些原則,使用 UnsafePointer 在 Swift 中進行 C API 的調用應該就不會有很大問題了。
“另外一個重要的課題是如何在指針的內容和實際的值之間進行轉換。比如我們如果由于某種原因需要涉及到直接使用 CFArray 的方法來獲取數(shù)組中元素的時候,我們會用到這個方法:
func CFArrayGetValueAtIndex(theArray: CFArray!, idx: CFIndex)
-> UnsafePointer<Void>
因為 CFArray 中是可以存放任意對象的,所以這里的返回是一個任意對象的指針,相當于 C 中的 void *。這顯然不是我們想要的東西。Swift 中為我們提供了一個強制轉換的方法 unsafeBitCast,通過下面的代碼,我們可以看到應當如何使用類似這樣的 API,將一個指針強制按位轉成所需類型的對象:
let arr = NSArray(object: "meow")
let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), to: CFString.self)
// str = "meow
“unsafeBitCast 會將第一個參數(shù)的內容按照第二個參數(shù)的類型進行轉換,而不去關心實際是不是可行,這也正是 UnsafePointer 的不安全所在,因為我們不必遵守類型轉換的檢查,而擁有了在指針層面直接操作內存的機會。
其實說了這么多,Apple 將直接的指針訪問冠以 Unsafe 的前綴,就是提醒我們:這些東西不安全,親們能不用就別用了吧 (作為 Apple,另一個重要的考慮是如果避免指針的話可以減少很多系統(tǒng)漏洞)!在日常開發(fā)中,我們確實不太需要經(jīng)常和這些東西打交道 (除了傳入 NSError 指針這個歷史遺留問題以外,而且在 Swift 2.0 中也已經(jīng)使用異常機制替代了 NSError)??傊?,盡可能地在高抽象層級編寫代碼,會是高效和正確的有力保證。無數(shù)先輩已經(jīng)用血淋淋“的教訓告訴我們,要避免去做這樣的不安全的操作,除非你確實知道你在做的是什么。”
關注我
慕課網(wǎng)



