Swift学习笔记(初始化过程)

2018-01-04 15:03:40 浏览数 (1)

导语 :Swift已经更新到4.0了,成为苹果推荐开发者进行iOS开发的语言,因此即使手头上的工程项目使用的还是object-c,但抽空学习下swift还是有必要的。因为swift从3.0开始已经日趋稳定,所以现在网上的资料大部分都是swift3.0的,本文的内容也是基于swift3.0,如果4.0有涉及新的更改,还需自己查阅。

km以及网上介绍swift基本语法的文章已经有不少,我这里就不累赘。这篇文章主要想讲的是:swift的初始化。

首先必须明确的两个基本点是:

1.swift中最基本的数据类型是结构体,类以及枚举。像object- c中常用的dictionary,array,string等,在swift中都是结构体类型。

2.swift中,类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。比如下面两个例子:

通过构造器初始化存储型属性:

代码语言:javascript复制
struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()

或者:属性申明的时候直接设置初始值:

代码语言:javascript复制
struct Fahrenheit {
    var temperature = 32.0
}

下面开始重点介绍下swift初始化过程中一些比较重要的点:

 1.可选类型属性

可选类型属性是指被允许被赋值为nil的属性,比如:

代码语言:javascript复制
var response: String?

可选类型的属性将自动初始化为nil,表示这个属性是有意在初始化时设置为空的。

2.常量属性在构造过程中的修改

在当前类的初始化函数里,可以修改常量属性的值,但是初始化函数结束时,常量属性必须是一个确认的值,并且之后不可修改。

  需要注意的是,子类的构造函数里不可以修改从父类继承来的常量属性。

3.默认构造函数

  如果结构体或类的所有属性都有默认值,但没有自定义的构造函数以及继承的构造函数,那么 Swift 会给这些结构体或类提供一个默认构造函数。这个默认构造函数会简单地将所有属性值都设置为默认值。

4.结构体逐一成员构造函数

除了上面提到的默认构造函数,swift中,如果一个结构体的属性申明的时候没有设置默认值,也没有自定义构造函数,swift也会帮助结构体创建一个逐一成员构造函数。比如下面的例子:

代码语言:javascript复制
struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

需要说明的是,如果有自定义构造函数,那么将不会生成默认构造函数以及逐一成员构造函数(结构体)。

5. 构造函数代理

所谓构造函数代理,指的是一个构造函数通过调用其他构造函数来完成部分初始化工作。下面分结值类型和类类型两种情况来介绍:

值类型:包括结构体和枚举。之所以称为值类型,是因为他们传递时都是进行值拷贝。由于值类型无法继承,所以构造函数代理中,你只能调用自己的其他构造函数。

类类型:由于类可以继承,所以构造函数代理涉及到调用父类的构造函数。所以下面将介绍swift中构造函数在类继承中发挥的作用。

类特有的构造函数特点:

1.指定构造函数和便利构造函数:

指定构造函数是指 初始化当前类中的所有属性,并调用父类的构造函数来完成父类属性的初始化。

代码语言:javascript复制
init(parameters) {
    statements  
}

便利构造函数是比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。

代码语言:javascript复制
convenience init(parameters) {
    statements
}

关于指定构造函数和便利构造函数,有下面三条规则:

规则 1

指定构造器必须调用其直接父类的的指定构造器。

规则 2

便利构造器必须调用_同_类中定义的其它构造器。

规则 3

便利构造器必须最终导致一个指定构造器被调用。

也就是说指定构造函数是向上代理,便利构造函数是横向代理

2.两段式构造过程

swift中的构造函数必须分为以下两个阶段:

第一个阶段,每个存储型属性被引入它们的类指定一个初始值。当每个存储型属性的初始值被确定后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步定制它们的存储型属性。

这和object-c 相似,但是object-c 属性的初值一般只能被默认初始化为nil或者0,而swift支持你自己定制初始值,更加灵活

swift将执行以下四个检查来满足两段式的要求:

安全检查 1

指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。

之所以这样,是因为一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。

安全检查 2

指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。

安全检查 3

便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。

安全检查 4

构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用self作为一个值。

原因是 类实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,该实例才会成为有效实例,才能访问属性和调用方法。

具体来说,两段式执行的是以下操作:

阶段 1
  • 某个指定构造器或便利构造器被调用。
  • 完成新实例内存的分配,但此时内存还没有被初始化。
  • 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
  • 指定构造器将调用父类的构造器,完成父类属性的初始化。
  • 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部。
  • 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。
阶段 2
  • 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。
  • 最终,任意构造器链中的便利构造器可以有机会定制实例和使用self

3.构造函数的继承与重写

与object-c不同的是,swift中,子类对父类的构造函数默认是不继承的,除非满足一定的条件

如果你重写父类的一个构造函数,那么你必须在重写函数前面加上override 修饰符,即使 你重写的是默认构造函数 或者 你将父类的指定构造函数重写成便利构造函数,因为在重写的构造函数中,有可能会调用到具有相同函数名和参数的原函数,所以必须加修饰符加以区分。

前面说到,swift中子类默认是不继承父类的构造函数的,但是满足以下规则的情况下,子类可以继承父类的构造函数:

规则 1

如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。

规则 2

如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承所有父类的便利构造器。

这里只要自定义父类的所有指定构造函数,即使是自定义成便利构造函数,也是满足规则的

swift中的初始化过程就介绍到这里,上面较为详细的介绍了各种规则和注意点,虽然实际开发中可能不一定都会用到,但是了解清楚可以一定程度的避免踩坑,也能更深入地体会到swift的特点。

0 人点赞