Swift从入门到精通第二十二篇 - 内存安全(Memory Safety) 初识

内存安全(Memory Safety)(学习笔记)

环境Xcode 11.0 beta4 swift 5.1
欢迎留言 pen me

  • 前言
    • 默认情况下,Swift会阻止在你代码中的不安全行为,例如:变量没初始化前不可使用、被销毁的内存不可访问、数组索引越界检查会报错;Swift也能确保在代码修改内存中值时将会独占该,从而确保多处同时访问相同内存时不会冲突。因为Swift自动管理内存,大部时候不需要去关心内存的访问;但了解潜在的内存冲突很重要,这样可以在写代码避免;如果代码包含了冲突,将可能在编译或运行时出现错误。
  • 理解内存访问冲突
    • 下面是内存的读和写

      // write memory
      var one = 1
      // read memory
      print("We'er number \(one)!")
    • 内存冲突可能发生在不同部分代码访问相同内存时发生,下图中的 Before During After 来说明这个问题
    • Before 和 After 状态访问时将获得正确的答案
    • 在 During 状态时是一个瞬时的状态,当添加物品时,总价可能还没来得及更改,此时访问可能得到不正确的答案
    • 有时候修复冲突的方法有多种,而且在修复之前要搞清楚你的需求是什么,例如在本例中,是要访问5还是300

Swift从入门到精通第二十二篇 - 内存安全(Memory Safety) 初识

  • 存储器访问特性,主要有3个特性:一、访问是读还是写; 二、访问的持续时间; 三、被访问的内存位置;如果有两个访问满足以下条件将会发生冲突
    • 至少有一个写访问
    • 访问同一块内存
    • 访问时间有重叠
  • 读访问和写访问还是有区别的,读要改变内存而写不需要;内存访问可以是瞬时的也可是持续的,如果其它代码无法在访问开始之后且在访问结束之前运行,则访问是瞬时的
    从本质上讲,瞬时访问不可能同时发生的,大多数访问是即时的。示例如下:

    func oneMore(than number: Int) -> Int {
        return number + 1
    }
    //
    var myNumber = 1
    myNumber = oneMore(than: myNumber)
    print(myNumber)
    // Prints "2"
  • 有多种方法访问内存,跨度超过其它代码的执行,这叫持续访问;一个持续访问可以与其它持续访问或瞬时访问发生重叠;这种重叠主要发生在 in-out 参数 和 结构体的 mutating 方法

  • in-out 参数访问冲突
    • 示例如下

      var stepSize = 1;
      func increment(_ number: inout Int){
          number += stepSize
      }
      increment(&stepSize)
      // Error: conflicting access to stepSize
      // 此处读访问与写时有重叠,如下图

      Swift从入门到精通第二十二篇 - 内存安全(Memory Safety) 初识

      // 解决冲突的方法
      // Make an explicit copy.
      var copyOfStepSize = stepSize
      increment(&copyOfStepSize)
      // Update the original.
      stepSize = copyOfStepSize
      // stepSize is now 2
  • 将同一个变量作为同一个函数的多个 in-out 参数传递会发生冲突
    swift func balance(_ x: inout Int, _ y: inout Int) { let sum = x + y x = sum / 2 y = sum - x } var playerOneScore = 42 var playerTwoScore = 30 balance(&playerOneScore, &playerTwoScore) // OK balance(&playerOneScore, &playerOneScore) // Error: conflicting accesses to playerOneScore

  • 方法内访问self冲突
    • 示例

      struct Player {
          var name: String
          var health: Int
          var energy: Int
          static let maxHealth = 10
          mutating func restoreHealth() {
              health = Player.maxHealth
          }
      }
      extension Player {
          mutating func shareHealth(with teammate: inout Player) {
              balance(&teammate.health, &health)
          }
      }
      //
      var oscar = Player(name: "Oscar", health: 10, energy: 10)
      var maria = Player(name: "Maria", health: 5, energy: 10)
      oscar.shareHealth(with: &maria)  // OK 访问的内存不同,如下图
      oscar.shareHealth(with: &oscar)
      // Error: conflicting accesses to oscar 如下下图

      Swift从入门到精通第二十二篇 - 内存安全(Memory Safety) 初识

Swift从入门到精通第二十二篇 - 内存安全(Memory Safety) 初识

  • 访问属性时冲突
    • 像结构体、元组、枚举是独立的值,因为它们是值类型,读或者写都是整一块内存的访问,如下访问元组的元素
    var playerInformation = (health: 10, energy: 20)
    balance(&playerInformation.health, &playerInformation.energy)
    // Error: conflicting access to properties of playerInformation
    • 访问结构体属性
    var holly = Player(name: "Holly", health: 10, energy: 10)
    balance(&holly.health, &holly.energy)  // Error
    // 但下面的情况是可以的,如果上面的变量 holly 是局部变量而不是全局变量,编译器可以证明这种重复访问是安全的,因为两个存储属性的内存不可能有交叉
    func someFunction() {
        var oscar = Player(name: "Oscar", health: 10, energy: 10)
        balance(&oscar.health, &oscar.energy)  // OK
    }
    • 当满足以下几点时,SWift编译器可以保证重复访问结构体属性时是安全的
    • 只访问存储属性,不访问计算属性和类型属性
    • 结构体变量是一个局部变量,非全局变量
    • 结构体既没有被闭包捕获也没有非逃逸闭包捕获
上一篇:100条常用写作谚语(1)(2)(3)(4)


下一篇:如何与 DevOps 为伍?