Objective-C和Swift中的对象初始化

Objective-C作为一门面向对象语言,我们在开发时所做的所有事情都是在与对象打交道。那么一个对象,也就是一个Object是怎么产生的呢?通常我们要为其分配内存,同时还要进行初始化。说起Objective-C的初始化,这当中的逻辑细说起来也有很多要点需要考虑的,不清楚这些就很可能犯低级错误,这篇文章就和新语言Swift一起,来整理下对象的初始化过程。

0. Objective-C初始化方法

本人之前是做Java开发的,Java中有用类名作为方法名的方法,可以带参数也可以不带参数,这就叫构造方法。在Java对象实例化时,直接一个new操作就都OK了,Java系统帮你把对象内存在堆上分配好,并初始化各项数据。

而在Objective-C中就有所不同了,为了最大程度让开发者控制整个分配内存和初始化的流程,苹果将其一分为二。即为对象分配内存用alloc,初始化使用init。

比如我们看下NSObject这个类:

  • alloc方法根据对象包含数据结构的需要,分配内存,此后设置isa(告诉系统这是什么类的对象,后面有时间会专门写篇文章讲这个),然后将必要是数据结构都设置为0
  • init方法,直接返回self对象

如果我们自己的类中包含了业务逻辑相关的数据,则需要在自己的init方法中,将这些变量初始化成可用的值。如果无法完成初始化,则应返回nil。

所以,我们通常将alloc和init的方法连起来调用,只有init过之后的才是可靠的,因为alloc的返回结果可能在init后发生变化,甚至不可用。只有init执行返回的对象才是可用的对象。

一般情况下不要这样使用:

id anObject = [SomeClass alloc];
[anObject init];
[anObject someOtherMessage];

因为init执行的返回结果,可能已经与anObject本身不同。

1. Objective-C的指定初始化器

面向对象在很大程度上是为了复用而设计,而为了最大程度的代码复用,也为了降低代码维护成本,更可控,苹果在Objective-C中阐述了一个概念——Designated Initializer,指定初始化器,或者叫指定初始化方法。

再提一提Java,Java在5以后的API中有个ThreadPoolExecutor类,这个类有4个构造方法,参数各不相同,最多的一个参数有7个。而其它3个方法,在实现上都是通过this()调用更多参数的方法,空余的参数用默认值补上。那么7个参数的构造方法在这里就可以认为是指定初始化器。

初始化器索要完成的任务是实质性的,比如根据用户给出的自定义参数初始化对象成员变量值,在有父类的情况下,负责调用父类(指定)初始化器。指定初始化器一般是类层次间的唯一初始化出口,也通常是参数最多的那个initXXX方法。

一个类通常只有一个指定初始化器,但在某些特殊情况,比如通过反序列化重建对象的时候,可能会有多个。苹果iOS系统库中的类的指定初始化器都会在文档中列出。

2. 继承和初始化链

当涉及到继承关系的时候,情况变得更加复杂一些(所以一个业界常问的问题就是在设计时继承优先还是组合优先)。

在Java中,这个通常不用自己操心,必要的时候调用super()就好。

在Objective-C中,首先,每个类的层次结构负责给自己定义的成员变量/属性赋予初始值。而一个子类对象只有在父类对象得到初始化时,再进行自定义初始化才有意义。所以,一个类要在init方法中进行父类初始化,而且需要手动调用父类初始化方法。如下是一个常规的初始化方法实现:

- (instancetype)init
{
    self = [super init];
    if (self) {
        ;
    }
    return self;
}

就这样,有继承关系的各个层次间的类构成了一道完美的初始化器链。

除此之外,继承还带来了额外两个问题(至少在Objective-C中是都存在的):

  • 为了充分满足子类的指定初始化器要求,如果子类初始化器和任何上层类的指定初始化方法名字有所不同(一般由于子类的特性,都会在参数上有所不同),必须在子类重写各层父类初始化方法,使其调用当前类指定初始化器,构成完整的初始化链
  • 子类的指定初始化器只能调用父类的指定初始化器,至于原因,我这里先不写明,让大家思考下(其实和其他语言的考虑一样)

Swift作为新生语言,作为Objective-C的后继者,自然应当有所改进,接下来我们一起看下。

3. Swift中的对象初始化

Swift是WWDC2014苹果推出的新语言,旨在降低语言门槛,提高开发效率。

在Swift中,对象初始化调用更像Java了,无需分别调用alloc和init来拿到对象,直接向下面这样使用就可以了:

var f = Fahrenheit()。

同时支持默认情况下property的初始化。init也没有返回值,无需alloc和init联合调用取最后返回值了。在init中还可改变常量。

初始化比较复杂的点还在于初始化方法的委派调用(delegation),对于非class的结构,比如struct的初始化,没有继承,只能通过调用self.init()的方式进行委派调用。而最复杂的委派调用,依然还是在有继承情况下:

  • 最基本的,非指定初始化器和指定初始化器构成的调用顺序链和Objective-C基本一致。非指定初始化方法和制定初始化方法写法略有不同,非指定初始化方法有一个convenience关键字。
  • 两阶段初始化,第一阶段完成自身变量有初始化值并保证父类的也都完成变量值初始化,逐层向上,第二阶段顺序反过来进一步在子类中自定义对象状态(此时可以使用self)。
  • 对于父类的初始化方法,按两种规则进行继承,其它情况下都不自动继承。这个基本上很好地解决了本文上一个section中的两个问题。规则一,如果本类中新增的属性成员有默认初始化值,而且没有定义制定初始化方法,那么将自动继承superclass中所有的指定初始化器;规则二,无论是通过自己实现还是通过规则一继承,如果当前类中拥有了所有父类实现过的指定初始化器,则自动继承superclass中所有的非指定初始化器。一个实例见下图。
Swift中类初始化器的自动继承

Swift中类初始化器的自动继承

关于Objective-C、Swift初始化的内容整理到这,欢迎各位补充交流。

相关文章:

此条目发表在 iOS, Objective-C, 开发, 计算机技术 分类目录,贴了 , , , , 标签。将固定链接加入收藏夹。

Objective-C和Swift中的对象初始化》有 2 条评论

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>