Toptal Developers的Swift最佳实践和技巧

Share

本资源包含由Toptal网络成员提供的Swift最佳实践和Swift技巧的集合.

本资源包含由我们的Toptal网络成员提供的Swift技巧和最佳实践的集合. As such, 本页将定期更新,以包含更多信息并涵盖新兴的Swift技术. 这是一个社区驱动的项目, 所以我们也鼓励你们做出贡献, 我们期待着你们的反馈.

Unlike Objective-C, Swift是2014年推出的一门新语言, 以一种更安全、更有抵抗力的方式来编写应用程序. 了解Objective-C的人都知道它是从一个非常繁琐的语法中复兴出来的. Swift很容易学, more concise, incredibly powerful, 最重要的是,它支持现代语言应该包含的所有特性:闭包, concurrency, 错误处理和泛型. 与其他语言相比,安全性是另一个重要的区别, 它包括可选属性和自动引用计数等功能. Swift仍在不断发展, 在开源之后, 开发人员可以通过直接对存储库做出贡献或投票支持更改来参与其中.

有关Swift的更多信息,请查看total资源页面 job description and Swift interview questions.

如何在Swift中使用字体?

今天,你能想到的每一个网站都使用图标. 如果你想在你的Swift项目中使用Font Awesome图标呢? 导入新字体需要添加属性 info.plist, 但最烦人的部分是将正确的Unicode代码点与字体文件中的符号相匹配. 我们必须知道这个 符号被编码为 f042 hexadecimal number. 要知道所有的十六进制数是相当困难和麻烦的. 更不用说,结果看起来是这样的:

label.text = "\u{f037}"

当然有更好的方法将图标添加到标签或按钮上. 简单地使用已经预定义并描述我们想要添加为文本的图标的单词不是更好吗, 比如FAArrows或FACog?

Font Awesome Swift 库正是为这些目的而设计的. 这个轻量级库提供了一个非常优雅的解决方案:

label.FAIcon = .FATwitter

添加额外文本的复合图标也可以很简单:

label.setfattext (prefixText: "prefix ",图标: .twitter, postfixText: " prefix", size: 30, iconSize: 20)

你注意到在一个字符串中调整文本和图标的大小了吗? NSAttributedStrings 在幕后运行Font Awesome Swift库的核心是什么.

Contributors

Patrik Vaberer

自由Swift开发者

Slovakia

Patrik是一名自由iOS开发者和企业家,拥有丰富的创业经验. 他的最新项目已经成为美国初创公司和约翰逊的顶级应用程序 & Johnson,用Swift完成. 帕特里克提供最高质量, 注重细节, 是那种能把事情做好的人.

Show More

从Toptal获取最新的开发人员更新.

订阅意味着同意我们的 privacy policy

如何正确使用NSDateFormatter?

Creating an NSDateFormatter 是一项昂贵的任务. For this reason, 在我们的应用程序的整个生命周期中,有一个系统来重用格式化器是一个好主意. 我们来看看怎么做.

首先,我们将定义一个 NSDateFormatter 我们声明的扩展 all 我们希望在应用中使用的格式化器是静态常量. 这样,任何时候我们使用它们,它们将被重用,而不是一次又一次地创建:

扩展NSDateFormatter {
    
    @nonobjc static让shortDateAndTime: NSDateFormatter =
        let formatter = NSDateFormatter()
        formatter.dateStyle = .ShortStyle
        formatter.timeStyle = .ShortStyle
        return formatter
    }()
    
    NSDateFormatter = {
        let formatter = NSDateFormatter()
        formatter.dateFormat = "月/日/年"
        return formatter
    }()
    
    让monthAndYear: NSDateFormatter = {
        let formatter = NSDateFormatter()
        formatter.setLocalizedDateFormatFromTemplate(“MMMyyyy”)
        return formatter
    }()
}

也许你会问自己 @nonobjc attribute? 尝试删除它,你会看到编译器报错:

一个声明不能同时是“final”和“dynamic”。.

出现这个问题是因为Swift试图为静态属性生成一个动态访问器, for ,objective - c的兼容性, 因为类继承自 NSObject. 类似地,请注意添加 @objc 会使你的扩展与Objective-C不兼容吗.

接下来,我们将创建一个 NSDate 扩展,使一个简单的API转换为字符串和返回:

extension NSDate {
    
    ///使用给定的格式化程序打印日期的字符串表示形式
    func string(with format: NSDateFormatter) -> String {
        return format.stringFromDate(自我)
    }
    
    ///从给定的字符串和格式化程序创建一个' NSDate '. 如果无法解析字符串,则为Nil
    convenience init?(string: string, formatter: NSDateFormatter) {
        Guard让date = formatter.dateFromString(string) else{返回nil}
        self.init (timeIntervalSince1970:日期.timeIntervalSince1970)
    }
}

现在,我们只需要调用 NSDate extension methods.

让我们看一些例子.

To create a String from a NSDate:

let date = NSDate()
let string = date.string(with: .shortDateAndTime)
//让字符串=日期.string(with: .dayMonthAndYear)
//让字符串=日期.string(with: .monthAndYear)

To create a NSDate from a String:

Let string = "06/17/2016"
让date = NSDate(string: string, formatter: .dayMonthAndYear)

Contributors

Manuel García-Estañ

自由Swift开发者

Spain

Manuel是一名工业工程师,拥有6年开发iPhone和iPad应用程序的经验, 既能作为团队成员工作,也能单独工作. A design fanatic, 他总是力求写出最好的代码, 创建从内到外都很华丽的应用程序.

Show More

为什么访问控制很重要?

Swift为我们提供了在一个文件中编写多个类的选项,而无需像Objective-C那样处理头文件. 换句话说,每一个属性 .h 文件意味着它从类外部可见,或者简单地说——它是公共的. 在Swift中,所有的属性和方法都是内部的. 它们可以从它们的定义模块访问,但不能在该模块之外的任何源文件中访问.

class A {
    var myVariable1 = true
    内部变量myVariable2 = true
}

这两个属性具有相同的访问控制,我们可以省略该属性. 它们可以从外面进入, 但是其他框架不能访问这些属性, 比如通过CocoaPods添加的库. 即使变量是公共的,它们仍然是不可见的,因为 class A is marked as internal by default. 我们必须明确地写出来 public class A { } 公开披露. By contrast, 私有访问控制只能从类内部访问, 这样调试就容易多了,因为没有外部因素改变类的行为.

我的习惯是把所有东西都标记为隐私, 然后只暴露那些你意识到必须从外部改变的部分. 私有属性和方法越多越好. 相反,创建pod需要注意在哪里添加公共属性,在哪里不添加公共属性. 如果不将它们标记为公开,你的pod将无法使用.

class A {    
    公共var myVariable = true
}

上面的代码没有意义. 很高兴我们透露了真相 myVariable 属性,但其他模块根本不能访问该类. 别担心,编译器会警告我们:“为内部类声明公共变量”. 在前面添加public class A 就能解决问题. 但是,单元测试和访问控制呢?

In general, 所有未标记为公共的类, 其他模块看不到, 测试对象也分属不同的模块. 想象一下,当所有东西都必须为单元测试标记为公共时的灾难. 请记住,这是斯威夫特2之前的现实.0. 现在,Swift提供了一个属性 @testable 为了避免这种行为.

Attribute 导入MyProject 将揭示单元测试类中的内部属性和方法吗. 请记住,这些私有属性从外部对其他模块是不可见的. 如果您需要访问私有属性, 考虑解耦代码或考虑代码的体系结构. 这表明您的代码不是为测试而设计的. 测试驱动的开发会更快地指出这个问题.

Contributors

Patrik Vaberer

自由Swift开发者

Slovakia

Patrik是一名自由iOS开发者和企业家,拥有丰富的创业经验. 他的最新项目已经成为美国初创公司和约翰逊的顶级应用程序 & Johnson,用Swift完成. 帕特里克提供最高质量, 注重细节, 是那种能把事情做好的人.

Show More

如何避免滞留循环?

保留周期是程序员可能犯的最危险的错误之一, 因为这些周期可能导致意外的崩溃和巨大的内存消耗. 有多种方法可以解决众所周知的保留周期问题.

想象一下下面的代码:

class Object {
    func doStuff(completion: (() -> Void)?) {
        completion?()
    }
}

类控制器:UIViewController {
    let object = object ()
    
    重载函数viewDidLoad() {
        super.viewDidLoad()
        
        object.doStuff {
            self.update()
        }
    }
    
    func update() {
        // implementation
    }
}

In our case, method update() 导致保留,因为闭包捕获自我作为对它的强引用. 我们的第一个假设是做一个弱拷贝 self in the closure.

    重载函数viewDidLoad() {
        super.viewDidLoad()
        
        object.doStuff {[weak self] in
            self?.update()
        }
    }

Why is weak self 在方括号内? As it implies, 它是一个对象数组,您可以将其放入括号内,以在闭包中指定多个捕获值. 上面的解决方案可能是足够的,但让我们深入了解更多 swifty solution. 如果有可能写出更好的代码,你就应该一直这样做.

Swift 2.0引入了一个新的语句, guard,以简化代码结构,并最终摆脱可怕的金字塔形if语句. 拥抱封闭内部的守卫的力量可以迅速改善我们的生活,并在编程中设定一个智能惯例.

重载函数viewDidLoad() {
    super.viewDidLoad()
    
    object.doStuff {[weak self] in
        guard let asself = self else {return}
        aSelf.update()
    }
}

guard语句是用来保护的 self, not to be nil,如果是这样,代码将不会在闭包中继续. 使用局部变量 aSelf 将确保不会有保留周期.

最后一个语法糖-使用just不是很好吗 self instead of aSelf? Yes, it would. 使用反引号和guard语句可以导致下面的代码:

Guard let ' self ' = self else {return}

To summarize:

object.doStuff {[weak self] in
    Guard let ' self ' = self else {return}
    self.update()
}

我几乎在每个闭包中都使用它来避免潜在的保留循环. 有一些例外,当你不需要使用 [weak self],例如:

UIView.animateWithDuration (1) { 
    self.update()
}

为什么没有必要在上面的例子中处理保留周期? 闭包将在执行后自动销毁,最重要的对象在 animateWithDuration 被调用时不保留块.

Contributors

Patrik Vaberer

自由Swift开发者

Slovakia

Patrik是一名自由iOS开发者和企业家,拥有丰富的创业经验. 他的最新项目已经成为美国初创公司和约翰逊的顶级应用程序 & Johnson,用Swift完成. 帕特里克提供最高质量, 注重细节, 是那种能把事情做好的人.

Show More

如何避免闭包中的作用域?

在Swift开发中,我们在很多情况下都会使用闭包. For example, 当我们调用服务器时, 闭包可以作为回调调用, 有成功或失败块. 在大多数情况下,委托也用闭包代替. 值得一提的是,苹果的新API方法也更喜欢闭包.

let alert = UIAlertController()
alert.addAction(UIAlertAction(title: "Submit", style: "Submit") .Default) { _ in
    //点击submit动作后执行的代码块
})

然而,在某些情况下,我们不需要使用for的作用域 self in the closure. 当我们没有任何闭包声明时, 默认情况下,编译器将存储对的保留引用 self as a strong type. 当然,也可以用类型声明一个闭包来存储 weak or unowned.

So, 如果您想避免闭包中的retain引用,并且不想担心内存泄漏, 解决办法是使用 @noescape 闭包声明之前.

class Requester {
    
    func getItems(@noescape completion: () -> Void) {
        completion()
    }
    
    deinit {
        print(“deinit请求者”)
    }
}

class Model {
    
    let requester = requester ()
    
    func updateData() {
        requester.getItems {
            build()
        }
    }
    
    func build() {
        //在这里你可以构建一些数据
        print("build")
    }
    
    deinit {
        打印(“deinit模式”)
    }
}

_ = {
    let model = Model()
    model.updateData()
}()
//调用updateData()后,将输出控制台日志
// build
// deinit Model
// deinit Requester

Here, _ = { … }() 只是一个将被执行的匿名函数吗. Otherwise, deinit 方法不会被触发,因为全局作用域永远不会被完成.

如果你把上面的例子复制粘贴到Swift playground中, 输出控制台日志将确认所有对象已被释放, 在上面的例子中证明没有内存泄漏.

重复一下,属性 @noescape 主要做两件事:

  1. 它避免了我们写显式的 self in the closure.
  2. 如果我们试图给属性分配一个完成块,编译器不会允许我们这样做,因为 @noclosure 表示无法分配该块. 否则,我们将面临潜在的严重内存泄漏.

Contributors

Patrik Vaberer

自由Swift开发者

Slovakia

Patrik是一名自由iOS开发者和企业家,拥有丰富的创业经验. 他的最新项目已经成为美国初创公司和约翰逊的顶级应用程序 & Johnson,用Swift完成. 帕特里克提供最高质量, 注重细节, 是那种能把事情做好的人.

Show More

Dima Pilipenko

CTO (Pinto)

通过实施高质量和有趣的项目来鼓舞人心, 为个人在不同领域的职业发展而努力,产生独特的想法并实现.

如何利用Swift匹配枚举?

枚举为一组逻辑上相连的相关值定义了一个公共类型:

enum Goods {
    case Bread
    case Roll
}
让myGoods: Goods = .Bread

We encapsulate Bread and Roll into one entity, Goods. 匹配简单的枚举是显而易见的,因为这是一个标准的比较.

if myGoods == .Bread {
    // matched
}

However, 有时,能够将关于其他类型的额外信息与这些case值一起存储并允许以后随时更改这些信息是很有用的. Swift将此特性称为关联值.

For example, 让我们假设所有的东西都存储在一个属性中,并带有关于库存有多少件的额外信息. 在Swift中,用来定义东西的枚举可以像这样:

enum Stuff {
    案例椅(数量:50)
    案例表(金额:Int)
}
让myStuff: Stuff = .Chair(amount: 10)

可以通过开关或特殊的if语句提取相关的值. 常规if条件不适用于关联的枚举.

if myStuff == .Chair(amount: _) {
    // implementation
}

相反,我们使用一个新的增强的if语句和额外的信息,即现在提取的量.

if case let .椅子(amount: amount) = myStuff {
    print(amount)
}

有些情况下,我们对关联值不感兴趣. 一个简短的版本是:

if case .椅子(数量:_)= myStuff {
    // implementation
}

Contributors

Patrik Vaberer

自由Swift开发者

Slovakia

Patrik是一名自由iOS开发者和企业家,拥有丰富的创业经验. 他的最新项目已经成为美国初创公司和约翰逊的顶级应用程序 & Johnson,用Swift完成. 帕特里克提供最高质量, 注重细节, 是那种能把事情做好的人.

Show More

如何在Swift中强制高亮TODO和FIXME?

In the old days of Objective-C, 当一段代码需要修复或一些额外的工作时,强制警告让我们知道是非常容易的. 我们只需要写:

#warning TODO:完成执行
- (void) foo {
}

In Swift我们已经失去了强迫警告提醒我们做这些事情的能力. 然而,我们有另一个特性允许我们将某些东西标记为 TODO or FIXME:

// TODO:请完成执行
func foo() {
}

//修复:请修复它总是崩溃!
func bar() {
    let array: [String] = []
    array[1]
}

这些标记的缺点是编译器不会突出显示它们. 我们可以在方法下拉框中看到它们 Xcode,但它们很容易被遗忘.

让编译器抱怨这些标记的解决方法是将以下脚本作为new添加 Build Phase:

如果你想包含你的可可荚警告,将这个变量设为false
EXCLUDEPODS=true

添加你想标记为警告和/或错误的标签
WARNINGTAGS = " TODO: | FIXME: |警告:“
ERRORTAG="ERROR:"

if $EXCLUDEPODS; then PODSTRING="-path ./Pods -prune"; else PODSTRING=""; fi

查找“${srroot}”$PODSTRING \(-name) *.h" -or -name "*.m" -or -name "*.swift" \) -print0 | xargs -0 egrep——with-filename——line-number——only-matching "($WARNINGTAGS).*\$|($ERRORTAG).* \ $ | perl - p - e " s / (WARNINGTAGS美元)/警告:🛠\ $ 1 /“| perl - p - e”(ERRORTAG美元)/错误:😱\ 1美元/”

添加这个之后,构建阶段标记 TODO and FIXME 被编译器视为警告. 此外,我们还可以添加两个标签来强制发出警告甚至错误:

//警告:这里有错误
//错误:这里确实有问题

重要的是要注意 ERROR 标记不会使构建失败,但会在read中标记为标记为错误.

Contributors

Manuel García-Estañ

自由Swift开发者

Spain

Manuel是一名工业工程师,拥有6年开发iPhone和iPad应用程序的经验, 既能作为团队成员工作,也能单独工作. A design fanatic, 他总是力求写出最好的代码, 创建从内到外都很华丽的应用程序.

Show More

如何隐藏长' UIViewController '加载语法从' UIStoryboard '

如果您正在使用故事板来构建屏幕,并且想要实例化 UIViewController 在你的代码中,你可能使用过类似的语法:

让userProfileViewController = UIStoryboard(name: "Main", bundle: nil).实例化viewcontroller (withIdentifier: "UserProfileViewController")作为! UserProfileViewController

让alsoUserProfileViewController = userProfileViewController.storyboard?.实例化viewcontroller (withIdentifier: "UserProfileViewController")作为! UserProfileViewController

let otherViewController: UserProfileViewController = UIStoryboard(name: "Main", bundle: nil).实例化viewcontroller (withIdentifier: "UserProfileViewController")作为! UserProfileViewController

让longSyntaxViewController: UserProfileViewController = otherViewController.storyboard?.实例化viewcontroller (withIdentifier: "UserProfileViewController")作为! UserProfileViewController

老实说,这太难看了. And long.

不幸的是,这并非个例. 示例需要有对 UIStoryboard 对象,也可以使用 .storyboard 其他的属性 UIViewController 实例或创建一个新的 UIStoryboard object. 再加上类型转换,你就得到了一行很长的代码.

As a rule of thumb, 如果一行代码超过80个字符, 这通常是一个好迹象,表明代码应该被重构或简化.

让我们试着做点什么吧. 我们可以采取的一种方法是创建 UIStoryboard 扩展名并将其移动到单独的文件中. You could name it UIStoryboard+Loader.swift, for example.

import UIKit

fil私人enum Storyboard:字符串{
    case main = "Main"
    //为项目中的每个故事板添加enum case
}

文件私有扩展UIStoryboard {
    
    static func loadFromMain(_ identifier: String) -> UIViewController {
        return load(from: .Main,标识符:标识符)
    }
    
    //可选地在这里为其他故事板添加方便方法 ...
    
    // ... 或者直接使用主加载方法时
    //从一个特定的故事板实例化视图控制器
    static func load(from storyboard: Storyboard, identifier: String) -> UIViewController {
        let uiStoryboard = uiStoryboard (name: storyboard.rawValue, bundle: nil)
        return uiStoryboard.instantiateViewController (withIdentifier:标识符)
    }
}

// MARK:应用视图控制器

扩展UIStoryboard {
    class func loadUserProfileViewController () -> UserProfileViewController {
        return loadFromMain("UserProfileViewController") as! UserProfileViewController
        
        //如果没有在fileprivate扩展名中添加便利方法,则语法会更长一些
        //返回load(from): .main,标识符:"UserProfileViewController")作为! UserProfileViewController
    }
    
    //在这里添加其他应用视图控制器加载方法 ...
}

Now, UIStoryboard 的加载和实例化 UIViewController 实例可以缩短为:

让userProfileViewController = UIStoryboard.loadUserProfileViewController ()

像这样组织代码,您就可以有一个集中的地方来管理您的代码 UIViewControllers 在你的项目中,它使你能够处理所有视图控制器标识符 Strings 在一个地方进行字体铸造. 而且,你不必担心如何得到 UIStoryboard 对象实例化时 UIViewController from your app code.

如果你的代码库很大并且有很多故事板, 这样的解决方案可以帮助您更好地组织代码.

Contributors

Dino Bartošak

自由Swift开发者

Croatia

作为一名软件工程师,迪诺的专长是开发移动应用程序. 他曾与各种规模的团队合作,并开发了迭代构建产品的技能, 发展分析能力和原型技术. 他喜欢在所有条件下编写干净、模块化和可维护的代码. iOS是他的首选平台.

Show More

如何创建具有可选方法或属性的协议?

填充表是iOS中最常见的活动之一. TableView’s DataSource contains many methods you can implement; however, 您是否注意到DataSource只有两个必需的方法? UIKit使用一种旧的方式来实现可选的方法,使用Objective-C风格. 然而,肯定有一种更好的方法,使用优雅的代码. 将多个特性组合在一起,比如扩展和计算属性,将帮助我们避免实现所有协议的方法和属性

实现所需的协议很简单:

CommentDelegate {
    func didSubmitComment ()
    func didRemoveComment ()
    var isSingleComment: Bool {get}
}

类A: RequiredCommentDelegate {
    函数didSubmitComment() {
        // implementation
    }
    函数didRemoveComment() {
        // implementation
    }
    var isSingleComment = true
}

类A必须实现两个方法和一个布尔属性. 它们都是强制性的,否则编译器会警告你. 如果我们不感兴趣 didRemoveComment() 方法时,可以省略方法体,但类a必须至少包含一个声明. 这并不一定是个大问题, 但是,想象一下有许多其他方法和依赖项的复杂项目. 拥有透明和结构化的代码对测试是有益的,可以帮助我们避免进一步的错误.

让我们来看看更多 swifty 如何在纯Swift中完成可选的方法和属性.

我们的新协议将与之前的协议类似:

OptionalCommentDelegate {
    func didSubmitComment ()
    func didRemoveComment ()
    var isSingleComment: Bool {get}
}

现在,扩展的力量开始发挥作用. 在协议上创建扩展将提供所需的效果. 对于方法,它很直接:

扩展可选评论委托{
    函数didSubmitComment() {}
    函数didRemoveComment() {}
}

使用空体实现方法和变量将使它们成为可选的. So simple, right? 但是,属性和扩展的行为不同. 我们的第一个假设是这样实现属性:

扩展可选评论委托{
    var isSingleComment = true
}

在写了这几行代码之后, 编译器会告诉我们:“扩展不能包含存储属性.换句话说,我们不能在扩展中创建属性并为它们赋值.

深入研究并试验这些属性, 我们发现有一个简单的技巧可以避免上述错误-使用计算属性.

扩展可选评论委托{
    var isSingleComment: Bool {
        return false
    }
}

Every time we call isSingleComment,它将执行属性的主体,在本例中是 return false. 对于那些不熟悉计算属性及其快捷方式的人, 我们可以将代码重写为:

扩展可选评论委托{
    var isSingleComment: Bool {
        get {
            return false
        }
    }
}

Now isSingleComment 是协议中的可选属性吗.

Contributors

Patrik Vaberer

自由Swift开发者

Slovakia

Patrik是一名自由iOS开发者和企业家,拥有丰富的创业经验. 他的最新项目已经成为美国初创公司和约翰逊的顶级应用程序 & Johnson,用Swift完成. 帕特里克提供最高质量, 注重细节, 是那种能把事情做好的人.

Show More

如何使用协议扩展绑定模型类与接口?

有时你必须用多个视图绑定相同类型的对象, 像界面控制器, UITableViewCell, 或UICollectionViewCell. Very often, 所有视图中的绑定代码都非常相似, 所以如果你能再利用它,那就太好了. 我们将使用Swift协议和协议扩展来实现它.

假设有a User class:

class User {
    var name = ""
    var email = ""
    var bio = ""
    var image: UIImage? = nil
    
    init(name: String, email: String, bio: String) {
        self.name = name
        self.email = email
        self.bio = bio
    }
}

我们将创建一个协议,该协议将采用我们想要绑定的所有接口 User instances. We will call it UserBindable.

协议UserBindable: AnyObject {
    var user: User? { get set }
    
    var nameLabel: UILabel! { get }
    var emailLabel: UILabel! { get }
    var bioLabel: UILabel! { get }
    var imageView: UIImageView! { get }
}

The first var user 用户是要绑定的,其他的都是 UIView 可以用来绑定用户的子类.

然后,我们创建一个协议扩展:

扩展UserBindable {
    
    //将视图设置为可选项
        
    var nameLabel: UILabel! {
        return nil
    }
    
    var emailLabel: UILabel! {
        return nil
    }
    
    var bioLabel: UILabel! {
        return nil
    }
    
    var imageView: UIImageView! {
        return nil
    }
    
    // Bind
    
    函数bind(user: user) {
        self.user = user
        bind()
    }
    
    func bind() {
        
        守卫让用户=自己.user else {
            return
        }
    
        如果让nameLabel = self.nameLabel {
            nameLabel.text = user.name
        }
        
        如果让bioLabel = self.bioLabel {
            bioLabel.text = user.bio
        }
        
        如果让emailLabel = self.emailLabel {
            emailLabel.text = user.email
        }
        
        if let imageView = self.imageView {
            imageView.image = user.image
        }
    }
}

在这里,我们将扩展分为两个部分:

  • 第一个用于提供默认值(nil),因此将实现该协议的对象不需要拥有所有视图. 例如,我们的一些观点可以排除 image,但其他一些可以包括它.
  • 我们获取用户属性的值并将它们设置为视图.

现在,大部分工作都完成了. 如果必须呈现用户列表,则需要创建一个 UITableViewCell. 让我们假设这个单元格只需要显示姓名和电子邮件:

类UserTableViewCell: UITableViewCell, UserBindable {

    var user: User?
    
    //我们可以在interface builder或by代码中设置标签. 
    @IBOutlet弱var nameLabel: UILabel!
    @IBOutlet弱var emailLabel: UILabel!
}

And that’s all. Our cell implements UserBindable,因此它知道如何将其接口绑定到用户对象. In our 需要显示 we can do:

    函数tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(“细胞”)! UserTableViewCell
        让user = //从数组或其他地方找到用户
        cell.bind(user)
        return cell
    }

如果我们想显示这个用户触摸后的细节, 我们可以有一个这样的视图控制器:

UserDetailViewController: UIViewController, UserBindable {
    
    var user: User?
    
    @IBOutlet弱var nameLabel: UILabel!
    @IBOutlet弱var emailLabel: UILabel!
    @IBOutlet弱var bioLabel: UILabel!
    @IBOutlet弱var imageView: UIImageView!
    
    重载函数viewDidLoad() {
        super.viewDidLoad()
	 //这里我们假设我们已经在viewDidLoad之前设置了user值
        bind()
    }
}

如您所见,所有绑定代码都得到了很好的重用.

Contributors

Manuel García-Estañ

自由Swift开发者

Spain

Manuel是一名工业工程师,拥有6年开发iPhone和iPad应用程序的经验, 既能作为团队成员工作,也能单独工作. A design fanatic, 他总是力求写出最好的代码, 创建从内到外都很华丽的应用程序.

Show More

考虑使用GUARD语句检查所需条件

为什么要用 guard 语句时,可以使用 if-else statement? 让我们考虑以下两个例子:

// IF-ELSE 
函数checkPassword(密码:字符串)?) -> Bool {
    如果password == nil {
        return false
    }
    如果让password = password, password.characters.count < 6 ||
        !containsRequiredCharacters(password: password) {

        showPasswordError()
        return false
    }
	
	var lenght: Int
	如果让password = password {
		lenght = password.characters.count
	}
	
    return true
}
// GUARD
函数checkPassword(密码:字符串)?) -> Bool {
    守卫让密码=密码,密码.characters.count >= 6 &&
        containsRequiredCharacters(password: password) else {
                
        showPasswordError()
        return false
    }
    让length = password.characters.count
    return true
}

First, with the guard 表述,我们要检查a的构成 valid 密码(如最小长度和所需字符),而使用 if-else 语句,我们只检查是什么构成了密码 invalid.

这是一个相对简单的例子, 但是如果我们需要检查更复杂的需求, 这样更容易也更自然地说明我们 do 而不是我们想要什么 don’t want.

让我们来看看下面两个例子:

if value < 0 || (value > 100 && value < 200) || value > 300 {
    // wrong value
    return
}
guard (value >= 0 && value == 200 && value <= 300) else {
    // wrong value
    return
}

You can see how the guard 对于人们来说,语句比它更容易、更自然地阅读 if-else 语句对应.

使用的另一个原因 guard 语句表示,如果可选变量通过guard语句,则将对其展开包装. When using the if-else 语句,我们需要自己打开它.

Contributors

Josip Petrić

自由Swift开发者

Croatia

作为一个有多年经验的软件工程师, Josip对可扩展的建筑有着敏锐的感觉, maintainable, 以及用户友好的应用程序. 他选择的平台是iOS,他已经为其设计和开发了广泛的应用程序, 从简单到复杂, 大规模的应用. 约瑟普是一个成熟的团队领导者和团队成员,具有模范的沟通和社交技巧.

Show More

使用扩展来更好地组织代码

Swift中的扩展为现有类添加新功能, structure, enumeration, or protocol type. 这包括扩展类型的能力,即使您无法访问原始源代码, 这也被称为追溯建模. 扩展类似于Objective-C中的类别. 然而,与Objective-C的类别不同,Swift扩展没有名称. 你可以阅读更多关于 官方文档中的扩展.

As described, 扩展是一个强大的特性,我们也可以使用它使我们的代码更具可读性和更好的组织性, 反过来,这将使我们的代码更易于维护.

因此,关于何时使用扩展是有利的,这里有两个技巧.

For one thing, 在遵循协议的情况下使用扩展将使协议方法和其他代码之间的代码清晰分离. 例如,我们可以使用扩展来分离属于 UITextFieldDelegate 协议的实现:

类MyViewController: UIViewController, UITextFieldDelegate {
    
    重载函数viewDidLoad() {
        ..
    }
    
    覆盖函数viewWillAppear(_动画:Bool) {
        ..
    }
    
    函数textFieldDidBeginEditing(textField: UITextField) {
        ...
    }
    
    func setupView() {
        ...
    }

    func textFieldShouldReturn(textField: UITextField) -> Bool {
        ...
    }
    
    函数bindViewModel() {
        ...
    }
}

如示例所示, 没有延期, 我们很难找到协议中所有的方法. 有了扩展,我们就有了清晰的职责分离,如下所示.

类MyViewController: UIViewController {
	 重载函数viewDidLoad() {
        ..
    }
    
    覆盖函数viewWillAppear(_动画:Bool) {
        ..
    }
    
    func setupView() {
        ...
    }
    
    函数bindViewModel() {
        ...
    }
}

扩展MyViewController: UITextFieldDelegate {
	函数textFieldDidBeginEditing(textField: UITextField) {
        ...
    }
    
    func textFieldShouldReturn(textField: UITextField) -> Bool {
        ...
    }
}

扩展的另一个有价值的用途是将负责某些公共任务或功能的某些方法组合在一起.g.(动画或验证).

Without extensions, 我们可能很难找到正确的方法, 代码很快就会变得一团糟. 首先,考虑一个例子 without 使用扩展对方法进行分组:

类MyViewController: UIViewController {
    
    重载函数viewDidLoad() {
        ...
    }
    
    函数animateSaveButton() {
        ...
    }
    
    func validatePersonalInfo() -> Bool {
        ...
    }
    
    func validatePaymentInfo() -> Bool {
        ...
    }
    
    函数animateCustomActivityIndicator() {
        ...
    }
}

相反,如果我们使用扩展,我们的代码会更清晰,更容易导航:

类MyViewController: UIViewController {
	...
}

//标记:-动画方法
扩展MyViewController {
	函数animateSaveButton() {
        ...
    }
    
    函数animateCustomActivityIndicator() {
        ...
    }
}

//标记:-验证方法
扩展MyViewController {
	func validatePersonalInfo() -> Bool {
		...
	}
	
	func validatePaymentInfo() -> Bool {
		...
	}
}

最后提示:如果扩展包含大量代码, 考虑将该扩展分离为一个单独的 .swift file.

Contributors

Josip Petrić

自由Swift开发者

Croatia

作为一个有多年经验的软件工程师, Josip对可扩展的建筑有着敏锐的感觉, maintainable, 以及用户友好的应用程序. 他选择的平台是iOS,他已经为其设计和开发了广泛的应用程序, 从简单到复杂, 大规模的应用. 约瑟普是一个成熟的团队领导者和团队成员,具有模范的沟通和社交技巧.

Show More

如何隐藏长' UIView '加载语法从' UINib '

如果你在 .xib 文件,然后这个视图需要从 .xib file. 加载语法看起来像这样:

让avatarViewFromNib = UINib(nibName: "AvatarView", bundle: nil).实例化(withOwner: self, options: nil)[0] as! UIView

我们可以采取与上面讨论的方法类似的方法 UIStoryboard extension case. 这一次,我们创建一个 UINib 扩展,我们可以命名它 UINib+Loader.swift, for example.

import UIKit

文件私有扩展UINib {
    
    static func nib(named nibName: String) -> UINib {
        返回UINib(nibName: nibName, bundle: nil)
    }
        
    loadSingleView(_ nibName: String, owner: Any?) -> UIView {
        返回nib(name: nibName).实例化(withOwner: owner, options: nil)[0] as! UIView
    }
}

// MARK: App Views

extension UINib {
    
    class func loadAvatarView(withOwner owner: AnyObject) -> UIView {
        返回loadSingleView("AvatarView", owner: owner)
    }
    
    //在这里添加其他app view load方法 ...
    
}

现在视图的创建看起来像下面的代码:

让avatarViewFromNib = UINib.loadAvatarView (withOwner:自我)

这种方法的另一个好处是,它为您提供了一个集中的位置来处理 UINib files.

Contributors

Dino Bartošak

自由Swift开发者

Croatia

作为一名软件工程师,迪诺的专长是开发移动应用程序. 他曾与各种规模的团队合作,并开发了迭代构建产品的技能, 发展分析能力和原型技术. 他喜欢在所有条件下编写干净、模块化和可维护的代码. iOS是他的首选平台.

Show More

Submit a tip

提交的问题和答案将被审查和编辑, 并可能会或可能不会选择张贴, 由Toptal全权决定, LLC.

*所有字段均为必填项

Toptal Connects the Top 3% 世界各地的自由职业人才.

加入Toptal社区.