@selector
是 Objective-C
時代的一個關鍵字,它能夠將一個方法轉換並賦值給一個 SEL
類型,它的表現很相似一個動態的函數指針。在 Objective-C 時 selector
很是經常使用,從設定target-action
,到自舉詢問是否響應某個方法,再到指定接受通知時須要調用的方法等等,都是由 selector
來負責的。在 Objective-C 裏生成一個 selector
的方法通常是這個樣子的git
-(void) callMe {
//...
}
-(void) callMeWithParam:(id)obj {
//...
}
SEL someMethod = @selector(callMe);
SEL anotherMethod = @selector(callMeWithParam:);
// 或者也可使用 NSSelectorFromString
// SEL someMethod = NSSelectorFromString(@"callMe");
// SEL anotherMethod = NSSelectorFromString(@"callMeWithParam:");
複製代碼
通常爲了方便,不少人會選擇使用 @selector,可是若是要追求靈活的話,可能會更願意使用 NSSelectorFromString 的版本 -- 由於咱們能夠在運行時動態生成字符串,經過方法名來調用對應的方法
程序員
在 Swift 中沒有 @selector
了,取而代之,從 Swift 2.2 開始咱們使用 #selector
來從暴露給 Objective-C 的代碼中獲取一個 selector
。相似地,在 Swift 裏對應原來 SEL
的類型是一個叫作 Selector
的結構體github
@objc func callMe() {
//...
}
@objc func callMeWithParam(obj: AnyObject!) {
//...
}
let someMethod = #selector(callMe)
let anotherMethod = #selector(callMeWithParam(obj:))
複製代碼
【注】selector
實際上是 Objective-C runtime
的概念。在 Swift 4 中,默認狀況下全部的 Swift 方法在 Objective-C 中都是不可見的,因此你須要在這類方法前面加上 @objc
關鍵字,將這個方法暴露給 Objective-C,才能進行使用編程
若是方法名字在方法所在域內是惟一的話,咱們能夠簡單地只是用方法的名字來做爲 #selector
的內容。相比於前面帶有冒號的完整的形式來講,這麼寫起來會方便一些swift
let someMethod = #selector(callMe)
let anotherMethod = #selector(callMeWithParam)
複製代碼
若是同一個做用域裏面存在一樣名字的兩個方法,可是參數不一樣,咱們能夠經過將方法強制轉換來使用api
@objc func commonFunc() {}
@objc func commonFunc(input: Int) -> Int {
return input
}
let method1 = #selector(commonFunc as ()->())
let method2 = #selector(commonFunc as (Int)->Int)
複製代碼
class MyClass {
func method(number: Int) -> Int {
return number + 1
}
}
複製代碼
想要調用method
方法的話,最普通的使用方式是生成MyClass
的實例,而後用 .method
來調用它數組
let cls = MyClass()
cls.method(number: 1)
複製代碼
咱們還能夠把剛纔的方法該成下面這樣安全
let f = MyClass.method
let object = MyClass()
let result = f(object)(1)
複製代碼
咱們觀察f
類:alt+單擊
bash
let f: (MyClass) -> (Int) -> Int
複製代碼
其實對於 Type.instanceMethod
這樣的取值語句,實際上剛纔多線程
let f = MyClass.method
複製代碼
作的事情相似於下面字面量的轉換
let f = { (obj: MyClass) in obj.method }
複製代碼
在OC中單例的公認寫法
@implementation MyManager
+ (id)sharedManager {
static MyManager * staticInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
staticInstance = [[self alloc] init];
});
return staticInstance;
}
@end
複製代碼
使用 GCD 中的 dispatch_once_t
能夠保證裏面的代碼只被調用一次,以此保證單例在線程上的安全
在Swift中移出了dispatch_once
,可是咱們有更簡單的寫法
class MyManager {
static let shared = MyManager()
private init() {}
}
複製代碼
在 C 系語言中,可使用#if
或者 #ifdef
之類的編譯條件分支來控制哪些代碼須要編譯,而哪些代碼不須要。Swift 中沒有宏定義的概念,所以咱們不能使用#ifdef
的方法來檢查某個符號是否通過宏定義。可是爲了控制編譯流程和內容,Swift 仍是爲咱們提供了幾種簡單的機制來根據需求定製編譯內容的。
首先是 #if
這一套編譯標記仍是存在的,#elseif
和#else
是可選的。
#if <condition>
#elseif <condition>
#else
#endif
複製代碼
可是這幾個表達式裏的 condition 並非任意的。Swift 內建了幾種平臺和架構的組合,來幫助咱們爲不一樣的平臺編譯不一樣的代碼,具體地
方法 | 可選參數 |
---|---|
os() | macOS, iOS, tvOS, watchOS, Linux |
arch() | x86_64, arm, arm64, i386 |
swift() | >= 某個版本 |
若是咱們統一咱們在 iOS 平臺和 Mac 平臺的關於顏色的 API 的話,一種可能的方法就是配合 typealias 進行條件編譯:
#if os(macOS)
typealias Color = NSColor
#else
typealias Color = UIColor
#endif
#if arch(x86_64)
#else
#endif
#if swift(>=14.0)
#else
#endif
複製代碼
對自定義符號進行編譯
咱們須要使用同一個 target 完成同一個 app 的收費版和免費版兩個版本,而且但願在點擊某個按鈕時收費版本執行功能,而免費版本彈出提示的話,可使用相似下面的方法
func someButtonPressed(sender: AnyObject!) {
#if FREE_VERSION
// 彈出購買提示,導航至商店等
#else
// 實際功能
#endif
}
複製代碼
在這裏咱們用 FREE_VERSION
這個編譯符號來表明免費版本。爲了使之有效,咱們須要在項目的編譯選項中進行設置,在項目的 Build Settings
中,找到 Swift Compiler - Custom Flags
,並在其中的Other Swift Flags
加上-D FREE_VERSION
就能夠了。
在 C 系語言中,程序的入口都是 main 函數。對於一個 Objective-C 的 iOS app 項目,在新建項目時, Xcode 將幫咱們準備好一個 main.m 文件,其中就有這個 main 函數
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil,
NSStringFromClass([AppDelegate class]));
}
}
複製代碼
這個方法將根據第三個參數初始化一個 UIApplication 或其子類的對象並開始接收事件 (在這個例子中傳入 nil,意味使用默認的 UIApplication)。最後一個參數指定了 AppDelegate 類做爲應用的委託,它被用來接收相似 didFinishLaunching 或者 didEnterBackground 這樣的與應用生命週期相關的委託方法。另外,雖然這個方法標明爲返回一個 int,可是其實它並不會真正返回。它會一直存在於內存中,直到用戶或者系統將其強制終止
新建一個 Swift 的 iOS app 項目後,咱們會發現全部文件中都沒有一個像 Objective-C 時那樣的 main
文件,也不存在 main 函數
。惟一和main
有關係的是在默認的 AppDelegate 類的聲明上方有一個 @UIApplicationMain
的標籤。
其實 Swift 的 app 也是須要 main 函數的,只不過默認狀況下是 @UIApplicationMain
幫助咱們自動生成了而已。
如咱們在刪除 @UIApplicationMain 後,在項目中添加一個 main.swift
文件,而後加上這樣的代碼
UIApplicationMain(Process.argc, Process.unsafeArgv, nil,
NSStringFromClass(AppDelegate))
複製代碼
如今編譯運行,就不會再出現錯誤了。固然,咱們還能夠經過將第三個參數替換成本身的 UIApplication 子類,這樣咱們就能夠輕易地作一些控制整個應用行爲的事情了。好比將 main.swift 的內容換成
UIApplicationMain(
CommandLine.argc,
UnsafeMutableRawPointer(CommandLine.unsafeArgv)
.bindMemory(
to: UnsafeMutablePointer<Int8>.self,
capacity: Int(CommandLine.argc)),
NSStringFromClass(MyApplication.self),
NSStringFromClass(AppDelegate.self)
)
import UIKit
class MyApplication: UIApplication {
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
print("Event sent:\(event)")
}
}
let cls = MyClass()
cls.mustProtocolMethod()
cls.mustProtocolMethod1()
複製代碼
這樣每次發送事件 (好比點擊按鈕) 時,咱們均可以監聽到這個事件了
Objective-C 中的 protocol
裏存在 @optional
關鍵字,被這個關鍵字修飾的方法並不是必需要被實現。咱們能夠經過協議定義一系列方法,而後由實現協議的類選擇性地實現其中幾個方法。最好的例子我想應該是 UITableViewDataSource 和 UITableViewDelegate。前者中有兩個必要方法
-tableView:numberOfRowsInSection:
-tableView:cellForRowAtIndexPath:
複製代碼
原生的 Swift protocol 裏沒有可選項,全部定義的方法都是必須實現的
protocol MyProtocol {
func mustProtocolMethod() //必須實現方法
func mustProtocolMethod1() //必須實現方法
}
class MyClass: MyProtocol {
func mustProtocolMethod() {
print("MyClass-->必須實現方法:mustProtocolMethod")
}
func mustProtocolMethod1() {
print("MyClass-->必須實現方法:mustProtocolMethod1")
}
}
複製代碼
若是咱們想要像 Objective-C 裏那樣定義可選的協議方法,就須要將協議自己和可選方法都定義爲Objective-C 的,也即在 protocol
定義以前以及協議方法以前加上 @objc
。另外和 Objective-C 中的 @optional
不一樣,咱們使用沒有 @
符號的關鍵字 optional
來定義可選方法
@objc protocol MyProtocol1 {
@objc optional func optionalProtocolMethod() //可選方法
func mustProtocolMethod1() //必須實現方法
}
class MyClass1: MyProtocol1 {
func mustProtocolMethod1() {
print("MyClass1-->必須實現方法:mustProtocolMethod1")
}
}
let cls1 = MyClass1()
cls1.mustProtocolMethod1()
複製代碼
一個不可避免的限制是,使用 @objc 修飾的 protocol 就只能被 class 實現了
,也就是說,對於 struct 和 enum 類型,咱們是沒法令它們所實現的協議中含有可選方法或者屬性的
在 Swift 2.0 中,咱們有了另外一種選擇,那就是使用 protocol extension。咱們能夠在聲明一個 protocol 以後再用 extension 的方式給出部分方法默認的實現。這樣這些方法在實際的類中就是可選實現的了
protocol MyProtocol2 {
func optionalProtocolMethod1() //可選方法
func optionalProtocolMethod2() //可選方法
func mustProtocolMethod1() //必須實現方法
}
extension MyProtocol2{
func optionalProtocolMethod1(){}
func optionalProtocolMethod2(){}
}
複製代碼
跟OC同樣,Swift也是採用基於引用計算的ARC內存管理方案(針對堆空間)
Swift中ARC有3種引用
weak
):經過weak
定義弱引用
nil
nil
時,不會觸發屬性觀察器unowned
):經過unowned
定義無主引用
unsafe_unretained
)Fatal error: Attempted to read an unowned reference but object 0x10070a460 was already deallocated
class Person {
func eat() {
}
deinit {
print("Person銷燬")
}
}
unowned var p = Person()
p.eat()
複製代碼
這段代碼就會產生運行時錯誤
循環引用
weak、unowned
都能解決循環引用的問題,unowned
要比weak
少一些性能消耗
閉包的循環引用
class Person {
var fn:(() -> ())?
func run() {
print("run")
}
deinit {
print("Person銷燬")
}
}
func test() {
let p = Person()
p.fn = {
p.run()
}
}
test()
複製代碼
下面這段代碼就會形成循環引用,想要解決這個問題,可使用weak或者unowned
func test() {
let p = Person()
p.fn = {[weak p] in
p?.run()
}
}
func test() {
let p = Person()
p.fn = {[unowned p] in
p.run()
}
}
複製代碼
若是想在定義閉包屬性的同時引用self
,這個閉包必須是lazy
的,由於在實例初始化完畢後才能引用self
class Person {
lazy var fun:(() -> ()) = {
[weak self] in
self?.run()
}
func run() {
print("run")
}
deinit {
print("Person銷燬")
}
}
複製代碼
閉包fn
內部若是用到了實例成員,屬性,方法,編譯器會強制要求明確的寫出self
【注】:編譯器強制要求明確的寫出self
的時候有可能會致使循環引用,須要注意的
若是lazy
屬性是閉包調用的結果,那麼不用考慮循環引用問題,(由於閉包調用後,閉包的聲明週期就結束了)
class Person {
var age: Int = 0
lazy var getAge: Int = {
self.age
}()
deinit {
print("Person銷燬")
}
}
複製代碼
內存(RAM)中有兩個區域,棧區(stack)和堆區(heap)。在 Swift 中,值類型,存放在棧區;引用類型,存放在堆區。
值類型(Value Type)
值類型,即每一個實例保持一份數據拷貝
在 Swift 中,典型的有 struct,enum,以及 tuple 都是值類型。而平時使用的 Int, Double,Float,String,Array,Dictionary,Set 其實都是用結構體實現的,也是值類型。
Swift 中,值類型的賦值爲深拷貝(Deep Copy),值語義(Value Semantics)即新對象和源對象是獨立的,當改變新對象的屬性,源對象不會受到影響,反之同理。
struct CoordinateStruct {
var x: Double
var y: Double
}
var coordA = CoordinateStruct(x: 0, y: 0)
var coordB = coordA
coordA.x = 100.0
print("coordA.x -> \(coordA.x)")
print("coordB.x -> \(coordB.x)")
複製代碼
若是聲明一個值類型的常量,那麼就意味着該常量是不可變的(不管內部數據爲 var/let)
let coordC = CoordinateStruct(x: 0, y: 0)
複製代碼
在 Swift 3.0 中,可使用 withUnsafePointer(to:_:)
函數來打印值類型變量的內存地址,這樣就能看出兩個變量的內存地址並不相同。
withUnsafePointer(to: &coordA) { print("\($0)") }
withUnsafePointer(to: &coordB) { print("\($0)") }
0x0000000100007670
0x0000000100007680
複製代碼
在 Swift 中,雙等號(== & !=
)能夠用來比較變量存儲的內容是否一致,若是要讓咱們的 struct
類型支持該符號,則必須遵照Equatable
協議。
extension CoordinateStruct: Equatable {
static func ==(left: CoordinateStruct, right: CoordinateStruct) -> Bool {
return (left.x == right.x && left.y == right.y)
}
}
if coordA != coordB {
print("coordA != coordB")
}
複製代碼
引用類型(Reference Type)
引用類型,即全部實例共享一份數據拷貝
在 Swift 中,class 和閉包是引用類型。引用類型的賦值是淺拷貝(Shallow Copy)
,引用語義(Reference Semantics)即新對象和源對象的變量名不一樣,但其引用(指向的內存空間)是同樣的
,所以當使用新對象操做其內部數據時,源對象的內部數據也會受到影響。
class Dog {
var height = 0.0
var weight = 0.0
}
var dogA = Dog()
var dogB = dogA
dogA.height = 50.0
print("dogA.height -> \(dogA.height)")
print("dogB.height -> \(dogB.height)")
// dogA.height -> 50.0
// dogB.height -> 50.0
複製代碼
在 Swift 3.0 中,可使用如下方法來打印引用類型變量指向的內存地址。從中便可發現,兩個變量指向的是同一塊內存空間。
print(Unmanaged.passUnretained(dogA).toOpaque())
print(Unmanaged.passUnretained(dogB).toOpaque())
//0x0000000100772ff0
//0x0000000100772ff0
複製代碼
在 Swift 中,三等號(=== & !==
)能夠用來比較引用類型的引用(即指向的內存地址)是否一
致。也能夠在遵照 Equatable 協議後,使用雙等號(== & !=
)用來比較變量的內容是否一致。
簡單來講:沒有特別須要,儘量的仍是使用String
,有如下三個緣由
String
和 NSString
有着良好的互相轉換的特性,可是如今 Cocoa 全部的 API 都接受和返回 String
類型。咱們沒有必要也沒必要給本身憑空添加麻煩去把框架中返回的字符串作一遍轉換String是struct
,相比起 NSObject 的 NSString 類來講,更切合字符串的 "不變" 這一特性。經過配合常量賦值 (let) ,這種不變性在多線程編程時就很是重要了,它從原理上將程序員從內存訪問和操做順序的擔心中解放出來。另外,在不觸及 NSString 特有操做和動態特性的時候,使用 String 的方法,在性能上也會有所提高for...in
的枚舉GCD中Swift和OC都差很少,爲了方便使用,咱們能夠簡單封裝如下GCD
typealias Task = (_ cancel : Bool) -> Void
@discardableResult
func delay(_ time: TimeInterval, task: @escaping ()->()) -> Task? {
func dispatch_later(block: @escaping ()->()) {
let t = DispatchTime.now() + time
DispatchQueue.main.asyncAfter(deadline: t, execute: block)
}
var closure: (()->Void)? = task
var result: Task?
let delayedClosure: Task = {
cancel in
if let internalClosure = closure {
if (cancel == false) {
DispatchQueue.main.async(execute: internalClosure)
}
}
closure = nil
result = nil
}
result = delayedClosure
dispatch_later {
if let delayedClosure = result {
delayedClosure(false)
}
}
return result;
}
func cancel(_ task: Task?) {
task?(true)
}
複製代碼
向一個對象發出詢問,以肯定他是否是屬於某個類,這種操做就稱爲自省。
在OC中一個對象詢問它是否是屬於某個類。經常使用的方法有下面兩類
OC方法
[obj1 isKindOfClass:[ClassA class]];
[obj2 isMemberOfClass:[ClassB class]];
複製代碼
-isKindOfClass:
判斷 obj1 是不是 ClassA 或者其子類的實例對象;isMemberOfClass:
則對 obj2 作出判斷,當且僅當 obj2 的類型爲 ClassB 時返回爲真Swift方法
class ClassA: NSObject {}
class ClassB: ClassA {}
let obj1 = ClassA()
let obj2 = ClassB()
print(obj1.isKind(of: ClassA.self))
print(obj2.isMember(of: ClassA.self))
//true
//false
複製代碼
對於一個不肯定的類型,咱們如今可使用 is
來進行判斷。is
在功能上至關於原來的 isKindOfClass
,能夠檢查一個對象是否屬於某類型或其子類型。is
和原來的區別主要在於亮點,首先它不只能夠用於 class
類型上,也能夠對 Swift 的其餘像是 struct
或enum
類型進行判斷
class ClassA { }
class ClassB: ClassA { }
let obj: AnyObject = ClassB()
if (obj is ClassA) {
print("屬於 ClassA")
}
if (obj is ClassB) {
print("屬於 ClassB")
}
複製代碼
在Swift中KVO僅限於NSObject的子類,咱們還須要作額外的工做,那就是將想要觀測的對象標記爲 dynamic 和 @objc
在 Swift 4 以前的版本中,爲一個 NSObject 的子類實現 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 日期發生變化 \(newDate)")
}
}
}
}
let obj = Class()
初始化 MyClass,當前日期: 2020-04-08 07:26:22 +0000
MyClass 日期發生變化 2020-04-08 07:26:25 +0000
複製代碼
Swift 4 中 Apple 引入了新的 KeyPath
的表達方式,如今,對於類型 Foo
中的變量 bar: Bar
,對應的 KeyPath
能夠寫爲 \Foo.bar
class AnotherClass: NSObject {
var myObject: MyClass!
var observation: NSKeyValueObservation?
override init() {
super.init()
myObject = MyClass()
print("初始化 AnotherClass,當前日期: \(myObject.date)")
observation = myObject.observe(\MyClass.date, options: [.new]) { (_, change) in
if let newDate = change.newValue {
print("AnotherClass 日期發生變化 \(newDate)")
}
}
delay(1) { self.myObject.date = Date() }
}
}
複製代碼
使用Swift 4.0 KeyPath的好處有不少
Swift 中使用 KVO 仍是有有兩個顯而易見的問題
dynamic 和 @objc
進行修飾,有時候咱們極可能也沒法修改想要觀察的類的源碼,遇到這種狀況,一個可行的方案是繼承這個類,而且將須要觀察的屬性使用dynamic 和 @objc
重寫class MyClass: NSObject {
var date = Date()
}
class MyChildClass: MyClass {
@objc dynamic override var date: Date {
get { return super.date }
set { super.date = newValue }
}
}
複製代碼
C 系語言中在方法內部咱們是能夠任意添加成對的大括號 {} 來限定代碼的做用範圍的。這麼作通常來講有兩個好處,首先是超過做用域后里面的臨時變量就將失效,這不只可使方法內的命名更加容易,也使得那些不被須要的引用的回收提早進行了,能夠稍微提升一些代碼的效率;另外,在合適的位置插入括號也利於方法的梳理,對於那些不太方便提取爲一個單獨方法,可是又應該和當前方法內的其餘部分進行一些區分的代碼,使用大括號能夠將這樣的結構進行一個相對天然的劃分
OC代碼
- (void)loadView {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
{
UILabel *titleLabel = [[UILabel alloc]
initWithFrame:CGRectMake(150, 30, 200, 40)];
titleLabel.textColor = [UIColor redColor];
titleLabel.text = @"Title";
[view addSubview:titleLabel];
}
{
UILabel *textLabel = [[UILabel alloc]
initWithFrame:CGRectMake(150, 80, 200, 40)];
textLabel.textColor = [UIColor redColor];
textLabel.text = @"Text";
[view addSubview:textLabel];
}
self.view = view;
}
複製代碼
Swift方法
在 Swift 中,直接使用大括號的寫法是不支持的,由於這和閉包的定義產生了衝突。若是咱們想相似地使用局部 scope
來分隔代碼的話,一個不錯的選擇是定義一個接受 ()->()
做爲函數的全局方法,而後執行它
override func loadView() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
view.backgroundColor = .white
local {
let titleLabel = UILabel(frame: CGRect(x: 150, y: 30, width: 200, height: 40))
titleLabel.textColor = .red
titleLabel.text = "Title"
view.addSubview(titleLabel)
}
local {
let textLabel = UILabel(frame: CGRect(x: 150, y: 80, width: 200, height: 40))
textLabel.textColor = .red
textLabel.text = "Text"
view.addSubview(textLabel)
}
self.view = view
}
複製代碼
咱們還可使用匿名閉包來實現
override func loadView() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
view.backgroundColor = .white
let titleLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 150, y: 30, width: 200, height: 40))
label.textColor = .red
label.text = "Title"
return label
}()
view.addSubview(titleLabel)
let textLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 150, y: 80, width: 200, height: 40))
label.textColor = .red
label.text = "Text"
return label
}()
view.addSubview(textLabel)
self.view = view
}
複製代碼
咱們常常會遇到給分類添加成員變量的問題,對於這類問題,OC的寫法你們都是耳熟能詳了。譬如給UIView
添加一個viewId
的成員變量
#import <objc/runtime.h>
static const void *RunTimeViewID = @"RunTimeViewID";
@implementation UIView (JHExtension)
- (NSString *)viewID{
NSString *ID = objc_getAssociatedObject(self, &RunTimeViewID);
return ID;
}
- (void)setViewID:(NSString *)viewID{
objc_setAssociatedObject(self, &RunTimeViewID, viewID, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
複製代碼
Swift
在 Swift 中這樣的方法依舊有效,只不過在寫法上可能有些不一樣。兩個對應的運行時的 get 和 set Associated Object 的 API 是這樣的
func objc_getAssociatedObject(object: AnyObject!,
key: UnsafePointer<Void>
) -> AnyObject!
func objc_setAssociatedObject(object: AnyObject!,
key: UnsafePointer<Void>,
value: AnyObject!,
policy: objc_AssociationPolicy)
複製代碼
struct RunTimeViewKey {
static let RunTimeViewID = UnsafeRawPointer.init(bitPattern: "RunTimeViewID".hashValue)
}
extension UIView {
var ViewID: String? {
set {
objc_setAssociatedObject(self, RunTimeViewKey.RunTimeViewID!, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, RunTimeViewKey.RunTimeViewID!) as? String
}
}
}
複製代碼
無併發,不編碼。而只要一說到多線程或者併發的代碼,咱們可能就很難繞開對於鎖的討論。簡單來講,爲了在不一樣線程中安全地訪問同一個資源,咱們須要這些訪問順序進行
OC方法
- (void)myMethod:(id)anObj {
@synchronized(anObj) {
// 在括號內持有 anObj 鎖
}
}
複製代碼
Swift方法
在Swift中去掉了synchronized
方法,其實 @synchronized
在幕後作的事情是調用了objc_sync 中的 objc_sync_enter
和 objc_sync_exit
方法,而且加入了一些異常判斷。所以,在 Swift 中,若是咱們忽略掉那些異常的話,咱們想要 lock
一個變量的話
//定義一個閉包
func synchronized(_ lock: AnyObject, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
func myMethodLocked(anObj: AnyObject!) {
synchronized(anObj) {
// 在括號內持有 anObj 鎖
}
}
複製代碼
舉一個具體的使用例子,好比咱們想要爲某個類實現一個線程安全的 setter,能夠這樣進行重寫
class Obj {
var _str = "123"
var str: String {
get {
return _str
}
set {
synchronized(self) {
_str = newValue
}
}
// 下略
}
}
複製代碼
相比於 Objective-C,Swift 最大的改變就在於方法調用上的優化
。
OC方法調用
在 Objective-C 中,全部的對於 NSObject 的方法調用在編譯時會被轉爲 objc_msgSend
方法。這個方法運用 Objective-C 的運行時特性,使用派發的方式在運行時對方法進行查找
。由於 Objective-C 的類型並非編譯時肯定的,咱們在代碼中所寫的類型不過只是向編譯器的一種「建議」
,不論對於怎樣的方法,這種查找的代價基本都是一樣的
這個過程的等效的表述可能相似這樣 (注意這只是一種表述,與實際的代碼和工做方式無關)
methodToCall = findMethodInClass(class, selector);
// 這個查找通常須要遍歷類的方法表,須要花費必定時間
methodToCall(); // 調用
複製代碼
Swift方法調用
Swift 由於使用了更安全和嚴格的類型,若是咱們在編寫代碼中指明瞭某個實際的類型的話 (注意,須要的是實際具體的類型,而不是像 Any 這樣的抽象的協議),咱們就能夠向編譯器保證在運行時該對象必定屬於被聲明的類型
由於有了更多更明確的類型信息,編譯器就能夠在類型中處理多態時創建虛函數表 (vtable),這是一個帶有索引的保存了方法所在位置的數組。在方法調用時,與原來動態派發和查找方法不一樣,如今只須要經過索引就能夠直接拿到方法並進行調用了,這是實實在在的性能提高。這個過程大概至關於:
let methodToCall = class.vtable[methodIndex]
// 直接使用 methodIndex 獲取實現
methodToCall(); // 調用
複製代碼
更進一步,在肯定的狀況下,編譯器對 Swift 的優化甚至能夠作到將某些方法調用優化爲 inline 的形式。好比在某個方法被 final 標記時,因爲不存在被重寫的可能,vtable 中該方法的實現就徹底固定了。對於這樣的方法,編譯器在合適的狀況下能夠在生成代碼的階段就將方法內容提取到調用的地方,從而徹底避免調用