import UIKit
/// 结构体和类
///swift中结构化的数据类型:结构体,枚举,类以及闭包捕获变量
// Mark: 结构体和类的区别
//1、结构体(和枚举)是值类型,编译器可以保证值不可变,类是引用类型,开发者自己按需保证
//2、内存管理方式不同。结构体被直接持有及访问。类通过引用间接访问。结构体不会被引用,但是会被赋值。也就是说结构体的持有者是惟一的,类的实例可能有多个持有者
//3、类可以继承,结构体不可以继承。想要在结构体之间共享代码,需要额外的技术:组合,泛型,协议扩展等
//Mark: 类和结构体如何选择
//当对象具有一定的生命周期,在初始化或销毁时需要特定的操作,需要比较他们的内存地址时一般选用类(引用类型):例如 文件句柄,通知中心, 网络接口, 数据库连接, view controller 等等
//当类型的值通过属性来定义,不需要改变,不关心内存地址时一般用结构体:例如 URL,二进制数据,日期,错误,字符串,通知
//值类型具有线程安全特性,因为不能改变的东西在多线程之间是安全共享的
// Mark: 问题: 既然值类型是不可变的,为什么还有 var stu = Student(xxx) 这样的结构体变量呢?
//答 : 因为var的可变性体现在变量上,指的是变量可变,而不是值可变
//改变一个结构体的属性 和 为整个变量赋值一个全新的结构体是等价的。 我们总是使用一个新的结构体,并设置被改变的属性,然后用它替换原来的结构体
//因为结构体的持有者只有一个,所以不会造成循环引用
// 使用let声明的结构体,编译器可以保证任何一个字节都不会改变
//值类型总是需要复制,听起来效率低,不过,编译器会优化,避免不必要的浅复制
//结构体的复制时按照字节进行浅复制,不用考虑引用
//编译器对值类型的优化和值语义类型的写时复制不是一回事儿。写时复制需要自己实现
//当使用结构体时,编译器可以生成非常快的代码:对包含结构体的数组进行操作的效率比包含引用的数组效率高。因为结构体更直接,值直接存储在数组中,而引用需要跳转。很多时候结构体时放在栈上的
struct Point {
var x: Int
var y: Int
}
struct Size {
var width: Int
var height: Int
}
// Mark : 为具有值语义的变量添加didSet
var point = Point(x:10,y: 10) {
didSet {
print("Point changed")
}
}
point.x = 20 // "Point changed"
var x = 10 {
didSet {
print("x changed")
}
}
x = 20 // “x changed”
struct Rectangle {
var origin: Point {
didSet {
print("origin changed")
}
}
var size: Size
}
var origin = Point (x: 50, y: 50)
var frame = Rectangle(origin: point, size: Size(width: 10, height: 10)) {
didSet {
print("frame changed")
}
}
// 即使结构体结构嵌套很深, 内部的属性改变,也会一层层传递到最外层,并且触发每一层的didSet方法
frame.origin.x = 100
// Mark: 通过“扩展” 扩展结构体初始化方法
// 如果在结构体中自己一初始化方法,那么swift就不在为它自动生成基于成员的初始化方法。通过“扩展”可以保留原来的初始化方法
extension Rectangle {
init(x: Int = 0, y: Int = 0, width: Int, height: Int) {
origin = Point(x: x,y: y)
size = Size(width: width, height: height)
}
}
// Mark: 可变方法
// 为Rectangle添加translate方法: origin偏移
func +(lhs: Point, rhs: Point) -> Point {
return Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
//extension Rectangle {
// func translate(by offset: Point) {
// origin = origin + offset // 因为结构体是值类型,self是不可变的,所以此处报错。如果要改变则需要标记为:mutating
// }
//}
extension Rectangle {
mutating func translate(by offset: Point) { //self相当于函数的隐式参数,mutating的作用是标记 self的属性可以被修改
origin = origin + offset
}
}
// 由于mutating标记了变量的可变性,所以不能对用let声明的不可变成员进行改变.集合的append方法就是mutating的,所以不能对let声明的集合调用append方法
//let errorScreen = frame
//errorScreen.translate(by: Point(x: 10, y: 10)) // Error: change 'let' to 'var' to make it mutable
// 注意: 任何mutating的方法的调用或者隐式的可变setter都可能触发willSet和didSet
// Mark : mutating 是如果工作的: inout参数
// 函数的参数传递是值传递,并且是不可变的: 相当于拷贝了一个副本,并声明成一个let类型。
//inout的作用是:拷贝一个副本,且是可变的,函数返回后会覆盖原来的值。: 相当于拷贝一个副本,并声明为var类型,函数返回后,覆盖原值
// mutating的作用:将隐式的self标记为inout
var var1: Int = 10
var var2: Int = 20
print("\(String(format: "%p", var1))")
func addOne(x: inout Int) {
x += 1
print("\(String(format: "%p, %d", x, x))")
}
addOne(x: &var1)
print(var1)
// Mark: += , *= , -= 等对左侧进行改变的运算符都是inout类型的
func +=(lhs: inout Point, rhs: Point) {
lhs = lhs + rhs
}
// Mark: 写时复制
//在swift标准库中,Array, Dictionary和Set是使用写时复制(copy-on-write)技术实现的
// Array结构体含有指向某个内存的引用,指向数组元素所存储的位置。 x1,和y1指向的是内存的同一个位置,共享了它们的存储部分,不过,当我们改变x1的时候,这个共享位置会被检测到,内存将会被赋值,这样会产生拥有独立地址的两个变量
var x1 = [1, 2, 3]
var y1 = x1
//Mark: 写时复制的工作方式:
// 每当数组改变时,它首先检查它对h存储缓冲区的引用是否唯一,或者说,检查数组本身是不是这块缓冲区的唯一拥有者,如果是,那么缓冲区可以进行原地变更;也不会有复制被执行。如果缓冲区有一个以上的持有者,那么数组就需要先进行复制,然后对复制的值进行变化,而保持其他的持有者不受影响
// Mark: 昂贵方式实现写时复制
struct MyData {
fileprivate var _data: NSMutableData
fileprivate var _dataForWriting: NSMutableData {
mutating get { //因为改变了结构体成员,所以声明成mutating
_data = _data.mutableCopy() as! NSMutableData //每次都会复制一个新值,所以昂贵
return _data
}
}
init() {
_data = NSMutableData()
}
init(_ data: NSData) {
_data = data.mutableCopy() as! NSMutableData
}
}
extension MyData {
mutating func append(_ byte: UInt8) {
var mutableByte = byte
_dataForWriting.append(&mutableByte, length: 1)
}
}
let theData = NSData(base64Encoded: "abde")!
var x2 = MyData(theData)
let y2 = x2
x2._data === y2._data
x2.append(0x55)
x2._data === y2._data
// Mark: 高效方式实现写时复制
//使用 isKnownUniquelyReferenced函数检查某个引用知否只有一个持有者,如果某实例只有一个强持有者,则返回true,如果有多个,则返回false。 对于OC类,直接返回false
//使用Swfit类来包装OC类,使isKnownUniquelyReferenced 生效
final class Box<A> {
var unBox: A
init(_ value: A) {
self.unBox = value
}
}
struct NewData {
fileprivate var _data: Box<NSMutableData>
fileprivate var _dataForWriting: NSMutableData {
mutating get {
if !isKnownUniquelyReferenced(&_data) {
_data = Box(_data.unBox.mutableCopy() as! NSMutableData)
print("Making a copy")
}
return _data.unBox
}
}
init() {
_data = Box(NSMutableData())
}
init(_ data: NSData) {
_data = Box(data.mutableCopy() as! NSMutableData)
}
}
extension NewData {
mutating func append(_ byte: UInt8) {
var mutableByte = byte
_dataForWriting.append(&mutableByte, length: 1)
}
}
var bytes = NewData()
var copy = bytes
for byte in 0..<5 as CountableRange<UInt8> {
print ("appending0x\(String(byte, radix: 16))")
bytes.append(byte)
}
//写时复制的陷阱:
//数组下标访问元素是直接访问内存的位置,而字典的下标访问是寻找值并返回(具有值语义)
//var otherArray = [SomeStruct()]
//otherArray[0].change() //不会触发复制
//var xxx = otherArray[0]
//xxx.change() // xxx是第一个元素的复制,所以引用不唯一,会触发写时复制
//
//var dic = ["key": SomeStruct()]
//dic["key"]?.change() //触发写时复制
// Mark : 切记: 当我们创建一个存储某个值的简单容器时,,通过直接访问存储属性,或者简介使用下标,都可以访问到这个值。当我们直接访问的时候,我们可以获得写时复制优化,当我们使用下标间接访问的时候,复制发生
struct ContainerStruct<A> {
var storage: A
subscript(s: String) -> A {
get { return storage } //值被赋值,引用不再唯一
set { storage = newValue }
}
}
var d = ContainerStruct(storage: NewData())
d.storage.append(0x11) //未触发写时复制
d["test"].append(0x12) //触发写时复制
// Mark: 内存
//在API中,是应该选择使用unowned呢,还是应该使用weak?
//从根本上来说,这个问题取决于相关对象的生命周期。如果这些对象的生命周期互不相关,也就是说,你不能保证哪一个对象存在的时间会比另一个长,那么弱引用就是唯一的选择
//如果你可以保证非强引用对象拥有和强引用对象同样或者更长的生命周期的话,unowned 引用通常会更方便一些。这是因为我们可以不需要处理可选值,而且变量将可以被 let 声明
//unowned 引用要比 weak 引用少一些性能损耗,因此访问一个 unowned 引用的属性或者调用它上面的方法都会稍微快一些;
Swift基础之结构体和类
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...