高级类(二)

2018-05-28  本文已影响3人  小橘子成长记

继承和类的初始化

上一章简要介绍了类初始化方法,它们与结构相似。对于子类,在创建实例方面还有一些考虑。

修改StudentAthlete(学生运动员)类,增加运动员比赛项目列表:

class StudentAthlete: Student {
  var sports: [String]
  // original code
}

因为sports没有初始值,StudentAthlete(学生运动员)必须在自己的初始设定项中提供一个:

class StudentAthlete: Student {
  var sports: [String]
  init(sports: [String]) {
    self.sports = sports
    // Build error - super.init isn’t called before
    // returning from initializer
}
  // original code
}

啊哦!编译器报错:在初始化方法结束之前你没有调用super。


QQ20180525-095435@2x.png

子类中的初始化方法需要调用super.init因为没有它,super类将无法为其所有的存储属性提供初始状态——在本例中是firstName和lastName。

现在我们让编译器正确编译:

class StudentAthlete: Student {
  var sports: [String]
  init(firstName: String, lastName: String, sports: [String]) {
    self.sports = sports
    super.init(firstName: firstName, lastName: lastName)
  }
  // original code
}

初始化方法现在调用它的super类的初始化方法,并且构建错误已经消失。

请注意,初始化方法现在使用firstName和lastName来满足Person初始化方法的要求。

在初始化sports属性后,调用super.init。这是一个的规则。

两阶段初始化

由于Swift要求所有存储的属性都具有初始值,所以子类的初始化方法必须遵守Swift的两阶段初始化约定。

•第一阶段:初始化类实例中的所有存储属性,从底层到类层次结构的顶部。在第一阶段完成之前,不能使用属性和方法。
•第二阶段:你现在可以使用属性和方法,并需要使用self的初始化。

没有两个阶段的初始化,类的方法和操作可能会在初始化之前与属性交互。
从阶段1到阶段2的转换发生在你初始化了类层次结构基类中的所有存储属性之后。
在子类初始化方法的范围内,你可以在这之后调用super.init。


QQ20180525-131026@2x.png

这是StudentAthlete(学生运动员)类,运动员自动获得一个grade:

class StudentAthlete: Student {
  var sports: [String]
  init(firstName: String, lastName: String, sports: [String]) {
    // 1
    self.sports = sports
     // 2
    let passGrade = Grade(letter: "P", points: 0.0,
// 3
credits: 0.0)
    super.init(firstName: firstName, lastName: lastName)
     // 4
    recordGrade(passGrade)
  }
  // original code
}

上面的初始化方法显示了两阶段初始化。

1 首先,你要初始化StudentAthlete(学生运动员)的sports(运动)属性。这是初始化阶段的一部分,必须在调用super.init方法之前完成。
2 虽然你可以为诸如grades之类的东西创建局部变量,但是你不能调用recordGrade(:),因为对象仍然处于第一个阶段。
3 你调用super.init。当这个返回时,你知道你已经对层次结构中的每个类进行了初始化,因为在每个类都应用了相同的规则。
4 super.init之后,初始化方法进入第2阶段,所以你可以调用recordGrade(
:)。

必要和便利的初始化

你知道在类中有多个初始化器是可能的,这意味着你可以从子类调用任何初始化方法。

通常,你会发现你的类有各种初始化方法,它们只是提供了初始化对象的“方便”方法:

class Student {
  let firstName: String
  let lastName: String
  var grades: [Grade] = []
  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
  init(transfer: Student) {
    self.firstName = transfer.firstName
    self.lastName = transfer.lastName
  }
  func recordGrade(_ grade: Grade) {
    grades.append(grade)
  }
}

在本例中,Student类可以由另一个Student对象构建。也许是学生换了专业?两个初始化方法都设置了self.firstName和self.lastName。

学生的子类在调用super.init时可能会依赖于基于Student的初始化方法。此外,子类甚至可能没有提供由firstName和lastName属性初始化的方法。如果希望firstName和lastName对所有子类都可用,有一个初始化firstName和lastName的方法是非常必要的。

Swift支持这一语言特性,因此有一个叫required initializers(必要的初始方法):

class Student {
  let firstName: String
  let lastName: String
  var grades: [Grade] = []
  required init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
  // original code
}

在上面的Student修改版本中,firstName和lastName基于名称的初始方法被标记了required关键字。这个关键字将强制所有Student的子类实现这个初始方法。

现在Student有了 required初始方法,StudentAthlete(学生运动员)必须重写并实现它。

class StudentAthlete: Student {
  // Now required by the compiler!
  required init(firstName: String, lastName: String) {
    self.sports = []
    super.init(firstName: firstName, lastName: lastName)
  }
  // original code
}

请注意,override关键字不是用在初始化方法的。在这个位置,使用required关键字来确保StudentAthlete学生运动员的任何子类必需实现这个初始化器。

你还可以将初始化方法标记为convenience(方便的)初始化方法:

class Student {
  convenience init(transfer: Student) {
    self.init(firstName: transfer.firstName,
              lastName: transfer.lastName)
}
  // original code
}

编译器强制一个convenience方便的初始化器调用一个非方便的初始化方法(直接或间接),而不是处理存储属性本身的初始化。

一个非方便的初始化称为指定的初始化方法,并遵循两阶段初始化的规则。你在前面的示例中编写的所有初始化方法实际上都是指定的初始化方法。

如果你只使用初始化方法作为初始化对象的简单方法,那么你可能希望将初始化器标记为方便,但是你仍然希望它使用你指定的初始化方法。

下面是使用指定的和convenience方便的初始化方法的编译器规则的摘要:
1 指定的初始化方法必须从它的直接superclass调用指定的初始化器。
2 一个convenience方便的初始化器必须从同一个类调用另一个初始化方法。
3一个convenience方便的初始化方法必须最终调用指定的初始化方法。

上一篇下一篇

猜你喜欢

热点阅读