swfit进阶-01-类与结构体(一)

418 阅读5分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

  • 这个专栏将会记录些swift进阶的学习

1. 类与结构体的区别

在 swift 的开发中,我们会经常使用到类与结构体,我们知道它们主要的区别: 类可以认为是一个指针类型引用类型,而结构体主要是存储值,是值类型

1.1 类

我们打印的地址一样说明 p 和 p1 指向的同一片内存空间类似我们 oc 中的浅拷贝,通过指针访问我们在内存开辟实例的内存空间

image.png 当我们通过 p1 访问 person 实例 age 的时候,修改后 p 访问的 age 也是 20,它们访问的是同一片内存空间

1.2 结构体

结构体主要是用来存储值的,我们把上面的 class 换成 structstruct 默认会给我们生成初始化的方法

struct  person {
    
    var name:String
    var age : Int
    
    init(_ name:String,_ age: Int) {
        self.name = name
        self.age = age
    }
    
}
  let p = person("ss", 18)
        
        print(p);
        var p1 = p;
        
        p1.age = 20;
        print(p1);

结构体会默认生成一个初始化的方法,包含定义的属性

image.png

打印结果是p和p1修改不再影响,他们之间进行赋值相当于深拷贝,修改的时候不再影响之前的内存空间,相当于再次复制一份。

image.png

1.3 总结

相同点

  1. 都可以定义属性用来存储值,可以定义方法,定义初始化的方法。
  2. 可以通过下标语法进行访问值,类似点语法
  3. 可以使用extension 来拓展功能,遵循协议来提供某种功能

不同点:

  1. 类有继承的特点,而结构体没有
  2. 类可以进行类型转换,可以在运行时检查和解释类实例的类型。
  3. 类有析构函数释放分配的内存空间
  4. 引用计数允许让一个类实例对象有多个引用

1.4 内存空间分布

我们知道程序运行的时候系统会给我们分配一定的内存空间,内存主要分为

可用内存空间和不可以内存空间。

可用内存空间:

栈区(stakc)局部变量和函数运行过程中的上下文

堆区(heap):存储所有对象

全局区/常量区 (global)存储全局变量;常量;代码区

image.png

Segment & SectionMach-O 文件有多个段( Segment ),每个段有不同的功能。然后每 个段又分为很多小的 Section

TEXT.text : 机器码

TEXT.cstring : 硬编码的字符串

TEXT.const: 初始化过的常量

DATA.data: 初始化过的可变的(静态/全局)数据 DATA.const: 没有初始化过的常量

DATA.bss: 没有初始化的(静态/全局)变量

DATA.common: 没有初始化过的符号声明

我们继续刚才的代码打印下当前结构体的内存信息使用

image.png

使用下面的命令行

frame varibale -L xxx

关于frame的一些命令行我们可以使用查看使用说明

help frame variable

image.png

结构体person的内存分布也就是name和age

这里我们也可以通过github上StructVsClassPerformance这个案例来直观的测试当前结 构体和类的时间分配。

image.png

说明在存储相同值的时候,没有一些继承关系的话可以优先使用struct来存贮值

2. 类的初始化器

我们在swift中,当存类存在属性的时候我们需要提供初始化器的方法,否则会报错。struct系统会默认提供初始化器

2.1 便捷初始化器

Swift 中创建类和结构体的实例时必须为所有的存储属性设置一个合适的初始值。所以 类 person 必须要提供对应的指定初始化器,同时我们也可以为当前的类提供便捷初始化器

class  person {
    
    var name:String
    var age : Int
    
    init(_ name:String,_ age: Int) {
        self.name = name
        self.age = age
    }
    
    init(_ name:String) {
        self.name = name;
        self.age = 19;
    }
    
    init(_ age:Int) {
        self.age = age;
        self.name = "ss"
    }
    
    
}

我们自定义3种初始化的方法,没有传入的设置默认值,但是我们想要和我们自身的初始化区分的话,我们可以使用convenience进行修饰,表示便捷初始化

image.png

我们使用convenience修饰的初始化方法需要自己实现自身的初始化方法,同时self使用必须在初始化之后

 convenience init(_ name:String) {
    self.init("ss",10);
        self.name = name;
        self.age = 19;
    }
    

我们定义一个person的子类,定义一个子类的属性。构造初始化的时候

image.png

我们要实现父类的初始化方法,按照我们oc的写法,先实现父类的初始化之后进行,子类属性的赋值

image.png

必须在子类的属性值赋值完成后才能调用父类的初始化,同时要注意不能使用父类便捷初始化的方法,否则任务初始化失败

image.png

image.png

  • 总结:

1.便捷初始化器必须调用当前类的初始化方法,之后给类的属性赋值覆盖之前初始化的值。

2.子类继承父类时存在自己的属性时,初始化之前需要实现父类的初始化的方法才可以调用父类的方法,同时自身初始化的属性需要赋值完成后才可以调用父类的初始化,父类的便捷初始化调用不行。

3.初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用 self 作为值。

2.2 可失败初始化器

我们一般默认时初始化成功的,但是我们在特定情况下,不符合我们要求的值的时候我们可以renter nil初始化失败。

init?(_ age:Int) {
        if age<18 {
            return nil
        }
           self.age = age;
           self.name = "ss"
       }

image.png

2.3 必要初始化器

在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须 实现该初始化器

image.png 我们在子类可以实现init

required convenience init(_ name: String, _ age: Int) {
        self.init("sleep")
    }

也可以实现但是不做

 required init(_ name: String, _ age: Int) {
        fatalError("init(_:_:) has not been implemented")
    }