Swift5.0 - day10- 從 OC 到 Swift

一、OC 到 Swift 基礎(chǔ)差異

  • 1.1、提示符:MARK、TODO、FIXME、 #warning("")

    • <1>、// MARK: 類似于OC中的 #pragma mark

      // MARK: 測試
      func test() -> () {
      }
      
      // MARK:` 類似于OC中的 `#pragma mark
    • <2>、// MARK: - 類似于OC中的 #pragma mark -

      // MARK: - 上面的方法
      // MARK: 方法一
      func test() -> () {
      }
      
      // MARK: - 下面的方法
      // MARK: 方法一
      func test3() -> () {
      }
      
      // MARK: -` 類似于OC中的 `#pragma mark -
    • <3>、// TODO: 用于標(biāo)記未完成的任務(wù)

      func test2() -> () {
        // TODO: 未完成的任務(wù)
      }
      
      // TODO: 用于標(biāo)記未完成的任務(wù)
    • <4>、// FIXME: 用于標(biāo)記待修復(fù)的問題

      func test3() -> () {
          // FIXME: 待修復(fù)
      }
      
      // FIXME: 用于標(biāo)記待修復(fù)的問題
    • <5>、#warning("") 警告信息

      `#warning("")` 警告信息
  • 1.2、條件編譯

    // 操作系統(tǒng):macOS\iOS\tvOS\watchOS\Linux\Android\Windows\FreeBSD 
    #if os(macOS) || os(iOS)
    // CPU架構(gòu):i386\x86_64\arm\arm64
    #elseif arch(x86_64) || arch(arm64)
    // swift版本
    #elseif swift(<5) && swift(>=3)
    // 模擬器
    #elseif targetEnvironment(simulator) 
    // 可以導(dǎo)入某模塊
    #elseif canImport(Foundation)
    #else
    #endif
    
    • 自定義條件編譯
      • 系統(tǒng)默認(rèn)的是 DEBUG,但是我們可以改名字,根據(jù)自己的需要了

        // debug模式 
        #if DEBUG
        // release模式 #else
        #endif
        
        #if TEST
        print("test")
        #endif
        
        #if OTHER
        print("other")
        #endif
        
  • 1.3、DUBUG 和 Release 模式下的打印

    • OC 中

      #ifdef DEBUG
      #define JKLog(...) NSLog(@"%s 第%d行: %@\n\n",__func__,__LINE__,[NSString stringWithFormat:__VA_ARGS__])
      #else
      #define JKLog(...)
      #endif
      
    • Swift 中

      /// 自定義打印
      /// - Parameter msg: 打印的內(nèi)容
      /// - Parameter file: 文件路徑
      /// - Parameter line: 打印內(nèi)容所在的函數(shù)
      /// - Parameter fn: 打印內(nèi)容的函數(shù)名
      func JKLog<T>(_ msg: T,
                     file: NSString = #file,
                     line:Int = #line,
                       fn: String = #function) {
          #if DEBUG
          let prefix = "------\n當(dāng)前文件是:\(file.lastPathComponent)\n第 \(line) 行\(zhòng)n函數(shù)名:\(fn)\n打印內(nèi)容:msg\n------"
          print(prefix)
          #endif
      }
      
  • 1.4、系統(tǒng)版本檢測

    • 對于iOS平臺,只在iOS10及以上版本執(zhí)行
    • 對于macOS平臺,只在macOS 10.12及以上版本執(zhí)行
    • 最后的*表示在其他所有平臺都執(zhí)行
    if #available(iOS 10, macOS 10.12, *) {
       
    }
    
  • 1.5、API 可用性說明

    @available(iOS 10, macOS 10.15, *)
    class Person {}
    struct Student {
        // 方法更名
        @available(*, unavailable, renamed: "study")
        func study_() {}
        func study() {}
        // 在某個系統(tǒng)下廢棄了
        @available(iOS, deprecated: 11)
        @available(macOS, deprecated: 10.12)
        func run() {}
    }
    
    • 方法更名: @available(*, unavailable, renamed: "study")
      study_() 方法改為 study() 方法
    • 更多的說明方法可以 參考官方
  • 1.6、拓展小技巧

    • 當(dāng)我們在寫一個函數(shù)的時候,可能里面不知道些什么,又不想讓它報錯,我們可以在作用域內(nèi)寫上: fatalError()

      func testDo() -> Int {
          fatalError()
      }
      

二、Swift 項目說明

  • 2.1、iOS 程序的入口

    • 在AppDelegate上面默認(rèn)有個 @UIApplicationMain 標(biāo)記,這表示:編譯器自動生成入口代碼(main函數(shù)代碼),自動設(shè)置AppDelegate為APP的代理

    • 也可以刪掉 @UIApplicationMain,自定義入口代碼:新建一個main.swift文件,如下:JKUIApplication 使我們自定義的入口

      import UIKit
      
      class JKUIApplication: UIApplication {}
      
      UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, NSStringFromClass(JKUIApplication.self), NSStringFromClass(UIApplication.self))
      
  • 2.2、Swift 調(diào)用 OC

    • 先建一個橋接文件:直接見一個OC類,Xcode會直接提示我們創(chuàng)建一個橋接文件,文件名格式默認(rèn)為: {targetName}-Bridging-Header.h
    橋接文件

    文件名格式默認(rèn)為: `{targetName}-Bridging-Header.h`

    bridging 路徑
  • {targetName}-Bridging-Header.h 文件中#import OC需要暴露給Swift的內(nèi)容,如下

    • 自定義類 Animal, 其中 .h 和 .m文件內(nèi)容如下

      // .h文件
      #import <Foundation/Foundation.h>
      
      NS_ASSUME_NONNULL_BEGIN
      
      int sum(int a, int b);
      
      @interface Animal : NSObject
      
      @property (nonatomic, assign) NSInteger age;
      @property (nonatomic, copy) NSString *name;
      - (instancetype)initWithAge:(NSInteger)age name:(NSString *)name;
      + (instancetype)personWithAge:(NSInteger)age name:(NSString *)name;
      - (void)run;
      + (void)run;
      - (void)eat:(NSString *)food other:(NSString *)other;
      + (void)eat:(NSString *)food other:(NSString *)other;
      
      @end
      
      NS_ASSUME_NONNULL_END
      
      // .m 文件
      #import "Animal.h"
      
      @implementation Animal
      
      - (instancetype)initWithAge:(NSInteger)age name:(NSString *)name {
           if (self = [super init]) {
               self.age = age;
               self.name = name;
           }
           return self;
      }
      + (instancetype)personWithAge:(NSInteger)age name:(NSString *)name {
           return [[self alloc] initWithAge:age name:name];
      }
      + (void)run {
           NSLog(@"Animal +run");
      }
      - (void)run {
           NSLog(@"%zd %@ -run", _age, _name);
      }
      + (void)eat:(NSString *)food other:(NSString *)other {
           NSLog(@"Animal +eat %@ %@", food, other);
      }
      - (void)eat:(NSString *)food other:(NSString *)other {
           NSLog(@"%zd %@ -eat %@ %@", _age, _name, food, other);
      }
      
      @end
      
      int sum(int a, int b) {
          return a + b;
      }
      
    • 在 {targetName}-Bridging-Header.h 橋接文件里面 導(dǎo)入 OC的文件,如下

      #import "Animal.h"
      
    • 調(diào)用 Swift 調(diào)用 OC 代碼

      let p = Animal(age: 10, name: "Jack")
      p.age = 18
      p.name = "Rose"
      // 18 Rose -run
      p.run()
      // 18 Rose -eat Apple Water
      p.eat("Apple", other: "Water")
      // Animal +run
      Animal.run()
      // Animal +eat Pizza Banana
      Animal.eat("Pizza", other: "Banana")
      // 30
      print(sum(10, 20))
      
    • Swift 調(diào)用 @_silgen_name
      如果我們在C 語言有一個方法和 swift 里面的方法重名,那么在調(diào)用的時候,在Swift項目里面會 優(yōu)先調(diào)用 swift 里面的方法,如果我們也想調(diào)在Swift里面調(diào)用C的方法,我們可以使用 @_silgen_name修改 C 函數(shù)名

      // C語言
      int sum(int a, int b) {
         return a + b; 
      }
      // Swift
      @_silgen_name("sum") func swift_sum(_ v1: Int32, _ v2: Int32) -> Int32
      print(swift_sum(10, 20)) // 30
      print(sum(10, 20)) // 30
      
  • 2.3、OC 調(diào)用 Swift
    Xcode已經(jīng)默認(rèn)生成一個用于OC調(diào)用Swift的頭文件,文件名格式是: {targetName}-Swift.h

    • (1)、在 OC 的文件里面導(dǎo)入 {targetName}-Swift.h 文件,

    • (2)、要求 Swift要在OC里面使用的類繼承于 NSObject,如果 Swift 類里面的某個成員或者方法我們想要暴露給外面,就要在 某個成員或者方法 前面加 @objc

    • (3)、使用 @objcMembers 修飾類
      代表默認(rèn)所有成員都會暴露給OC(包括擴(kuò)展中定義的成員)
      最終是否成功暴露,還需要考慮成員自身的訪問級別

      @objcMembers class Car: NSObject {
      
          var price: Double
          var band: String
          init(price: Double, band: String) {
               self.price = price
               self.band = band
          }
          func run() { print(price, band, "run") }
               static func run() { print("Car run") 
          }
      }
      
      extension Car {
          func test() { print(price, band, "test") }
      }
      

      提示:Xcode會根據(jù)Swift代碼生成對應(yīng)的OC聲明,寫入{targetName}-Swift.h 文件

      • 上述代碼在編譯后就會在 {targetName}-Swift.h 文件 中生成如下代碼

        @interface Car : NSObject
        @property (nonatomic) double price;
        @property (nonatomic, copy) NSString * _Nonnull band;
        - (nonnull instancetype)initWithPrice:(double)price band:(NSString * _Nonnull)band OBJC_DESIGNATED_INITIALIZER;
        - (void)run;
        + (void)run;
        - (nonnull instancetype)init SWIFT_UNAVAILABLE;
        + (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
        @end
        
        @interface Car (SWIFT_EXTENSION(JKSwiftDemo))
        - (void)test;
        @end
        

      在 OC 文件中使用Swift的類,先導(dǎo)入 #import "targetName-Swift.h"

      #import "targetName-Swift.h"
      
      Car *c = [[Car alloc] initWithPrice:10.5 band:@"BMW"];
      c.band = @"Bently";
      c.price = 108.5;
      [c run]; // 108.5 Bently run
      [c test]; // 108.5 Bently test
      [Car run]; // Car run
      
      • 通過 @objc 重命名Swift暴露給OC的符號名(類名、屬性名、函數(shù)名等),如下

        @objc(JKCar)
        @objcMembers class Car: NSObject {
             var price: Double
             @objc(name)
             var band: String
             init(price: Double, band: String) {
                 self.price = price
                 self.band = band
             }
            @objc(drive)
            func run() { print(price, band, "run") }
            static func run() { print("Car run") }
        }
        extension Car {
            @objc(newTest)
            func test() { print(price, band, "test") }
        }
        

        在OC里面的調(diào)用

        JKCar *c = [[JKCar alloc] initWithPrice:10.5 band:@"BMW"]; 
        c.name = @"Bently";
        c.price = 108.5;
        [c drive]; // 108.5 Bently run
        [JKCar run]; // Car run
        
  • 2.4、根據(jù)2.2和2.3 列舉的幾個問題

    • 1.為什么 Swift 暴露給 OC 的類最終要繼承自 NSObject?
      答:因為 在OC 里面用類 ,必然繼承于 NSObject,在OC里面調(diào)用方法必然還要用到 runtime 那套流程,里面牽扯到 isa指針,isa指針來自NSObject
    • 2.p.run()底層是怎么調(diào)用的?反過來,OC調(diào)用 Swift 底層又是如何調(diào)用?
      答:前面的:OC的東西在Swift里面調(diào)用,我們可以看到調(diào)用了runtime那套機(jī)制;后面的:Swift的東西在OC里面調(diào)用,打斷點(diǎn)看匯編可以發(fā)現(xiàn)調(diào)用的也是runtime那套機(jī)制
    • 3.Swift 里面的 car.run() 底層是怎么調(diào)用的?
      答:走的是Swift那套流程,如果我們強(qiáng)行讓它走OC那套runtime機(jī)制,可以在 run() 函數(shù)前加 dynamic
  • 2.5、選擇器(Selector)
    Swift中依然可以使用選擇器,使用 #selector(name) 定義一個選擇器,但是 必須是被 @objcMembers@objc 修飾的方法才可以定義選擇器

    @objcMembers class Person: NSObject {
         func test1(v1: Int) { print("test1") }
         func test2(v1: Int, v2: Int) { print("test2(v1:v2:)") }
         func test2(_ v1: Double, _ v2: Double) { print("test2(_:_:)") }
         func run() {
             perform(#selector(test1))
             perform(#selector(test1(v1:)))
             perform(#selector(test2(v1:v2:)))
             perform(#selector(test2(_:_:)))
             perform(#selector(test2 as (Double, Double) -> Void))
          }
    }
    

三、字符串

  • 3.1、Swift的字符串類型String,跟OC的NSString,在API設(shè)計上還是有較大差異

    • 空字符串

      var emptyStr1 = ""
      var emptyStr2 = String()
      
    • 字符串前綴和后綴的判斷

      var str = "123456" 
      print(str.hasPrefix("123")) // true 
      print(str.hasSuffix("456")) // true
      
    • 其他用法

      var str: String = "1" 
      // 拼接,jack_rose 
      str.append("_2")
      // 重載運(yùn)算符 +
      str = str + "_3" 
      // 重載運(yùn)算符 += 
      str += "_4"
      // \()插值
      str = "\(str)_5"
      // 長度,9,1_2_3_4_5 
      print(str.count)
      
  • 3.2、String的插入和刪除

    var str = "1_2"
    // 插入 單個字符,結(jié)果是:1_2_
    str.insert("_", at: str.endIndex)
    // 插入 字符串,結(jié)果是:1_2_3_4
    str.insert(contentsOf: "3_4", at: str.endIndex)
    // 在某個索引后面插入,結(jié)果是:1666_2_3_4
    str.insert(contentsOf: "666", at: str.index(after: str.startIndex))
    // 在某個索引后面插入,結(jié)果是:1666_2_3_8884
    str.insert(contentsOf: "888", at: str.index(before: str.endIndex))
    // 在某個索引后面插入,偏移索引,結(jié)果是:1666hello_2_3_8884
    str.insert(contentsOf: "hello", at: str.index(str.startIndex, offsetBy: 4))
    // 刪除值為1的第一個索引的值,,結(jié)果是:666hello_2_3_8884
    str.remove(at: str.firstIndex(of: "1")!)
    // 刪除值為字符為 6 的字符,結(jié)果是:hello_2_3_8884
    str.removeAll { $0 == "6" }
    //刪除某個區(qū)間的字符
    var range = str.index(str.endIndex, offsetBy: -4)..<str.index(before: str.endIndex)
    // hello_2_3_4
    str.removeSubrange(range)
    
  • 3.3、Substring 子串

    • String可以通過下標(biāo)、 prefix、 suffix等截取子串,子串類型不是String,而是Substring

    • Substring和它的base,共享字符串?dāng)?shù)據(jù)

    • Substring發(fā)生修改 或者 轉(zhuǎn)為String時,會分配新的內(nèi)存存儲字符串?dāng)?shù)據(jù),也就是深度拷貝

      var str = "1_2_3_4_5"
      // 1_2
      var substr1 = str.prefix(3)
      // 4_5
      var substr2 = str.suffix(3)
      // 1_2
      var range = str.startIndex..<str.index(str.startIndex, offsetBy: 3)
      var substr3 = str[range]
      // 最初的String,1_2_3_4_5 
      print(substr3.base)
      // Substring -> String
      var str2 = String(substr3)
      
      • prefix(3)代表從 頭 截取三位
      • suffix(3)代表從 尾 截取三位
      • 子串在沒有進(jìn)行修改前 和 原字符串公用一塊內(nèi)存,在子串進(jìn)行修改后,那么就要進(jìn)行深度拷貝了
  • 3.4、String 與 Character

    for c in "jack" {
       // c是Character類型
       print(c)
    }
    
    var str = "jack"
    // c是Character類型
    var c = str[str.startIndex]
    
  • 3.5、String 相關(guān)的協(xié)議

    • BidirectionalCollection 協(xié)議包含的部分內(nèi)容
      • startIndex 、 endIndex 屬性、index 方法
      • String、Array 都遵守了這個協(xié)議
    • RangeReplaceableCollection 協(xié)議包含的部分內(nèi)容
      • append、insert、remove 方法
      • String、Array 都遵守了這個協(xié)議
    • Dictionary、Set 也有實現(xiàn)上述協(xié)議中聲明的一些方法,只是并沒有遵守上述協(xié)議
  • 3.6、多行String

    • 放在 三個雙引號之間的代表是多行,如下

      let str = """ 
      1
            "2" 
      3
            '4' 
      """
      
    • 如果需要顯示三個 引號,至少轉(zhuǎn)義一個引號

      let str = """
      Escaping the first quote \"""
      Escaping two quotes \"\"" 
      Escaping all three quotes \"\"\" 
      """
      
    • 縮進(jìn)以結(jié)尾的 三引號為對齊線

      let str = """ 
              1
                  "2" 
           3
               '4' 
           """
      
    • 以下兩個字符串是等價的

      let  str1 = "These are the same."
      let str2 = """ 
      These are the same.
      """
      
  • 3.7、String 與 NSString

    • String 與 NSString 之間可以隨時隨地橋接轉(zhuǎn)換

    • 如果你覺得String的API過于復(fù)雜難用,可以考慮將String轉(zhuǎn)為NSString

      var str1: String = "jack"
      var str2: NSString = "rose"
      var str3 = str1 as NSString
      var str4 = str2 as String
      
      // OC的使用
      var str5 = str3.substring(with: NSRange(location: 0, length: 2))              
      print(str5)
      
    • 比較字符串內(nèi)容是否等價

      String使用 == 運(yùn)算符
      NSString使用 isEqual 方法,也可以使用 == 運(yùn)算符(本質(zhì)還是調(diào)用了isEqual方法)
      
    • Swift、OC橋接轉(zhuǎn)換表

      Swift、OC橋接轉(zhuǎn)換表
      • 提示:不可以由 不可變 強(qiáng)轉(zhuǎn)成 可變的

四、OC 與 Swift 其他的不同點(diǎn)

  • 4.1、只能被class繼承的協(xié)議

    protocol Runnable1: AnyObject {}
    protocol Runnable2: class {}
    @objc protocol Runnable3 {}
    

    @objc 修飾的協(xié)議,還可以暴露給OC去遵守實現(xiàn)

  • 4.2.可選協(xié)議

    • 第一種:可以通過 @objc 定義可選協(xié)議,這種協(xié)議只能被class 遵守

      @objc protocol Runnable {
          func run1()
          @objc optional func run2()
          func run3()
      }
      class Dog: Runnable {
          func run3() { print("Dog run3") }
          func run1() { print("Dog run1") }
      }
      
      var d = Dog()
      d.run1() // Dog run1
      d.run3() // Dog run3
      
    • 第二種:我們可以通過擴(kuò)展,如下Dog類就不需要實現(xiàn)run2(),因為擴(kuò)展中已經(jīng)實現(xiàn)

      protocol Runnable {
          func run1()
          func run2()
      }
      
      extension Runnable {
          func run2(){
          }
      }
      
      class Dog: Runnable {
          func run1() { print("Dog run1") }
      }
      
      var d = Dog()
      d.run1() // Dog run1
      
  • 4.3、dynamic
    @objc dynamic 修飾的內(nèi)容會具有動態(tài)性,比如調(diào)用方法會走runtime那一套流程

    class Dog: NSObject {
        @objc dynamic func test1() {}
        func test2() {}
    }
    var d = Dog()
    d.test1()
    d.test2()
    

  • 4.4、KVO / KVC

    • Swift 支持 KVC \ KVO 的條件 ,必須滿足以下條件

    • (1)、屬性所在的類、監(jiān)聽器最終繼承自 NSObject

    • (2)、 用 @objc dynamic 修飾對應(yīng)的屬性

      class Observer: NSObject {
            override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?) {
                print("observeValue", change?[.newKey] as Any)
            }
      }
      
      class Person: NSObject {
            @objc dynamic var age: Int = 0
            var observer: Observer = Observer()
            override init() {
               super.init()
               self.addObserver(observer,
                     forKeyPath: "age",
                     options: .new,
                     context: nil)
             }
             deinit {
                 self.removeObserver(observer,
                        forKeyPath: "age")
             }
      }
      
      var p = Person()
      // observeValue Optional(20)
      p.age = 20
      // observeValue Optional(25)
      p.setValue(25, forKey: "age")
      
  • 4.5、Block式的KVO

    class Person: NSObject {
         @objc dynamic var age: Int = 0
         var observation: NSKeyValueObservation?
         override init() {
             super.init()
             observation = observe(\Person.age, options: .new) {
                 (person, change) in
                 print(change.newValue as Any)
             }
         }
    }
    
    var p = Person()
    // Optional(20)
    p.age = 20
    // Optional(25)
    p.setValue(25, forKey: "age")
    
  • 4.6、關(guān)聯(lián)對象

    • 在Swift中,class依然可以使用關(guān)聯(lián)對象

    • 默認(rèn)情況,extension不可以增加存儲屬性 ,借助關(guān)聯(lián)對象,可以實現(xiàn)類似extension為class增加存儲屬性的效果

      class Person {}
      extension Person {
          private static var AGE_KEY: Void?
          var age: Int {
             get {
                 (objc_getAssociatedObject(self, &Self.AGE_KEY) as? Int) ?? 0
             } 
             set {
                 objc_setAssociatedObject(self, &Self.AGE_KEY, newValue, .OBJC_ASSOCIATION_ASSIGN)
             }
          }
      }
      
      var p = Person1()
      print(p.age) // 0
      p.age = 10
      print(p.age) // 10
      

      提示:唯一的存儲空間 private static var AGE_KEY: Void?,我們使用Void和Bool 都是 1 個存儲空間,節(jié)省內(nèi)存

  • 4.7、資源名管理

    • 平時的做法:直接加載圖片名字或者按鈕的名字,如下

      let img = UIImage(named: "logo")
      
      let btn = UIButton(type: .custom)
      btn.setTitle("添加", for: .normal)
      
    • 優(yōu)化后的做法,先定義一個資源枚舉 JKResource

      enum JKResource {
      
         /// 按鈕名字
         enum string: String {
             case add = "添加"
         }
      
         /// 圖片名字
         enum image: String {
             case logo
         }
      
         enum segue: String {
             case login_main
         }
      }
      
      // 調(diào)用
      let img = UIImage(named: JKResource.image.logo.rawValue)
      let btn = UIButton(type: .custom)
      btn.setTitle(JKResource.string.add.rawValue, for: .normal)
      
      • 提示:這種做法實際上是參考了Android的資源名管理方式
    • 通過擴(kuò)展進(jìn)一步管理資源名

      extension UIImage {
      
          convenience init?(_ name: R.image) {
              self.init(named: name.rawValue) 
          }
      }
      
      extension UIButton {
      
         func setTitle(_ title: R.string, for state: UIControl.State) {
             setTitle(title.rawValue, for: state) }
      }
      
    • 資源名管理的其他思路

      enum JKResource {
           enum image {
               static var logo = UIImage(named: "logo")
           }
           enum font {
               static func arial(_ size: CGFloat) -> UIFont? {
                   UIFont(name: "Arial", size: size) }
           }
      }
      // 使用如下
      let img = JKResource.image.logo
      let font = JKResource.font.arial(14)
      

      更多優(yōu)秀的思路參考如下

五、多線程

  • 5.1、多線程開發(fā)-異步

    public typealias Task = () -> Void
    
    public struct JKAsyncs {
    
        public static func async(_ task: @escaping Task) {
             _async(task)
        }
    
        public static func async(_ task: @escaping Task,
                           _ mainTask: @escaping Task) {
             _async(task, mainTask)
        }
        
        private static func _async(_ task: @escaping Task,
                             _ mainTask: Task? = nil) {
             let item = DispatchWorkItem(block: task)
             DispatchQueue.global().async(execute: item)
             if let main = mainTask {
                    item.notify(queue: DispatchQueue.main, execute: main)
              }
         }
    }
    // 調(diào)用如下
    JKAsyncs.async({
          print(Thread.current)  // 自線程
    }) {
          print(Thread.current)  // 主線程
    }
    

    提示:開辟線程任務(wù)可能是在大括號之外完成所以加上 @escaping :逃逸閉包

    • DispatchWorkItem 的使用,子線程和其他線程分開,更加的直觀

      let item = DispatchWorkItem {
          print(Thread.current)
      }
      DispatchQueue.global().async(execute: item)
      item.notify(queue: DispatchQueue.main) {
          print(Thread.current)
      }
      
  • 5.2、多線程開發(fā)-主線程延遲

    • 平時的用法

      let seconds:Double = 5
      let item = DispatchWorkItem {
            print("\(seconds)秒后打印",Thread.current)   
      }
      DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
      
    • 寫到封裝的 JKAsyncs 結(jié)構(gòu)體里面

      @discardableResult
      public static func delay(_ seconds: Double,
                        _ block: @escaping Task) -> DispatchWorkItem {
          let item = DispatchWorkItem(block: block)
          DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
          return item
      }
      

      提示:@discardableResult 代表在使用的時候我們可以忽略函數(shù)的返回值,也就是可以不用接收返回值

      • 返回值itme的作用:item.cancel() 取消 方法的執(zhí)行,也就是取消延遲
      • 延遲操作是在子線程
  • 5.3、多線程開發(fā)-異步延遲

    @discardableResult
    public static func asyncDelay(_ seconds: Double,
                                _ task: @escaping Task) -> DispatchWorkItem {
        return _asyncDelay(seconds, task)
    }
    
    @discardableResult
    public static func asyncDelay(_ seconds: Double,
                                _ task: @escaping Task,
                                _ mainTask: @escaping Task) -> DispatchWorkItem {
        return _asyncDelay(seconds, task, mainTask)
    }
    
    private static func _asyncDelay(_ seconds: Double,
                                  _ task: @escaping Task,
                                  _ mainTask: Task? = nil) -> DispatchWorkItem {
        let item = DispatchWorkItem(block: task)
        DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + seconds,
                                        execute: item)
        if let main = mainTask {
            item.notify(queue: DispatchQueue.main, execute: main)
        }
        return item
    }
    
  • 5.4、多線程開發(fā) - once:一次性代碼,dispatch_once 在 Swift 中已被廢棄,取而代之 如下方式

    • 可以用類型屬性或者全局變量\常量(整個程序啟動后只有一份內(nèi)存)

      fileprivate let initTask2: Void = {
             print("initTask2---------")
      }()
      
      class ViewController: UIViewController {
      
          static let initTask1: Void = {
              print("initTask1---------")
          }()
      
          override func viewDidLoad() {
          super.viewDidLoad()
      
             let _ = Self.initTask1
             let _ = initTask2
      
             let _ = Self.initTask1
             let _ = initTask2
          }
      }
      

      打印結(jié)果

      initTask1---------
      initTask2---------
      

      提示:默認(rèn)自帶 lazy + dispatch_once 效果

      • 第一個字母大寫的 Self 代表當(dāng)前的類
      • 懶加載的屬性里面只會走一次
  • 5.5、多線程開發(fā)-加鎖(線程同步技術(shù),防止資源搶奪)

    • 第一種鎖:gcd 信號量

      class Cache {
          private static var data = [String: Any]()
          // 設(shè)置信號量的鎖
          private static var lock = DispatchSemaphore(value: 1)
          static func get(_ key: String) -> Any? {
               data[key]
          }
          static func set(_ key: String, _ value: Any) {
               // 加鎖
               lock.wait()
               defer { 
                   // 解鎖
                   lock.signal()
                }
               data[key] = value
          }
      }
      
    • NSLock鎖

      class Cache {
          private static var data = [String: Any]()
          private static var lock = NSLock()
          static func get(_ key: String) -> Any? {
               data[key]
          }
          static func set(_ key: String, _ value: Any) {
               // 加鎖
               lock.lock()
               defer { 
                   // 解鎖
                   lock.unlock()
                }
               data[key] = value
          }
      
    • 遞歸鎖:如果一個調(diào)用存在調(diào)用自身(遞歸),那么我們就是用遞歸鎖:NSRecursiveLock(),加鎖解鎖和上面 NSLock鎖 一樣

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容