《Swift从入门到精通》(二十二):内存安全

内存安全(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

内存安全01
  • 存储器访问特性,主要有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
      // 此处读访问与写时有重叠,如下图
      
      
内存安全02
```swift
// 解决冲突的方法
// Make an explicit copy.
var copyOfStepSize = stepSize
increment(&copyOfStepSize)
// Update the original.
stepSize = copyOfStepSize
// stepSize is now 2

*   将同一个变量作为同一个函数的多个 in-out 参数传递会发生冲突

    ```
    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 如下下图

        ```

![内存安全03](https://upload-images.jianshu.io/upload_images/6935167-407ed76482b682f4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![内存安全04](https://upload-images.jianshu.io/upload_images/6935167-a485843a1fdaaf96.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

*   **访问属性时冲突**

    *   像结构体、元组、枚举是独立的值,因为它们是值类型,读或者写都是整一块内存的访问,如下访问元组的元素

    ```
    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编译器可以保证重复访问结构体属性时是安全的
    *   只访问存储属性,不访问计算属性和类型属性
    *   结构体变量是一个局部变量,非全局变量
    *   结构体既没有被闭包捕获也没有非逃逸闭包捕获


©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 默认情况下,Swift可以防止代码中出现不安全行为。例如,Swift确保变量在使用之前被初始化,内存在被释放后不被...
    WSJay阅读 1,706评论 1 7
  • 1、隐式解析可选类型 有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值,这时候每次使用时都判断和...
    Mr_xuy阅读 502评论 1 4
  • 默认情况下,Swift 会阻止你代码里不安全的行为。 例如:Swift 会保证变量在使用之前就完成初始化,在内存被...
    DevXue阅读 322评论 0 0
  • 默认情况下,Swift可以防止代码中发生不安全行为。例如,Swift确保变量在使用前被初始化,内存被释放后不会被访...
    微笑中的你阅读 542评论 0 0
  • 案例代码下载 内存安全 默认情况下,Swift可以防止代码中发生不安全行为。例如,Swift确保变量在使用之前进行...
    酒茶白开水阅读 439评论 0 0