swift第一章:类和结构体上

315 阅读4分钟

结构体和类的相同点:

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

结构体与类的不同点:

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

一.对于类和结构体最主要的区别是类是内存地址引用,结构体是值copy

import Foundation
class Teacher{
    var age: Int
    var name: String
    init(_ age: Int, _ name: String) {
        self.age = age
        self.name = name
    }
}
var t = Teacher(1,"lisi")
var t1 = t
print("end")

image.png

  • 说明class实例对象的赋值是引用类型的赋值
  • 说明class对象本身的地址是在栈开辟,并且t和t1相差8个字节

值类型存储的是具体的实例,比较典型的例子就是struct,稍加修改上面的例子

image.png

t和t1都是直接存储的值,t和t1之间是相互独立的关系,一般值类型都存储在栈上,引用类型存储在堆上,数据具体存储位置分布图如下:

image.png

如果没有其他额外的需求,只是用作存储数据,那么我们应该优先选择结构体,因为结构体在大量的创建性能优化上是比class优异的多,因为class需要去维护堆内存的创建,销毁,寻找,引用关系的创建等等,十分消耗性能

二.类的初始化

import Foundation
struct Teacher{
    var age: Int
    var name: String
//    init(_ age: Int, _ name: String) {
//        self.age = age
//        self.name = name
//    }
}
var t = Teacher(age: 1, name: "lishi")
var t1 = t
print("end")

结构体是默认就初始化函数的,class如果没有设置初始化函数就会报错,所以class里面必须需要有咱们自己的初始化器,同时我们的便捷初始化器里面必须调用另一个初始化器,如果不这样做就会报错

import Foundation
class Teacher{
    var age: Int
    var name: String
    init(_ age: Int, _ name: String) {
        self.age = age
        self.name = name
    }
    
    convenience init(age: Int, name: String) {
        self.init(age, name)
    }
}
var t = Teacher(1,"lisi")
var t1 = t
print("end")

这里我们记住: 

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

可失败初始化器: 这个也非常好理解,也就意味着当前因为参数的不合法或者外部条件的不满足,存在初始化失败的情况。这种 Swift 中可失败初始化器写 return nil 语句,来表明可失败初始化器在何种情况下会触发初始化失败。写法也非常简单: 

import Foundation
class Teacher{
    var age: Int
    var name: String
    init?(_ age: Int, _ name: String) {
        if age < 18 {
            return nil
        }
        self.age = age
        self.name = name
    }
    
    convenience init?(age: Int, name: String) {
        self.init(age, name)
    }
}
var t = Teacher(18,"lisi")
var t1 = t
print("end")

必要初始化器:在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器,不然就会报错

import Foundation
class Teacher{
    var age: Int
    var name: String
    required init(_ age: Int, _ name: String) {
        self.age = age
        self.name = name
    }
    
}
class sonTeacher : Teacher{
    var subName: String
    init(_ subName: String) {
        self.subName = subName
        super.init(22, subName)
    }
    
     required init(_ age: Int, _ name: String) {
         self.subName = "subName"
         super.init(12, "name")
     }
}
var t = sonTeacher("222")
var t1 = t
print("end") 

三.类的生命周期

iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的,如下图所示:

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

image.png

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
}

由上面可以看出swift的内存结构跟oc类似,oc中是通过对象的isa指针指向class类,class类中有属性列表,方法列表,协议列表等,class类的isa指向根元类

  • oc更多是动态语法决议导致有了很多不确定因素,当然也有很多的容错可能,利弊共存 swift中对性能的追求更加的极致,在能用struct来优化性能的地方,系统都给我们做好了,最明显的例子就是UUID这个对象就是一个值对象
  • 底层对内存的使用与优化都是同宗同源