Swift5.0 - day10- 从 OC 到 Swift

一、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锁 一样

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容