swift-类和结构体

结构体和类

  • swift 中储存结构化的数据 可以用 结构体,枚举,类及使用闭包捕获变量。
  • 类和结构体不同点: 1.结构体是值类型类是引用类型,编译器可以保证结构体的不变性,类要我们自己来保证 2.内存管理方式不同接头体可以直接持有及访问,类的实例只能通过地址间接访问,结构体是唯一的,类能有多个持有者。3.类可以代码共享,结构体不能被继承
  1. 值类型
  • 我们经常处理一些有生命周期的类型 ViewController 有init,delloc等等,而有的则不需要生命周期例如 URL 它一旦创建就不会被改变,它们结束时不需要额外操作。比较两个URL的值时 我们不关心是否指向同一地址,而是它们的属性是否一样。这种类型我们称之为值类型。NSURL 是一个不可变对象 而URL 是一个结构体。
  • 值类型具有天然的线程安全性。不可变的东西是可以多线程共享的。、
  • 结构体中也能定义 var 类型 但这个可变性只体现在变量自己身上。当我们改变结构体中的属性时 它总是生成一个全新的结构体来取代
  • 结构体只能有一个持有者,当我们把它传递给一个函数是它被复制了一份 函数只能改变这个副本,这被叫做 值语义。而对象传递的是地址指针,称为引用语义。
  • 结构体只有一个持有者所以不会造成循环引用,除非结构体包含类属性,否则就不需要考虑引用计数问题,let声明的结构体 一个字节也不会改变
  1. 可变性
  • 可变性是造成bug主要原因之一,而swift可以让我们写出安全代码的同时,保留可变代码的风格
var mutabelArray: NSMutableArray = [1,2,3]
for _ in mutabelArray {
    mutabelArray.removeLastObject()
}
  • 我们知道数组的变化会破坏迭代器的内部结构 这个是不被允许的,我们知道这一点,不会犯这种错误。 然而我们不能保证 removeLastObject 不被其他地方调用。这种错误很难被发现
    swift 就避免了这种错误
var mutabelArray = [1,2,3]
for _ in mutabelArray {
    mutabelArray.removeLast()
}
  • 赋值问题 引用类型会改变赋值它的变量的属性 这是很强大的特性,但有时也会造成bug
var mutabelArray: NSMutableArray = [1,2,3]
var new = mutabelArray
new.add(4)

我们实现一个扫描器

class BinaryScanner {
    var position: Int
    let data: Data
    init(data: Data) {
        self.position = 0
        self.data = data
    }
}

extension BinaryScanner {
    func scanByte() -> UInt8? {
        guard position < data.endIndex else {
            return nil
        }
        position += 1
        return data[position - 1]
    }
}

正常可运行
func scanRemainingBytes(scanner: BinaryScanner) {
    while let byte = scanner.scanByte() {
        print(byte)
    }
}

有可能偶发线程不安全
for _ in 0..
  1. 结构体
  • 几乎在所有编程中 标量都是值类型
  • 当我们把一个结构体赋值给另一个时,swift 自动对它进行赋值,听起来很昂贵, 但编译器会对这种赋值进行优化,称为写时复制。
struct Point {
    var x: Int
    var y: Int
}

let origin = Point(x: 0, y:0)
origin.x = 10 // ❎

var otherPoint = Point(x: 0, y:0)
otherPoint.x = 10 // ✅

当我们把一个结构体赋值给另一个时
var otherPoint = origin
otherPoint.x = 10 // ✅
otherPoint // x: 10, y: 0
origin // x: 0, y: 0

struct Size {
    var width: Int
    var height: Int
}

静态变量
extension Point {
    static let origin = Point(x: 0, y: 0)
}

struct Rectangle {
    var origin: Point
    var size: Size
}

写在扩展中的初始化方法,系统会保留原始的初始化方法。
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)
    }
}


var screen = Rectangle(width: 320, height: 480) {
    didSet {
        print("screen did Changed \(screen)")
    }
}

screen.origin.x = 10
我们只是改变了结构体的数量 但是它的didset会被触发

var array = [screen] {
    didSet {
        print("array did Changed")
    }
}

array[0].origin.x = 10
数组是结构体 数组内元素的变化 数组本身 也会触发 didset
如果Rectangle是类 那么 didset就不会被触发


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) {
        origin = origin + offset
    }
}

标记了mutating 意味着 self是可变的 表现的想一个 var
当然 let 声明的结构体 依然是不可变得

很多情况下 方法都有可变和不可变两种版本 ,sort() 原地排序 sorted() 返回新的数组

extension Rectangle {
    func translated(by offset: Point) {
            var copy = self
                copy.translate(offset: offset)        
            return copy
    }
}

事实上 mutating 方法只是结构体上 普通的方法 只是 self 被隐士的标记为 inout 了 &

我们再来看上面的scanner 问题 如果 BinaryScanner 是 结构体那么 每个方法调用的结构体都是一个 副本这样 就可以安全的迭代了而不用担心被其他线程更改。

  1. 写时复制 copy-on-write

var x = [1,2,3]
var y = x
如果创建一个y 把并把x赋值它时 会发生复制
x 和 y 含有独立的结构体。
但是 数组内含有指向 元素位置的指针 在这时 x 和 y 共享了他们的部分储存 不过 当x改变时 这种共享 会被检测到,内存将被复制。
只有在 有一个 发生改变时 内存才被复制 成为写时复制

  • 昂贵方式
struct Mydata {
    fileprivate var _data: NSMutableData
    var _dataFOrWriting: NSMutableData {
        mutating get {
            _data = _data.mutableCopy() as! NSMutableData
            return _data
        }
    }

    init(data: NSData) {
        self._data = data.mutableCopy() as! NSMutableData
    }
}

  • 高效方式

在 swift 中 可以用 isKnownUniquelyReferenced(&<#T##object: T##T#>)
来检查引用的唯一性。
获取时 可以通过 是否被唯一引用来决定是否 复制

得益于写时复制和相关联的编译器优化 大量不必要的操作被移除了。
如果我们写一个 结构体 而不能保证其中属性的不变性,我们可以考虑使用类来实现。

  • 写时复制陷阱

我们使用array[0] 时 直接用下标访问 是不会发生写时复制的 因为它直接访问了内存中元素的位置。而字典和set 不同

  1. 闭包和可变性
    一个函数 每次生成一个唯一的整数直到Int.max可以将状态移动到函数外部,换句话说函数对i进行了闭合
var i = 0

func uniqueInteger() -> Int {
    i += 1
    return i
}

swift 函数也是引用类型

let otherFunction = uniqueInteger
传递函数它会已引用方式存在 并共享了 其中的状态

func uniqueIntegerProvider() -> () -> Int {
    var i = 0
    return {
        i += 1
        return i
    }
}
返回一个从零开始的方法
也可以封装成 AnyIterator 一个整数发生器
func uniqueIntegerProvider() -> AnyIterator {
    var i = 0
    return AnyIterator {
        i += 1
        return i
    }
}
  • 结构体 一般被储存在栈上,而非堆上这其实是一种优化,默认结构体是在堆上的,但大多数情况下 优化都会生效
  • 当一个结构体被函数闭合了 它就在堆上就算函数退出了作用域,它仍然存在,同样结构体过大 也会放在堆上
  1. 内存 swift 的强引用和循环引用类似于OC weak(必然是可选值) unowned [weak self]

你可能感兴趣的:(swift-类和结构体)