swift自定义错误_swift uialertcontroller 自定义 - CSDN
  • Swift基础 错误处理

    2017-11-20 15:50:16
    自定义错误struct ValueError : Error { let reason: String }实现Error协议便可自定义错误。抛出异常fun getValue100(error: Bool) throws -> Int { if error { throw ValueError(reason: "100")

    Swift的错误处理和Java的相似!!

    自定义错误

    struct ValueError : Error {
        let reason: String
    }

    实现Error协议便可自定义错误。

    抛出异常

    fun getValue100(error: Bool) throws -> Int {
        if error {
            throw ValueError(reason: "100")
        }
        return 100
    }

    其抛出异常也和Java的很像相似。

    传递错误

    和Java一样,也可以将错误传递出去

    fun getValue() throws -> Int {
        return try getValue100(false)
    }

    这样的话,就先不会处理,交给外部调用tryError方法时处理,不过还是要“try”关键字。

    错误处理 Do-Catch

    Swift的错误捕捉和处理

    // 如果不想处理ValueError
    do {
        try getValue100(true)
    } catch {
        ...
    }
    // 如果想判断错误来自于ValueError
    do {
        try getValue100(true)
    } catch is ValueError {
        ...
    }
    // 如果想要处理ValueError
    do {
        try getValue100(true)
    } catch let error as ValueError {
        ...
    }

    将错误转成可选值

    如果调用方法(可能会抛出错误)有返回值,而且你可能不太想处理抛出的错误,你可以把这个方法(加关键字)赋给一个变量,那么这个变量就是一个可选类型。

    var value = try? getValue100(true)  // value此时的类型是Int?,可能为nil
    if let v = value {
        print("value is \(v)")
    }   

    利用这种方式,我们可以写出比较优雅的代码

    func getValue100(error: Bool) throws -> Int {
        if error {
            throw ValueError(reason: "100")
        }
        return 100
    }
    func getValue200(error: Bool) throws -> Int {
        if error {
            throw ValueError(reason: "200")
        }
        return 200
    }
    func getValue() -> Int? {
        if let value = try? getValue100(error: false) {
            return value
        }
        if let value = try? getValue200(error: false) {
            return value
        }
        return nil
    }

    禁止错误传递

    虽然getValue100()方法可能会报错,但是如果传入为false的话,肯定不会报错,我们可以禁止错误传递

    let value = try! getValue100(error: false)

    到此,应该大概能猜出“!”的意义了吧,“!”一般用于强制性的操作,如可选类型。

    展开全文
  • Swift 自定义init方法

    2017-07-26 16:37:53
    在开发中,自定义init初始化构造函数实现参数传递比较常见 1,在Swift中, 类的初始化器有两种, 分别是DesignatedInitializer(指定初始化器)和ConvenienceInitializer(便利初始化器) 2,如果子类没有定义...

    在开发中,自定义init初始化构造函数实现参数传递比较常见

    1,在Swift, 类的初始化器有两种, 分别是DesignatedInitializer(指定初始化器)和ConvenienceInitializer(便利初始化器)

    2,如果子类没有定义任何的指定初始化器,那么会默认继承所有来自父类的指定初始化器。

    3,如果子类提供了所有父类指定初始化器的实现,那么自动继承父类的便利初始化器

    4,如果子类只实现部分父类初始化器,那么父类其他的指定初始化器和便利初始化器都不会继承。

     

    5,子类的指定初始化器必须要调用父类合适的指定初始化器。


    错误分析: 指定构造器必须调用它最近父类的指定构造器。

    importUIKit


    classWebViewController:UIViewController{


       varimageUrl:String!

       

       init(imageUrl:String){

           

          super.init(nibName:nil,bundle: nil)

           

          self.imageUrl=imageUrl

       }

       

       requiredinit?(coderaDecoder: NSCoder){

          fatalError("init(coder:)has not been implemented")

       }

       

       overridefuncviewDidLoad(){

          super.viewDidLoad()


          self.view.backgroundColor=UIColor.whiteColor()


       }

    }


    展开全文
  • 神奇的 Swift 错误处理

    2016-08-01 10:54:02
    Swift 中的错误处理从 O-C 沿袭而来,但 Swift 1.0 之后逐渐发生了巨大改变。重要的改变发生在 Swift 2,它率先使用了“处理非异常的状态和条件”的做法,使你的 app 变得更加简单。类似于其它编程语言,在 Swift 中...

    原文:Magical Error Handling in Swift
    作者: Gemma Barlow
    译者:kmyhy

    Swift 中的错误处理从 O-C 沿袭而来,但 Swift 1.0 之后逐渐发生了巨大改变。重要的改变发生在 Swift 2,它率先使用了“处理非异常的状态和条件”的做法,使你的 app 变得更加简单。

    类似于其它编程语言,在 Swift 中,选择使用哪种错误处理技术,需要根据具体的错误类型和 app 整体架构而定。

    本教程将演示一个“魔法”,在这个例子中,不但有男巫、女巫和蝙蝠,还有蟾蜍,以此来演示在常见错误处理过程中的最佳实践。你还可以看到,如何将使用 Swift 早期版本编写的错误处理进行升级,最终使用你的水晶球看到未来 Swift 的错误处理将是什么样子。

    注:本教程假设你已经熟悉了 Swift 2 语法——尤其是枚举和可空。如果你不知道这些概念,请阅读 Greg Heo 的 What’s New in Swift 2 post

    好了,让我们开始领略 Swift2 的错误处理的迷人魅力吧!

    开始

    本教程有两个开始项目(playground)。一节一个,分别是:Avoiding-Errors-with-nil-Starter.playgroundAvoiding-Errors-with-Custom-Handling-Starter.playground

    打开第一个 playground 文件。

    阅读代码,你将发现几个类、结构和枚举。

    注意如下代码:

    protocol MagicalTutorialObject {
      var avatar: String { get }
    }

    这个协议会被教程中所有类和结构所采用,并用于提供一个能够将每个对象打印到控制台的 String 对象。

    enum MagicWords: String {
    case Abracadbra = “abracadabra”
    case Alakazam = “alakazam”
    case HocusPocus = “hocus pocus”
    case PrestoChango = “presto chango”
    }

    这个枚举用于表示“咒语”,它将被“念”(spell)出来。

    struct Spell: MagicalTutorialObject {
      var magicWords: MagicWords = .Abracadbra
      var avatar = "*"
    }

    这个结构用于将咒语“念”出来。默认情况下,其 magicWords 属性的初始值是 Abracadabra。

    你已经了解在这个魔法世界的基本知识了,你可以开始练习咒语了。

    为什么要进行错误处理?

    “错误处理是一门让错误变得优雅的艺术。”
    –Swift Apprentice,第 21 章(错误处理)

    良好的错误处理能增强用户体验,让软件维护者更容易发现问题,了解出错的原因以及错误的严重性。当代码中的错误的处理无所不在的时候,诊断问题就变得更加容易了。错误处理还会让系统以正确的方式终止执行,避免用户产生不必要的困扰。

    当然并不是所有的错误都需要被处理。当不对错误进行处理时,语言特性也会进行某种级别的错误处理。一般,如果你能够避免错误的发生,则尽量避免。如果实在无法避免,则最好的做法就是错误处理。

    避免 Swift 引用为空错误

    由于 Swift 已经有了优雅的可空处理机制,类似这种错误:在你以为有值的地方却没有值——是可以完全避免的。作为一个聪明的程序员,你可以利用这种特性,在某种错误发生时故意返回一个 nil。如果你不想在错误发生时采取任何动作时,这种方式很好用,例如在事故发生时采取不作为措施。

    避免 Swift 引用为空的两个典型例子就是:允许失败的初始化方法,以及 guard 语句。

    允许失败的初始化方法

    允许失败的初始化方法防止你创建出不完全满足创建条件的对象。在 Swift 2 之前(已经其它语言),这种方法通常在工厂方法设计模式中用到。

    在 Swift 中的这种设计模式体现在 createWithMagicWords 中:

    static func createWithMagicWords(words: String) -> Spell? {
      if let incantation = MagicWords(rawValue: words) {
        var spell = Spell()
        spell.magicWords = incantation
        return spell
      }
      else {
        return nil
      }
    }

    上述初始化方法企图用指定的咒语创建一个 Spell 对象,如果提供给它的 words 参数不是一个合法的咒语,则返回一个 nil 对象。

    在本教程底部检查 Spell 对象的创建语句,你会看到:

    第一个语句用“abracadabra”成功创建了一个 Spell 对象,但第二句使用”ascendio” 就不行了,返回了一个 nil 对象。(哈,巫师不是每次都能成功念出咒语的)

    工厂方法是一种古旧的编程风格。其实在 Swift 中我们可以有更好的选择。你可以将 Spell 中的工厂方法修改为“允许失败的初始化方法”。

    删除createWithMagicWords(_:) 并替换为:

    init?(words: String) {
      if let incantation = MagicWords(rawValue: words) {
        self.magicWords = incantation
      }
      else {
        return nil
      }
    }

    这里,在这个方法声明中,我们没有显式地创建和返回一个 Spell 对象。

    噢,这两句出现编译错误了:

    let first = Spell.createWithMagicWords("abracadabra")
    let second = Spell.createWithMagicWords("ascendio")

    你需要将它们修改成调用新方法。将上面的语句修改为:

    let first = Spell(words: "abracadabra")
    let second = Spell(words: "ascendio")

    这样,错误消失,playground 编译成功。这种改变让你的代码更整洁——但你还有更好的解决办法!

    Guard 语句

    guard 语句是一种更好的断言某些情况为 true 的方式:例如,判断一个值大于 0,或者判断某个值是否能够被解包的时候。如果这种情况都不满足,你可以执行语句块。

    guard 语句在 Swift 2 才开始引入,通常用于在调用堆栈中进行冒泡法错误处理,这种方法中,错误将在最后才被处理。guard 语句能够尽早从方法/函数中退出,比起需要判断某个条件满足剩下的逻辑才会执行来说,显得更加简单。

    将 Spell 的允许失败的初始化方法修改为 guard 语句:

    init?(words: String) {
      guard let incantation = MagicWords(rawValue: words) else {
        return nil
      }
      self.magicWords = incantation
    }

    在这里,我们不需要将 else 放在单独的行上,而且对断言失败的处理变得显眼,因为它被更放在了方法的头部。同时,“黄金路径”缩进最少。“黄金路径”是指当每件事都如预期即没有错误发生时的执行路径。而缩进最少,则使它更易于被看到。

    注,虽然 first 和 second 最终值不会有任何改变,但代码变得更加合理化。

    对错误进行定制化处理

    在完成 Spell 的初始化方法并利用 nil 避免某些错误之后,你将学习某些更高级的错误处理。

    对于本教程的第二部分内容,请打开 Avoiding Errors with Custom Handling – Starter.playground。

    看一下这些代码:

    struct Spell: MagicalTutorialObject {
    
      var magicWords: MagicWords = .Abracadbra
      var avatar = "*"
    
      init?(words: String) {
        guard let incantation = MagicWords(rawValue: words) else {
          return nil
        }
        self.magicWords = incantation
      }
    
      init?(magicWords: MagicWords) {
        self.magicWords = magicWords
      }
    }

    这是 Spell 的初始化方法,在第一部分内容的基础上修改而来。注意,MagicalTutorialObject 协议的使用,以及第二个允许失败的初始化方法,为了方便我们添加了它。

    protocol Familiar: MagicalTutorialObject {
      var noise: String { get }
      var name: String? { get set }
      init()
      init(name: String?)
    }

    Familiar 协议会被使用到各种动物(比如蝙蝠和蟾蜍)。

    注:Familiar 的意思是仆从,也就是男巫或女巫的动物精灵,拥有类人的特点。比如《哈利波特》中的猫头鹰(名为 Hedwig),或者《The Wizard of Oz》中的飞猴。

    虽然它不是 Hewig,但仍然很漂亮,不是吗?

    struct Witch: MagicalBeing {
      var avatar = "*"
      var name: String?
      var familiar: Familiar?
      var spells: [Spell] = []
      var hat: Hat?
    
      init(name: String?, familiar: Familiar?) {
        self.name = name
        self.familiar = familiar
    
        if let s = Spell(magicWords: .PrestoChango) {
          self.spells = [s]
        }
      }
    
      init(name: String?, familiar: Familiar?, hat: Hat?) {
        self.init(name: name, familiar: familiar)
        self.hat = hat
      }
    
      func turnFamiliarIntoToad() -> Toad {
        if let hat = hat {
          if hat.isMagical { // When have you ever seen a Witch perform a spell without her magical hat on ? :]
            if let familiar = familiar {   // Check if witch has a familiar
              if let toad = familiar as? Toad {  // Check if familiar is already a toad - no magic required
                return toad
              } else {
                if hasSpellOfType(.PrestoChango) {
                  if let name = familiar.name {
                    return Toad(name: name)
                  }
                }
              }
            }
          }
        }
        return Toad(name: "New Toad")  // This is an entirely new Toad.
      }
    
      func hasSpellOfType(type: MagicWords) -> Bool { // Check if witch currently has appropriate spell in their spellbook
        return spells.contains { $0.magicWords == type }
      }
    }

    最后,是女巫。请看下面:

    • 女巫的初始化需要一个名字和一只精灵,或者一个名字、一只精灵和一顶帽子。
    • 女巫会念许多咒语,用一个 spells 保存,即一个 Spell 数组。
    • 女巫有一个嗜好,当她一念到咒语:“PrestoChango”,她的精灵就会被变成一只蟾蜍,这个动作用 turnFamiliarIntoToad() 方法

    注意 turnFamiliarIntoToad() 方法中的缩进。在这个方法中,如果遇到任何错误,会返回一只全新的蟾蜍。这看起来有点不对劲(这是错误的!)。在下一部分,你将用自定义错误处理来解决这个问题。

    用 Swift 错误进行重构

    Swift 提供了运行时抛出、捕获、传递和操纵可恢复类型错误的支持。
    -《The Swift Programming Language (Swift 2.2)》

    与“死亡之庙”不同,在 Swift 或其它语言中,“厄运金字塔”是另外一种相反的模型。使用这种模型会在控制流中使用多级嵌套。例如上面的 turnFamiliarIntoToad() 方法,使用了 6 个 } 符号才能结束嵌套,基本构成了一条对角线。这样的代码阅读起来相当费劲。

    厄运金字塔

    使用先前提到的 guard 语句,以及可空绑定,能够避免出现“厄运金字塔”代码。do-catch 机制能够将错误处理从控制流中解耦出来,从减少“厄运金字塔”的出现。

    do-catch 机制常用的关键字包括:

    • throws
    • do
    • catch
    • try
    • defer
    • ErrorType

    要试一试 do-catch 机制,你将抛出多个自定义错误。首先,你需要定义一个枚举,将所有你想处理的状态列到其中,而这些状态可能表明某个地方东西出错了。

    在 Witch 类定义之上添加如下代码:

    enum ChangoSpellError: ErrorType {
      case HatMissingOrNotMagical
      case NoFamiliar
      case FamiliarAlreadyAToad
      case SpellFailed(reason: String)
      case SpellNotKnownToWitch
    }

    关于 ChangoSpellError 有两点需要注意:

    • 它采用了 ErrorType 协议,这是必须的。在 Swift 中, ErrorType 表明了这是一种错误。
    • 在 SpellFailed 的 case 分支,你可以指定一种自定义的原因,表示为什么咒语会念错。

    注:ChangoSpellError 的名字来自于咒语“Presto Chango!”——女巫在将精灵变成蟾蜍时念的咒语。

    好了,亲爱的,赶紧施展你的魔法吧。很好。在方法签名中添加一个 throws 关键字,表明方法调用时可能会抛出错误:

    func turnFamiliarIntoToad() throws -> Toad {
    Update it as well on the MagicalBeing protocol:
    protocol MagicalBeing: MagicalTutorialObject {
      var name: String? { get set }
      var spells: [Spell] { get set }
      func turnFamiliarIntoToad() throws -> Toad
    }

    现在,你拥有了错误状态列表,接下来需要重新编写 turnFamiliarIntoToad() 方法,针对每个错误类型编写不同的处理语句。

    处理帽子的错误

    首先,修改下列语句,确保女巫已经佩戴了她永不离身的魔法师帽。

    修改之前的代码:

    if let hat = hat {

    修改之后的代码:

    guard let hat = hat else {
      throw ChangoSpellError.HatMissingOrNotMagical
    }

    注:不要忘记在方法底部将对应的 } 也删掉。否则 playground 会编译错误!

    下一句是对一个布尔值进行检查,这也和魔法师帽有关:

    if hat.isMagical {

    你可以再用一个 guard 语句进行检查,也可以将两个检查合并到一个 guard 语句——这显然要清晰和简洁得多。因此将第一个 guard 语句修改为:

    guard let hat = hat where hat.isMagical else {
      throw ChangoSpellError.HatMissingOrNotMagical
    }

    然后将 if hat.isMagical { 删除。

    在接下来的部分,你将继续破解“金字塔”问题。

    处理精灵的错误

    接着,判断巫师是否有一只精灵:

    if let familiar = familiar {

    将这句用抛出一个 .NoFamiliar 错误来替换:

    guard let familiar = familiar else {
      throw ChangoSpellError.NoFamiliar
    }

    忽略此时出现的任何错误,因为接下来的代码会让它们消失。

    处理蟾蜍的错误

    接下来一句,如果女巫在试图用 turnFamiliarIntoToad() 方法时发现她的精灵其实已经是一只蟾蜍了,则返回已有的蟾蜍。但这里更好的做法是,用一个错误来表示这种情况。将下列代码:

    if let toad = familiar as? Toad {
      return toad
    }

    修改为:

    if familiar is Toad {
      throw ChangoSpellError.FamiliarAlreadyAToad
    }

    注意,我们将 as? 改为了 is。在需要检查某个对象是否能够转换为某个协议,但同时不需要使用转换结果时,这种写法更加简洁。is 关键字也可以更加泛型化的方式进行类型比较。如果你想了解更多内容,请阅读The Swift Programming Language“类型转换”一节。

    将 else 之内的代码移到 else 之外,然后删除 else 语句,它没用了。

    处理咒语的错误

    最后,调用了 hasSpellOfType(type:) 方法,以检查女巫的魔法书中确实有相应的咒语。将下列代码:

    if hasSpellOfType(.PrestoChango) {
      if let toad = f as? Toad {
        return toad
      }
    }

    修改为:

    guard hasSpellOfType(.PrestoChango) else {
      throw ChangoSpellError.SpellNotKnownToWitch
    }
    
    guard let name = familiar.name else {
      let reason = "Familiar doesn’t have a name."
      throw ChangoSpellError.SpellFailed(reason: reason)
    }
    
    return Toad(name: name)

    现在,删除最后一行不安全的代码。也就是这行:

    return Toad(name: "New Toad")

    现在,你的方法变得更清晰和整洁,已经能够使用了。我在上述的代码添加了注释,以解释这个方法所做的工作:

    func turnFamiliarIntoToad() throws -> Toad {
    
      // When have you ever seen a Witch perform a spell without her magical hat on ? :]
      guard let hat = hat where hat.isMagical else {
        throw ChangoSpellError.HatMissingOrNotMagical
      }
    
      // Check if witch has a familiar
      guard let familiar = familiar else {
        throw ChangoSpellError.NoFamiliar
      }
    
      // Check if familiar is already a toad - if so, why are you casting the spell?
      if familiar is Toad {
        throw ChangoSpellError.FamiliarAlreadyAToad
      }
      guard hasSpellOfType(.PrestoChango) else {
        throw ChangoSpellError.SpellNotKnownToWitch
      }
    
      // Check if the familiar has a name
      guard let name = familiar.name else {
        let reason = "Familiar doesn’t have a name."
        throw ChangoSpellError.SpellFailed(reason: reason)
      }
    
      // It all checks out! Return a toad with the same name as the witch's familiar
      return Toad(name: name)
    }

    你曾经在 turnFamiliarIntoToad() 方法中返回一个可空来表示“在念咒语时出了差错”,但使用自定义错误能够更加清晰地表达错误的状态,以便你根据这些状态采取对应措施。

    自定义错误还有什么好处?

    现在,你有一个方法抛出了一个自定义 Swift 错误,你需要处理它们。接下来的标准动作是使用 do-catch 语句,这就好比 Java 等语言中的 try-catch 语句。

    在 playground 的底部加入下列代码:

    func exampleOne() {
      print("") // Add an empty line in the debug area
    
      // 1
      let salem = Cat(name: "Salem Saberhagen")
      salem.speak()
    
      // 2
      let witchOne = Witch(name: "Sabrina", familiar: salem)
      do {
        // 3
        try witchOne.turnFamiliarIntoToad()
      }
      // 4
      catch let error as ChangoSpellError {
        handleSpellError(error)
      }
      // 5
      catch {
        print("Something went wrong, are you feeling OK?")
      }
    }

    一下是对这个方法的解释:

    • 创建女巫的精灵,它的名字叫 Salem。
    • 创建女巫,名字叫 Sabrina。
    • 试图将猫咪变成蟾蜍。
    • 捕获 ChangoSpellError 并进行相应的处理。
    • 捕获其它错误,打印友好信息。

    写完上述代码,你会看到一个编译错误——让我们来搞定它。

    handleSpellError() 方法还没有定义,在 exampleOne() 方法之上加入这个方法:

    func handleSpellError(error: ChangoSpellError) {
      let prefix = "Spell Failed."
      switch error {
        case .HatMissingOrNotMagical:
          print("\(prefix) Did you forget your hat, or does it need its batteries charged?")
    
        case .FamiliarAlreadyAToad:
          print("\(prefix) Why are you trying to change a Toad into a Toad?")
    
        default:
          print(prefix)
      }
    }

    最后,在 playground 最后执行这个方法:

    exampleOne()

    点击 Xcode 工作空间左下角的上箭头,打开 Debug 控制台,你就会看到 playground 的输出了:

    捕获错误

    下面对上述代码中的每个语法特性进行简单讨论。

    catch

    你可以用 Swift 的模板匹配来处理某种错误,或者将错误类型进行分组处理。
    前面的代码示范了 catch 的两个用法:一个是用于捕捉 ChangoSpell 错误,一种用于捕捉剩下的错误。

    try

    try 与 do-catch 语句配合使用,用于清晰定位是哪行语句或代码块将抛出错误。
    try 语句有几种不同的用法,上面用到了其中之一:

    • try: 标准用法,在简单的、立即的 do-catch 语句中使用。就是前面代码中的用法。
    • try?: 处理错误,以忽略该错误的方式;如果有错误抛出,这个语句的结果是 nil。
    • try!: 类似强制解包,这个前缀会创建期望的对象,理论上这个语句会抛出错误,但实际上这种错误永远不会发生。 try! 可以用于执行加载文件的动作,特别是当你明确知道文件是肯定存在的。就如前置解包一样,使用这种结构需要特别谨慎。

    让我们来体验一下 try? 的使用。复制粘贴下列代码到 playgournd 的底部:

    func exampleTwo() {
      print("") // Add an empty line in the debug area
    
      let toad = Toad(name: "Mr. Toad")
      toad.speak()
    
      let hat = Hat()
      let witchTwo = Witch(name: "Elphaba", familiar: toad, hat: hat)
    
      let newToad = try? witchTwo.turnFamiliarIntoToad()
      if newToad != nil { // Same logic as: if let _ = newToad
        print("Successfully changed familiar into toad.")
      }
      else {
        print("Spell failed.")
      }
    }

    注意和 exampleOne 不同的地方。在这里我们不需要知道具体的错误输出了些什么,只是在它们抛出是捕获它们。Toad 对象最终会创建失败,因此 newToad 的值应当为 nil。

    传递错误

    throws

    在 Swift 中,如果方法或函数代码中会抛出错误,则必须用到 throws 关键字。被抛出的错误会自动在调用堆栈中进行传递,但如果让错误从现场地向上冒泡太多并不是一个好主意。在代码库中充斥大量的错误传递会增加错误不被正确处理的可能性,因此 throws 是强制性的,以确保错误的传递被代码所记录——对于程序员来说是显而易见的。

    rethrows

    目前你所见到的例子都是关于 throws 的,而没有它的亲兄弟 rethrows 的吗?

    rethrows 告诉编译器,这个函数会抛出一个错误,同时它的参数也会抛出一个错误。下面是一个例子(不需要将它加到你的 playground 里):

    func doSomethingMagical(magicalOperation: () throws -> MagicalResult) rethrows -> MagicalResult {
      return try magicalOperation()
    }

    这个方法只会抛出 magicalOperation 参数抛出的那个错误。如果成功,它返回一个 MagicalResult 对象。

    操纵错误处理的行为

    defer

    尽管大部分情况下,我们让错误自动传播就可,但某些情况下,你可能想控制错误在调用堆栈中传递时 app 的行为。

    defer 语句提供一种机制,让你在当前作用域结束时执行某些“清理”动作,比如方法或函数返回时。它可以清理某些资源,而无论动作是否执行成功或失败,尤其在错误处理上下文中有用。

    要测试这种行为,请在 Witch 结构中加入如下方法:

    func speak() {
      defer {
        print("*cackles*")
      }
      print("Hello my pretties.")
    }

    在 playground 底部加入代码:

    func exampleThree() {
      print("") // Add an empty line in the debug area
    
      let witchThree = Witch(name: "Hermione", familiar: nil, hat: nil)
      witchThree.speak()
    }
    
    exampleThree()

    在 Debug 控制台,你将看到女巫在每说一句话之后都会“咯咯笑”(cackles)。
    有趣的是,defer 语句的执行顺序与书写顺序相反。
    在 speak() 方法中添加另一个 defer 语句,这样当女巫说完一句话后,会先尖叫,然后再发出“咯咯”的笑声。

    func speak() {
      defer {
        print("*cackles*")
      }
    
      defer {
        print("*screeches*")
      }
    
      print("Hello my pretties.")
    }

    打印顺序是否如你所想?呵,神奇的 defer!

    其它和错误有关的事

    总而言之,Swift 已经和其他主流语言站到了同一起跑线上,同时 Swift 也不再采用 O-C 的基于 NSError 的错误处理机制。O-C 错误很多时候是被转换过的了,由编译器中的静态分析器帮你很好地完成了注入捕捉什么样的错误和错误何时发生的工作。

    尽管 do-catch 和相关特性在其他语言中有不小的开销,但在 Swift 中,它们被视作和其它语句完全相同。这使得它们保持经济和高效。

    虽然你可以创建自定义错误并随意抛出它们,但不意味着你就应该那样做。在每个项目中,当你需要抛出并捕捉错误时,你都应当遵循一定的开发指南。我建议:

    • 不管在哪个代码库中,你的错误类型都需要命名清晰。
    • 在只有一种错误状态时,使用可空就可以了。
    • 在超过一种以上的错误状态是,才使用自定义错误。
    • 不要让错误从错误现场传递到太远的地方。

    Swift 将来的错误处理

    在各大 Swift 论坛中,有很多关于未来的错误处理的想法。其中讨论得最多的一个是无类型传递。

    “…我们觉得应该在当前的处理模型中增加对无类型传递的支持,以针对通用型的错误。做好这一点,尤其是在不增加代码尺寸和性能代价的前提下,需要有足够的决心和远见。因此,这被看成是 Swift 2.0 以后的任务。”
    – from Swift 2.x Error Handling

    无论你是否喜欢这种观点,Swift 3 中的错误处理必将有重大改变,或者你只关心眼前的一切,你也需要知道随着这门语言的演进,那种清晰的错误处理机制正在被激烈地讨论和改进当中。

    结束

    你可以下载本教程完整的 playgrounds

    本文的补充内容,我建议阅读下列文章,本教程也引用了其中一些内容:

    如果你渴望了解 Swift 3 中将会有什么,我推荐你去看[Swift Language Proposals](Swift Language Proposals)中当前开放的提议,干嘛你不提交你自己的提议呢?
    希望现在你已经体会到 Swift 错误处理的魅力。如果你有任何问题或建议,请在下面的讨论中留言!

    展开全文
  • override init(frame: CGRect) { super.init(frame: frame) } required init?(coder: NSCoder) { fatalError("init(coder:) ... - 成员成员变量必须要赋值初始值,不然会报没有初始化成员变量的错误 可以用懒加...
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    > -  成员成员变量必须要赋值初始值,不然会报没有初始化成员变量的错误 可以用懒加载 也可以用隐式拆包设置默认值
    > - 重写系统初始化方法  override init(frame: CGRect) 
    > - 在自定义方法里面self. 调用系统方法 然后调用自己的操作
    
    `前缀能写就尽量写吧,你写的越精确swift的性能越高,也不枉官网吹就说swift是为研究高性能代码的最终结果,在这里又想说一句它的性能确实高,我在爬数据的时候8万多条数据从掉接口到生成就几秒钟,OC是万万比不上的`
    比较麻烦 还是要吐槽一下的,OC里面就方便多了直接加参数
    
    展开全文
  • 原文:Building a Custom Collection in Swift 作者:Eric Cerney 译者:kmyhy 数组、字典和集合是常见的集合类型,它们都内置在 Swift 标准库中。但如果它们不能满足你的 App 的需要的时候怎么办?一种最常见的...

    原文:Building a Custom Collection in Swift
    作者:Eric Cerney
    译者:kmyhy

    数组、字典和集合是常见的集合类型,它们都内置在 Swift 标准库中。但如果它们不能满足你的 App 的需要的时候怎么办?

    一种最常见的办法是使用 Array 或 Dictionary,然后用一堆业务逻辑去保存你的数据结构。但这种方式太过于直接且难于维护。

    这样,创建自定义集合类型就变得有意义了。在本文,你将学习用 Swift 的 collection 协议创建自定义集合类型。

    当文本结束,你会拥有一个强大的自定义集合类型,拥有 Swift 内置集合的所有功能。

    注:本文用 Swift 3.0。小于次的版本无法编译,因为 Swift 标准库发生了剧烈改变。

    开始

    在本文中,你将从头开始创建一个“多集合”(Bag)类型。

    一个 Bag 对象就像一个 Set,用于存储不会重复的对象。在一个 Set 集合中,重复对象会被忽略。在一个 Bag 中,每个对象都会被算进去。

    一个好例子是购物清单。你拥有一个清单,每个商品都和一个数量关联。如果添加了重复的商品,则我们会增加已有商品的数量,而不是重新插入一条商品记录。

    在介绍 collection 协议前,首先来实现一个基本的 Bag。
    创建一个新的 Playground:在 Xcode 中,选择 File\New\Playground… 然后给 playground 命名为 Bag。 你可以选择任何平台,因为本教程是和平台无关的,你只需要选择 Swift 语言就可以了。
    点击 Next,选择一个地方保存 playground,然后点 Create。

    编辑 playground 文件为:

    struct Bag<Element: Hashable> {
    
    }

    Bag 结构是一个泛型结构,需要元素类型必须是 Hashable 的。Hashable 允许你对元素进行比较,在 0(1)时间复杂度上只存储唯一值。也就是说,无论内容有多复杂,Bag 存取速度相同。你通过定义一个结构体,强制让它具备值语义(C++ 术语),这就和 Swift 标准库保持一致了。

    然后为 Bag 添加属性:

    // 1
    fileprivate var contents: [Element: Int] = [:]
    
    // 2
    var uniqueCount: Int {
      return contents.count
    }
    
    // 3
    var totalCount: Int {
      return contents.values.reduce(0) { $0 + $1 }
    }

    这是 Bag 的基本属性:

    • 用一个作为内部存储结构。因为字典的键是唯一的,你可以用它来存储数据。字典的值则表示每个元素的个数。fileprivate 关键字表示这个属性是隐藏的,在外部不可访问。
    • uniqueCount 返回了字典的每一种对象的统计值,并不累加它们每一个的数量。例如,一个 Bag 中有 4 个橙子和 2 个苹果只会返回 2。
    • totalCount 返回的是 Bag 中所有对象的总计。以同一个例子为例,totalCount 返回值为 6。

    现在,需要几个方法以便增减 Bag 中的内容。在属性声明下面加入:

    // 1
    mutating func add(_ member: Element, occurrences: Int = 1) {
      // 2
      precondition(occurrences > 0, "Can only add a positive number of occurrences")
    
      // 3
      if let currentCount = contents[member] {
        contents[member] = currentCount + occurrences
      } else {
        contents[member] = occurrences
      }
    }

    代码解释如下:

    • add(_:occurrences:) 方法提供了增加元素的方法。它需要两个参数:泛型参数 Element 和一个 Optional 的元素个数。如果 Bag 实例以常量形式 let 定义而不是变量 var 形式定义的话,则这个方法无效。
    • precondition(_:_:)方法的第一个参数是一个 Boolean 表达式,如果为 false,则程序会中断,并在 Debug 窗口输出第二个参数的内容。这个方法有一个前置条件,以保证 Bag 能够被正确使用。这个方法检查了调用 add 方法时符合我们的预设。
    • if 语句判断元素是否已经存在,如果存在,则累加它的计数器,否则加入新的元素。

    另外,你还需要一个删除元素的方法。在 add 方法后新增方法:

    mutating func remove(_ member: Element, occurrences: Int = 1) {
      // 1
      guard let currentCount = contents[member], currentCount >= occurrences else {
        preconditionFailure("Removed non-existent elements")
      }
    
      // 2
      precondition(occurrences > 0, "Can only remove a positive number of occurrences")
    
      // 3
      if currentCount > occurrences {
        contents[member] = currentCount - occurrences
      } else {
        contents.removeValue(forKey: member)
      }
    }

    remove(_:occurrences:) 方法使用的参数和 add 方法一模一样,只不过做了相反的事情:

    • 首先判断元素是否存在,确保在删除它时起码有足够的数目能够被删除。
    • 然后保证元素个数大于 0.
    • 最后,如果元素存在则将元素个数减少。如果元素个数减少后小于等于 0,直接删除整个元素。

    这里,Bag 还不能干更多的事情,甚至无法访问它的内容。你还不能使用 Dictionary 中存在的高阶方法。

    但亡羊补牢为时未晚。我们开始在 Bag 中一一添加这些代码。现在的任务是保持你的代码整洁。

    请先等一下!Swift 提供了让 Bag 符合传统集合的所有工具。
    你需要先了解一下在 Swift 中,让一个对象变成集合需要做些什么。

    自定义集合需要做些什么?

    要理解什么是 Swift 集合,首先需要它继承的协议层次:

    Sequence 协议表示类型支持排序、以迭代的方式访问其元素。你可以把一个 Sequence 对象视作一个元素的列表,允许你挨个挨个地访问其中的元素。

    迭代(Iteration)是一个简单概念,但它能给你的对象提供许多功能。它允许你各种强大的操作比如:

    • map(_:): 用一个闭包将 Sequence 中的每个元素挨个进行转换,并构成另一个数组返回。
    • filter(_:): 用一个闭包过滤所需的元素,将符合闭包谓词所指定条件的元素放到新数组中返回。
    • reduce(_:_:): 用一个闭包将 Sequence 中的所有元素合并成一个值返回。
    • sorted(by:): 根据指定的闭包谓词,将 Sequence 中的元素进行排序并返回排序后的数组。

    这只是其中很少的一部分功能。要查看 Sequence 中提供的所有方法,请查看 Sequence 的文档

    需要说明的一点是,采用 Sequence 协议的类型强制要求是破坏性的或者是非破坏性的。这意味着,在迭代之后,无法保证下一次迭代会从头开始。

    这是一个大问题,如果你的数据准备迭代不止一次的话。要实现非破坏性的迭代,你的对象需要使用 Collection 协议。

    Collection 协议继承了 Sequence 和 Indexable 协议。Collection 和 Sequence 的主要区别是,你可以迭代多次,而且可以用索引来访问。

    实现 Collection 协议之后,你会获得更多“免费”的方法和属性,例如:

    • isEmpty: 返回一个布尔值,表示集合是否为空。
    • first: 返回集合中的第一个元素。
    • count: 返回集合中的元素个数。

    依据集合中的元素类型的不同,你还可能拥有更多的方法和属性。如果你想了解更多,请查看Collection 的文档

    在实现这些协议之前,Bag 还有一个地方需要改进。

    打印对象

    当前,Bag 对象可以用 print(_:) 方法或在 Result Sidebar 视图中暴露出的信息很少。
    在 Playground 中加入如下代码:

    var shoppingCart = Bag<String>()
    shoppingCart.add("Banana")
    shoppingCart.add("Orange", occurrences: 2)
    shoppingCart.add("Banana")
    shoppingCart.remove("Orange")

    这里创建了一个 Bag 对象,并加入了几种水果。如果你查看Playground 的调试窗口,你会看到这些对象的类型信息而不是它保存的内容。

    你可以用 Swift 标准库中的一个协议来解决这个问题。在 shoppingCart 变量上面的 Bag 类型定义结束的 } 之后添加:

    extension Bag: CustomStringConvertible {
      var description: String {
        return String(describing: contents)
      }
    }

    采用 CustomStringConvertible 协议需要实现一个属性,叫做 description。这个属性返回一个实例对象的文字表示。

    在这里,你可以放入任何足以表示你的数据的逻辑。因为字典已经继承了这个协议,你可以简单调用 contents 对象的 description 值。

    看一眼 shopingCart 的 debug 信息:

    漂亮!现在你已经为 Bag 添加了功能,你可以对它的 contents 进行校验了。

    在 Playground 中编写代码时,你可以使用 precondition(_:_:) 来检验返回结果。这会避免你突然破坏之前编写的功能。可以用这个工具作为你的单元测试——将它放到你的日常编码中去做是一个不错的主意!

    在最后一次调用 remove(_:occurrences:) 之后加入:

    precondition("\(shoppingCart)" == "\(shoppingCart.contents)", "Expected bag description to match its contents description")

    如果 shoppingCart 的 description 属性不等于 contents 的 description,则会导致一个错误。

    为了创建我们屌爆了的集合类型,接下来的步骤自然就是初始化。

    初始化

    每次只能加一个元素真的很烦。通常的办法是在初始化的时候用另一个集合来进行初始化。

    这正是我们希望 Bag 能够做到的。在 Playground 的最后加入:

    let dataArray = ["Banana", "Orange", "Banana"]
    let dataDictionary = ["Banana": 2, "Orange": 1]
    let dataSet: Set = ["Banana", "Orange", "Banana"]
    
    var arrayBag = Bag(dataArray)
    precondition(arrayBag.contents == dataDictionary, "Expected arrayBag contents to match \(dataDictionary)")
    
    var dictionaryBag = Bag(dataDictionary) 
    precondition(dictionaryBag.contents == dataDictionary, "Expected dictionaryBag contents to match \(dataDictionary)")
    
    var setBag = Bag(dataSet)
    precondition(setBag.contents == ["Banana": 1, "Orange": 1], "Expected setBag contents to match \(["Banana": 1, "Orange": 1])")

    无法进行编译,因为还没有定义针对这些类型的初始化方法。不要为每种类型创建一种初始化方法,你可以使用泛型。
    在 Bag 定义的 totalCount 后面添加:

    // 1
    init() { }
    
    // 2
    init<S: Sequence>(_ sequence: S) where S.Iterator.Element == Element {
      for element in sequence {
        add(element)
      }
    }
    
    // 3
    init<S: Sequence>(_ sequence: S) where S.Iterator.Element == (key: Element, value: Int) {
      for (element, count) in sequence {
        add(element, occurrences: count)
      }
    }

    代码解释如下:

    • 首先,创建一个空的 init 方法。在定义了其他初始化方法之后,你必须添加这个方法,否则编译器会报错。
    • 然后,定义一个初始化方法并接受一个元素为 Sequence 集合的参数。sequence 参数的类型必须和元素类型匹配。例如,Array 和 Set 对象。然后,对 sequence 进行迭代,挨个添加元素。
    • 最后一个方法类似,但元素类型变成元素了(Element, Int)。这种情况最典型的例子就是字典。 这里,你依然对 sequence 中的元素进行迭代并以指定的个数来添加元素。

    这些泛型初始化方法为 Bag 对象添加了大量的数据源。但是,它们仍然你初始化另一个 Sequence 对象然后传递给 Bag。

    为了避免这个,Swift 标准库提供了两个协议。这两个协议支持以 Sequence 的写法进行初始化。这种写法能让你用更简短的方式定义数据,而不必显式地创建一个对象。

    在 Playground 最后加入下列代码,来看看如何使用这种写法:

    var arrayLiteralBag: Bag = ["Banana", "Orange", "Banana"]
    precondition(arrayLiteralBag.contents == dataDictionary, "Expected arrayLiteralBag contents to match \(dataDictionary)")
    
    var dictionaryLiteralBag: Bag = ["Banana": 2, "Orange": 1]
    precondition(dictionaryLiteralBag.contents == dataDictionary, "Expected dictionaryLiteralBag contents to match \(dataDictionary)")

    没说的,编译器报错了,我们后面再来解决。这种写法用初始化数组和字典的写法来进行初始化,而不需要创建对象。

    在 Bag 的其它扩展之后定义两个扩展:

    extension Bag: ExpressibleByArrayLiteral {
      init(arrayLiteral elements: Element...) {
        self.init(elements)
      }
    }
    
    extension Bag: ExpressibleByDictionaryLiteral {
      init(dictionaryLiteral elements: (Element, Int)...) {
        // The map converts elements to the "named" tuple the initializer expects.
        self.init(elements.map { (key: $0.0, value: $0.1) })
      }
    }

    ExpressibleByArrayLiteral 和 ExpressibleByDictionaryLiteral 扩展需要实现一个初始化方法,以处理它们对应的参数的那种写法。由于前面已经定义的初始化方法,它们的实现都非常简单。

    现在 Bag 已经非常像原生的集合类型了,我们该来点猛货了。

    Sequence

    集合类型的最常用的操作是对其元素进行迭代。来看一个例子,在 Playground 最后添加如下代码:

    for element in shoppingCart {
      print(element)
    }

    超级简单。就像数组和字典一样,你可以遍历一个 Bag 对象。因为 Bag 还没有实现 Sequence 协议,编译不能通过。
    在 ExpressibleByDictionaryLiteral 扩展后加入另一个扩展:

    extension Bag: Sequence {
      // 1
      typealias Iterator = DictionaryIterator<Element, Int>
    
      // 2
      func makeIterator() -> Iterator {
        // 3
        return contents.makeIterator()
      }
    }

    不需要实现太多方法。代码解释如下:

    • 定义了一个类型别名,叫做 Iterator,这是 Sequence 中指定的,需要实现 IteratorProtocol 协议。DictionaryIterator 类型是字典用于迭代其元素的。你可以用它,因为 Bag 在底层使用了字典来存储数据的。
    • makeIterator() 方法返回一个 Iterator,它会用来对序列中的每个元素进行迭代。
    • 调用 contents 的 makeIterator() 方法创建一个 Iterator,它已经实现了 Sequence 协议。

    这就是 Bag 对 Sequence 协议进行实现的全部。
    你现在可以迭代 Bag 对象中的每个元素了,并可以获得每个对象的个数。在之前的 for-in 循环后加入:

    for (element, count) in shoppingCart {
      print("Element: \(element), Count: \(count)")
    }

    打开 Debug 视图,你会看到每个元素都打印出来了:

    实现 Sequence 协议之后,你就可以使用许多 Sequence 中有的方法了。

    在 Playground 最后加入代码试一试:

    // 查找所有数目大于 1 的对象
    let moreThanOne = shoppingCart.filter { $0.1 > 1 }
    moreThanOne
    precondition(moreThanOne.first!.key == "Banana" && moreThanOne.first!.value == 2, "Expected moreThanOne contents to be [(\"Banana\", 2)]")
    
    // 获取所有对象的数组(不需要数量)
    let itemList = shoppingCart.map { $0.0 }
    itemList
    precondition(itemList == ["Orange", "Banana"], "Expected itemList contents to be [\"Orange\", \"Banana\"]")
    
    // 获得所有对象的加总数据
    let numberOfItems = shoppingCart.reduce(0) { $0 + $1.1 }
    numberOfItems
    precondition(numberOfItems == 3, "Expected numberOfItems contents to be 3")
    
    // 所有商品按照数量降序排序
    let sorted = shoppingCart.sorted { $0.0 < $1.0 }
    sorted
    precondition(sorted.first!.key == "Banana" && moreThanOne.first!.value == 2, "Expected sorted contents to be [(\"Banana\", 2), (\"Orange\", 1)]")

    所有 Sequence 对象能有的方法都能用——它们完全是免费的。

    现在,你可能满足于以这种方式使用 Bag,但还有比这更好玩的吗?当前的 Squence 实现仍然还有改进的余地。

    加强版的 Sequence

    当前,你依赖于字典为你提供底层支持。这很好,对你来说,这不乏为一种轻松实现功能强大的集合的方式。问题是,它会让 Bag 的用户感到奇怪和困惑。

    例如,Bag 会返回一个 DictionaryIterator 类型的 Iterator 好像不妥。你可以创建自己的 Iterator 类型,但这次不是免费的了。

    Swift 提供了一个 AnyIterator 类型,将底层的 itertator 隐藏起来。
    将 Sequence 的实现修改为:

    extension Bag: Sequence {
      // 1
      typealias Iterator = AnyIterator<(element: Element, count: Int)>
    
      func makeIterator() -> Iterator {
        // 2
        var iterator = contents.makeIterator()
    
        // 3
        return AnyIterator {
          return iterator.next()
        }
      }
    }

    Playground 报了一堆错,等会来解决。除了使用了一个 AnyIterator 外,这个实现和之前没有太大区别:

    1. AnyIterator 是一个无类型的 Iterator,它只暴露出底层实际的 Iterator 的 next() 方法给你。这样你就可以将实际使用的 Iterator 类型隐藏起来。
    2. 跟前面一样,通过 contents 创建了一个新的 DictionaryIterator 实例。
    3. 最后,将 Iterator 包装成 AnyIterator 以传递其 next() 方法。

    现在来解决先前的报错。你看到的是这两个错误:

    前面,你用 DictionaryIterator 的元组命名为 key 和 value。现在你已经将 DictionaryIterator 隐藏起来了,并且将元组的名字修改为 element 和 count。要解决这个错误,将 key 和 value 替换成 element 和 count。

    这样你的 precondition 语句的问题就解决了。这就是前置条件的好处了,它能保证某些东西不会被意外修改。

    现在,任何人都不知道你在用字典进行所有的工作。
    现在的 Bag 让你感觉更好了吧?是时候让它回家了。呃,将你激动的心情收起来吧,它是属于集合的!

    Collectdion

    闲话少说,接下来还有一道大菜,创建一个集合……即 Collection 协议!再次声明,集合是能够通过索引进行访问并进行多次非破坏性迭代的结合。

    为了符合 Collection 协议,你需要提供这些数据:

    1. startIndex 和 endIndex: 指定集合的边界,并说明遍历的起点。
    2. subscript (position:): 允许你通过索引找到集合中的任意元素。这个访问方法的时间复杂度应控制在 0(1) 上。
    3. index(after:): 返回传入的索引的下一个索引。

    使用 Cellection 协议时只需要这 4 个数据。在 Sequence 扩展后新增扩展:

    extension Bag: Collection {
      // 1
      typealias Index = DictionaryIndex<Element, Int>
    
      // 2
      var startIndex: Index {
        return contents.startIndex
      }
    
      var endIndex: Index {
        return contents.endIndex
      }
    
      // 3
      subscript (position: Index) -> Iterator.Element {
        precondition(indices.contains(position), "out of bounds")
        let dictionaryElement = contents[position]
        return (element: dictionaryElement.key, count: dictionaryElement.value)
      }
    
      // 4
      func index(after i: Index) -> Index {
        return contents.index(after: i)
      }
    }

    代码非常简单:

    1. 首先用 DictionaryIndex 来声明一个 Index 类型,DictionaryIndex 在 Collection 协议中已定义。注意,其实编译器会基于你后面的实现来推断这个类型,但为了保持代码的清晰和可维护性,我们显示地指定了类型。
    2. 然后用 contents 来返回第一个索引和最后一个索引。
    3. 用一个前置条件来强制校验索引的有效性。然后,以元组的方式返回 contents 中位于该索引的元素。
    4. 最后,调用 contents.index(after:) 方法并返回结果。

    通过添加几个属性和方法,你创建了一个功能完整的集合!在 Playground 最后用几行代码来测试这些功能:

    // 读取 Bag 中的第一个对象
    let firstItem = shoppingCart.first
    precondition(firstItem!.element == "Orange" && firstItem!.count == 1, "Expected first item of shopping cart to be (\"Orange\", 1)")
    
    // 判断 Bag 是否为空
    let isEmpty = shoppingCart.isEmpty
    precondition(isEmpty == false, "Expected shopping cart to not be empty")
    
    // 获取 Bag 中的商品种类数
    let uniqueItems = shoppingCart.count
    precondition(uniqueItems == 2, "Expected shoppingCart to have 2 unique items")
    
    // 查找第一个名为 Banana 的元素
    let bananaIndex = shoppingCart.indices.first { shoppingCart[$0].element == "Banana" }!
    let banana = shoppingCart[bananaIndex]
    precondition(banana.element == "Banana" && banana.count == 2, "Expected banana to have value (\"Banana\", 2)")

    漂亮!(当你对自己所做的一切感到心满意足时,“等等,你还可以做得更好”这句话又在等着你了……)

    是的,你说的没错!你可以做得更好。仍然能够从 Bag 中看出一丝字典的模样。

    加强版的 Collection

    Bag 暴露了太多底层实现细节。Bag 的用户仍然需要使用 DictionaryIndex 对象去访问集合中的元素。
    这个很好搞定。在 Collection 扩展后面增加:

    // 1
    struct BagIndex<Element: Hashable> {
      // 2
      fileprivate let index: DictionaryIndex<Element, Int>
    
      // 3
      fileprivate init(_ dictionaryIndex: DictionaryIndex<Element, Int>) {
        self.index = dictionaryIndex
      }
    }

    没有任何新奇的玩意儿,但我们还是来过一下吧:

    1. 定义了一个泛型类型 BagIndex,和 Bag 一样,为了访问字典对象,它需要一个 Hashable 的泛型参数。
    2. index 类型的真实类型是一个私有的 DictionaryIndex 对象。BagIndex 仅仅是一个封装,隐藏了它真正的 index 类型。
    3. 最后,创建一个私有的初始化方法,接收一个 DictionaryIndex 参数。

    Collection 对象需要索引对象实现 Comparable 协议,能够对两个索引进行比较以进行某些操作。因此,BagIndex 必须实现 Comparable 协议。在 BagIndex 扩展之后添加:

    extension BagIndex: Comparable {
      static func == (lhs: BagIndex, rhs: BagIndex) -> Bool {
        return lhs.index == rhs.index
      }
    
      static func < (lhs: BagIndex, rhs: BagIndex) -> Bool {
        return lhs.index < rhs.index
      }
    }

    这个逻辑非常简单;方法返回的结果直接调用 DictionaryIndex 已实现的 Comparable 协议的相同方法。

    现在将 Bag 修改为使用 BagIndex。将 Collecdtion 扩展替换成:

    extension Bag: Collection {
      // 1
      typealias Index = BagIndex<Element>
    
      var startIndex: Index {
        // 2.1
        return BagIndex(contents.startIndex)
      }
    
      var endIndex: Index {
        // 2.2
        return BagIndex(contents.endIndex)
      }
    
      subscript (position: Index) -> Iterator.Element {
        precondition((startIndex ..< endIndex).contains(position), "out of bounds")
        // 3
        let dictionaryElement = contents[position.index]
        return (element: dictionaryElement.key, count: dictionaryElement.value)
      }
    
      func index(after i: Index) -> Index {
        // 4
        return Index(contents.index(after: i.index))
      }
    }

    注释中的数字标出了修改之处。分别解释如下:

    1. 首先将 Index 的类型从 DictionaryIndex 修改为 BagIndex。
    2. 然后,是 startIndex 和 endIndex,你换成新的 BagIndex。
    3. 接着,用这个 BagIndex 从 contents 后访问对应元素并返回。
    4. 最后,结合上面几个步骤。从 contents 中获取 DictionaryIndex,然后用它来创建一个 BagIndex。

    就这样!用户不会知道你底层是用什么来存储数据的了。你未来还有可能对索引对象的获得更大的控制。

    在完成之前,还有一个很重要的地方。除了基于索引来访问元素,你还以通过一段连续索引来访问集合中的值。

    要实现这个,你可以看一下集合中是如何进行切片操作的。

    切片

    切片就是查看集合中多个连续的元素。它允许你在集合元素的子集上进行某些操作,而不用复制这些元素。
    切片只会保存原来集合中的元素的引用。它还存储了元素子集的起、始索引。切片的时间复杂度为 0(1),因为它们直接引用了它的原始集合。

    切片直接重用原始集合的索引,这使得它们尤其有用。
    要测试切片操作,在 Playground 最后添加代码:

    // 1
    let fruitBasket = Bag(dictionaryLiteral: ("Apple", 5), ("Orange", 2), ("Pear", 3), ("Banana", 7))
    
    // 2
    let fruitSlice = fruitBasket.dropFirst() // No pun intended ;]
    
    // 3
    if let fruitMinIndex = fruitSlice.indices.min(by: { fruitSlice[$0] > fruitSlice[$1] }) {
      // 4
      let minFruitFromSlice = fruitSlice[fruitMinIndex]
      let minFruitFromBasket = fruitBasket[fruitMinIndex]
    }

    我们来看一下这些代码做了些什么,以及它们的意思:

    1. 首先,创建一个由 4 种水果构成的水果篮。
    2. 然后拿走第一种水果。这会创建一个水果篮的切片,但第一种水果不见了。
    3. 通过切片找到数量最少的水果的索引。
    4. 尽管上一步的索引是从切片中得到的,你可以把这个索引同时用在原来的集合和后面的切片中来访问对象。

    注意:切片对于基于哈希的字典和 Bag 来说,用处不大,因为它们的顺序在任何方向上都是不确定的。而数组则相反,数组是集合中的一个极端例子,在执行顺序操作时,切片扮演很重要的角色。

    祝贺你——你现在是一个集合方面的专家了!你可以创建任意内容的 Bag 对象来表示庆祝。

    结束

    完整的 Playground 代码可以在这里下载。如果你想了解或者实现更完整的 Bag,请从 github 中 checkout 项目。

    在这篇文章里,你学习了在 Swift 中,如何通过一个数据结构来创建自己的集合。你使用了 Sequence 、Collection 、CustomStringConvertible、 ExpressibleByArrayLiteral、ExpressibleByDictionaryLiteral 协议以及资第一的索引类型。

    这仅仅是对 Swift 提供的用于创建健壮、使用的结合类型的协议的一点尝试。如果你想看一下还有什么其他的协议,你可以参考:

    希望你能喜欢这篇教程!创建自定义的集合并不是一个常见的需求,当它能加深你对 Swift 内置集合类型的裂解。

    如果有任何疑问或建议,请在下面留言。

    展开全文
  • 这里分享一下swift自定义uicollectionviewcell 首先我的viewcontroller不是直接继承uicollectionviewcontroller,而是添加的uicollectionview到我的storyboard, 然后再新建一个swift的文件,让这个swift继承...
  • 错误类型 开发过程常见的错误: ...自定义错误 Swift中可以通过Error协议自定义运行时的错误信息 enum SomeError : Error { case illegalArg(String) case outOfBounds(Int, Int) case outO...
  • 使用自定义类型对程序中的数据进行建模时,可能经常需要检查两个值是否相同或不同,或者值列表中是否包含特定值。 此功能以及将值存储在集合中或用作字典中的键的功能受两个相关的标准库协议Equatable和Hashable约束...
  • 刚开始想的是用扫二维码那个方法,但是失败了,对相机这一块也不是太熟,于是各种招资料,发现扫描身份证的不是很多,最后自己想了个办法,实验后是成功了,但是效果不是太理想,但是其中用到了自定义相机拍照的功能...
  • 1. 添加了3个tableViewCell进tableView,初始状态没有任何显示,点击第二或第三个tableViewCell能显示出内容,点击第一个却不能,左划均出现删除按钮。三个tableViewCell点击均有点击事件
  • 原文链接 : ...自定义init初始化构造函数实现参数传递 对于UIKit框架类,不能像下面一样简单的重写: import UIKit class WebViewController: UIViewController {  var imageUrl:
  • 在开发中我们经常会使用enum来枚举不同的状态。 举个例子,在测试登录服务器的时候,可能会有多中权限的账号存在,比如管理员、普通用户、开发者等,我们可以用一个enum来表示他们 enum Enum_Account{ ...
  • swift自定义cell的坑

    2016-05-13 17:50:17
    swift自定义cell的坑
  • UIView自定义控件-Swfit

    2019-08-23 15:02:26
    UIView自定义 资料参考 UIView中与AutoLayout相关的几个方法对比: 详细介绍了UIView约束布局相关调用顺序,好好理解有助于子空间布局和约束更新。 UIView在AutoLayout下的布局过程 iOS UIkit 提供简单的基本控件...
  • Swift 2.0版本中,Swift语言对其错误处理进行了新的设计,当然了,重新设计后的结果使得该错误处理系统用起来更爽。今天博客的主题就是系统的搞一下Swift中的错误处理,以及看一下Swift中是如何抛出异常的。在编译...
  • Swift中枚举类型非常强大,内置的实现可以大大减少我们手敲的代码量.下面碰巧就有这么一个需求:Person类里面有一个type属性,其值包含2个内容,一个是name,类型为String,另一个是logo,类型也为字符串,不过表现为绘...
  • 认识系统 UIMenuController * 创建系统 UIMenuController// 要让添加 menu 的目标控件成为第一响应者,否则某些功能会错乱,例如 label 未成为第一响应者但是 label 的 menu 有 paste 功能会直接 paste 到第一响应...
  • swift 提示框

    2015-02-25 22:45:48
    var alert = UIAlertView() alert.title = "Refresh?" alert.message = "All data[\(tvData.getData(indexPath) as String)] will be selected." alert.addButtonWithTitle("Cancel")
  • Swift自定义类的存储

    2017-10-14 20:24:08
    Swift开发中,许多时候会涉及到存储自定义的类,不管是存储到本地文件还是远程服务器,都会涉及到编码和解码的问题。下面就来介绍一下在Swift中怎么存储自定义的类。 在Swift中存储自定义的类有两种方法,一种是...
  • swift错误处理

    2020-06-05 17:47:44
    自定义错误类型3. try?的使用方式4. do...catch的使用5. try!的使用方式6. defer的使用7. 养眼图片一张 1. 开发环境说明 系统版本:macOS Catalina 10.15.4 Xcode版本: Version 11.5 (11E608c) swift版本: Apple ...
1 2 3 4 5 ... 20
收藏数 7,611
精华内容 3,044
关键字:

swift自定义错误