authors are vetted experts in their fields 和 write on topics in 哪一个 they have demonstrated experience. All of our content is peer re视图ed 和 validated by Toptal experts in the same field.
尼尔森Souto
验证专家 在工程
13 的经验

Nilson(拥有BCS/BScTech双重身份)从事iOS开发和2D/3D美术工作已有8年以上, 专注于物理和车辆模拟, 游戏, 和图形.

专业知识

分享

Coming from an Objective-C background, in the beginning, I felt like 斯威夫特 was holding me back. 由于斯威夫特的强类型特性,它不允许我取得进展, 这有时会让人愤怒.

与Objective-C不同,斯威夫特在编译时强制执行许多要求. 在Objective-C中放松的东西,比如 id 类型和隐式转换在斯威夫特中是不存在的. 即使你有 Int 和一个 , you want to add them up, you will have to convert them to a single type explicitly.

也, 可选选项是语言的基本组成部分, 尽管它们是一个简单的概念, 需要一些时间来适应它们.

一开始, 您可能需要强制打开所有内容, 但这最终会导致崩溃. 当你熟悉了这门语言, you start to love how you hardly have runtime errors since many mistakes are caught at compile time.

大多数 斯威夫特的程序员 有使用Objective-C的丰富经验吗, 哪一个, 除此之外, might lead them to write 斯威夫特 code using the same practices they are familiar with in other languages. 这可能会导致一些严重的错误.

在本文中, we outline the most common mistakes that 斯威夫特 developers make 和 ways to avoid them.

别搞错了——Objective-C的最佳实践不是斯威夫特的最佳实践.

1. Force-Unwrapping可选

可选类型的变量(如.g. 字符串?)可能包含值,也可能不包含值. 当它们没有值时,它们等于 . 要获取可选参数的值,首先必须 打开 它们可以用两种不同的方式来制作.

一种方法是可选绑定 如果让 或者一个 ,即:

  var optional字符串:字符串?
  //...
  如果让s = optional字符串 {
      //如果optional字符串不为零,测试结果为
      // true和s现在包含optional字符串的值
  }
  其他{
      //否则optional字符串为零, if条件求值为false
  }

命令强制展开 ! 操作符,或者使用隐式取消包装的可选类型(例如.g. 字符串!). 如果可选选项是 ,强制展开将导致运行时错误并终止应用程序. Further, attempting to access the value of an implicitly 打开ped optional will cause the same.

We sometimes have variables that we can’t (or don’t want to) initialize in the class/结构体 initializer. 因此,我们必须将它们声明为可选的. 在某些情况下,我们认为他们不会 在我们代码的某些部分, so we force 打开 them or declare them as implicitly 打开ped optionals because that’s easier than having to do optional binding all the time. 这件事应该小心做.

这与使用 IBOutletS,它们是在nib或storyboard中引用对象的变量. 的y won’t be initialized upon the parent object’s initialization (usually a 视图 controller or custom UIView),但我们可以肯定他们不会 视图DidLoad (在视图控制器中)或 awakeFromNib (在视图中)被调用,因此我们可以安全地访问它们.

在一般情况下, the best practice is to avoid forcing 打开 和 using implicitly 打开ped optionals. 总是考虑可选的可能 并适当地处理它,要么使用可选绑定,要么检查它是否不是 before forcing an 打开, 或者一个ccessing the variable in case of an implicitly 打开ped optional.

2. 不知道强引用循环的陷阱

A strong reference cycle exists 当 a pair of objects keeps a strong reference to each other. 这对斯威夫特来说并不新鲜, 因为Objective-C也有同样的问题, 经验丰富的Objective-C开发人员应该妥善管理这一点. 重要的是要注意强引用和什么引用什么. 斯威夫特文档中有一个 部分专门讨论这个主题.

在使用闭包时,管理引用尤为重要. 默认情况下, 闭包(或块), 保持对其中引用的每个对象的强引用. 如果这些对象中的任何一个对闭包本身有强引用, 我们有一个强参考循环. 有必要利用 获取列表 正确管理如何捕获您的引用.

If there’s the possibility that the instance captured by the block will be deallocated before the block gets called, 你必须把它作为一个 弱引用,这将是可选的,因为它可以 . 现在, if you’re sure the captured instance will not be deallocated during the lifetime of the block, 你可以把它作为一个 无主参考. 使用的优势 无主 而不是 is that the reference won’t be an optional 和 you can use the value directly without the need to 打开 it.

在下面的例子中,你可以在Xcode Playground中运行 容器 类有一个n array 和一个n optional closure that is invoked 当ever its array changes (it uses 财产观察员 这样做). 的 无论 类有一个 容器 实例,并在其初始化器中,将闭包分配给 arrayDidChange 这个闭包引用 自我,从而在两者之间建立了牢固的关系 无论 实例和闭包.

    结构体 容器 {
        var array: [T] = [] {
            did集 {
                arrayDidChange?(数组:数组)
            }
        }

        var arrayDidChange: ((array: [T]) -> Void)?
    }

    类无论 {
        var 容器: 容器<字符串>

        init () {
            容器 = 容器<字符串>()


            容器.arrayDidChange ={数组中
                自我.f(数组)
            }
        }

        deinit {
            打印(“deinit”)
        }

        函数f(s: [字符串]) {
            打印(s)
        }
    }

    var w:随便! = ()
    // ...
    W =零

如果运行这个示例,您将注意到这一点 deinit无论 永远不会被打印出来,也就是说我们的实例 w 不会从内存中释放. 为了解决这个问题,我们必须使用捕获列表来不捕获 自我 强:

    结构体 容器 {
        var array: [T] = [] {
            did集 {
                arrayDidChange?(数组:数组)
            }
        }

        var arrayDidChange: ((array: [T]) -> Void)?
    }


    类无论 {
        var 容器: 容器<字符串>

        init () {
            容器 = 容器<字符串>()

            容器.arrayDidChange ={[无主的自我]数组
                自我.f(数组)
            }
        }

        deinit {
            打印(“deinit”)
        }

        函数f(s: [字符串]) {
            打印(s)
        }
    }

    var w:随便! = ()
    // ...
    W =零

在这种情况下,我们可以使用 无主,因为 自我 永远不会 在闭包的生命周期内.

几乎总是使用捕获列表来避免引用循环是一种很好的做法, 这将减少内存泄漏, 最后是一个更安全的密码.

3. 使用 自我 到处都是

与Objective-C不同,在斯威夫特中,我们不需要使用 自我 在方法中访问类或结构的属性. 我们只需要在闭包中这样做,因为它需要捕获 自我. 使用 自我 不需要的地方不是一个错误吗, 它运行得很好, 不会有错误,也不会有警告. 但是,为什么要编写比必须编写的代码更多呢? 此外,保持代码的一致性也很重要.

4. 不了解自己的类型

快速使用 值类型引用类型. 此外, instances of a value type exhibit a slightly different behavior of instances of 引用类型. Not knowing what category each of your instances fit in will cause false expectations on the behavior of the code.

在大多数面向对象的语言中, 当 we create an instance of a class 和 pass it around to other instances 和一个s an argument to methods, 我们期望这个实例在任何地方都是一样的. 这意味着对它的任何更改都会在任何地方反映出来, 因为事实上, 我们所拥有的只是一堆对完全相同数据的引用. Objects that exhibit this behavior 是引用类型, in 斯威夫特, all types declared as class 是引用类型.

接下来,我们有值类型声明使用 结构体 or 枚举. 值类型有 copied 当 they’re assigned to a variable or passed as an argument to a function or method. If you change something in the copied instance, the original one will not be modified. 值类型有 不可变的. 如果将新值分配给值类型实例的属性,例如 CGPoint or CGSize,则使用更改创建一个新实例. 这就是为什么我们可以在数组上使用属性观察器(如上面的例子中 容器 类)将更改通知我们. 到底发生了什么, is that a new array is created with the changes; it is assigned to the property, 然后 did集 调用.

因此, 如果您不知道正在处理的对象是引用类型还是值类型, 您对代码将要做什么的期望, 可能是完全错误的.

5. 没有充分利用枚举的潜力

当我们讨论枚举时, 我们通常认为是基本的C枚举, 它只是一列相关的常量下面都是整数. 在斯威夫特中,枚举要强大得多. 例如,可以为每个枚举用例附加一个值. Enums also have methods 和 read-only/computed properties that can be used to enrich each case with more information 和 details.

官方的 关于枚举的文档 非常直观,而 错误处理文档 给出了枚举在斯威夫特中额外功能的几个用例. 此外,请查看以下广泛的 斯威夫特中枚举的探索 去学习你能用它们做的几乎所有事情.

6. 不使用功能特性

的 斯威夫特 St和ard Library provides many methods that are fundamental in functional programming 和 allow us to do a lot with just one line of code, 如 map, 减少, 过滤器除其他外.

让我们来看几个例子.

比如说,你需要计算表格视图的高度. 如果你有 UITableViewCell 子类,如:

  类CustomCell: UITableViewCell {
      // 集s up the cell with the given model object (to be used in tableView:cellForRowAtIndexPath:)
      函数configureWithModel(model: model)
      // Returns the height of a cell for the given model object (to be used in tableView:heightForRowAtIndexPath:)
      class func heightForModel(model: Model) -> CGFloat
  }

考虑一下,我们有一个模型实例数组 model数组; we can compute the height of the table 视图 with one line of code:

  让tableHeight = model数组.map {CustomCell.heightForModel(0美元)}.减少(0,组合:+)

map 将输出一个数组 CGFloat,包含每个单元格的高度 减少 把它们加起来.

如果你想从数组中删除元素,你可能会这样做:

  var 超级跑车 = ["Lamborghini", “布加迪”, “AMG”, “阿尔法罗密欧”, “科尼赛克”, “保时捷”, “法拉利”, “麦克拉伦”, “阿巴特”, “摩根”, “卡特勒姆”, “劳斯莱斯”, “奥迪”)

  func isSupercar(s: 字符串) -> Bool {
      返回年代.字符.count > 7
  }

  对于超级跑车中的s {
      if !isSupercar(s),设i = 超级跑车.indexOf () {
          超级跑车.removeAtIndex(我)
      }
  }

这个例子看起来并不优雅,也不是很有效,因为我们调用了 indexOf 对于每个项目. 考虑下面的例子:

  var 超级跑车 = ["Lamborghini", “布加迪”, “AMG”, “阿尔法罗密欧”, “科尼赛克”, “保时捷”, “法拉利”, “麦克拉伦”, “阿巴特”, “摩根”, “卡特勒姆”, “劳斯莱斯”, “奥迪”)

  func isSupercar(s: 字符串) -> Bool {
      返回年代.字符.count > 7
  }

  对于超级跑车中的(i, s).枚举erate ().Reverse(){//从结束到开始反转移除
      if !isSupercar (s) {
          超级跑车.removeAtIndex(我)
      }
  }

现在,代码更加高效了,但是还可以通过使用 过滤器:

  var 超级跑车 = ["Lamborghini", “布加迪”, “AMG”, “阿尔法罗密欧”, “科尼赛克”, “保时捷”, “法拉利”, “麦克拉伦”, “阿巴特”, “摩根”, “卡特勒姆”, “劳斯莱斯”, “奥迪”)

  func isSupercar(s: 字符串) -> Bool {
      返回年代.字符.count > 7
  }

  超级跑车.过滤器(isSupercar)

下一个示例说明如何删除类的所有子视图 UIView 满足一定的标准,比如框架与特定的矩形相交. 你可以这样说:

  对于视图中的v.子视图{
    如果CGRectIntersectsRect (v.框架,矩形){
      v.removeFromSuper视图 ()
    }
  }
  ```
  我们可以在一行中使用过滤器来完成
  ```
  视图.子视图.{CGRectIntersectsRect($0.框架,矩形)}.forEach {$0.removeFromSuper视图 ()}

我们必须小心, 虽然,因为 you might be tempted to chain a couple of calls to these methods to create fancy 过滤器ing 和 transforming, 这可能会导致一行难以读懂的意大利面条式代码.

7. 呆在舒适区,不尝试面向协议的编程

斯威夫特被认为是第一个 面向协议的编程语言,正如WWDC中提到的 斯威夫特中面向协议的编程 会话. 基本上, that means we can model our programs around protocols 和一个dd behavior to types simply by conforming to protocols 和 extending them. 例如,已知a 形状 协议,我们可以扩展 CollectionType (这与诸如 数组, , 字典), 和一个dd a method to it that calculates the total area accounting for intersections

  形状{
      var区域:浮动{get}
      func intersect(shape: 形状) -> 形状?
  }

  扩展CollectionType 在发电机.元素:形状{
      func totalArea() -> Float {
          让面积=自我.减少(0) { (a: Float, e: 形状) -> Float in
              返回a + e.area
          }

          返回区域- intersectionArea()
      }

      func intersectionArea() -> Float {
          / * * /
      }
  }

该声明 在发电机.元素:形状 is a constraint that states the methods in the extension will only be available in instances of types that conform to CollectionType,其中包含符合的类型的元素 形状. 的实例上调用这些方法 数组<形状>,但不是在…的例子上 数组<字符串>. 如果我们上课 多边形 这符合 形状 的实例,那么这些方法将可用于 数组<多边形> 也.

通过协议扩展, 您可以为协议中声明的方法提供默认实现, 哪一个 will then be available in all types that conform to that protocol without having to make any changes to those types (classes, 结构体或枚举). 这在斯威夫特标准库中被广泛使用,例如 map减少 的扩展中定义的 CollectionType的类型共享相同的实现,例如 数组字典 没有任何额外的代码.

此行为类似于 mixin 从其他语言,如Ruby或Python. 通过简单地遵循带有默认方法实现的协议, 向类型添加功能.

Protocol oriented programming might look quite awkward 和 not very useful at first sight, 这可能会让你忽略它,甚至不给它一个机会. 这篇文章 很好地掌握了在实际应用中使用面向协议的编程.

正如我们所知,斯威夫特不是一门玩具语言

斯威夫特 was initially received with a lot of skepticism; people seemed to think that Apple was going to replace Objective-C with a toy language for kids or with something for non-programmers. However, 斯威夫特 has proven to be a serious 和 powerful language that makes programming very pleasant. 因为它是强类型的, 犯错误是很难的, 因此, 很难列出你在使用这门语言时可能犯的错误.

当你习惯了斯威夫特并回到Objective-C时,你会注意到其中的区别. You’ll miss nice features 斯威夫特 offers 和 will have to write tedious code in Objective-C to achieve the same effect. Other times, you’ll face runtime errors that 斯威夫特 would have caught during compilation. 对于苹果程序员来说,这是一个巨大的升级, 随着语言的成熟,还有很多东西要做.

聘请Toptal这方面的专家.
现在雇佣
尼尔森Souto

尼尔森Souto

验证专家 在工程
13 的经验

贝拉维斯塔,巴拿马城,巴拿马,巴拿马

2013年2月19日成为会员

作者简介

Nilson(拥有BCS/BScTech双重身份)从事iOS开发和2D/3D美术工作已有8年以上, 专注于物理和车辆模拟, 游戏, 和图形.

authors are vetted experts in their fields 和 write on topics in 哪一个 they have demonstrated experience. All of our content is peer re视图ed 和 validated by Toptal experts in the same field.

专业知识

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 隐私政策.

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 隐私政策.

Toptal开发者

加入总冠军® 社区.