Swift 知識小集

如下內容均是筆者學習過程當中收集的知識點,順序比較跳躍,初衷是爲了方便查閱,順便加深記憶。內容會不斷更新,若是有什麼問題或者有好的 Swift 方面的語法糖或者知識點也能夠提出來,我會挑選斟酌後收錄,歡迎你們關注~git

環境:github

Swift 4.0
Xcode 9.1

Associated Object

Objective-C 的 runtime 裏的 Associated Object 容許咱們在使用 Category 擴展示有的類的功能的時候,直接添加實例變量。在 Swift 中 extension 不能添加存儲屬性,咱們能夠利用 Associated Object 來實現,好比下面的 title 「實際上」是一個存儲屬性:shell

// MyClass.swift
class MyClass {}

// MyClassExtension.swift
private var key: Void?

extension MyClass {
    var title: String? {
        get {
            return swift_getAssociatedObject(self, &key) as? String
        }

        set {
            swift_setAssociatedObject(self,
                &key, newValue,
                .swift_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}
// 測試
func printTitle(_ input: MyClass) {
    if let title = input.title {
        print("Title: \(title)")
    } else {
        print("沒有設置")
    }
}

let a = MyClass()
printTitle(a)
a.title = "Swifter.tips"
printTitle(a)

// 輸出:
// 沒有設置
// Title: Swifter.tips」

Delegate 聲明爲 weak

Swift 中 Delegate 須要被聲明成 weak,來避免訪問到已被回收的內存而致使崩潰,若是咱們像下面這樣,是編譯不過的:express

protocol MyClassDelegate {
    func method()
}

class MyClass {
    weak var delegate: MyClassDelegate?
}

class ViewController: UIViewController, MyClassDelegate {
    // ...
    var someInstance: MyClass!

    override func viewDidLoad() {
        super.viewDidLoad()

        someInstance = MyClass()
        someInstance.delegate = self
    }

    func method() {
        print("Do something")
    }

    //...
}

// 編譯失敗
// 'weak' may only be applied to class and class-bound protocol types, not 'MyClassDelegate'

這是由於 Swift 的 protocol 是能夠被除了 class 之外的其餘類型遵照的,而對於像 struct 或是 enum 這樣的類型,自己就不經過引用計數來管理內存,因此也不可能用 weak 這樣的 ARC 的概念來進行修飾。json

想要在 Swift 中使用 weak delegate,咱們就須要將 protocol 限制在 class 內:swift

  • 一種作法是將 protocol 聲明爲 Objective-C 的,這能夠經過在 protocol 前面加上 @objc 關鍵字來達到,Objective-C 的 protocol 都只有類能實現,所以使用 weak 來修飾就合理了:
@objc protocol MyClassDelegate {
    func method()
}
  • 另外一種可能更好的辦法是在 protocol 聲明的名字後面加上 class,這能夠爲編譯器顯式地指明這個 protocol 只能由 class 來實現,避免了過多的沒必要要的 Objective-C 兼容:
protocol MyClassDelegate: class {
    func method()
}

可選協議和協議擴展

Objective-C 中的 protocol 裏存在 @optional 關鍵字,被這個關鍵字修飾的方法並不是必需要被實現,原生的 Swift protocol 裏沒有可選項,全部定義的方法都是必須實現的,若是不是實現是沒法編譯的:數組

class ViewController: UIViewController,MyProtocol { }

// 編譯失敗
// Type 'ViewController' does not conform to protocol 'MyProtocol'

若是咱們想要像 Objective-C 裏那樣定義可選的協議方法,就須要將協議自己和可選方法都定義爲 Objective-C 的,也即在 protocol 定義以前加上 @objc,方法以前加上 @objc optional安全

@objc protocol MyProtocol {
    @objc optional func myMethod()
}

另外,對於全部的聲明,它們的前綴修飾是徹底分開的,也就是說你不能像是在 Objective-C 裏那樣用一個 @optional 指定接下來的若干個方法都是可選的了,必須對每個可選方法添加前綴,對於沒有前綴的方法來講,它們是默認必須實現的:性能優化

@objc protocol MyProtocol {
    @objc optional func optionalMethod()        // 可選
    func necessaryMethod()                      // 必須
    @objc optional func anotherOptionalMethod() // 可選
}

一個不可避免的限制是,使用 @objc 修飾的 protocol 就只能被 class 實現了,也就是說,對於 structenum 類型,咱們是沒法令它們所實現的協議中含有可選方法或者屬性的。另外,實現它的 class 中的方法還必須也被標註爲 @objc,或者整個類就是繼承自 NSObject。對於這種問題,在 Swift 2.0 中,咱們有了另外一種選擇,那就是使用 protocol extension。咱們能夠在聲明一個 protocol 以後再用 extension 的方式給出部分方法默認的實現,這樣這些方法在實際的類中就是可選實現的了:閉包

protocol MyProtocol {
    func optionalMethod()        // 可選
    func necessaryMethod()       // 必須
    func anotherOptionalMethod() // 可選
}

extension MyProtocol {
    
    //默認的可選實現
    func optionalMethod() {
        print("optionalMethod")
    }
    
    //默認的可選實現
    func anotherOptionalMethod() {
        print("anotherOptionalMethod")
    }   
}
class ViewController: UIViewController,MyProtocol {
    
    // 必須的實現
    func necessaryMethod() {
        print("necessaryMethod")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.optionalMethod();
        self.necessaryMethod();
        self.anotherOptionalMethod();
    }
}

// 輸出:
// optionalMethod
// necessaryMethod
// necessaryMethod

單例

Swift 中的單例很是簡單,Swift 1.2 以及以後:

class Singleton  {
    static let sharedInstance = Singleton()
    private init() {}
}

這種寫法不可是線程安全的,也是懶加載的,let 定義的屬性自己就是線程安全的,同時 static 定義的是一個 class constant,擁有全局做用域和懶加載特性。

另外,這個類型中加入了一個私有的初始化方法,來覆蓋默認的公開初始化方法,這讓項目中的其餘地方不可以經過 init 來生成本身的 Singleton 實例,也保證了類型單例的惟一性。若是你須要的是相似 default 的形式的單例 (也就是說這個類的使用者能夠建立本身的實例) 的話,能夠去掉這個私有的 init 方法。

輸出格式化

在 Objective-C 中的 %@ 這樣的格式在指定的位置設定佔位符,而後經過參數的方式將實際要輸出的內容補充完整。例如 Objective-C 中經常使用的向控制檯輸出的 NSLog 方法就使用了這種格式化方法:

float a = 1.234567;
NSString *b = @"Helllo";
NSLog(@"float:%.2f  str:%p",a,b);

// 輸出:
// float:1.23  str:0x1024a1078

對應 Swift 中咱們能夠這樣:

let a = 1.234567
let b = "Helllo"
let c = String(format:"float:%.2f str:%p",a,b)
print(c)

// 輸出:
// float:1.23 str:0x604000249e10

Selector

@selector 是 Objective-C 時代的一個關鍵字,它能夠將一個方法轉換並賦值給一個 SEL 類型,它的表現很相似一個動態的函數指針。在 Swift 中沒有 @selector 了,取而代之,從 Swift 2.2 開始咱們使用 #selector 來從暴露給 Objective-C 的代碼中獲取一個 selector,而且由於 selector 是 Objective-C runtime 的概念,在 Swift 4 中,默認狀況下全部的 Swift 方法在 Objective-C 中都是不可見的,因此你須要在這類方法前面加上 @objc 關鍵字,將這個方法暴露給 Objective-C,才能進行使用:

let btn = UIButton.init(type: .system)
btn.backgroundColor = UIColor.red
btn.frame = CGRect(x: 100, y: 100, width: 150, height: 40)
btn.setTitle("Button", for: .normal)
//無參數
btn.addTarget(self, action: #selector(btnClick), for: .touchUpInside)
view.addSubview(btn)

@objc func btnClick()  {
    print("button click !")
}
...
//有參數
btn.addTarget(self, action: #selector(btnClick(_ :)), for: .touchUpInside)
...

@objc func btnClick(_ button: UIButton)  {
    print("button click !")
}

將 protocol 的方法聲明爲 mutating

Swift 的 protocol 不只能夠被 class 類型實現,也適用於 structenum,由於這個緣由,咱們在寫給別人用的協議時須要多考慮是否使用 mutating 來修飾方法。Swift 的 mutating 關鍵字修飾方法是爲了能在該方法中修改 struct 或是 enum 的變量,因此若是你沒在協議方法裏寫 mutating 的話,別人若是用 struct 或者 enum 來實現這個協議的話,就不能在方法裏改變本身的變量了,好比下面的代碼是編譯不過的:

protocol Vehicle {
    
    func changeColor()
    
}

struct MyCar: Vehicle {
    
    var color = "blue"
    
    func changeColor() {
        color = "red"
    }
}

// 編譯失敗
// Cannot assign to property: 'self' is immutable

咱們應該加上 mutating 關鍵字:

protocol Vehicle {
    
    mutating func changeColor()
    
}

struct MyCar: Vehicle {
    
    var color = "blue"
    
    mutating func changeColor() {
        color = "red"
    }
}

override func viewDidLoad() {
    super.viewDidLoad()

    var car = MyCar()
    print(car.color)
    car.changeColor()
    print(car.color)    
}

// 輸出:
// blue
// 輸出:
// red

數組遍歷 enumerate

使用 NSArray 時一個很常碰見的的需求是在枚舉數組內元素的同時也想使用對應的下標索引,在 Objective-C 中最方便的方式是使用 NSArray 的 enumerateObjectsUsingBlock: ,在 Swift 中存在一個效率,安全性和可讀性都很好的替代,那就是快速枚舉某個數組的EnumerateGenerator,它的元素是同時包含了元素下標索引以及元素自己的多元組:

let arr = ["a","b","c","d","e"]
for (idx, str) in arr.enumerated() {
    print("idx: \(idx) str: \(str)")
}

// 輸出:
idx: 0 str: a
idx: 1 str: b
idx: 2 str: c
idx: 3 str: d
idx: 4 str: e

輸入輸出參數 inout

函數參數默認是常量,若是試圖在函數體中更改參數值將會致使編譯錯誤,好比下面的例子中嘗試着交換值:

func swapTwoInts(_ a: Int, _ b: Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

// 編譯失敗
// Cannot assign to value: 'a' is a 'let' constant
// Cannot assign to value: 'b' is a 'let' constant

若是想要一個函數能夠修改參數的值,而且想要在這些修改在函數調用結束後仍然存在,那麼就應該把這個參數定義爲輸入輸出參數(In-Out Parameters):

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

Default 參數

Swift 的方法是支持默認參數的,也就是說在聲明方法時,能夠給某個參數指定一個默認使用的值。在調用該方法時要是傳入了這個參數,則使用傳入的值,若是缺乏這個輸入參數,那麼直接使用設定的默認值進行調用。和其餘不少語言的默認參數相比較,Swift 中的默認參數限制更少,並無所謂 "默認參數以後不能再出現無默認值的參數"這樣的規則,舉個例子,下面兩種方法的聲明在 Swift 裏都是合法可用的:

func sayHello1(str1: String = "Hello", str2: String, str3: String) {
    print(str1 + str2 + str3)
}

func sayHello2(str1: String, str2: String, str3: String = "World") {
    print(str1 + str2 + str3)
}
sayHello1(str2: " ", str3: "World")
sayHello2(str1: "Hello", str2: " ")

//輸出都是 Hello World

延遲加載 lazy

延時加載或者說延時初始化是很經常使用的優化方法,在構建和生成新的對象的時候,內存分配會在運行時耗費很多時間,若是有一些對象的屬性和內容很是複雜的話,這個時間更是不可忽略。另外,有些狀況下咱們並不會當即用到一個對象的全部屬性,而默認狀況下初始化時,那些在特定環境下不被使用的存儲屬性,也同樣要被初始化和賦值,也是一種浪費。在 Objective-C 中,一個延遲加載通常是這樣的:

// ClassA.h
@property (nonatomic, copy) NSString *testString;

// ClassA.m
- (NSString *)testString {
     if (!_testString) {
         _testString = @"Hello";
        NSLog(@"只在首次訪問輸出");
     }
     return _testString;
}

對應在 Swift 中,使用 lazy 做爲屬性修飾符時,只能聲明屬性是變量,且咱們須要顯式地指定屬性類型,不然會編譯錯誤:

class ClassA {
    lazy let str: String = {
        let str = "Hello"
        print("只在首次訪問輸出")
        return str
    }()
}

// 編譯失敗
// 'lazy' cannot be used on a let

class ClassA {
    lazy var str = {
        let str = "Hello"
        print("只在首次訪問輸出")
        return str
    }()
}

// 編譯失敗
// Unable to infer complex closure return type

咱們應該聲明爲 var 並指定好類型:

class ClassA {
    lazy var str: String = {
        let str = "Hello"
        print("只在首次訪問輸出")
        return str
    }()
}

override func viewDidLoad() {
    super.viewDidLoad()

    let ca = ClassA()
    print(ca.str)
    print(ca.str)
    print(ca.str)   
}

// 輸出:
// 只在首次訪問輸出
// Hello
// Hello
// Hello

若是不須要作什麼額外工做的話,也能夠對這個 lazy 的屬性直接寫賦值語句:

lazy var str: String = "Hello"

咱們還能夠利用 lazy 配合像 map 或是 filter 這類接受閉包並進行運行的方法一塊兒,讓整個行爲變成延時進行的。在某些狀況下這麼作也對性能會有不小的幫助。例如,直接使用 map 時:

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("操做完畢")

// 輸出:
// 正在處理 1
// 正在處理 2
// 正在處理 3
// 準備訪問結果
// 操做後結果爲 2
// 操做後結果爲 4
// 操做後結果爲 6
// 操做完畢

而若是咱們先進行一次 lazy 操做的話,咱們就能獲得延時運行版本的容器:

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("操做完畢")

// 準備訪問結果
// 正在處理 1
// 操做後結果爲 2
// 正在處理 2
// 操做後結果爲 4
// 正在處理 3
// 操做後結果爲 6
// 操做完畢

對於那些不須要徹底運行,可能提早退出的狀況,使用 lazy 來進行性能優化效果會很是有效。

編譯標記

在 Objective-C 中,咱們常常在代碼中插入 #param 符號來標記代碼的區間,這樣在 Xcode 的導航欄中咱們就能夠看到組織分塊後的方法列表。在 Swift 中咱們能夠用 MARK: 來代替:

在 Objective-C 中還有一個很經常使用的編譯標記,那就是 #warning,一個 #warning 標記能夠在 Xcode 的代碼編輯器中顯示爲明顯的黃色警告條,很是適合用來提示代碼的維護者和使用者須要對某些東西加以關注。在 Swift 中咱們能夠用 FIXME:TODO: 配合 shell 來代替:

腳本:

TAGS="TODO:|FIXME:"
echo "searching ${SRCROOT} for ${TAGS}"
find "${SRCROOT}" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"

效果:


換行符

在 Swift 3 中,須要換行時是須要 \n

let str = "xxxx\nxxx"

// 輸出:
// xxxx
// xxx

在 swift 4 中,咱們可使用 """

let jsonStr = """
        {
            "id": 123455,
            "nickname": "xxxx",
            "isMale": true,
            "birthday": "2000年3月24日",
            "personalURL": "https://xxxxxx.github.io"
        }
        """
          
// 輸出:
{
    "id": 123455,
    "nickname": "xxxx",
    "isMale": true,
    "birthday": "2000年3月24日",
    "personalURL": "https://xxxxxx.github.io"
}

字符串切割 split

咱們須要切割某個字符串時能夠用 split 方法,須要注意的是,返回的結果是個數組

let str = "Hello,world !"
print(str.split(separator: ","))

// 輸出:
// ["Hello", "world !"]

KVC

class MyClass {
    var name = "ifelseboyxx"
}

Swift 4 中 Apple 引入了新的 KeyPath 的表達方式,如今,對於類型 MyClass 中的變量 name,對應的 KeyPath 能夠寫爲 \MyClass.name,利用 KVC 修改 name 值的話,咱們能夠這麼操做:

let object = MyClass()
print("name: \(object.name)")
// set
object[keyPath: \MyClass.name] = "ifelseboy"
// get
print("name: \(object[keyPath: \MyClass.name])")

// 輸出:
// name: ifelseboyxx
// name: ifelseboy

另外 Swift 4 中 struct 一樣支持 KVC :

struct MyStruct {
    var age: Int
}

var obj = MyStruct(age: 18)
print("我今年 \(obj.age) 歲了")
obj[keyPath: \MyStruct.age] = 8
print("我今年 \(obj[keyPath: \MyStruct.age]) 歲了")

// 輸出:
// 我今年 18 歲了
// 我今年 8 歲了

Swift 中值類型和引用類型注意點

KVC 一節中代碼裏有個注意點:

var obj = MyStruct(age: 18)
//替換爲
let obj = MyStruct(age: 18)

是編譯不過的,會報錯:

Cannot assign to immutable expression of type 'Int'

筆者初次也犯了這樣的錯誤,想固然的認爲 MyClasslet 聲明的是沒有問題的,struct 也同樣:

let object = MyClass()

其實緣由很簡單,swift 中 Class 是引用類型的,而 struct 是值類型的:值類型在被賦給一個變量,或被傳給函數時,實際是作了一次拷貝。引用類型在被賦給一個變量,或被傳給函數時,傳遞的是引用。

KVO

很遺憾,依然只有 NSObject 才能支持 KVO,另外因爲 Swift 爲了效率,默認禁用了動態派發,所以想用 Swift 來實現 KVO,咱們還須要作額外的工做,那就是將想要觀測的對象標記爲 dynamic@objc,下面的 ? 是 ViewController 監聽 MyClassdate 屬性:

class MyClass: NSObject {
    @objc dynamic var date = Date()
}

class ViewController: UIViewController {
    
    var myObject: MyClass!
    var observation: NSKeyValueObservation?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        myObject = MyClass()
        print("當前日期:\(myObject.date)")
    
        observation = myObject.observe(\MyClass.date, options: [.old,.new], changeHandler: { (_, change) in
            if let newDate = change.newValue , let oldDate = change.oldValue {
                print("日期發生變化 old:\(oldDate) new:\(newDate) ")
            }
        })
        
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
            self.myObject.date = Date()
        }
    }
}

// 輸出:
// 當前日期:2017-12-07 06:31:26 +0000
// 日期發生變化 old:2017-12-07 06:31:26 +0000 new:2017-12-07 06:31:27 +0000

在 Objective-C 中咱們幾乎能夠沒有限制地對全部知足 KVC 的屬性進行監聽,而如今咱們須要屬性有 dynamic@objc 進行修飾。大多數狀況下,咱們想要觀察的類包含這兩個修飾 (除非這個類的開發者有意爲之,不然通常也不會有人願意多花功夫在屬性前加上它們,由於這畢竟要損失一部分性能),而且有時候咱們極可能也沒法修改想要觀察的類的源碼。遇到這樣的狀況的話,一個可能可行的方案是繼承這個類而且將須要觀察的屬性使用 dynamic@objc 進行重寫。好比剛纔咱們的 MyClass 中若是 date 沒有相應標註的話,咱們可能就須要一個新的 MyChildClass 了:

class MyClass: NSObject {
    var date = Date()
}

class MyChildClass: MyClass {
    @objc dynamic override var date: Date {
        get { return super.date }
        set { super.date = newValue }
    }
}

class ViewController: UIViewController {
    
    var myObject: MyChildClass!
    var observation: NSKeyValueObservation?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        myObject = MyChildClass()
        print("當前日期:\(myObject.date)")
    
        observation = myObject.observe(\MyChildClass.date, options: [.old,.new], changeHandler: { (_, change) in
            if let newDate = change.newValue , let oldDate = change.oldValue {
                print("日期發生變化 old:\(oldDate) new:\(newDate) ")
            }
        })
        
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
            self.myObject.date = Date()
        }
    }
}

// 輸出:
// 當前日期:2017-12-07 06:36:50 +0000
// 日期發生變化 old:2017-12-07 06:36:50 +0000 new:2017-12-07 06:36:51 +0000

Swift UIButton 狀態的疊加

在 Objective-C 中,若是咱們想疊加按鈕的某個狀態,能夠這麼寫:

UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTitle:@"Test" forState:UIControlStateNormal | UIControlStateSelected];

對應的 Swift 咱們能夠這麼寫:

let btn = UIButton.init(type: .custom)
btn.setTitle("hehe", for: [.normal ,.selected])

把須要疊加的狀態用個數組裝起來就好了。

Swift 中的 「@synchronized」

在 Objective-C 中,咱們能夠用 @synchronized 這個關鍵字能夠用來修飾一個變量,併爲其自動加上和解除互斥鎖。這樣,能夠保證變量在做用範圍內不會被其餘線程改變:

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

雖然這個方法很簡單好用,可是很不幸的是在 Swift 中它已經 (或者是暫時) 不存在了。其實 @synchronized 在幕後作的事情是調用了 objc_sync 中的 objc_sync_enterobjc_sync_exit 方法,而且加入了一些異常判斷。所以,在 Swift 中,若是咱們忽略掉那些異常的話,咱們想要 lock 一個變量的話,能夠這樣寫:

func myMethod(anObj: AnyObject!) {
    objc_sync_enter(anObj)

    // 在 enter 和 exit 之間持有 anObj 鎖

    objc_sync_exit(anObj)
}

更進一步,若是咱們喜歡之前的那種形式,甚至能夠寫一個全局的方法,並接受一個閉包,來將 objc_sync_enterobjc_sync_exit 封裝起來:

func synchronized(_ lock: AnyObject, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

// 使用:
synchronized(self) {
            
}

這樣使用起來就和 Objective-C 中 @synchronized 很像了。

再舉個 ? ,若是咱們想爲某個類實現一個線程安全的 setter,能夠這樣:

class Obj {
    var _str = "123"
    var str: String {
        get {
            return _str
        }
        set {
            synchronized(self) {
                _str = newValue
            }
        }
    }
}

自定義日誌輸出

在 Objective-C 中,咱們一般會自定義日誌輸出來完善信息以及避免 release 下的輸出,好比下面這種,能夠額外提供行數、方法名等信息:

#ifdef DEBUG
#define XXLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define XXLog(...)
#endif
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    XXLog(@"ifelseboyxx");

    return YES;
}

// 輸出:
// 2017-12-08 13:32:02.211306+0800 Demo[17902:88775537] -[AppDelegate application:didFinishLaunchingWithOptions:] [Line 28] ifelseboyxx

在 Swift 中,咱們能夠這樣自定義:

func xxprint<T>(_ message: T, filePath: String = #file, line: Int = #line, function: String = #function) {
    #if DEBUG
        let fileName = (filePath as NSString).lastPathComponent.replacingOccurrences(of: ".Swift", with: "")
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale.current
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        print("[" + dateFormatter.string(from: Date()) + " " + fileName + " " + function + " \(line)" + "]:" + "\(message)")
    #endif
}
class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        xxprint("ifelseboyxx")
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
            xxprint("ifelseboyxx")
        }
    }
}

// 輸出:
// [2017-12-08 13:49:38 ViewController.swift viewDidLoad() 27]:ifelseboyxx
// [2017-12-08 13:49:39 ViewController.swift viewDidLoad() 29]:ifelseboyxx

Swift 中的 「readonly」

在 Objective-C 中,咱們一般把屬性聲明爲 readonly 來提醒別人:「不要修改!!」,一般這麼寫:

@interface Person : NSObject

@property (nonatomic, readonly, copy) NSString *name;

@end

若是外部嘗試修改的話,會編譯錯誤:

- (void)viewDidLoad {
    [super viewDidLoad];
   
    Person *p = [Person new];
    p.name = @"ifelseboyxx";
    
}

// 編譯錯誤:
// Assignment to readonly property

有些狀況下,咱們但願內部能夠點語法訪問 name 屬性,也就是 self.name,可是由於是 readonly 的,會編譯錯誤:

@implementation Person

- (instancetype)init {
    self = [super init];
    if (self) {
        self.name = @"ifelseboyxx";
    }
    return self;
}

@end

// 編譯錯誤:
// Assignment to readonly property

這時候咱們就會在內部的 extension 從新聲明一個 readwrite的一樣的屬性,也就是「外部只讀,內部可寫」

@interface Person ()

@property (nonatomic, readwrite, copy) NSString *name;

@end

在 Swift 中,咱們可能有一樣的場景。這裏就不得不提到 privatefileprivate 關鍵字了。
private表示聲明爲私有的實體只能在其聲明的範圍內被訪問。好比我在 MyClass 中聲明瞭一個私有的 name 屬性,外部訪問的話會編譯錯誤:

class MyClass {
    private var name: String = "Test"
}

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let only = MyClass()
        print(only.name) 
        only.name = "ifelseboyxxv587"       
    }
}

// 編譯異常:
// 'name' is inaccessible due to 'private' protection level

fileprivate,看命名咱們大概能猜到,就是將對實體的訪問權限於它聲明的源文件。通俗點講,好比我上面的代碼都是在 ViewController.swift 這個文件裏的,我把 private 修改成 fileprivate,就不會編譯錯誤了:

class MyClass {
    fileprivate var name: String = "Test"
}

那麼若是非 ViewController.swift 文件,也想訪問 MyClassname 屬性該怎麼辦呢?咱們能夠把 name 屬性聲明爲 fileprivate(set),就要就達到相似 Objective-C 中的 readonly 效果了 :

// ViewController.swift 文件

class MyClass {
    fileprivate(set) var name: String = "Test"
}

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let only = MyClass()
        print(only.name)
        only.name = "ifelseboyxxv587"
        print(only.name)
    }
}

// 編譯正常,ViewController.swift 文件內可讀可寫
// 輸出:
// Test
// ifelseboyxxv587
// AppDelegate.swift 文件

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

    let only = MyClass()
    print(only.name) //只能讀
    only.name = "ifelseboyxxv587" //這裏報錯,不能寫
        
    return true
}

// 編譯異常:
// Cannot assign to property: 'name' setter is inaccessible

做用域:do 語句塊

在 Objective-C 中,咱們能夠利用 {} 來開闢新的做用域,來避免對象名稱重複的問題:

NSString *ha = @"測試一";
    
{
    NSString *ha = @"測試二";
    NSLog(@"%@",ha);
}
    
NSLog(@"%@",ha);

// 輸出:
// 2017-12-11 16:55:20.303132+0800 Demo[48418:93027416] 測試二
// 2017-12-11 16:55:20.303316+0800 Demo[48418:93027416] 測試一

在 Swift 中,取代 {} 的是 do {}

let ha = "測試一"
        
do {
    let ha = "測試二"
    print(ha)
}
        
print(ha)

// 輸出:
// 測試二
// 測試一

倒序 reversed()

在 Objective-C 中,咱們若是想倒序數組通常這樣:

NSArray *array = @[@"1",@"2",@"3"];
    
NSArray *reversedArray = [[array reverseObjectEnumerator] allObjects];

// 輸出:
// 2017-12-11 17:39:57.127466+0800 Demo[49004:93210504] (
    3,
    2,
    1
)

在 Swift 中,相對簡單點:

let arr:[String] = ["1","2","3"]
let reversedArr:[String] = arr.reversed()

// 輸出:
// ["3", "2", "1"]

標籤語句:指定跳出某個條件語句

在 Objective-C 中,若是遇到多層嵌套的條件語句,咱們若是想要指定跳出某個條件語句是很不方便的。好比有兩個循環,一旦找到它們相同的,就馬上中止循環,咱們可能會這麼作:

NSArray *arr1 = @[@"1",@"2",@"3",@"4",@"5"];
NSArray *arr2 = @[@"4",@"6",@"8",@"9",@"2"];

BOOL finded = NO;
for (NSString *x in arr1) {
    if (finded) {
        break;
    }
    NSLog(@"x:%@",x);
    for (NSString *y in arr2) {
        NSLog(@"y:%@",y);
        if ([x isEqualToString:y]) {
            NSLog(@"找到相等的了:%@",x);
            finded = YES;
            break;
        }
    }
}

咱們須要藉助 finded 這個 BOOL,來方便咱們跳出循環。在 Swift 中,咱們就能夠利用標籤語句,來指定具體跳出哪一個循環,語法是這樣的:

標籤名: 條件語句 {

}

上面的 ? 咱們能夠這麼寫:

let arr1 = ["1","2","3","4","5"]
let arr2 = ["4","6","8","9","2"]
        
label: for x in arr1 {
    print("x: \(x)")
    for y in arr2 {
        print("y: \(y)")
        if x == y {
            print("找到相等的了:\(y)")
            break label
        }
    }
}

上面代碼,咱們把第一層循環定義了標籤:label。在第二層循環中,一旦條件成立,馬上跳出第一層循環 label。這個特性,能夠說十分方便了!

優雅的定義通知名稱

在 Objective-C 中,咱們自定義通知時,對於名稱的定義通常都有規範:

// xxx.h 
UIKIT_EXTERN NSString * const XXXXNotification;

// xxx.m 
NSString * const XXXXNotification = @"XXXXNotification";

在 Swift 中,咱們能夠參考 Alamofire 的方式,建立個專門存放通知名的文件,擴展 Notification.Name 並以結構體 struct 方式聲明:

// XXNotification.swift 文件

import Foundation

extension Notification.Name {
    public struct Task {
        public static let 通知名 = Notification.Name(rawValue: "org.target名稱.notification.name.task.通知名")
    }
}

而後咱們就能夠愉快的使用了:

// add
NotificationCenter.default.addObserver(self, selector: #selector(myNotification(_ :)), name: NSNotification.Name.Task.通知名, object: self)

// post
NotificationCenter.default.post(name: NSNotification.Name.Task.通知名, object: self)

// remove
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.Task.通知名, object: self)
相關文章
相關標籤/搜索