美文网首页Swift开发Swift 专栏
Swift5.0 - day10- 从 OC 到 Swift

Swift5.0 - day10- 从 OC 到 Swift

作者: IIronMan | 来源:发表于2019-11-20 21:40 被阅读0次

    一、OC 到 Swift 基础差异

    • 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: 用于标记未完成的任务

        func test2() -> () {
          // TODO: 未完成的任务
        }
        
        // TODO: 用于标记未完成的任务
      • <4>、// FIXME: 用于标记待修复的问题

        func test3() -> () {
            // FIXME: 待修复
        }
        
        // FIXME: 用于标记待修复的问题
      • <5>、#warning("") 警告信息

        `#warning("")` 警告信息
    • 1.2、条件编译

      // 操作系统:macOS\iOS\tvOS\watchOS\Linux\Android\Windows\FreeBSD 
      #if os(macOS) || os(iOS)
      // CPU架构:i386\x86_64\arm\arm64
      #elseif arch(x86_64) || arch(arm64)
      // swift版本
      #elseif swift(<5) && swift(>=3)
      // 模拟器
      #elseif targetEnvironment(simulator) 
      // 可以导入某模块
      #elseif canImport(Foundation)
      #else
      #endif
      
      • 自定义条件编译
        • 系统默认的是 DEBUG,但是我们可以改名字,根据自己的需要了

          // 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: 打印的内容
        /// - Parameter file: 文件路径
        /// - Parameter line: 打印内容所在的函数
        /// - Parameter fn: 打印内容的函数名
        func JKLog<T>(_ msg: T,
                       file: NSString = #file,
                       line:Int = #line,
                         fn: String = #function) {
            #if DEBUG
            let prefix = "------\n当前文件是:\(file.lastPathComponent)\n第 \(line) 行\n函数名:\(fn)\n打印内容:msg\n------"
            print(prefix)
            #endif
        }
        
    • 1.4、系统版本检测

      • 对于iOS平台,只在iOS10及以上版本执行
      • 对于macOS平台,只在macOS 10.12及以上版本执行
      • 最后的*表示在其他所有平台都执行
      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() {}
          // 在某个系统下废弃了
          @available(iOS, deprecated: 11)
          @available(macOS, deprecated: 10.12)
          func run() {}
      }
      
      • 方法更名: @available(*, unavailable, renamed: "study")
        study_() 方法改为 study() 方法
      • 更多的说明方法可以 参考官方
    • 1.6、拓展小技巧

      • 当我们在写一个函数的时候,可能里面不知道些什么,又不想让它报错,我们可以在作用域内写上: fatalError()

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

    二、Swift 项目说明

    • 2.1、iOS 程序的入口

      • 在AppDelegate上面默认有个 @UIApplicationMain 标记,这表示:编译器自动生成入口代码(main函数代码),自动设置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 调用 OC

      • 先建一个桥接文件:直接见一个OC类,Xcode会直接提示我们创建一个桥接文件,文件名格式默认为: {targetName}-Bridging-Header.h
      桥接文件
      文件名格式默认为: `{targetName}-Bridging-Header.h`
      bridging 路径
    • {targetName}-Bridging-Header.h 文件中#import OC需要暴露给Swift的内容,如下

      • 自定义类 Animal, 其中 .h 和 .m文件内容如下

        // .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 桥接文件里面 导入 OC的文件,如下

        #import "Animal.h"
        
      • 调用 Swift 调用 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 调用 @_silgen_name
        如果我们在C 语言有一个方法和 swift 里面的方法重名,那么在调用的时候,在Swift项目里面会 优先调用 swift 里面的方法,如果我们也想调在Swift里面调用C的方法,我们可以使用 @_silgen_name修改 C 函数名

        // 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 调用 Swift
      Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是: {targetName}-Swift.h

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

      • (2)、要求 Swift要在OC里面使用的类继承于 NSObject,如果 Swift 类里面的某个成员或者方法我们想要暴露给外面,就要在 某个成员或者方法 前面加 @objc

      • (3)、使用 @objcMembers 修饰类
        代表默认所有成员都会暴露给OC(包括扩展中定义的成员)
        最终是否成功暴露,还需要考虑成员自身的访问级别

        @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会根据Swift代码生成对应的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的类,先导入 #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的符号名(类名、属性名、函数名等),如下

          @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里面的调用

          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、根据2.2和2.3 列举的几个问题

      • 1.为什么 Swift 暴露给 OC 的类最终要继承自 NSObject?
        答:因为 在OC 里面用类 ,必然继承于 NSObject,在OC里面调用方法必然还要用到 runtime 那套流程,里面牵扯到 isa指针,isa指针来自NSObject
      • 2.p.run()底层是怎么调用的?反过来,OC调用 Swift 底层又是如何调用?
        答:前面的:OC的东西在Swift里面调用,我们可以看到调用了runtime那套机制;后面的:Swift的东西在OC里面调用,打断点看汇编可以发现调用的也是runtime那套机制
      • 3.Swift 里面的 car.run() 底层是怎么调用的?
        答:走的是Swift那套流程,如果我们强行让它走OC那套runtime机制,可以在 run() 函数前加 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设计上还是有较大差异

      • 空字符串

        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")
        // 重载运算符 +
        str = str + "_3" 
        // 重载运算符 += 
        str += "_4"
        // \()插值
        str = "\(str)_5"
        // 长度,9,1_2_3_4_5 
        print(str.count)
        
    • 3.2、String的插入和删除

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

      • String可以通过下标、 prefix、 suffix等截取子串,子串类型不是String,而是Substring

      • Substring和它的base,共享字符串数据

      • Substring发生修改 或者 转为String时,会分配新的内存存储字符串数据,也就是深度拷贝

        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)代表从 尾 截取三位
        • 子串在没有进行修改前 和 原字符串公用一块内存,在子串进行修改后,那么就要进行深度拷贝了
    • 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 相关的协议

      • BidirectionalCollection 协议包含的部分内容
        • startIndex 、 endIndex 属性、index 方法
        • String、Array 都遵守了这个协议
      • RangeReplaceableCollection 协议包含的部分内容
        • append、insert、remove 方法
        • String、Array 都遵守了这个协议
      • Dictionary、Set 也有实现上述协议中声明的一些方法,只是并没有遵守上述协议
    • 3.6、多行String

      • 放在 三个双引号之间的代表是多行,如下

        let str = """ 
        1
              "2" 
        3
              '4' 
        """
        
      • 如果需要显示三个 引号,至少转义一个引号

        let str = """
        Escaping the first quote \"""
        Escaping two quotes \"\"" 
        Escaping all three quotes \"\"\" 
        """
        
      • 缩进以结尾的 三引号为对齐线

        let str = """ 
                1
                    "2" 
             3
                 '4' 
             """
        
      • 以下两个字符串是等价的

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

      • String 与 NSString 之间可以随时随地桥接转换

      • 如果你觉得String的API过于复杂难用,可以考虑将String转为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)
        
      • 比较字符串内容是否等价

        String使用 == 运算符
        NSString使用 isEqual 方法,也可以使用 == 运算符(本质还是调用了isEqual方法)
        
      • Swift、OC桥接转换表

        Swift、OC桥接转换表
        • 提示:不可以由 不可变 强转成 可变的

    四、OC 与 Swift 其他的不同点

    • 4.1、只能被class继承的协议

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

      @objc 修饰的协议,还可以暴露给OC去遵守实现

    • 4.2.可选协议

      • 第一种:可以通过 @objc 定义可选协议,这种协议只能被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
        
      • 第二种:我们可以通过扩展,如下Dog类就不需要实现run2(),因为扩展中已经实现

        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 修饰的内容会具有动态性,比如调用方法会走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)、属性所在的类、监听器最终继承自 NSObject

      • (2)、 用 @objc dynamic 修饰对应的属性

        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、关联对象

      • 在Swift中,class依然可以使用关联对象

      • 默认情况,extension不可以增加存储属性 ,借助关联对象,可以实现类似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 个存储空间,节省内存

    • 4.7、资源名管理

      • 平时的做法:直接加载图片名字或者按钮的名字,如下

        let img = UIImage(named: "logo")
        
        let btn = UIButton(type: .custom)
        btn.setTitle("添加", for: .normal)
        
      • 优化后的做法,先定义一个资源枚举 JKResource

        enum JKResource {
        
           /// 按钮名字
           enum string: String {
               case add = "添加"
           }
        
           /// 图片名字
           enum image: String {
               case logo
           }
        
           enum segue: String {
               case login_main
           }
        }
        
        // 调用
        let img = UIImage(named: JKResource.image.logo.rawValue)
        let btn = UIButton(type: .custom)
        btn.setTitle(JKResource.string.add.rawValue, for: .normal)
        
        • 提示:这种做法实际上是参考了Android的资源名管理方式
      • 通过扩展进一步管理资源名

        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)
        

        更多优秀的思路参考如下

    五、多线程

    • 5.1、多线程开发-异步

      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)
                }
           }
      }
      // 调用如下
      JKAsyncs.async({
            print(Thread.current)  // 自线程
      }) {
            print(Thread.current)  // 主线程
      }
      

      提示:开辟线程任务可能是在大括号之外完成所以加上 @escaping :逃逸闭包

      • DispatchWorkItem 的使用,子线程和其他线程分开,更加的直观

        let item = DispatchWorkItem {
            print(Thread.current)
        }
        DispatchQueue.global().async(execute: item)
        item.notify(queue: DispatchQueue.main) {
            print(Thread.current)
        }
        
    • 5.2、多线程开发-主线程延迟

      • 平时的用法

        let seconds:Double = 5
        let item = DispatchWorkItem {
              print("\(seconds)秒后打印",Thread.current)   
        }
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
        
      • 写到封装的 JKAsyncs 结构体里面

        @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 代表在使用的时候我们可以忽略函数的返回值,也就是可以不用接收返回值

        • 返回值itme的作用:item.cancel() 取消 方法的执行,也就是取消延迟
        • 延迟操作是在子线程
    • 5.3、多线程开发-异步延迟

      @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、多线程开发 - once:一次性代码,dispatch_once 在 Swift 中已被废弃,取而代之 如下方式

      • 可以用类型属性或者全局变量\常量(整个程序启动后只有一份内存)

        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
            }
        }
        

        打印结果

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

        提示:默认自带 lazy + dispatch_once 效果

        • 第一个字母大写的 Self 代表当前的类
        • 懒加载的属性里面只会走一次
    • 5.5、多线程开发-加锁(线程同步技术,防止资源抢夺)

      • 第一种锁:gcd 信号量

        class Cache {
            private static var data = [String: Any]()
            // 设置信号量的锁
            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
            }
        
      • 递归锁:如果一个调用存在调用自身(递归),那么我们就是用递归锁:NSRecursiveLock(),加锁解锁和上面 NSLock锁 一样

    相关文章

      网友评论

        本文标题:Swift5.0 - day10- 从 OC 到 Swift

        本文链接:https://www.haomeiwen.com/subject/qwzbictx.html