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