Swift基础之结构体和类

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

推荐阅读更多精彩内容