swift 进阶:值类型和引用类型

swift 进阶之路:学习大纲

前言

  • 值类型和引用类型是Swift中的核心概念,了解它们是每位Swift开发人员的基础, 在以后的探索中多次提到值类型和引用类型,所以在这里做个笔记。供以后参考。

内容

    1. 值类型和引用类型的概念
    1. 值类型和引用类型的内存管理
    1. 值类型和引用类型的选择

一 、值类型和引用类型的定义

值类型(Value Type):即每个实例保持一份数据拷贝。

引用类型(Reference Type):即所有实例共享一份数据拷贝。

Swift有三种声明类型的方式:classstructenum。它们可以分为值类型(struct和enum)和引用类型(class)。它们在内存中的存储方式不同决定它们之间的区别:

  • 值类型是这样一种类型,当它被赋值给一个变量、常量或者被传递给一个函数的时候,其值会被拷⻉。
  • 实际上,Swift 中所有的基本类型:整数 (integer)、浮点数(floating-point number)、布尔值(boolean)、字符串串(string)、数组 (array)和字典(dictionary),都是值类型,其底层是使用结构体实现的。Swift 中所有的结构体和枚举类型都是值类型。这意味着它们的实例例,以及实例例中所包含的任何 值类型的属性,在代码中传递的时候都会被复制。
  • 与值类型不同,引⽤类型在被赋予到一个变量量、常量或者被传递到一个函数时,其值不会被拷贝。因此,使用的是已存在实例的引⽤,而不是其拷贝。

验证值类型:

import UIKit

struct Point {
    var x: Double
    var y: Double
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let point1 = Point(x: 3, y: 5)
        var point2 = point1
        
        print(point1)           // Point(x: 3.0, y: 5.0)
        print(point2)           // Point(x: 3.0, y: 5.0)
        
        point2.x = 5
        
        print(point1)           // Point(x: 3.0, y: 5.0)
        print(point2)           // Point(x: 3.0, y: 5.0)
        
    }
}

//打印point1 不随着 point2 而变化 。说明他们内存独立
Point(x: 3.0, y: 5.0)
Point(x: 3.0, y: 5.0)
Point(x: 5.0, y: 5.0)

验证引用类型:

import UIKit

class Point {
    var x: Double = 0.0
    var y: Double = 0.0
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let point1 = Point()
        point1.x = 3.0
        point1.y = 5.0
        let point2 = point1
        
        print(point1)
        print(point2)
        
        point2.x = 5
        
        print(point1.x , point1.y)
        print(point2.x , point2.y)
    }
}

//打印point1 随着 point2 而变化 。说明他们公用一块内存
5.0 5.0
5.0 5.0

二、 值类型和引用类型的内存管理

  • 值类型存储在栈区。每个值类型变量都有其自己的数据副本,并且对一个变量的操作不会影响另一个变量。

  • 引用类型存储在其他位置(堆区),我们在内存中有一个指向该位置的引用。引用类型的变量可以指向相同类型的数据。因此,对一个变量进行的操作会影响另一变量所指向的数据

栈区存储临时数据:方法的参数和局部变量。每次我们调用一个方法时,都会在栈上分配一块新的内存。该方法退出时,将释放该内存。除特殊情况(下面会讲),所有Swift值类型都在此处。

堆区存储具有生存期的对象。这些都是Swift引用类型,还有一些值类型的情况。堆和栈朝着彼此增长堆区的分配一般按照地址从小到大进行,而栈区的分配一般按照地址从大到小进行分配。

【堆与栈分配的成本】

栈区内存分配和销毁的工作原理与数据结构中的栈相同。你只能从栈顶压栈或出栈。指向栈顶的指针足以实现这两个操作。因此,栈指针可以腾出空间来分配其他更多的内存。当函数执行完退出时,我们将栈指针增加到调用此方法之前的位置。(为什么增加才能回到调用之前的地址,刚说了栈是从大到小进行分配的)

  • 栈分配和释放的成本相当于整数复制的成本

堆分配过程涉及的东西很多。我们必须搜索堆区以找到适合它大小的空内存块。我们还必须同步堆,因为多个线程可能同时在其中分配内存。为了从堆中释放内存,我们必须将该内存重新插入适当的位置。

  • 堆分配和释放的成本比栈要大得多
【引用类型的内存分配】
  • 引用类型的存储属性不会直接保存在栈上,系统会在栈上开辟空间用来保存实例的指针,栈上的指针负责去堆上找到相应的对象。

  • 引用类型的赋值不会发生 “拷贝”,当你尝试修改示例的值的时候,实例的指针会 “指引” 你来到堆上,然后修改堆上的内容。
    举例:

  //因为 Point 是类,所以 Point 的存储属性不能直接保存在栈上
class Point {
    var x: Double
    var y: Double
    init(x: Double, y: Double) {
        self.x = x
        self.y = y
     }
}

let point1 = Point(x: 3, y: 5)
let point2 = point1
 
print(point1.x, point1.y)           // 3.0  5.0
print(point2.x, point2.y)           // 3.0  5.0


           栈              堆
point1   [    ] --|
                  |-->  类型信息
point2   [    ] --|     引用计数
                        x: 3
                        y: 5

实际:公用一块堆
  • 分析: 因为 Point 是类,所以 Point 的存储属性不能直接保存在栈上,系统会在栈上开辟两个指针的长度用来保存 point1 和 point2 的指针,栈上的指针负责去堆上找到对应的对象,point1 和 point2 两个实例的存储属性会保存在堆上。

  • 当使用“=” 进行赋值时,栈上会生成一个 point2 的指针,point2 指针与 point1 指针指向堆的同一地址

  • 相比在栈上保存 point1 和 point2,堆上需要的内存空间要更大,除了保存 x 和 y 的空间,在头部还需要两个 8 字节的空间,一个用来索引类的类型信息的指针地址,一个用来保存对象的 “引用计数”

当尝试修改 point2 的值的时候,point2 的指针会 “指引” 你来到堆上,然后修改堆上的内容,这个时候 point1 也被修改了。

point2.x = 5
 
print(point1.x, point1.y)           // 5.0  5.0
print(point2.x, point2.y)           // 5.0  5.0

我们称 point1 和 point2 之间的这种关系为 “共享”。“共享” 是引用类型的特性,在很多时候会给人带来困扰,“共享” 形态出现的根本原因是我们无法保证一个引用类型的对象的不可变性。

三、值类型和引用类型的选择

想要创建一个新的类型,该如何选择呢?当你写Cocoa程序的时候,大多数APIs都需要从NSObject继承,你就已经是一个类了(引用类型),针对其他情况,这里有些指导规则:

  • 使用值类型:

    • 通过使用==去比较实例的数据
    • 想得到一个实例的独立副本
    • 数据在多线程环境下被修改
  • 使用引用类型(比如使用一个类):

    • 通过使用===去判断两个实例是否恒等
    • 想要创建一个共享的,可变的对象

在Swift里,Array、String和Dictionary都是值类型,他们的行为和C语言中的int类似,
每个实例都有自己的数据,你不需要额外做任何事情,比如做一个显式的copy,防止其他代码在你不知情的情况下修改等,更重要的是,你能安全地在线程间传递它,而不需要使用同步技术。在提高安全性的精神下,这个模型将帮助你在Swift中写出更多可预知的代码。

面试题:说说Swift为什么将String,Array,Dictionary设计成值类型?

要解答这个问题,就要和Objective-C中相同的数据结构设计进行比较。Objective-C中,字符串,数组,字典,皆被设计为引用类型。

  • 值类型相比引用类型,最大的优势在于内存使用高效。值类型在栈上操作,引用类型在堆上操作。栈上的操作仅仅是单个指针的上下移动,而堆上的操作则牵涉到合并、移位、重新链接等。也就是说Swift这样设计,大幅减少了堆上的内存分配和回收的次数。同时copy-on-write又将值传递和复制的开销降到了最低。

  • String,Array,Dictionary设计成值类型,也是为了线程安全考虑。通过Swift的let设置,使得这些数据达到了真正意义上的“不变”,它也从根本上解决了多线程中内存访问和操作顺序的问题。

  • 设计成值类型还可以提升API的灵活度。例如通过实现Collection这样的协议,我们可以遍历String,使得整个开发更加灵活高效。

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

推荐阅读更多精彩内容