新手應該怎麼寫 Swift

CepheusSun發表於2017-12-22

image.png

已經有好幾個人跟我抱怨過為什麼 swift 裡面有那麼多問號(?)還有歎號(!)了。恰恰哈, 在剛剛開始寫 swift 的時候, 我也面臨著這種問題。

昨天一個朋友發了我一行程式碼, 讓我看看應該怎麼寫:

let pageiid = (self.pageid?.intValue)! + 1
複製程式碼

這段程式碼看起來很操蛋, 但是糟心的是, 在剛剛寫 swift 的時候, 我寫過更噁心的程式碼。

既然大家在剛剛開始寫 swift 的時候都遇到了這個問題。今天就來看看, 這樣的程式碼應該怎樣寫才能讓我們更爽。

我們都知道, 有問號(?)和歎號(!)的原因是什麼?—Optional

相信準備要試試 swift 的人,或多或少都看到過, 或者聽說過這是 swift 相較於 oc 很大的區別。在我看來,除了語法上的變化以外, swift 和 oc 最大的區別就在 於optional 了。為了照顧到實在是太新的新手(畢竟我自己也是新手)。還是簡單的講講這個東西吧!

什麼是 Optional

這一點就沒什麼可說的,在 swift 中 Optional 實際上是一個列舉。如果要自己實現一個類似的東西的話, 核心的程式碼應該是這樣的:

// 這段程式碼不重要
public enum SYOptional<Wrapped> {
    
    case none
    case some(Wrapped)
}
複製程式碼

這段程式碼只是要告訴你, 這個列舉只有兩個 case, 一個是 .none 代表這個 optional 是沒有值的, 也就是說他是 nil。另外一個值 .some 代表這個 optional 是有值的。

蘋果為什麼要引入 optional 這個概念, 在這個地方就不打算贅述了。看下面一段話:

“Optional 可以說是 Swift 的一大特色,它完全解決了 “有” 和 “無” 這兩個困擾了 Objective-C 許久的哲學概念,也使得程式碼安全性得到了很大的增加。”

摘錄來自: 王巍 (onevcat). “Swifter - Swift 必備 Tips (第四版)”。 iBooks.

個人認為 optional 確實是 swift 中非常好的新特性了。

接下來, 我們看看 Optional 的那幾個操作符號: ??/ !/?

這些無非就是一些 swift 中的語法糖而已。具體什麼意思,我們來往下看。

在宣告一個變數或者屬性的時候:

var optionalString: String?
複製程式碼

最後的?表示這個 optionalString 是一個可選型別(optional)。這裡的 String? 就是 Optional<String> 的意思。

在使用變數的時候:

如果這樣寫編譯器是會報錯的,

optionalString.lowercased()
複製程式碼

這時候編譯器會提示你在 optionalString 後面新增一個?

這個問號就是告訴編譯器這個 optionalString 是一個可選型別。

class Person {
    var name: String!
    var son: Son!
}

class Son {
    var name: String?
}

var p: Person?

print(p?.son.name)
// Playground: nil
複製程式碼

這段程式碼表示,如果在一行程式碼裡的某個地方出現 nil 著, 這行程式碼也將會返回 nil。(這一點有點類似 oc 中給 nil 傳送訊息)。這樣寫有一個好處,就是在維護程式碼的時候, 看到 ?就知道這個東西是可選型別了。也就是告訴我們在程式執行期間這個東西是可能為空的。

接下來就是 ! 了。這個東西跟? 一樣。

在宣告一個變數或者屬性的時候:

var something: String!
複製程式碼

這個用法有一個專門的叫法:隱式解包可選型別。 這是一個特殊的可選型別,在對他的成員或者是方法進行訪問的時候,編譯器會自動的幫我們自動解包。也就說說編譯器會自動幫我們加上! 這個符號。換成我們自己的話可以這樣理解:

**在宣告一個變數或者屬性的時候,如果我們明確的知道在程式執行過程中訪問到這個變數或者屬性的時候,他的值一定不為空。那麼就可以使用隱式解包可選型別。**如果要舉例的話: 我想我會舉 XIB 的例子。從 SB 或者 XIB 中拖出來的控制元件, 都是這樣子宣告的。

在訪問變數或者屬性的時候:

對於一個可選型別來說, 有些時候編譯器會提醒我們在這個物件後面新增! 就像最開始我朋友發給我的程式碼一樣:

let pageiid = (self.pageid?.intValue)! + 1
複製程式碼

如果一個方法需要傳入的是一個不可選型別作為引數。這時候如果強制傳入一個可選型別的話。編譯器就會報錯,並且提醒我們在這個可選型別變數後面新增一個! 。 這個做法就是強制解包。 相當於是直接訪問這個可選型別的 .some

??

這個只需要花一句話就能夠講清楚了,這是給這個 optional 預設值。

var optionalString: String?
var defaultValue = optionalString ?? "defaultValue"
複製程式碼

如果 optionalString 有值的話 defaultValue 就是 optionalString 的值。反之就是 "defaultValue"

怎麼寫好 Optional

大概講完了一些基本的概念。下面就來說說如何避免在程式碼中出現各種 ? \ ! 的情況。其實對新手來說。幾乎都是因為編譯器提示,然後自動加上去的各種 ?! 。 不得不說,這樣的程式碼是非常醜陋的。要解決這個問題, 知道 optional 的原理當然是最重要的。

避免使用 Optional

寫了一段時間之後,我發現很多時候 optional 的使用都是沒有什麼意義的。就像我朋友給我的例子一樣。我們可以通過設定初始值的方法來避免使用 optional

var pageid: Int = 0
複製程式碼

通過這種方法就能夠避免使用到 optional, 也就不會有下面的事情了。當然還可以使用懶載入:

lazy var tableView = UITableView()
複製程式碼

保證在第一次使用這個屬性的時候這個屬性是肯定被初始化出來了的。

當然還可以通過使用隱式解包可選型別去避免之後的程式碼中出現 ? \ !

但是這個其實是不被鼓勵的。

預設不要隱式解包可選型別。 在大多數場景中你都可能會忘掉這件事情。但是在一些特殊情況下應該這樣做來減少編譯器的壓力。而且我們也需要去理解這件事情背後的邏輯。

如何訪問 Optional

既然設計出來的 Optional 肯定在編碼的過程中不可避免的要使用到它。那麼在使用 Optional 的時候怎麼去避免出現像最開始的那種情況呢?

還是來看這行程式碼:

let pageiid = (self.pageid?.intValue)! + 1
複製程式碼

在這裡如果 pageid 為空的話, 強制解包是肯定會崩潰的。這種情況應該怎麼寫呢?除了最開始說的宣告的時候設定初始值,還有就是給預設值。另外還有 Optional Map 這種方法來訪問 Optioal:

if let optionalVal = optional {
 // do someThing
}
// 等價於
optional.map{ // do someThing }
複製程式碼

另外還有一些我在網上搜集的 snippet 也能夠很舒服的解決一些問題:給 Optional 加一個 extension:

extension Optional { }

複製程式碼

新增一些方法:基本上都來自 GitHub 這個庫

require

可以強制要求某個 Optional 在當前行不為空,為空的話會跑出異常。這個相當於是優化了強制解包的異常資訊:

    /// 強制要求這個 optional 不為空
    ///
    /// 這個方法返回 optional 的值,或者在optional 為空的時候觸發 error
    ///
    ///
    /// - Parameters:
    ///   - hint: 為空丟擲的錯誤資訊
    ///
    /// - Returns: optional 的值.    
	func require(hint hintExpression: @autoclosure() -> String? = nil,
                 file: StaticString = #file,
                 line: UInt = #line) -> Wrapped {
        guard let unwrapped = self else {
            var message = "required value was nil \(file), at line \(line)"
            if let hint = hintExpression() {
                message.append(". Debugging hit: \(hint)")
            }
            #if !os(Linux)
            let exception = NSException(name: .invalidArgumentException,
                                        reason: message,
                                        userInfo: nil)
            exception.raise()
            #endif
            
            preconditionFailure(message)
        }
        return unwrapped
    }
複製程式碼

or

    /// 用來代替 ?? 操作符, 這樣寫可讀性高些
    ///
    /// - Sample:
    //  var a: String? = nil
    //  let res = a.or("b")
    func `or`(value: Wrapped?) -> Optional {
        return self ?? value
    }
複製程式碼

hasSome

/// 用來判斷這個 Optional 是不是為空了。
var hasSome: Bool {
    switch self {
    case .none: return false
    case .some: return true
    }
}
複製程式碼

ifSome ifNone

    // 如果 optional 不為空的話,執行閉包, 並返回這個 Optional
	@discardableResult
    func ifSome(_ handler: (Wrapped) -> Void) -> Optional {
        switch self {
        case .some(let wrapped): handler(wrapped); return self;
        case .none: return self
        }
    }

    // 如果 optional 為空的話,執行閉包, 並返回這個 Optional
    @discardableResult
    func ifNone(_ handler: () -> ()) -> Optional {
        switch self {
        case .some: return self;
        case .none: handler(); return self
        }
    }
複製程式碼

總結

為了避免寫出滿是 ?! 的程式碼, 掌握 Swift 中可選型別的基本知識是很必要的。另外也需要去了解 Swift 中為什麼要引入可選型別這個概念。在編寫 Swift 程式碼的時候,需要我們程式設計師時刻知道程式的邏輯是怎麼樣的,在設計一個類的時候, 要清楚它的屬性在其生命週期中那些是可能為空的。在沒有必要的時候,儘量的避免是用 Optional 減少 Optional 的使用,一方面能讓你的程式碼邏輯更可控,一方面也能讓你的程式碼更漂亮。至少不會再被編譯器一步一步的搞出那些噁心的東西。想清楚邏輯, 合理的規避, 加上一些小手段, 讓程式碼更漂亮, 是一件很幸福的事情。 Optional 能夠讓程式碼邏輯更明確, 減少很多不必要的crash,如果不當使用, crash 也不會少哦。

最後我一直覺得,掌握了 Optional 是怎麼回事, 以及 Optional 怎麼用最好, 基本上就算是入門了 Swift 了。

對了朋友的程式碼:

var pageiid: NSString?   // 這是屬性宣告
let pageiid = (self.pageid?.intValue)! + 1  // 這是某個方法裡面的程式碼
複製程式碼
  • 為什麼要用 NSString?
  • 為什麼要 Optional
  • 為什麼要強制解包?

為什麼要 NSString?這個我真不知道, 他只說了介面要一個字串;為什麼要 Optional?這個我也不知道;為什麼要強制解包?這我知道,肯定是 Xcode 自動幫他改的啊?

然後我給他改成了這樣:

var pageid: Int = 0 
self.pageid +=1
"\(self.pageid)"
複製程式碼

初始化的時候預設初始值為0,避免 Optional 的使用也避免了後面的強制解包。

使用 Int 代替 NSString。第一是不想用 NSString, 第二是,在業務邏輯中,這個值應該就是 int 型別的

在介面組裝引數的地方,將 int 轉換成字串。這個邏輯應該是介面的事情,不應該拿業務層的邏輯去將就它。

相關文章