Swift学习-进阶02

swift对象本质,一个swift的对象默认占用16字节大小,在内存中的结构:

HeapObject {
    metadata
    refcounted
}

swift类结构:

struct swift_class_t: NSObject{
    void *kind; //isa, kind(unsigned long) 
    void *superClass;
    void *cacheData
    void *data
    uint32_t flags; //4
    uint32_t instanceAddressOffset; //4
    uint32_t instanceSize;//4
    uint16_t instanceAlignMask; //2
    uint16_t reserved; //2
    uint32_t classSize; //4
    uint32_t classAddressOffset; //4
    void *description;
    // ...
};

属性

存储属性:占用对象大小,需要在申请内存时一起申请
计算属性:只有get,set方法,不占用对象大小
属性观察:编译器会在set方法执行前后分别插入willSet和didSet方法
lazy变量本质:变量会被声明成一个Optional类型的,在第一次访问变量时才会进行真正的赋值操作
static类型属性:属性会被声明成全局属性,global_init方法中会对属性进行初始化,并且会通过dispatch_one确保只初始化一次,并且线程安全

struct本质:在其init方法中在栈区分配对应大小的内存,赋值给self,然后返回
mutating本质:一般用在值类型中,就是把不可修改的self用inout修饰,使其能修改

方法调度

struct中方法:静态派发,函数代码直接在mach-0文件的text段中,调用方法时直接通过地址跳转
class中方法:
一般的方法,函数表调用,类中的方法都是放在sil-vtable中的,调用时会先找函数表地址(函数表相对于对象的metadata偏移值在编译时就能确定了),然后通过函数表的偏移查找具体的方法,最后执行跳转
extension的方法,直接调用,为什么?因为如果也放vtable中的话,那么在编译过程中是没法确定extension中的方法应该放到函数表的哪个位置的
@objc声明的方法,函数表调用,@objc只是另外生成了一个方法供oc调用,该方法内部会调用swift原有的方法,但前提是当前类需要继承自NSObject
dynamic声明的方法,函数表调用,但该方法可以通过在另一个方法上添加@_dynamicReplacement(for:xxx)声明达到hook swift方法的效果
@objc dynamic声明的方法,msgSend消息转发,所以@objc只是暴露方法给oc调用,配合dynamic才能实现方法调度的改变

指针

指针分为typePointer和rawPointer,前者需要指定类型,后者不需要,有以下几种指针:
unSafePointer<T> ---- const T *
unSafeMutablePointer<T> ---- T *
unSafeRawPointer ---- const void *
unSafeMutableRawPointer ---- void *

拿值类型的指针需要通过withUnsafePointer:

struct Teacher {
    var age: Int = 10
}
var t = Teacher()
withUnsafePointer(to: &t) { pt in
    print(pt)
}

引用类型本身返回的就是一个地址,可以通过passUnretained转换

class Teacher {
    var age: Int = 10
}
var t = Teacher()
let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()

使用示例,用指针查看一下对象的metadata在底层的数据结构:

struct HeadObject {
    var kind: UnsafeRawPointer
    var strongref: UInt32
    var unownedref: UInt32
}
struct lg_swift_class {
    var kind: UnsafeRawPointer
    var superClass: UnsafeRawPointer
    var cachedata1: UnsafeRawPointer
    var cachedata2: UnsafeRawPointer
    var data: UnsafeRawPointer
    var flags: UInt32
    var instanceAddressOffset: UInt32
    var instanceSize: UInt32
    var flinstanceAlignMaskags: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressOffset: UInt32
    var description: UnsafeRawPointer
}

class Teacher {
    var age: Int = 10
//    var name: String = "12345678912345"
}
var t = Teacher()
let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
let hepObject = ptr.bindMemory(to: HeadObject.self, capacity: 1)
let metaPtr = hepObject.pointee.kind.bindMemory(to: lg_swift_class.self, capacity: 1)
print(metaPtr.pointee)
print("end")

bindMemory:更改内存绑定的类型,如果没有绑定过,则是首次绑定,否则会重新绑定成该类型,一般用在rawpointer向typepointer类型转换上

struct HeapObject {
    var value1 = 10
    var value2 = 20
}

func testPointer(_ value: UnsafePointer<Int>) {
    print(value)
}

var t = HeapObject()
withUnsafePointer(to: &t) { ptr in
    let value1Ref = UnsafeRawPointer(ptr) + MemoryLayout<HeapObject>.offset(of: \HeapObject.value1)!
    testPointer(value1Ref.assumingMemoryBound(to: Int.self))
}

assumingMemoryBound:假定内存绑定,只是告诉编译器不需要检查类型了,一般用在rawpointer向typepointer类型转换上

var a = 10

func testPointer(_ value: UnsafePointer<Int64>) {
    print(value)
}

withUnsafePointer(to: &a) { ptr in
    ptr.withMemoryRebound(to: Int64.self, capacity: 1) { ptr2 in
        testPointer(ptr2)
    }
}

withMemoryRebound:临时更改内存绑定类型,一般用在typepointer向typepointer转换上

引用计数

swift对象HeapObject中有一个refcounted属性,64位8字节,里面存放了引用计数相关信息。

0           1-31        32         33-62         63
isImmoral   无主引用   正在释放标识    强引用    userSlowRC

默认的强引用和无主引用计数都为1,当赋值给一个变量时会增加它的强引用值。
当一个变量用weak修饰时,会触发swift_weakInit,创建WeakRefrence对象,最后把HeapObjectSideTableEntry的地址放到对象的refcounted下。

捕获列表

在编译到捕获列表的时候就赋值了
var age = 0
var height = 0
let closure = { [age] in
print(age) // 0
print(height) //10
}
age = 10
height = 10

swift的runtime

不管是原生的swift类还是继承自nsobject的类,只有被@objc修饰的变量和方法才能通过runtime的方法获取到方法和属性列表,即使是使用dynamic修改的变量也不行。另外对于纯swift类其默认基类swiftObject,和oc的objc_class类在数据结构上保持了部分一致,所以才让这些类具备了一些runtime的特性。那既然纯swift类默认基类也有一部分oc的影子,那为什么还有继承NSObject这一说呢,那是因为在编译层面还是有差别的,对于继承自oc的类和非继承在调用底层的一些构造方法以函数会有不同的分支。

type(of:)的注意点

动态获取一个值的类型,经常看运行时该对象的真实类型就行了。但有一种情况比较特殊,当单纯的协议或者单纯的范型时都能正常识别出来,但当范型遇上协议时,只能识别成协议类型,需要把协议声明的对象进行一次转换才能识别到它运行时的具体类型。

protocol TestProtocol {}
class Teacher:TestProtocol {}

func test<T>(_ value: T) {
//    let typestr = type(of: value) // TestPrtcol
    let typestr = type(of: value as Any) // Teacher
    print(typestr)
}
let t: TestProtocol = Teacher()
test(t)

错误

swift中的错误Error是一个协议,只要实现协议就可以使用throw关键字进行异常的拋出,并且在对应的方法返回值前通过关键字throws声明方法可能会返回异常

enum CustomError: LocalizedError {
    case err1
    case err2

    var errorDescription: String? {
        switch self {
        case .err1:
            return "msg err1"
        case .err2:
            return "msg err2"
        }
    }
}

func test(_ value: Int) throws -> Int {
    if value < 10 {
        throw CustomError.err1
    }
    if value < 20 {
        throw CustomError.err2
    }
    print("test end")
    return value + 10000
}

let a = 8
do {
    let t = try test(a)
    print("success \(t)")
} catch {
    print("\((error as? CustomError)?.localizedDescription ?? "")")
}

swift调用c函数方法

//在Test.c中有如下代码
#include "Test.h"

int lg_add(int a, int b) {
    return a + b;
}

//在main.swift中有如下代码
@_silgen_name("lg_add")
func swift_lg_add(a: Int, b: Int) -> Int

let ret = swift_lg_add(a: 10, b: 20)
print(ret)

反射

Mirror的本质是从对象的metadata的description属性中获取其对应信息,type,属性数量,属性名称,属性值

struct StructMetaData {
    var kind: Int
    var desc: UnsafeMutablePointer<StructMetadataDesc>
}

struct StructMetadataDesc {
    var flags: Int32
    var parent: Int32
    var name: RelativeDirectPointer<CChar>
    var accessFun: RelativeDirectPointer<UnsafeRawPointer>
    var fields: RelativeDirectPointer<FieldDescriptor>
    var numFields: Int32
    var fieldOffsetVectorOffset: Int32
}

struct FieldDescriptor {
    var typeName: RelativeDirectPointer<CChar>
    var superclass: RelativeDirectPointer<CChar>
    var kind: UInt16
    var fieldRecordSize: Int16
    var numFields: Int32
    var fields:FieldRecord
}

struct FieldRecord {
    var Flags: Int32
    var MangledTypeName: RelativeDirectPointer<CChar>
    var FieldName: RelativeDirectPointer<CChar>
}
struct RelativeDirectPointer<T> {
    var offset: Int32
    mutating func get() -> UnsafeMutablePointer<T> {
        let offset = self.offset
        return withUnsafeMutablePointer(to: &self) { p in
            return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
        }
    }
}

struct Teacher {
    var age = 18
    var name = "aaa"
}

var t = Teacher()

var t1 = Teacher.self

//值类型的metadata没法直接拿到,需要使用按位强转
let ptr = unsafeBitCast(Teacher.self as Any.Type, to: UnsafeMutablePointer<StructMetaData>.self)

//type(of:)也差不多
let namePtr = ptr.pointee.desc.pointee.name.get()

print(String(cString: namePtr))
print(ptr.pointee.desc.pointee.numFields)

let fieldDescriptor = ptr.pointee.desc.pointee.fields.get()

let recordPtr = withUnsafePointer(to: &fieldDescriptor.pointee.fields) {
    return UnsafeMutablePointer(mutating:UnsafeRawPointer($0).assumingMemoryBound(to: FieldRecord.self).advanced(by: 0))
}

print(String(cString: recordPtr.pointee.FieldName.get()))

枚举

rawValue

1.rawValue本质:继承自string或者int的enum在编译阶段会多出一个rawValue的计算属性,获取该值时会根据枚举值返回不同的字符串
2.let b = Weak.init(rawValue: "HELLO"),这句代码的底层原理,系统会把所有的rawValue值放到一个数组中,然后根据传进来的"HELLO"返回其在数组中的index,然后再拿这个index到枚举中去匹配,如果匹配到了直接返回枚举,否则返回nil

关联值

关联值在匹配时有一个比较特殊的情况,如果想在一个case中匹配多个时,它们的关联值的形参在case中需要保持一致

enum Shape {
    case circle(radius: Double)
    case rectangle(width: Int, height: Int)
    case rectangle1(name: Int, age: Int)
}

var circle = Shape.rectangle(width: 10, height: 50)

var tmpR: Double
var w: Int
var h: Int = -1

switch circle {
case .circle(let ss):
    tmpR = ss
case .rectangle(10, let x), .rectangle1(20, let x):
    h = x
    default:
        print("end")
}

另外只想匹配一种情况时:

if case let Shape.rectangle(width, height) = circle {
    print("\(width) \(height)")
}
枚举大小

原始值:只占用一个字节
关联值:最大case的内存值大小经过字节对齐之后+1,另外系统为了内存优化会把最后存储case的1字节和case的变量内存进行合并存储

取决于最大Case的内存大小
enum Shape{
    case circle(radious: Double)
    case rectangle(width: Double) // 8 + 1(case)
}


print(MemoryLayout<Shape>.stride) // 16 对齐之后的大小(内存空间中)
print(MemoryLayout<Shape>.size) // 9 实际大小
indirect关键字

当需要表达递归的枚举时需要用indirect关键字修饰

enum List<T>{
    case end
    indirect case node(T, next: List<T>)
}

var node = List<Int>.node(10, next: List<Int>.end)

print(MemoryLayout<List<String>>.size)// 8
print(MemoryLayout<List<String>>.stride) // 8

//lldb
(lldb) po withUnsafePointer(to: &node){print($0)}
0x0000000100008068
0 elements

(lldb) x/8gx 0x0000000100008068
0x100008068: 0x0000000100607c80 0x00007fff88a99ff0
0x100008078: 0x00007fff88a99ff0 0x000000010068e3d0
0x100008088: 0x0000000000000001 0xffffffffffffffff
0x100008098: 0x0000000000000000 0x0000000000000000
(lldb) x/8gx 0x0000000100607c80
0x100607c80: 0x0000000100004030 0x0000000000000003
0x100607c90: 0x000000000000000a 0x0000000000000000
0x100607ca0: 0x000000004d55545a 0x000020a000000000
0x100607cb0: 0x4d55545a00000000 0x0000000000000000

indirect的本质:把case的值放到了堆空间上

闭包捕获值

本质:1.堆上开辟内存空间,捕获的值放到这个内存空间里面。
2.修改捕获值的时候是修改的堆上的值
3.闭包是引用类型,闭包的底层数据结构是一个结构体(函数的地址+捕获变量的值),函数也是引用类型,在底层传递过程中也是一个结构体,里面存储了函数的地址

struct HeapObject {
    var type: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}

struct FunctionData<T> {
    var ptr: UnsafeRawPointer
    var captureValue: UnsafePointer<T>
}

struct Box<T> {
    var refCounted: HeapObject
    var value: T
}

struct VoidIntFun {
    var f: () -> Int
}

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}

var makeInc = VoidIntFun(f: makeIncrementer())

let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)

ptr.initialize(to: makeInc)

let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) {
    $0.pointee
}
print(ctx.ptr)
print(ctx.captureValue.pointee)
逃逸闭包&非逃逸闭包

逃逸闭包,@escaping:
1.在函数内部调用,并且生命周期比函数长
2.被存储起来后面再调用
3.延迟调用,gcd
4.必须显示的引用self,是因为作用域会延长,有可能导致循环引用,强制写self让开发者注意
非逃逸:
1.不会产生循环引用,作用域一般在函数调用作用域内
2.系统会做一些优化,减少一些retain、release操作
3.编译器优化,上下文会保存在栈上??(捕获的变量)

自动闭包

自动闭包会把传进来的参数用一个闭包包裹,然后根据情况执行,自动闭包不接收参数,能起到优化的使用,例如下面的donSomething方法是不会执行的

func debugOutPut(_ condition: Bool, message: @autoclosure () -> String) {
    if condition {
        print("debug: \(message())")
    }
}

func donSomething() -> String {
    return "network error"
}

debugOutPut(false, message: donSomething()) 
//debugOutPut接收一个闭包类型参数,
但传了一个方法进去,因为是声明成自动闭包,所以真实传递的类似 {
    return donSomething()
},
所以根据条件donSomething方法不会被调用

Optional

本质:是一个范型枚举,有none和some两个case,所以?和!只是两个语法糖

Equatable

绝大多数类型都实现了该协议,所以可用使用==运算符号进行比较,如果是自定义的类,需要手动实现它

protocol

1.对于实现协议的类或者结构体在底层的sil中会生成一张pwt(witness_table)存储协议中的方法,并且会为类或结构体生成相应的方法实现。
2.对于声明成某个协议类型的变量,在底层传递过程中是以下面样一个结构存在:

包装后的内存结构
value
value
value
type(metadata)
pwt(witness_table)

看一个默认声明和不声明的区别:

protocol MyProtocol {
    func teach() //此处声明和不声明结果不一样
}

extension MyProtocol {
    func teach() {
        print("MyProtocol")
    }
}

class Teacher: MyProtocol {
    func teach() {
        print("Teacher")
    }
}

let obj: MyProtocol = Teacher()
obj.teach()
let obj2: Teacher = Teacher()
obj2.teach()

当协议中没有teach函数声明时会分别打印MyProtocol和Teacher:第一个MyProtocol是因为extension的函数属于静态调用,后面的Teacher是函数表调用
当有声明时会打印两个Teacher:第一个Teacher是先从witness_table是查找方法实现,然后由于本身的Teacher类中的teach方法覆盖导致最终会调用到vtable中的teach方法,第二个Teacher是函数表调用。
看一下协议在底层对对象类型的包装:

protocol Shape{
    var area: Double{ get }
}

class Circle: Shape{
    var radious: Double

    init(_ radious: Double) {
        self.radious = radious
    }

    var area: Double{
        get{
            return radious * radious * 3.14
        }
    }
}

var circle: Shape = Circle.init(10.0)
print(MemoryLayout.size(ofValue: circle)) //40
print(MemoryLayout.stride(ofValue: circle)) //40

var circle1: Circle = Circle.init(10.0)
print(MemoryLayout.size(ofValue: circle1))  // 8
print(MemoryLayout.stride(ofValue: circle1)) // 8


struct HeapObject {
    var type: UnsafeRawPointer
    var refcount1: UInt32
    var refcount2: UInt32
}

struct protocolData {
    var value1: UnsafeRawPointer //当前对象
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    var type: UnsafeRawPointer //当前对象的metadata
    var pwt: UnsafeRawPointer //witness table
}

withUnsafePointer(to: &circle) { ptr in
    ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
        print(pointer.pointee)
    }
}
print("end")

//lldb输出
protocolData(value1: 0x0000000103030090, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x0000000100008180, pwt: 0x0000000100004030)
(lldb) x/4gx 0x0000000103030090
0x103030090: 0x0000000100008180 0x0000000000000003
0x1030300a0: 0x4024000000000000 0x6f746f72702e726f
(lldb) cat address 0x0000000100004030
&0x0000000100004030,  protocol witness table for TestSwift.Circle : TestSwift.Shape in TestSwift <+0> , ($s9TestSwift6CircleCAA5ShapeAAWP)TestSwift.__DATA_CONST.__const

看一下协议对结构体这种值类型在底层的包装,当value值起过3个就会被存储到堆上:

struct protocolData {
    var value1: UnsafeRawPointer
    var value2: UnsafeRawPointer
    var value3: UnsafeRawPointer
    var type: UnsafeRawPointer
    var pwt: UnsafeRawPointer
}
//
//
protocol Shape{
    var width: Double{ get set}

    var area: Double{ get }
}
//
////共享状态
struct Rectangle: Shape{
    var width: Double
    var height: Double
    var width1: Double
    var height1: Double = 40.0

    init(_ width: Double, _ height: Double, _ width1: Double) {
        self.width = width
        self.height = height
        self.width1 = width1
    }

    var area: Double{
        get{
            return 0
        }
    }
}
var circle: Shape = Rectangle.init(10.0, 20.0, 30.0)


withUnsafePointer(to: &circle) { ptr in
    ptr.withMemoryRebound(to: protocolData.self, capacity: 1) { pointer in
        print(pointer.pointee)
    }
}

//lldb输出
protocolData(value1: 0x0000000103804130, value2: 0x0000000000000000, value3: 0x0000000000000000, type: 0x0000000100004158, pwt: 0x0000000100004048)
(lldb) x/8gx 0x0000000103804130
0x103804130: 0x0000000100004030 0x0000000000000003
0x103804140: 0x4024000000000000 0x4034000000000000
0x103804150: 0x403e000000000000 0x4044000000000000
0x103804160: 0x0000000000000000 0x7000000010383c00
(lldb) p/f 0x403e000000000000
(Int) $R0 = 30
(lldb) p/f 0x4044000000000000
(Int) $R2 = 40
(lldb) cat address 0x0000000100004048
&0x0000000100004048,  protocol witness table for TestSwift.Rectangle : TestSwift.Shape in TestSwift <+0> , ($s9TestSwift9RectangleVAA5ShapeAAWP)TestSwift.__DATA_CONST.__const
(lldb) cat address 0x0000000100004158
&0x0000000100004158,  type metadata for TestSwift.Rectangle <+0> , ($s9TestSwift9RectangleVN)TestSwift.__DATA_CONST.__const
(lldb) 

范型

看一个例子,引出范型是如何知道不同的类型,并进行相应的内存管理的呢?首先范型方法在调用时,其底层会把当前值的type传进去,然后会依赖其metadata中的vwt来对对象的内存进行管理,vwt中存储了对象的size、alignment、copy、destroy等方法。所以范型通过VWT来进行内存管理,VWT由编译器生成

class LGTeacher{}
////Metadata
func testGen<T>(_ value: T) -> T{
    //Metadata-vwt: size,
    //copy
    let tmp = value
    //destory
    return tmp
}

testGen(10)
testGen((10, 20))
testGen(LGTeacher())

当把一个函数传给一个范型时,底层会被这个结构进行一次包装,GenData

struct HeapObject {
    var type: UnsafeRawPointer
    var refcount1: UInt32
    var refcount2: UInt32
}

struct FunctionData<T> {
    var ptr: UnsafeRawPointer
    var captureValue: UnsafePointer<T>?
}

struct Box<T> {
    var refCounted: HeapObject
    var value: T
}

struct GenData<T> {
    var ref: HeapObject
    var function: FunctionData<T>
}

func makeIncrement() -> (Int) -> Int {
    var runningTotal = 10
    return {
        runningTotal += $0
        return runningTotal
    }
}

func testGen<T>(_ value: T) {
    let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
    ptr.initialize(to: value)

    let ctx = ptr.withMemoryRebound(to: FunctionData<GenData<Box<Int>>>.self, capacity: 1) { pp in
        pp.pointee.captureValue?.pointee.function.captureValue!
    }

    print(ctx?.pointee.value)
}

let m = makeIncrement()
testGen(m)

Codable

Codable是Decodable和Encodable这两个组合协议的别名。
在调用JSONDecoder().decode方法时其实经过了以下4步:
1.通过JSONSerialization.jsonObject序列化操作
2.使用上面序列化的数据和我们传进来的解析策略,构造内部类_JSONDecoder
3.然后调用unbox方法
4.unbox其实会调用我们自定义类型的init(from decoder: Decoder)方法来进行解码

自定义类型遵守Codable协议时编译器会默认帮我们生成init(from decoder: Decoder)方法,我们也可以重写。CodingKey这个协议也是自动生成了一个枚举来遵守它的。

unbox内部首页会根据传进来的type类型进行进一步的unbox解析(递归),最终会走到init(from decoder: Decoder)方法中来。然后获取decoder的container方法拿到一个container,最后调用container的decode方法,把对应的CodingKeys枚举和字段类型传进去,decode方法未开源所以后续怎么解析不清楚了。

Codable坑点一,继承:

子类中的属性在encode和decode时都被编码和解码,因为编译器只帮我们把父类的init和decode方法实现了,然后子类只是重写了父类的方法并调用,对子类自己的属性并未进行操作编码操作,所以需要我们手动重写

class LGPerson: Codable {
    var name: String?
    var age: Int?
}

class LGTeacher: LGPerson {
    var subjectName: String?

    enum CodingKeys: String, CodingKey{
       case subjectName
    }
    //此处需要主动进行子类的编码操作
    override func encode(to encoder: Encoder) throws {
       var container = encoder.container(keyedBy: CodingKeys.self)
       try container.encode(subjectName, forKey: .subjectName)
       try super.encode(to: encoder)
    }
}

class LGPartTimeTeacher: LGPerson{
    var partTime: Double?
}

let t = LGTeacher()
t.age = 10
t.name = "Kody"
t.subjectName = "Swift"

let encoder = JSONEncoder()
let encoderData = try encoder.encode(t)
let jsonStr = String(data: encoderData, encoding: .utf8)!
print("jsonStr:\(jsonStr)")

let p = try JSONDecoder().decode(LGTeacher.self, from: jsonStr.data(using: .utf8)!)
print("p:\(p)")

PropertyWrapper

属性包装,被propertyWrapper注解声明的结构体可以用于修饰属性,在其调用时会重写被修饰属性的get、set方法,并进入到你自定义的wrappedValue的get,set方法中来

@propertyWrapper
struct Default<T> {
    private var defaultValue: T
    var value: T?
    public init(_ defaultValue: T) {
        self.defaultValue = defaultValue
    }

    public var wrappedValue: T? {
        get { return value ?? defaultValue }
        set { value = newValue }
    }
}

struct Item {
    @Default(3) var intValue: Int?
}

let item = Item()
print(item.intValue!)

dynamicMemberLookUp

当访问一个不存在的属性时,让开发者有一次机会可以动态的返回数据

struct Test {
    var a: Int = 10
    var b: Int = 20
}

@dynamicMemberLookup
struct Person {
    public subscript<T>(dynamicMember keyPath: WritableKeyPath<Test, T>) -> T {
        get {
            let test = Test()
            return test[keyPath: keyPath]
        }
    }
}

let p = Person()
let aa: Int = p.a
print(aa)

KeyPath

Key paths实质上可以让我们将任意一个属性当做一个独立的值。因此,它们可以传递,可以在表达式中使用,启用一段代码去获取或者设置一个属性,而不用确切地知道它们使用哪个属性

struct CellConfigurator<Model> {
    let titleKeyPath: KeyPath<Model, String>
    let subtitleKeyPath: KeyPath<Model, String>
    let imageKeyPath: KeyPath<Model, UIImage?>
 
    func configure(_ cell: UITableViewCell, for model: Model) {
        cell.textLabel?.text = model[keyPath: titleKeyPath]
        cell.detailTextLabel?.text = model[keyPath: subtitleKeyPath]
        cell.imageView?.image = model[keyPath: imageKeyPath]
    }
}

let songCellConfigurator = CellConfigurator<Song>(
    titleKeyPath: \.name,
    subtitleKeyPath: \.artistName,
    imageKeyPath: \.albumArtwork
)
 
let playlistCellConfigurator = CellConfigurator<Playlist>(
    titleKeyPath: \.title,
    subtitleKeyPath: \.authorName,
    imageKeyPath: \.artwork
)

LazySequence

常用于对大的数组进行遍历,但又不需要访问其所有元素时。通过Sequence的lazy属性可以创建出一个LazySequence对象,内部保存原数组对象,然后.map方法又会产生一个LazyMapSequence对象,内部保存闭包和原集合,所以可以实现闭包方法的延时调用

let numbers = Array(1...10000)
let mapNumbers = numbers.lazy.map{
    //map方法在下面print中打印时才会调用到
    $0 * 2
}
print(mapNumbers[1])
print("end")

Array

数组是一个值类型,在底层创建过程中会申请一块连续的内存空间,

var numbers = [1, 2, 3, 4, 5]
withUnsafePointer(to: &numbers) {print($0)}
print("end")

//输出
0x0000000100008058
(lldb) x/4gx 0x0000000100008058
0x100008058: 0x000000010055e520 0x0000000000000000
0x100008068: 0x0000000000000000 0x0000000000000000
(lldb) x/8gx 0x000000010055e520
0x10055e520: 0x00007fff8897bf98 0x0000000000000003
0x10055e530: 0x0000000000000005 0x000000000000000a
0x10055e540: 0x0000000000000001 0x0000000000000002
0x10055e550: 0x0000000000000003 0x0000000000000004

截屏2021-03-15 下午3.51.19.png

数组每次扩容是原来的2倍,扩容后其内部的storage地址会变。写时复制,利用内部storeage对象的引用计数来判断是否需要开辟新的buffer容量,如果两个变量指向同一个数组时,它们的内部的storage地址一开始是一模一样的,但当数组中的元素有改变时,此时会对数组整个copy一份。

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

推荐阅读更多精彩内容