为什么我从 Objective-C 转向 Swift

3,947 阅读4分钟

Swift 已经出了5个年头了,到现在,它的语法已经很稳定,iOS developers 可以放心使用了。我从1.0版本就开始使用 Swift,尽管也穿插了一些 Objective-C 项目,但总体来说还算用得熟手。目前团队已经全面普及了 Swift,基本不会去写 Objective-C 了。本文写给还在用 Objective-C 的同行,希望你们认真考虑一下 Swift。

理由1:实用的 Optional 特性

一开始接触 Swift 的人可能会对 ?! 的操作很疑惑,甚至是浑身不自在。使用一段时间之后,就真香了。这个特性的用处是让编译器在编译时就把nil异常检测出来,让开发者提前处理,从而不会把有这种问题的代码发到线上去。

Swift 的编译检测非常强大,这也是一门纯编译语言的优势。我也兼职写过一些 ruby 的后台,对 ruby 的运行时空异常深恶痛绝。

NoMethodError (undefined method `hello' for nil:NilClass)

你永远都不想在线上出问题,这就是 Optional 特性的意义。

打个比方,我简单地想使用一个url,这个url string是传过来的。

let someURLString = "https://Çhello world"
guard let url = URL(string: someURLString) else {
    fatalError("url went wrong \(someURLString)")
}

//..do something with url
print(url)

当我的url字符串含有非法字符的时候,我会得到一个nil,并可能在后续下面的代码抛出错误,导致crash。

如果幸运的话,直接在当前的代码块就抛错了,那还好修复。如果这个nil经过了很多层,那就糟糕了。

理由2:更加先进的枚举

作为一门全新的语言,Swift 站在巨人的肩膀上,把枚举这种语言特性设计得简直完美。 比如,你的枚举类型可以是字符串的。

enum Gender: String {
    case male
    case female
}
print(Gender.male.rawValue)

这样的话,避免一堆未定义的字符串横行。

它还可以在内部定义方法,或者返回一个值,还可以遍历。举一个例子:

class ViewController: UIViewController {
    let tableView = UITableView()
    init() {
        CellType.allCases.forEach { (cellType) in
            cellType.register(to: tableView)
        }
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
extension ViewController {
    enum CellType: String, CaseIterable {
        case business1
        case business2
        
        var cellId: String {
            return self.rawValue
        }
        
        func register(to tableView: UITableView) {
            switch self {
            case .business1: tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
            case .business2: tableView.register(UINib(nibName: "SomeCell", bundle: nil), forCellReuseIdentifier: cellId)
            }
        }
    }
}

当理解了业务是一种互斥关系的时候,用枚举把这些列举出来,并在内部处理之后,返回一个值,减少大量的if else,这样组织的话,代码就舒服了。

理由3:改进的闭包

Objective-C 的 block 在实用方面已经做得很好,Swift 的闭包在此基础上再进一步。可以直接在里面声明所捕获的变量(比如self),众周所知,闭包/block这种代码组织方式可以让代码更加紧凑,同时有捕获局部变量的好处。但是当一个方法写了一大块,里面含有一个闭包/block,不知道捕获了什么导致引用循环,这是难以调试的错误,因此在闭包内声明所捕获的变量是大有裨益的。

通常来说,这个用在 weak self 来弱引用 self 就能完成大部分的事情。

login(phone: "xxx", password: "pswd") { [weak self] (success) in
    self?.tableView.reloadData()
}

由于 Swift 有空的检测,所以只用 weak self 不会有问题,比如你在闭包执行这样的代码是不行的,直接报编译错误:

var titles = [String]()
titles.append(self?.title)

但是 Objective-C 的block可能会有存在的问题

[titles addObject: weakSelf.title];

还有一点,或许是最重要的,就是我们终于摆脱了 Objective-C 奇怪的 block 语法了!!这里有一篇总结得很好(注意网站名) fuckingblocksyntax.com

理由4:养眼的代码风格

最近看了一部纪录片,叫《单挑荒野》,长期混B站的人可能熟悉,里面的主人翁艾德一个主旨就是,不但要生存下去,而且要活得滋润。程序员也一样,不但要实现功能,还得优雅地实现,写出让自己感到满意,舒服的代码。

实现同样的逻辑,Swift 可能不会比 Objective-C 少多少代码,但一定好看,舒服。

比如,你不需要声明这个变量的类型,因为在初始化的时候就已经知道。

let tableView = UITableView()

vs

UITableView *tableView = [[UITableView alloc] init];

比如枚举,你会经常看到 Objective-C 这样的代码:

typedef NS_ENUM(NSInteger, UIModalTransitionStyle) {
    UIModalTransitionStyleCoverVertical = 0,
    UIModalTransitionStyleFlipHorizontal __TVOS_PROHIBITED,
    UIModalTransitionStyleCrossDissolve,
    UIModalTransitionStylePartialCurl NS_ENUM_AVAILABLE_IOS(3_2) __TVOS_PROHIBITED,
}

而 Swift 就舒服多了

enum UIModalTransitionStyle : Int {
    case coverVertical
    case flipHorizontal
    case crossDissolve
    case partialCurl
}

我不太喜欢用NSAttributedString,因为感觉总是把代码写得乱糟糟,但在 Swift 则舒服得多。

let attributedString = NSAttributedString(
    string: "Hello world",
    attributes: [
    .font: UIFont.systemFont(ofSize: 14),
    .foregroundColor: UIColor.blue
    ]
)

还可以稍微扩展一下 Int 类型,可以写出更简洁明了的语句

extension Int {
    var font: UIFont {
        return UIFont.systemFont(ofSize: CGFloat(self))
    }
    var lineHeight: NSMutableParagraphStyle {
        let para = NSMutableParagraphStyle()
        para.maximumLineHeight = CGFloat(self)
        para.minimumLineHeight = CGFloat(self)
        return para
    }
}

let attributedString = NSAttributedString(
    string: "Hello world",
    attributes: [
    .font: 14.font,
    .paragraphStyle: 20.lineHeight,
    .foregroundColor: UIColor.blue
    ]
)

差不多就想到这些,权当盘砖引玉,短短篇幅也没办法覆盖所有的 Swift 优点。