十幾個小知識點,讓你理解OC到Swift的都有哪些轉變

一、Selector

@selectorObjective-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 就能夠了。

五、@UIApplicationMain

在 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(){}
}
複製代碼

七、內存管理,weak 和 unowned

跟OC同樣,Swift也是採用基於引用計算的ARC內存管理方案(針對堆空間)

Swift中ARC有3種引用

  • 一、強引用:默認狀況下,引用都是強引用
  • 二、弱引用(weak):經過weak定義弱引用
    • 必須是可選類型的var,由於實例銷燬後,ARC會自動將弱引用設置爲nil
    • ARC自動給弱引用設置nil時,不會觸發屬性觀察器
  • 三、無主引用(unowned):經過unowned定義無主引用
    • 不會產生強引用,實例銷燬後仍然存儲着實例的內存地址(相似於OC中的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 少一些性能消耗

  • 生命週期中可能被置爲nil使用weak
  • 初始化賦值之後不會被置爲nil使用unowned

閉包的循環引用

  • 閉包表達式默認會對用到的外層對象產生額外的強引用(對外層進行了retain操做)
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 仍是 NSString

簡單來講:沒有特別須要,儘量的仍是使用String,有如下三個緣由

  • 一、雖然 StringNSString 有着良好的互相轉換的特性,可是如今 Cocoa 全部的 API 都接受和返回 String類型。咱們沒有必要也沒必要給本身憑空添加麻煩去把框架中返回的字符串作一遍轉換
  • 二、由於在 Swift 中 String是struct,相比起 NSObject 的 NSString 類來講,更切合字符串的 "不變" 這一特性。經過配合常量賦值 (let) ,這種不變性在多線程編程時就很是重要了,它從原理上將程序員從內存訪問和操做順序的擔心中解放出來。另外,在不觸及 NSString 特有操做和動態特性的時候,使用 String 的方法,在性能上也會有所提高
  • 三、由於 String 實現了 Collection 這樣的協議,所以有些 Swift 的語法特性只有 String 才能使用,而 NSString 是沒有的。一個典型就是 for...in 的枚舉

十、GCD

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 的其餘像是 structenum類型進行判斷

class ClassA { }
class ClassB: ClassA { }

let obj: AnyObject = ClassB()

if (obj is ClassA) {
    print("屬於 ClassA")
}

if (obj is ClassB) {
    print("屬於 ClassB")
}
複製代碼

十二、KVO

在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的好處有不少

  • 一、設定觀察和處理觀察的代碼被放在了一塊兒,讓代碼維護難度下降不少;
  • 二、其次在處理時咱們獲得的是類型安全的結果,而不是從字典中取值;
  • 三、咱們再也不須要使用 context 來區分是哪個觀察量發生了變化,並且使用 observation 來持有觀察者可讓咱們從麻煩的內存管理中解放出來,觀察者的生命週期將隨着 AnotherClass 的釋放而結束

Swift 中使用 KVO 仍是有有兩個顯而易見的問題

  • 一、在 Objective-C 中咱們幾乎能夠沒有限制地對全部知足 KVC 的屬性進行監聽,而如今咱們須要屬性有 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 }
    }
}
複製代碼
  • 二、另外一個大問題是對於那些非 NSObject 的 Swift 類型怎麼辦。咱們能夠經過屬性觀察器來處理

1三、局部scope

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
    }
複製代碼

1四、關聯對象

咱們常常會遇到給分類添加成員變量的問題,對於這類問題,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
        }
    }
    
}
複製代碼

1五、Lock

無併發,不編碼。而只要一說到多線程或者併發的代碼,咱們可能就很難繞開對於鎖的討論。簡單來講,爲了在不一樣線程中安全地訪問同一個資源,咱們須要這些訪問順序進行

OC方法

- (void)myMethod:(id)anObj {
    @synchronized(anObj) {
        // 在括號內持有 anObj 鎖
    }
}
複製代碼

Swift方法

在Swift中去掉了synchronized方法,其實 @synchronized 在幕後作的事情是調用了objc_sync 中的 objc_sync_enterobjc_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
            }
        }
    // 下略
    }
} 
複製代碼

1六、性能方面

相比於 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 中該方法的實現就徹底固定了。對於這樣的方法,編譯器在合適的狀況下能夠在生成代碼的階段就將方法內容提取到調用的地方,從而徹底避免調用

總結

  • 一、文章是讀王巍 (onevcat). 「Swifter - Swift 必備 Tips (第四版)總結所得
  • 二、文章中代碼的demo地址
相關文章
相關標籤/搜索