Selector
selector是Objective-C runtime的概念,在調用一個selector前,要求selector方法加上@objc
修飾。
實例方法的動態(tài)調用
我們可以做到在運行時才決定對哪個實例調用哪個方法。
class MyClass {
func method(number: Int) -> Int {
return number + 1
}
}
let object = MyClass()
let f = MyClass.method
let objectMethod = f(object)
let result = objectMethod(1)
條件編譯
Swift依然可以使用條件編譯。
Swift內建了幾種平臺和架構的組合,來幫助我們?yōu)椴煌钠脚_編譯不同的代碼:
方法 | 可選參數(shù) |
---|---|
ox | OSX, iOS |
arch() | x86_64, arm, arm64, i386 |
#if os(OSX)
typealias Color = NSColor
#else
typealias Color = UIColor
#endif
也可以對自定義的符號進行條件編譯,比如定義一個免費版本標記FREE_VERSION
:
#if ok
print("免費版本")
#else
print("收費版本")
#endif
為了使之有效,還需要在項目的編譯選項中進行設置,在項目的Build Settings中,找到Swift Compiler - Custom Flags,并在其中的 Other Swift Flags 加上 -D FREE_VERSION
就可以了。
編譯標記
Xcode將在導航欄顯示出編譯標記
// MARK: 你的標記
// MARK: -
// TODO:
// FIXME:
@objc和dynamic
添加@objc
修飾符并不意味著這個方法或者屬性會變成動態(tài)派發(fā),Swift依然可能會將其優(yōu)化為靜態(tài)調用。如果你確實需要動態(tài)調用的特性,就加上dynamic
。
weak 和 unowned
如果您是一直寫 Objective-C 過來的,那么從表面的行為上來說 unowned 更像以前的 unsafe_unretained,而 weak 就是以前的 weak。用通俗的話說,就是 unowned 設置以后即使它原來引用的內容已經被釋放了,它仍然會保持對被已經釋放了的對象的一個 "無效的" 引用,它不能是 Optional 值,也不會被指向 nil。如果你嘗試調用這個引用的方法或者訪問成員屬性的話,程序就會崩潰。而 weak 則友好一些,在引用的內容被釋放后,標記為 weak 的成員將會自動地變成 nil (因此被標記為 weak 的變量一定需要是 Optional 值)。
關于兩者使用的選擇,Apple 給我們的建議是如果能夠確定在訪問時不會已被釋放的話,盡量使用unowned,如果存在被釋放的可能,那就選擇用 weak。
值類型和引用類型
Swift 的值類型,特別是數(shù)組和字典這樣的容器,在內存管理上經過了精心的設計。值類型的一個特點是在傳遞和賦值時進行復制,每次復制肯定會產生額外開銷,但是在 Swift 中這個消耗被控制在了最小范圍內,在沒有必要復制的時候,值類型的復制都是不會發(fā)生的。也就是說,簡單的賦值,參數(shù)的傳遞等等普通操作,雖然我們可能用不同的名字來回設置和傳遞值類型,但是在內存上它們都是同一塊內容。
值類型被復制的時機是值類型的內容發(fā)生改變時。
如果確實需要引用類型的容器,可以使用Cocoa的NSMutableArray
和NSMutableDictionary
。
UnsafePointer
為了與龐大的 C 系帝國進行合作,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.memory)
}
對于其他的 C 中基礎類型,在 Swift 中對應的類型都遵循統(tǒng)一的命名規(guī)則:在前面加上一個字母 C 并將原來的第一個字母大寫:比如 int,bool 和 char 的對應類型分別是 CInt,CBool 和 CChar。在上面的 C 方法中,我們接受一個 int 的指針,轉換到 Swift 里所對應的就是一個 CInt 的 UnsafePointer 類型。
獲取對象類型
使用type(of:)
let str = "hello"
let t = type(of: str)
debugPrint(t)
// Swift.String
自省
向一個對象發(fā)出詢問,以確定它是不是屬于某個類,這種操作就稱為自省。
在以前的Objective-C項目中,我們用
[obj1 isKindOfClass: [ClassA class]];
[obj2 isMemberOfClass: [ClassB class]];
-isKindOfClass:
判斷obj1
是否是ClassA
或者其子類的實例對象;
-isMemberOfClass:
判斷obj2
是否就是ClassB
的實例。
在Swift中,用關鍵字is
就可以起到isKindOfClass
的作用,并且可以用在struct
或enum
類型上。
class A {
}
class A1: A {
}
class B: NSObject {
}
class B1: B {
}
enum E1 {
case ok
}
let aaa = A1()
print(aaa is A)
let bbb = B1()
print(bbb is B)
let eee = E1.ok
print(eee is E1)
KVO
在Swift中我們也是可以使用KVO的,但是僅限于在NSObject
的子類中。這是可以理解的,因為KVO是基于KVC(Key-Value Coding)以及動態(tài)派發(fā)技術實現(xiàn)的,而這些東西都是Objective-C運行時的概念。
由于Swift為了效率,默認禁用了動態(tài)派發(fā),我們想要讓KVO正常工作,需要在被觀測屬性前加dynamic
,在 Swift 4 后,需要同時加@objc dynamic
。
舉個栗子:
class MyClass: NSObject {
@objc dynamic var date = Date()
}
private var myContext = 0
class ViewController: UIViewController {
var myObject: MyClass!
override func viewDidLoad() {
super.viewDidLoad()
myObject = MyClass()
print("當前日期:\(myObject.date)")
myObject.addObserver(self, forKeyPath: "date", options: .new, context: &myContext)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
print("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 date = change[.newKey] {
print("日期發(fā)生變化:\(date)")
}
}
}
}
打印結果:
當前日期:2017-12-04 06:18:52 +0000
3秒后
日期發(fā)生變化:2017-12-04 06:18:55 +0000
在Swift中使用KVO有兩個問題:
- 屬性必須要有
dynamic
才能被觀察,而有的類我們可能無法修改其源碼。這種情況下,一個可能可行的方案是繼承這個類,并將需要觀察的屬性使用dynamic
進行重寫。
class MyClass: NSObject {
var date = Date()
}
class MyChildClass: MyClass {
@objc dynamic override var date: Date {
get {
return super.date
}
set {
super.date = newValue
}
}
}
- 對于非
NSObject
的類型,Swift暫時還沒有類似KVO的觀察機制。我們可能只能通過屬性觀察來實現(xiàn)一套自己的類似替代了。
局部scope
在Objective-C中,我們有時會在方法內使用一對大括號來創(chuàng)創(chuàng)建臨時的作用域,以此分隔不相關聯(lián)的代碼,這在手寫視圖布局時特別有用。
但是在Swift中,直接寫大括號與閉包的定義沖突,這時可以定義一個接受() -> ()
作為參數(shù)的全局方法,然后執(zhí)行它:
func local(closure: () -> ()) {
closure()
}
class ViewController: UIViewController {
override func loadView() {
local {
// ...
}
local {
// ...
}
}
}
在Swift 2.0 中,為了處理異常,Apple加入了do
這個關鍵字來作為捕獲異常的作用域。這一功能恰好為我們提供了一個完美的局部作用域,現(xiàn)在我們可以簡單地使用do
來分隔代碼了:
class ViewController: UIViewController {
override func loadView() {
do {
// ...
}
do {
// ...
}
}
}
判等
對字符串的內容判等,我們可以簡單地使用==
操作符來進行。
在Equatable
里聲明了這個操作符的接口方法:
public protocol Equatable {
public static func ==(lhs: Self, rhs: Self) -> Bool
}
實現(xiàn)了Equatable
的類型就可以使用==
以及!=
來進行相等判定了。!=
由標準庫自動取反實現(xiàn)。
Swift的基本類型都重載了自己對應版本的==
,而對于NSObject
的子類來說,如果我們使用 == 并且沒有對于這個子類的重載的話,將轉為調用這個類的-isEqual:
方法,如果子類沒有實現(xiàn)-isEqual:
方法,則會使用NSObject
的實現(xiàn),直接比較對象的內存地址。
如果要進行對象指針的判定,在Swift中是使用另一個操作符===
。
Swizzle
Swizzle是Objective-C運行時的黑魔法之一。
在Swift中也可以使用它,前提要把方法聲明為@objc
。
比如,置換UIButton的事件發(fā)送方法,以統(tǒng)計全局點擊:
extension UIButton {
class func jx_swizzleSendAction() {
let cls: AnyClass! = UIButton.self
let originalSelector = #selector(sendAction(_:to:for:))
let swizzledSelector = #selector(jx_sendAction(_:to:for:))
let originalMethod = class_getInstanceMethod(cls, originalSelector)
let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector)
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
@objc func jx_sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
print("swizzle tap!")
jx_sendAction(action, to: target, for: event)
}
}
jx_swizzleSendAction
是我們定義用來設置方法置換的代碼,由于新版本Swift已經不能重寫+load
和+initialize
方法了,所以我們只好在一個比較早的時機手動調用它,讓置換生效。
補充一點,可能你會疑惑為什么jx_sendAction
方法中又調用了jx_sendAction
,這不就死循環(huán)了嗎?
不會的。在A和B方法發(fā)生置換以后,你可以想象成兩個方法的方法名與方法體已經被相互交換了,當調用A方法時,實際執(zhí)行的是B的實現(xiàn)。
輸出格式化
如果想使用像%.2f
這樣的方式取得格式化字符串,可以這樣:
let n = 1.23456789
let format = String.init(format: "%0.2f", n)
print(format)
// 1.23
數(shù)組enumerate
在Swift中,可以用for
循環(huán)配合enumerated()
取代OC的enumerateObjectsUsingBlock
了。
let arr = [1, 2, 4, 5]
var result = 0
for (idx, num) in arr.enumerated() {
result += num
if idx == 2 {
break
}
}
sizeof和sizeofValue
Swift3以后,sizeof功能由MemoryLayout
類封裝。
求類型所占內存大小,使用它的計算屬性size
:
let stringSize = MemoryLayout<String>.size
print("string size: \(stringSize)")
print("Uint16 size: \(MemoryLayout<UInt16>.size)")
// string size: 24
// Uint16 size: 2
求值(變量)的所占內存大小,用size(ofValue value: T) -> Int
方法
let numArray: [UInt16] = [1, 2, 3, 4, 5]
print("numArray size: \(MemoryLayout.size(ofValue: numArray))")
// numArray size: 8
上例的numArray被sizeofValue后,得到結果為8,這其實是64位系統(tǒng)一個引用的長度。由此可見sizeofValue所返回的是這個值實際的大小,而非其意義內容的大小。
以下對枚舉做個測試,可體會一下:
enum MyEnum: UInt16 {
case A = 0
case B = 65535
}
print("MyEnum size: \(MemoryLayout<MyEnum>.size)")
print("MyEnum.A size: \(MemoryLayout.size(ofValue: MyEnum.A))")
print("MyEnum.B size: \(MemoryLayout.size(ofValue: MyEnum.B))")
print("MyEnum.A.rawValue size: \(MemoryLayout.size(ofValue: MyEnum.A.rawValue))")
// MyEnum size: 1
// MyEnum.A size: 1
// MyEnum.B size: 1
// MyEnum.A.rawValue size: 2
delegate
Cocoa 開發(fā)中接口-委托 (protocol-delegate) 模式是一種常用的設計模式,它貫穿于整個 Cocoa 框架中,為代碼之間的關系清理和解耦合做出了不可磨滅的貢獻。
一般我們希望delegate引用是weak的,但在Swift中如果直接這么寫的話,編譯器會報錯:
protocol MyDelegate {
func method()
}
class AClass {
weak var delegate: MyDelegate?
}
// 'weak' may only be applied to class and class-bound protocol types, not 'MyDelegate'
這是因為Swift的protocol除了可以被class遵守外,還可以被struct或enum這樣的非class遵守的,它本身不通過引用計數(shù)來管理內存,所以也不能用weak修飾。
想要在Swift中使用weak delegate,我們就需要將protocol限制在class內,在聲明后加上class
關鍵字以限制:
protocol MyDelegate: class {
func method()
}
class AClass {
weak var delegate: MyDelegate?
}
Associated Object
Swift仍然不能通過Category向已有類添加成員變量,但我們還是可以使用OC運行時,將一個對象關聯(lián)到已有的要擴展的對象上。
class MyClass: NSObject {
}
private var key: Void?
extension MyClass {
var title: String? {
get {
return objc_getAssociatedObject(self, &key) as? String
}
set {
objc_setAssociatedObject(self, &key, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}
這樣title
在使用起來就像普通的屬性一樣。
synchronized
現(xiàn)版本的Swift是沒有@synchronized的,如果我們想保護一個對象在某個作用域內不被其它線程改變,可以這么做:
var anObject: Any = ""
objc_sync_enter(anObject)
// 在 enter 和 exit 之間,anObject 不會被其它線程改變
objc_sync_exit(anObject)
當然,也可以寫個全局方法,封裝起來,這樣就和以前的@synchronized的很像了:
func synchronized(_ object: Any, closure: () -> ()) {
objc_sync_enter(object)
closure()
objc_sync_exit(object)
}
synchronized(anObject) {
// 在括號內,anObject不會被其它線程改變
}
參考
- Swifter - 100個 Swift 必備 Tips