Swift底层探索1 - 类和结构体(一)

1、类和结构体

1.1 基础认知

类和结构体十分相似,如:

class/struct Person{
    var age: Int
    var name: String

    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
}

相同点:

  • 定义存储值的属性、方法、初始化器、以及下标以使用下标语法提供对其的访问
  • 使用extension来扩展功能
  • 遵循协议来提供功能

不同点:

  • 类能继承,结构体不行
  • 类型转换使你能够在运行时检查和解释类实例的类型
  • 类有析构函数用来释放其分配的资源
  • 引用计数允许对一个类实例有多个引用

1.2 引用类型和值类型

引用类型:即一个类类型的变量并不直接存储具体的实例对象,是对当前存储具体实例内存地址的引用

值类型:相比较类类型的变量中存储的是地址,那么值类型存储的就是具体的实例

  • 要区分的第一件事就是:类是引用类型、结构体是值类型,可以借助lldb的pox/8g指令来查看当前的变量的内存结构

po:p和po的区别在于使用po只会输出对应的值,而p则会返回值的类型以及命令结果的引用名。

x/8g:读取内存中的值(8g:8字节格式的输出)

我们可以通过运行以下代码来查看下内存结构:

class/struct Person{
    var age: Int
    var name: String

    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
}


var person = Person(age: 10, name: "小明")
var person_1 = person

结果1:person和person_1存放了相同的内存地址


类变量存放的是内存地址

结果2:person和person_1存放了相同的结构体实例


结构体变量存放的是具体的实例
  • 要区分的第二件事:引用类型和值类型的存储位置不同:一般情况下,值类型存储在栈上,引用类型存储在堆上
    内存示例图

内存区域的一些基本认知
1、内存的排列是从高到低
2、一般只有从0x00000001到0x000007F之间的内存才是我们可以操作的区域
3、一般我们把操作区域分为Stack(栈 - 存放的是局部变量和函数运行过程中的上下文),Heap(堆 - 存储所有对象),Global(全局区 + 常量区 + 指令区 - 存储全局变量 + 常量 + 代码区)
4、当栈和堆拉伸后导致内存地址重合,即溢出
5、内存分配栈空间比堆空间快,因为堆空间需要查找合适的区域再拷贝值,完成实例对象分配,离开作用域后销毁查找并把内存块插入,而栈空间每次会直接分配新的空间,离开作用域直接回滚销毁。简单来说就是堆始终需要寻找,而栈不用

所以鉴于堆在时间和效率上略差,应将class尽量用struct替换,也就是尽量用值类型替换引用类型

1.3 class和struct的内存分布

已知class是在堆,struct在栈,那么具体内存分布又会是如何,借助frame variable命令进行查看

frame varibale -L xxx 查看变量内存地址

  • class:

通过打印结果可以知道类的内存分布是比较复杂的,首先8字节存的是指向类型信息,第二个8字节是引用计数,然后才是属性信息,p变量仍在堆上,只是存的栈地址

内存存值 说明
0x0000000100008230 指向类型信息(Metadata)
3 引用计数(RefCount)
10 person.age
小明 person.name
0x0000000100617338 person.p
  • struct:
    image.png

通过打印结果可知结构体的内存分布是比较简单的,是直接分布在栈上,即使结构体内部含有引用类型,p变量仍然在栈上,只是存的是person这个对象的堆空间地址,也就是说

内存存值 说明
10 person.age
小明 person.name
0x0000000109c04110 person.p

2、类的初始化器

class Person {
    var name: String
    var age: Int

    // 指定初始化器 (Designated Initializer) + 可失败 ?
    init?(name: String, age: Int, minAge: Int) {
        if minAge > age {
            return nil
        }
        self.name = name
        self.age = age
    }

    // 指定初始化器 (Designated Initializer) + 必需
    required init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    // 便捷初始化器 (Convenience Initializer) + 必需 + 可失败
    convenience required init?(name: String) {
        let defaultAge: Int = 8
        self.init(name: name, age: defaultAge)
    }

    // 便捷初始化器 (Convenience Initializer)
    convenience init(age: Int) {
        let defaultName: String = "小明"
        self.init(name: defaultName, age: age)
    }

}

class People : Person{
    var address : String
    
    override init?(name: String, age: Int, minAge: Int) {
        //指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成
        self.address = "北京"
        super.init(name: name, age: age, minAge: minAge)
        //指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值
        self.name = "小红"
    }
    
    convenience required init?(name: String) {
        //required 必需继承,便捷初始化器必须先委托同类中的其它初始化器
        self.init(name: name, age: 8, minAge: 1)
    }
    
    required init(name: String, age: Int) {
        self.address = "北京"
        super.init(name: name, age: age)
    }
    
}

  • 当前的类编译器默认不会自动提供成员初始化器,但是对于结构体来说编译 器会提供默认的初始化方法(前提是我们自己没有指定初始化器)!
  • 创建类和结构体的实例时必须为所有的存储属性设置一个合适的初始值!

1、指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
2、指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖
3、便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖。
4、初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用 self 作为值。

  • 可失败初始化器:就意味着当前因为参数的不合法或者外部条件的不满足,存在初始化失败的情况。这种 Swift 中可失败初始化器写 return nil 语句(init?())
  • 必要初始化器:在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须 实现该初始化器(requeired init())

3、类的生命周期和数据结构

  • OC和Swift均是通过LLVM进行编译的

OC 通过 clang 编译器,编译成 IR,然后再生成可执行文件 .o(这里也就是我们的机器码)
Swift 则是通过 Swift 编译器编译成 IR,然后在生成可执行文件。

  • Swift 对象内存分配:
    __allocating_init -> swift_allocObject -> swift_allocObject -> swift_slowAlloc -> Malloc
  • Swift 对象的内存结构 HeapObject (OC objc_object) ,有两个属性: 一个是Metadata ,一个是 RefCount ,默认占用 16 字节大小。
  • Swift类的数据结构
struct Metadata{
    var kind: Int
    var superClass: Any.Type
    var cacheData: (Int, Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutableRawPointer
    var iVarDestroyer: UnsafeRawPointer
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 类和结构体是通用的,灵活的结构,成为程序代码的基础。 您可以通过使用与常量,变量和函数完全相同的语法来定义属性和方...
    Joker_King阅读 319评论 0 0
  • 类和结构体对比 Swift 中类和结构体有很多共同点。共同处在于: 定义属性用于存储值 定义方法用于提供功能 定义...
    小驴拉磨阅读 131评论 0 0
  • 1. 结构体和类对比 共同点,两者都可以:定义属性用于存储值定义方法用于提供功能定义下标操作用于通过下标语法访问它...
    DevXue阅读 162评论 0 0
  • 较传统的OC语言,Swift使用了更多的结构体,在 Swift 中,所有的基本类型,都是结构体类型 整数(Inte...
    热干面一元五阅读 1,596评论 3 3
  • 最近项目使用的是OC,后头看之前用Swift开发的一个项目时,发现很多细节都忘记了😭😭。为了回忆和以后方便查看,现...
    wg刚阅读 518评论 0 0