协议为办法、特点、以及其他特定的使命需求或功用界说蓝图。协议可被类、结构体、或枚举类型选用以供给所需功用的详细完成。满足了协议中需求的恣意类型都叫做遵从了该协议。
除了指定遵从类型有必要完成的要求外,你能够扩展一个协议以完成其间的一些需求或完成一个契合类型的能够利用的附加功用。
协议基本语法
协议界说
界说协议的办法和类、结构体、枚举类型类似,运用protocol
来声明协议
protocol SomeProtocol {
// protocol definition goes here
}
遵从协议
在Swift中, class
、struct
、enum
都能够遵从协议
在自界说类型声明时,将协议名放在类型名的冒号之后来表示该类型选用一个特定的协议。多个协议能够用逗号分开列出:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// structure definition goes here
}
若一个类具有父类,将这个父类名放在其选用的协议名之前,并用逗号分隔:
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// class definition goes here
}
相同,协议也能够遵从协议
protocol SomeProtocol: Protocol1
假如你一个协议只想让类能够遵从这个协议,那么你能够让协议承继自AnyObject
当你遵从一个协议时,你有必要完成协议中的所有没有没有完成的计算特点和办法,不然编译会报错。
协议里边添加特点
协议能够要求所有遵从该协议的类型供给特定姓名和类型的实例特点或类型特点。在协议里边界说一个特点有必要清晰是可读的或可读的和可写的,一起这个特点要求界说为变量特点,在特点称号前面运用var
关键字。可读写的特点运用{getset}
来写在声明后面来清晰,运用{get}
来清晰可读的特点。
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
你也能够在协议中界说类型特点,在界说类型特点时往前面添加static
关键字就能够了。
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
协议里边界说办法
协议里边界说类办法和实例办法
协议能够要求选用的类型完成指定的实例办法和类办法。这些办法作为协议界说的一部分,书写办法与正常实例和类办法的办法完全相同,可是不需要大括号和办法的主体。允许变量具有参数,与正常的办法运用相同的规矩。但在协议的界说中,办法参数不能界说默许值。
当协议中界说类型办法时,你总要在其之前添加static
关键字。即使在类完成时,类型办法要求运用class
或static
作为关键字前缀。
protocol SomeProtocol {
static func someTypeMethod()
}
添加实例办法则不需要在前面加上static
关键字。
protocol RandomNumberGenerator {
func random() -> Double
}
协议里边界说异变办法
有时一个办法需要改动其所属的实例。在办法的func
关键字之前运用mutating
关键字,来表示在该办法能够改动其所属的实例,以及该实例的所有特点。这允许结构体和枚举类型能选用相应协议并满足办法要求。
在下面Togglable
协议的界说中,toggle()
办法运用mutating
关键字符号,来表明该办法在调用时会改动遵从该协议的实例的状况:
protocol Togglable {
mutating func toggle()
}
现在界说一个名为OnOffSwitch
的枚举。这个枚举在两种状况间改动,即枚举成员On
和Off
。该枚举的toggle
完成运用了mutating
关键字,以满足Togglable
协议需求:
enum OnOffSwitch: Togglable {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
协议里边界说初始化办法
协议能够要求遵从协议的类型完成指定的初始化器。你能够经过完成指定初始化器或便捷初始化器来使遵从该协议的类满足协议的初始化器要求。在这两种情况下,你都有必要运用required
关键字润饰初始化器的完成:
protocol SomeProtocol {
init(someParameter: Int)
}
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// initializer implementation goes here
}
}
由于final
的类不会有子类,假如协议初始化器完成的类运用了final
符号,你就不需要运用required
来润饰了。由于这样的类不能被承继子类。
假如一个子类重写了父类指定的初始化器,并且遵从协议完成了初始化器要求,那么就要为这个初始化器的完成添加required
和override
两个润饰符:
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// initializer implementation goes here
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// "required" from SomeProtocol conformance; "override" from SomeSuperClass
required override init() {
// initializer implementation goes here
}
}
协议扩展
运用协议扩展供给默许完成
你能够运用协议扩展来给协议的恣意办法或许计算特点要求供给默许完成。假如遵从类型给这个协议的要求供给了它自己的完成,那么它就会替代扩展中供给的默许完成。
protocol MyProtocol{
var age: Int{get}
func test()
}
extension MyProtocol{
var age: Int {return 10}
func test(){}
}
给协议扩展添加约束
当你界说一个协议扩展,你能够清晰遵从类型有必要在扩展的办法和特点可用之前满足的约束。在扩展协议姓名后边运用where
分句来写这些约束。比如说,你能够给Collection
界说一个扩展来应用于恣意元素遵从上面MyProtocol
协议的集合。
extension Collection where Iterator.Element: MyProtocol {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joined(separator: ", ") + "]"
}
}
和OC交互
假如OC类需要遵从协议,需要在协议前面添加关键字@objc
,假如想运用OC的optionl
的特点,需要在办法或许特点添加关键字@objc optional
。
@objc protocol MyProtocl {
//可选的
@objc optional func func1()
var age: Int { get }
}
协议的办法调度
咱们在之前的办法篇中了解过,类的办法经过VTable
来调度,而结构体和枚举类型的办法是经过静态派发的办法。那么协议里边的办法是经过什么办法来派发的呢?
protocol Incrementable {
func increment(by: Int)
}
class LGTeacher: Incrementable {
func increment(by: Int) {
print(by)
}
}
let t: LGTeacher = LGTeacher()
t.increment(by: 20)
接下来咱们转成sil文件看一下:
能够看到,此刻的increment(by: Int)
办法仍是一个class_method
,因此咱们能够知道这个办法仍是经过VTable
来调度的。咱们再LGTeacher
里边的sil_vtable
里也找到了这个办法。
假如咱们把变量t
声明为协议类型,那么这时分办法是如何调度的呢?
let t: Incrementable = LGTeacher()
转成sil文件后,对应的main
函数里边的代码如下:
能够看到increment
函数不再是class_method
,此刻变成了witness_method
。那这个witness_method
是什么呢?咱们去swift的sil文档里边查看一下:
文档里边的意思是,从一个遵从协议的类型去寻觅协议办法的完成,会经过witness_method
这种办法去调度,假如协议运用@objc
润饰,会变成objc
的调用办法。
一起,咱们在sil里边找到witness_table
,里边包含了协议办法increment
。
接下来咱们经过寻觅increment
的称号,然后查找一下,探究是如何找到LGTeacher
类里边的increment
办法。
能够看到,经过witness_table
里边办法称号,直接从遵从这个协议的类里边,找到这个办法的详细完成,一起经过这个类的V_table
去调度这个办法。
经过上面的剖析咱们能够对协议的办法调用做以下总结:
-
假如实例目标的静态类型便是确认的类型,那么这个协议办法经过
VTalbel
进行调度。 -
假如实例目标的静态类型是协议类型,那么这个协议办法经过
witness_table
中对应的协议办法,然后经过协议办法去查找恪守协议的类的VTable
进行调度。
结构体遵从协议
上面剖析的是类遵从协议后的办法调度,那假如是结构体struct
呢?
把上面的代码改成struct后,咱们再进入到sil文件里边探个终究:
能够发现,和类相同,也是先经过witness_table
里边协议办法去寻觅遵从协议的结构体里的的办法完成,可是由于结构体里边的办法是静态派发,没有VTable
,因此会直接去调用结构体里边的办法。
协议在extension中供给了默许办法完成
假如在一个协议中声明晰办法,然后再extension
中完成了该办法。那会是什么情况呢?
protocol Incrementable {
func increment(by: Int)
}
extension Incrementable {
func increment(by: Int) {
print("Extension Increment")
}
}
class LGTeacher: Incrementable {
func increment(by: Int) {
print("LGTeacher Increment")
}
}
class LGStudent: Incrementable {}
let t1: LGTeacher = LGTeacher()
let t: Incrementable = LGTeacher()
let s: LGStudent = LGStudent()
let s1: Incrementable = LGStudent()
t.increment(by: 10)
t1.increment(by: 20)
s.increment(by: 10)
s1.increment(by: 20)
//打印成果
LGTeacher Increment
LGTeacher Increment
Extension Increment
Extension Increment
能够看到,当恪守协议的类完成了协议办法,那么会去走VTable
调用办法。假如遵从协议的类没有完成协议办法,而且这个协议的extension
供给了办法的默许完成,那么这个类会经过witness_table
直接去静态调用extension
里边的办法完成。
协议中没有声明办法
假如在协议中没有声明办法,然后在extension
中声明办法,那又会怎样样呢?
protocol Incrementable {
}
extension Incrementable {
func increment(by: Int) {
print("Extension Increment")
}
}
class LGTeacher: Incrementable {
func increment(by: Int) {
print("LGTeacher Increment")
}
}
class LGStudent: Incrementable {}
let t1: LGTeacher = LGTeacher()
let t: Incrementable = LGTeacher()
let s: LGStudent = LGStudent()
let s1: Incrementable = LGStudent()
t.increment(by: 10)
t1.increment(by: 20)
s.increment(by: 10)
s1.increment(by: 20)
//打印成果
Extension Increment
LGTeacher Increment
Extension Increment
Extension Increment
打印出来sil文件查看
能够看到,当协议里边没有声明办法时,witness_table
里边没有任何办法,所以无法经过witness_table
调用办法。而LGTeacher
类里边有完成办法,所以t1
会经过V_Table
直接派发。LGStudent
里边没有完成办法,所以是经过extension
静态派发的办法来调用办法。
总结
关于确认类型则并且供给了办法完成的和没有恪守协议的时分相同 关于协议类型,则声明之后就需要经过PWT
办法,再依据实例目标的类型和目标类型中是否完成办法决议调度办法V-Table
派发仍是静态派发。
协议中声明 | 协议中未声明 | |
---|---|---|
确认类型 + 完成 | V-Table | V-Table |
确认类型 + 未完成 | 静态派发 | 静态派发 |
协议类型 + 完成 | PWT+V-Table | 静态派发 |
协议类型 + 未完成 | PWT+静态派发 | 静态派发 |
witness_table和类型的联系
- 当一份协议被多个类型恪守的时分
protocol Incrementable {
func increment(by: Int) -> Int
}
class test : Incrementable {
func increment(by: Int) -> Int {
return by + 1
}
}
class test1: Incrementable {
func increment(by: Int) -> Int {
return by + 2
}
}
能够看到,每个遵从协议的类型都会有一个自己的witness_table
。
- 当一个类恪守多份协议的时分
protocol Incrementable {
func increment(by: Int) -> Int
}
protocol myProtocol {
func test()
}
class test : Incrementable, myProtocol {
func increment(by: Int) -> Int {
return by + 1
}
func test() {
print("123")
}
}
能够看到,一个类遵从多个协议,会为每一个协议添加一个witness_table
,一个类中witness_table
的数量取决于这个类遵从了多少个协议。
- 当一个类遵从了协议,一起有子类。
protocol Incrementable {
func increment(by: Int) -> Int
}
protocol myProtocol {
func test()
}
class test : Incrementable, myProtocol {
func increment(by: Int) -> Int {
return by + 1
}
func test() {
print("123")
}
}
class test1: test {
override func increment(by: Int) -> Int {
return by + 2
}
}
class test2: test {}
能够看到,这个类遵从一个协议,就会有一个witness_table
,假如这个类有子类,那么子类和父类会共用一个witness_table
。
协议的内存结构的底层布局
协议的底层存储
咱们在上面剖析了协议如何调度办法,以及遵从协议的类型和witness_table
的联系,现在咱们来看一下协议是怎样存储的,首要咱们来看一下遵从协议的类型的内存大小。
protocol Shape {
var area: Double { get }
}
class Circle: Shape {
var radious: Double
init(_ radious: Double) {
selfradious = radious
}
var area: Double {
get {
return radious * radious
}
}
}
var circle: Circle = Circle.init(10.0)
var circle1: Shape = Circle.init(10.0)
var t = type(of: circle1)
print(class_getInstanceSize(Circle.self)) //24
print(class_getInstanceSize((t as! AnyClass))) //24
print(MemoryLayout.size(ofValue: circle)) //8
print(MemoryLayout.size(ofValue: circle1)) //40
print(MemoryLayout<Circle>.size) //8
print(MemoryLayout<Shape>.size) //40
能够看到,遵从协议的详细类的实例变量的内存大小和遵从协议的变量的内存大小是不相同的。所以它们在底层的内存结构也是不相同的。咱们先来看一下circle:Cricle
的内存结构。
经过lldb
指令把特点值转成float
类型,能够看到,特点值正是10。
接下来咱们看一下circle1:Shape
的内存结构:
从上面的内存剖析中,咱们能够得到协议的大约存储结构:
- 0-7:实例目标的堆空间地址
- 8-23:不知道
- 24-31: 实例目标的
metadata
- 32-40: 协议的
protocol witness table
由此,咱们能够得到协议类型的大致结构:
struct LGProtocolBox {
var heapObject: UnsafeRawPointer
var unknow1: UnsafeRawPointer
var unknow2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeRawPointer
}
接着咱们把代码转成IR代码看一下:
define i32 @main(i32 %0, i8** %1) #0 {
entry:
// %T4main5ShapeP = type { [24 x i8], %swift.type*, i8** }
%2 = bitcast i8** %1 to i8*
// 创立Circle类的metadata
%3 = call swiftcc %swift.metadata_response @"$s4main6CircleCMa"(i64 0) #4
%4 = extractvalue %swift.metadata_response %3, 0
// 创立一个Circle类的实例目标 s4main6CircleCACycfC == __allocating_init
%5 = call swiftcc %T4main6CircleC* @"$s4main6CircleCACycfC"(%swift.type* swiftself %4)
// 把metadata寄存的%T4main5ShapeP数组的第二个成员变量里。
// %T4main5ShapeP = type { [24 x i8], metadata, i8** }
store %swift.type* %4, %swift.type** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"$s4main7circle1AA5Shape_pvp", i32 0, i32 1), align 8
//s4main6CircleCAA5ShapeAAWP = protocol witness table for main.Circle
//把protocol witness table存储到数组的第三个成员变量里
// %T4main5ShapeP = type { [24 x i8], metadata, witness_table }
store i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"$s4main6CircleCAA5ShapeAAWP", i32 0, i32 0), i8*** getelementptr inbounds (%T4main5ShapeP, %T4main5ShapeP* @"$s4main7circle1AA5Shape_pvp", i32 0, i32 2), align 8
//把实例目标的地址寄存到数组的第一个成员变量里
//%T4main5ShapeP = type { [heapObject, Unknown, Unknown], metadata, witness_table }
store %T4main6CircleC* %5, %T4main6CircleC** bitcast (%T4main5ShapeP* @"$s4main7circle1AA5Shape_pvp" to %T4main6CircleC**), align 8
ret i32 0
}
接下来咱们看一下witness_table的内存结构
//s4main6CircleCAA5ShapeAAWP = protocol witness table for main.Circle,
//witness_table的内存结构
@"$s4main6CircleCAA5ShapeAAWP" = hidden constant [2 x i8*] [i8* bitcast (%swift.protocol_conformance_descriptor* @"$s4main6CircleCAA5ShapeAAMc" to i8*), i8* bitcast (double (%T4main6CircleC**, %swift.type*, i8**)* @"$s4main6CircleCAA5ShapeA2aDP4areaSdvgTW" to i8*)], align 8
这里边存储了两个变量,一个是protocol_conformance_descriptor
,一个是遵从了shape
协议完成了area
特点的protocol witness
由此咱们能够得到witness_tabel
的大约数据结构
struct TargetWitnessTable {
var protocol_conformance_descriptor: UnsafeRawPointer
var protocol_witness: UnsafeRawPointer
}
struct LGProtocolBox {
var heapObject: UnsafeRawPointer
var unknow1: UnsafeRawPointer
var unknow2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeMutablePointer<TargetWitnessTable>
}
接下来,咱们去源码里边寻觅witness_tabel
的详细细节。首要咱们大局查找TargetWitnessTable
,找到的代码如下:
TargetWitnessTable
里边有一个TargetProtocolConformanceDescriptor
类型的description
特点。咱们再去查看TargetProtocolConformanceDescriptor
类。
能够看到,这个类里边有四个特点:
TargetRelativeContextPointer<Runtime, TargetProtocolDescriptor> Protocol;
TargetTypeReference<Runtime> TypeRef;
RelativeDirectPointer<const TargetWitnessTable<Runtime>> WitnessTablePattern;
ConformanceFlags Flags;
接下来咱们对这四个特点进行剖析,咱们先看protocol
特点,这是一个指向TargetProtocolDescriptor
类型的TargetRelativeContextPointer
。咱们先看一下TargetRelativeContextPointer
。
能够看到其实和# Swift进阶(四)—— 指针中的TargetRelativeDirectPointer
其实是相同的功用便是获取相对地址绝对地址,能够直接用TargetRelativeDirectPointer
代替。
接下来咱们看一下TargetProtocolDescriptor
,代码如下:
咱们把TargetProtocolDescriptor
复原成下面的结构体:
struct TargetProtocolDescriptor {
var flags: UInt32
var parent: TargetRelativeDirectPointer<UnsafeRawPointer>
var name: TargetRelativeDirectPointer<CChar>
var NumRequirementsInSignature: UInt32
var NumRequirements: UInt32
var AssociatedTypeNames: TargetRelativeDirectPointer<CChar>
}
witness_table的内存结构
经过上面的剖析咱们能够把witness_table
的内存结构复原出来,代码如下:
struct LGProtocolBox {
var heapObject: UnsafeRawPointer
var unknow1: UnsafeRawPointer
var unknow2: UnsafeRawPointer
var metadata: UnsafeRawPointer
var witness_table: UnsafeMutablePointer<TargetWitnessTable>
}
struct TargetWitnessTable {
var protocol_conformance_descriptor: UnsafePointer<TargetProtocolConformanceDescriptor>
var protocol_witness: UnsafeRawPointer
}
struct TargetProtocolConformanceDescriptor {
var ptotocolDesc: TargetRelativeDirectPointer<TargetProtocolDescriptor>
var typeRef: UnsafeRawPointer
var WitnessTablePattern: UnsafeRawPointer
var flags: UInt32
}
struct TargetProtocolDescriptor {
var flags: UInt32
var parent: TargetRelativeDirectPointer<UnsafeRawPointer>
var name: TargetRelativeDirectPointer<CChar>
var NumRequirementsInSignature: UInt32
var NumRequirements: UInt32
var AssociatedTypeNames: TargetRelativeDirectPointer<CChar>
}
struct TargetRelativeDirectPointer<Pointee> {
var offset: Int32
mutating func getmeasureRelativeOffset() -> UnsafeMutablePointer<Pointee> {
let offset = self.offset
return withUnsafePointer(to: &self) { p in
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self))
}
}
}
接下来咱们来验证witness_table
的内存结构。代码如下:
var circle: Shape = Circle.init(10.0)
withUnsafePointer(to: &circle) { ptr **in**
ptr.withMemoryRebound(to: LGProtocolBox.**self**, capacity: 1) { pointer **in**
print(pointer.pointee)
let descPtr = pointer.pointee.witness_table.pointee.protocol_conformance_descriptor.pointee.ptotocolDesc.getmeasureRelativeOffset()
print("协议称号:\(String(cString: descPtr.pointee.name.getmeasureRelativeOffset()))")
print("协议办法的数量:\(descPtr.pointee.NumRequirements)")
print("witnessMethod:\(pointer.pointee.witness_table.pointee.protocol_witness)")
}
}
打印成果如下:
咱们在剖析 IR 代码的时分,应该有注意到 TargetWitnessTable
的 protocol_witness
,这一个其实存储的便是咱们的 witnessMethod
,在上面的 IR 代码中其完成已写的很清楚了,但咱们仍是来验证一下。
- 在终端运用
nm -p <可执行文件> | grep <内存地址>
打印出这个办法的符号信息。 - 接着用
xcrun swift-demangle <符号信息>
复原这个符号信息。 复原成果如下:
所以,这个协议见证表(witness_table)的实质其实便是 TargetWitnessTable。第一个元素存储的是一个 descriptor,记载协议的一些描述信息,例如称号和办法的个数等。那么从第二个元素的指针开始存储的便是函数的指针。
从上面的IR代码中,咱们知道witness_table 变量是一个接连的内存空间,所以这个 witness_table
变量寄存的可能是许多个协议的见证表。
寄存多个协议见证表的因素取决于变量的静态类型,假如这个变量的类型是协议组合类型,那么 witness_table 寄存的便是协议组合中所有协议的见证表,假如这个变量的类型是指定单独的某个协议,那么 witness_table 寄存的只有这个协议的见证表。
Existential Container
咱们在上面的代码中知道了witness_table
的内存结构,那么存储了witness_table
的LGProtocolBox
又是什么东西呢?
在Swift里边,它有一个称号叫做Existential Container(存在容器)。这是编译器生成的一种特殊的数据类型,用于办理恪守了相同协议的协议类型,由于这些类型的内存大小不一致,所以经过当前的 Existential Container 统一办理。规矩如下:
- 关于小容量的数据,直接存储在 Value Buffer
- 关于大容量的数据,经过堆区分配,存储堆空间的地址
那这个Existential Container(存在容器) 是怎样完成的呢?咱们经过代码来观察一下。
经过上面剖析,咱们知道,当遵从协议的类型是引证类型的时分,它的第一个内存存储的是实例目标的堆空间地址。那当遵从协议的类型是值类型时,那这个Existential Container(存在容器) 是怎样存储的呢?
首要,咱们界说一个遵从shape协议的struct类。然后给这个结构体添加特点。代码如下:
protocol Shape {
var area: Double { get }
}
struct Circle: Shape {
var radious: Double
var width: Double = 20
var height: Double = 30
init_ radious: Double) {
self.radious = radious
}
var area: Double {
get {
return 10.0
}
}
func getter() {
print( #function)
}
}
var circle: Shape = Circle.init(10.0)
print(MemoryLayout.size(ofValue: circle))
//打印成果
40
能够看到,遵从协议的结构体实例目标的内存大小和类实例目标相同,都是40。然后咱们去查看一下内存结构。
能够看到,和引证类型的内存结构有所不相同,第二个8字节和第三个8字节都有存储值。而且第一个8字节里边存储的也不是引证地址。经过expr
指令解析。成果如下:
能够看到,里边刚好存储的是值类型的3个特点值。而这便是上面讲的Existential Container(存在容器) 的第一条办理规矩:关于小容量的数据,直接存储在 Value Buffer。
假如咱们给Circle结构体再添加一个特点值,会变成什么样呢?成果如下: 咱们发现,当结构体的特点值比较多的时分,它的内存结构又变了。变成和类实例目标的内存结构相同。
经过剖析第一个字节存储的内存地址,咱们发现,当值类型的特点比较多的时分,编译器会专门在堆区分配一个空间用来存储这些特点值,一起把这个堆空间的内存地址存储在实例目标的内存结构中,这便是上面讲的Existential Container(存在容器) 的第二条办理规矩:关于大容量的数据,经过堆区分配,存储堆空间的地址。
写时仿制
上面讲过,当值类型的数据比较大的时分,会在堆空间分配存储空间用来存储,那这样还会坚持值类型的特性吗?
protocol Shape {
var area: Double {get}
var radious: Double{get set}
}
class Circle: Shape{
var radious: Double
var width: Double = 20
var height: Double = 30
var height1: Double = 50
init(_ radius: Double) {
self.radius = radius
}
var area: Double{
get{
return radius * radius * 3.14
}
}
var area1: Double{
get{
return radius * radius * 3.14
}
}
}
var circle1 : Shape = Circle.init(10.0)
var c = circle1
c.radious = 20
print(circle1.radious) // 10.0
print(c.radious)//20.0
能够看到,当c
的radius
特点值改动的时分,circle
的特点值没变。仍是坚持着值类型的特性,那这个是怎样完成的呢?咱们经过内存结构来看一下。
咱们先把断点打在c
的radius
特点值未产生改动的当地,然后看下c
和circle
有没有产生变化。
当c
的radius
特点值未产生改动时,c
和circle
存储的堆空间地址都是相同的。
然后,咱们对c
的radius
特点值进行修改,会产生什么呢?
能够看到,当对c
的radius
特点值进行修改后,c
存储的堆空间地址产生了改动,而这个新的堆空间地址寄存着修改后的特点值。
针对遵从协议的具有比较大的数据的值类型,Swift选用了一种写时仿制的技能,即会去判别这个堆空间的引证计数,当引证计数大于2的时分,也便是有多个实例变量在引证这个堆空间的地址,当其间一个实例变量产生特点值改动的时分,就会在堆空间从头仿制一个新的存储空间,并把这个新的空间地址,传给要改动特点值的实例变量,用来坚持修改后的实例值,这样做除了会坚持值类型的特性外,还减少了内存分配的耗费,避免了创立了一些没有用的存储空间。