资讯专栏INFORMATION COLUMN

ios开发:对于Swift中构造器的理解

bladefury / 1433人阅读

摘要:下面就开始看,关于构造器,到底有哪些不同的地方。这实际就是构造函数的重载。能直接初始化的就是指定构造器,不能直接初始化而依赖于指定构造器初始化的构造函数是便利构造器。中的子类不会默认继承父类的构造器。

在说构造器之前我们先来普及一下之前没有提到的类的一些遗漏的知识点。

1.类的继承使用C++的方式,使用一个冒号就可以继承了(并非使用Java或PHP的extends关键字):

class A{}

class B : A{}

2.在进行重写的时候我们同样需要override关键字,和Java不同的是,Java语言的override是一个注解,重写方法时override只是一个提示,让开发者能够意识到这个方法是被重写的,即使把这个override去掉也没有关系,但是在Swift中,如果你真的重写了一个方法,那么就必须写这个关键字。这么做,你就表明了你是想供一个重写版本,而非错误地供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少override 关键字的重写都会在编译时被诊断为错误。

3.同样使用final关键字来防止继承,包括类的继承,方法的继承,变量的继承。以前这个关键字也需要加@符号,即@final,但是现在已经变成了不要@了(和lazy一样,以前有@,现在没有了)。

4.Java中使用的this,在Swift中是self。

好了,以上只是补充而已,下面才是正式的内容。构造器在Swfit的学习当中可能是遇到的比较复杂的东西吧。因为它和其他语言在很多地方都不太一样。下面就开始看,关于构造器,到底有哪些不同的地方。

构造器有两类,指定构造器和便利构造器。他们的名字是init(和Java不一样哦),并且它不是一个真正的方法(但是为了方便,我们下面可能还会称呼它为构造函数),因为它不用func关键字,也没有任何返回值。便利构造器比指定构造器多一个convenience关键字。例如:

class TestInit {

var a :Int
var b : Int

init(param1 :Int, param2:Int) {
    a = param1
    b = param2
}

convenience init(param:Int){
    self.init(param,0)
}

}

第一个构造器是指定构造器,第二个构造器是便利构造器。我们不经会问,为什么会有两种。如果我们去百度一些资料发现,它们的那些说法都没有说到根本的点上,说便利构造器就是为了方便构造等等。当然,他们的说法没错,就是为了方便构造,但是为什么会这么设计呢,实际上,这两种构造器的使用我们都见过,只不过Swift将它们区分了开来。例如在Java中,我们的一个类有三个构造函数:

public class TestInit {

private int var1;
private int var2;
private int var3;

public TestInit(){
    var1 = 0;
    var2 = 0;
    var3 = 0;
}

public TestInit(int a,int b){
    this.TestInit(a,b,0);
}

public TestInit(int a,int b,int c){
    var1 = a;
    var2 = b;
    var3 = c;
}

}

上面的例子,在Java中非常的常见(其他很多语言应该也是这样吧)。这实际就是构造函数的重载。但是呢,在Swfit中,它不想这么搞,它就是想把这几种构造函数区分开来,可以看到,有两个参数的构造函数内部完全是依靠三个构造函数的构造函数来初始化的,所以说这样的构造函数,它本身没有初始化的功能,它只是把对应的参数和默认的值传递给有构造能力的构造函数,所以说如果这是Swfit,那两个参数的这个构造函数应该是便利构造函数,应该写上convenience关键字。而没有参数和有三个参数的构造函数就是指定构造器。当然,实际没有参数的构造函数可以把内部变成this.TestInit(0,0,0);从而变成便利构造器。这样和Java一对比,我们知道了,实际Swfit将构造函数分为两种的原因就是为了区分一个构造函数有没有直接能力初始化变量值。能直接初始化的就是指定构造器,不能直接初始化而依赖于指定构造器初始化的构造函数是便利构造器。

说到这里,我们就又有疑问了,为什么说构造器有初始化的能力,到底初始化什么呢?实际初始化的就是类中的存储属性。因为Swfit中要求类中的存储属性在使用前必须得赋值,有人说,当然了,Java不是也是这样的吗?但是Swfit比较特别,因为Swift并不会像Java那样给变量自动赋值,例如int自动为0,Object自动为null。Swift需要我们显示的进行赋值,即使是0和nil也得手动赋值。所以说构造器里就得将这些没有在声明时赋值的变量常量进行赋值。

鉴于以上的分析和并且考虑类本身的继承关系,Swift采用以下三条规则来限制构造器之间的代理调用:

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

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

便利构造器必须最终以调用一个指定构造器结束。

用一句很简答的话来概括就是:

指定构造器必须总是向上代理,便利构造器必须总是横向代理。

用图举个例子就是:

同样,由于存储属性必须手动赋值,那么在子类当中子类的存储属性的初始化要优先于父类的。这个特性很是特别,因为一般来说继承关系都是先初始化父类,然后初始化子类,而Swift中却正好相反。为什么会有这么奇怪的事情。因为父类在初始化时可能调用其他的方法,而这些方法如果被子类重写,重写的这个方法可能会调用子类的没有被初始化的变量,导致初始化失败。举个例子来说就是:

class Father {

var f :Int

init(_ param:Int){
    b = f
    self.test()
}

func test() {
    print(f)
}

}

class Child : Father {

var c :Int

override init(param: Int) {
    c = param
    super.init(param)
}

override func test() {
    print(c)
}

}

如上所写的那样是没有问题的,如果将子类init方法里的c=param赋值放到super下面,那么将会导致错误。因为如果先调用super再进行c的赋值,那么就会在父类调用init时走进test方法而输出没有赋值的c。所以说c的赋值必须在super之前,也就是子类的初始化要早于父类。有人说,如果我test方法里不使用c可以嘛?这样父类先初始化就没有问题了,实际呢,这样也不行,因为系统要保证初始化的正确性,在编译期间就已经做出了逻辑的判断,即c的赋值必须在super之前,不管你有没有用到。但是如果这么做子类何时才能改变父类的原有变量,进行自我的定制化呢?实际在父类初始化之后就可以进行子类定制化父类的内容了。说到这里我们就应该说到Swfit的两段式构造过程了。

Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性通过引入它们的类的 构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。

两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器意外地赋予不同的值。

Swift编译器将执行4种有效的安全检查,以确保两段式构造过程能顺利完成:

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

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

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

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

以下是两段式构造过程中基于上述安全检查的构造流程展示:

阶段一

某个指定构造器或便利构造器被调用;

完成新实例内存的分配,但此时内存还没有被初始化;

指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化;

指定构造器将调用父类的构造器,完成父类属性的初始化;

这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;

当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的 内存被认为已经完全初始化。此阶段完成。

阶段二

从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。最终,任意构造器链中的便利构造器可以有机会定制实例和使用self。

实际以上的描述可能过于抽象,读者只需要写个例子,然后把子类的赋值写在super前后试一下便知。只要心中铭记一点-使用之前必须初始化-这样就能理解所有的结论了。

了解了两段式构造过程我们再来看一下构造器的继承情况。

Swift中的子类不会默认继承父类的构造器。Swift的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。但是如果特定条件可以满足,父类构造器是可以被自动继承的。假设要为子类中引入的任意新属性 供默认值,请遵守以下 2 个规则:

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

二、如果子类重写了所有父类指定构造器,它将自动继承所有父类的便利构造器。

由于本篇大部分都是来自于原文档的内容结合实际试验的效果给出的,可能还有其他的一些规则和逻辑,这里就不一一找出了,感觉在实际编码的时候尽量避免非常废杂的构造器的继承过程吧,不然自己可能都会晕掉。

说到这里顺便一提的是反初始化。C++里的析构函数大家都知道,相应的Swift里的是deinit(和init相对应),deinit实际也不是一个函数,init好歹会有一个小括号传参,而deinit没有小括号,只有一个大括号:

deinit{

    //do something
}

并且deinit是系统自己调用的,我们不要手动调用它。

原文地址:http://www.code4app.com/blog-...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/16842.html

相关文章

  • Swift 2.0 到底「新」在哪?

    摘要:一年之后,苹果不负众望,在年的会议中推出了。在大会上,苹果测量了分贝的新功能普及性。会议中两次最大的掌声,一次是苹果宣布支持测试,另一次则是的开源。这可是件大事下半年,苹果还将公开发布在标准许可下的的源代码,包括编译器和标准库。 【编者按】2015年6月,一年一度的苹果 WWDC 大会如期而至,在大会上苹果发布了 Swift 2.0,引入了很多新的特性,以帮助开发者更快、更简单地构建应...

    Mertens 评论0 收藏0
  • Swift基础教程》作者Boisy Pitre:Objective-C很不错,但它已经是过去时

    摘要:问你写作基础教程的目的是什么当苹果引入时,我就知道这门语言会在几年之内成为计算机语言版图中的重要一块。但是话说回来,苹果开源语言的决定肯定会让各种变化朝更好的方向发展。我相信苹果很努力想让遵从自己的主张,成为自己的语言。 非商业转载请注明作译者、出处,并保留本文的原始链接:http://www.ituring.com.cn/article/203600 Boisy G. Pitre是一...

    zhkai 评论0 收藏0
  • 从Java到Swift

    摘要:函数的定义形如函数可以返回多个返回值,这个功能真是太猛了。支持函数类型,根据输入参数和返回值确定一个函数类型。例如函数的参数可以另外一个函数,注意,不是另外一个函数的返回值,而是另外一个函数,只要类型符合即可。 我们学习的新事物时,通常并不是从0开始,而是从已知开始,将新事物与已知的进行比较分析,从而快速全面地了解新事物。而我熟悉Java,所以在学习Swift时,就会将Swift与Ja...

    lemon 评论0 收藏0
  • iOS进阶

    摘要:然而,副作用对于系统的可测试性来说就是一剂毒药,并且可能会因应用程序和请求的不同而出现差异性。这些事件并不具备特定时序性,甚至它们可能同时发生。粘性动画中,粘性小球会根据移动距离的大小拥有不同的弹性程度。 PPAsyncDrawingKit - 实现了一系列基础 UI 控件的轻量级 ASDK 一款轻量级的 ASDK,实现了一系列基础 UI 控件。 iOS 开发之 Runtime 常用示...

    Cheng_Gang 评论0 收藏0
  • Swift 开发者大会组织者梁杰:你到来是一个重要决定

    摘要:这次翻译的组织者正是梁杰,他和他的翻译组在更新完之后,仍然活跃在翻译国外最新文章的第一线。苹果刚发布的时候,我看到微博上有一个人翻译了编程语言的第一章。 非商业转载请注明作译者、出处,并保留本文的原始链接:http://www.ituring.com.cn/article/206501 梁杰,前端工程师,业余喜欢写Python和Swift。组织翻译了苹果官方的《Swift编程语言》和A...

    张率功 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<