Swift的字面量類型(Literal Type)和字面量協議(Literal Protocol)

前言

你自定義了一個數據類型后,是否希望像var num: Int = 10這樣通過一個字面量初始化一個類型的實例呢?是的話,請看下文詳細介紹。

一、字面量類型(Literal Type)

在介紹字面量類型前,我們先認識下字面量的概念。

所謂字面量,是指一段能表示特定類型的值(如數值、布爾值、字符串)的源碼表達式(it is the source code representation of a fixed value)。比如下面例子:

let num: Int = 10
let flag: Bool = true
let str: String = "hello"

例子中的10truehello都是字面量。

那什么是字面量類型呢?

字面量類型就是支持通過字面量進行實例初始化的數據類型,如例子中的IntBoolString類型。

在Swift中,其的字面量類型有:

  • 所有的數值類型: Int、Double、Float以及其的相關類型(如UInt、Int16、Int32等)
  • 布爾值類型:Bool
  • 字符串類型:String
  • 組合類型:Array、Dictionary、Set
  • 空類型:Nil

二、字面量協議(Literal Protocol)

Swift是如何讓上述的數據類型具有字面量初始化的能力呢?

答案是:實現指定的字面量協議。

所以,如果我們希望自定義的數據類型也能通過字面量進行初始化,只要實現對應的字面量協議即可。

Swift中的字面量協議主要有以下幾個:

  • ExpressibleByNilLiteral // nil字面量協議
  • ExpressibleByIntegerLiteral // 整數字面量協議
  • ExpressibleByFloatLiteral // 浮點數字面量協議
  • ExpressibleByBooleanLiteral // 布爾值字面量協議
  • ExpressibleByStringLiteral // 字符串字面量協議
  • ExpressibleByArrayLiteral // 數組字面量協議
  • ExpressibleByDictionaryLiteral // 字典字面量協議

其中, ExpressibleByStringLiteral 字符串字面量協議相對復雜一點,該協議還依賴于以下2個協議(也就是說,實現ExpressibleByStringLiteral時,還需要實現下面2個協議):

  • ExpressibleByUnicodeScalarLiteral
  • ExpressibleByExtendedGraphemeClusterLiteral

在 Swift3.0 之前,上述的字面量協議的名稱對應如下:

  • NilLiteralConvertible
  • IntegerLiteralConvertible
  • FloatLiteralConvertible
  • BooleanLiteralConvertible
  • StringLiteralConvertible
  • ArrayLiteralConvertible
  • DictionaryLiteralConvertible

三、字面量協議例子(Literal Protocol Example)

下面將會通過具體例子為大家演示如何通過實現上述的字面量協議。

下面的例子均已經上傳GitHub,查看下載請點擊LiteralProtocolExample

1、定義Moeny類型,實現通過整數字面量、浮點數字面量、字符串字面量、布爾值字面量初始化Money實例:
//: Playground - noun: a place where people can play

import UIKit
import Foundation

struct Money {
    var value: Double

    init(value: Double) {
        self.value = value
    }
}

// 實現CustomStringConvertible協議,提供description方法
extension Money: CustomStringConvertible {
    public var description: String {
        return "\(value)"
    }
}

// 實現ExpressibleByIntegerLiteral字面量協議
extension Money: ExpressibleByIntegerLiteral {
    typealias IntegerLiteralType = Int

    public init(integerLiteral value: IntegerLiteralType) {
        self.init(value: Double(value))
    }
}

// 實現ExpressibleByFloatLiteral字面量協議
extension Money: ExpressibleByFloatLiteral {
    public init(floatLiteral value: FloatLiteralType) {
        self.init(value: value)
    }
}

// 實現ExpressibleByStringLiteral字面量協議
extension Money: ExpressibleByStringLiteral {

    public init(stringLiteral value: StringLiteralType) {
        if let doubleValue = Double(value) {
            self.init(value: doubleValue)
        } else {
            self.init(value: 0)
        }
    }

    // 實現ExpressibleByExtendedGraphemeClusterLiteral字面量協議
    public init(extendedGraphemeClusterLiteral value: StringLiteralType) {
        if let doubleValue = Double(value) {
            self.init(value: doubleValue)
        } else {
            self.init(value: 0)
        }
    }

    // 實現ExpressibleByUnicodeScalarLiteral字面量協議
    public init(unicodeScalarLiteral value: StringLiteralType) {
        if let doubleValue = Double(value) {
            self.init(value: doubleValue)
        } else {
            self.init(value: 0)
        }
    }
}

// 實現ExpressibleByBooleanLiteral字面量協議
extension Money: ExpressibleByBooleanLiteral {
    public init(booleanLiteral value: BooleanLiteralType) {
        let doubleValue: Double = value ? 1.0 : 0.0
        self.init(value: doubleValue)
    }
}

// 通過整數字面量初始化
let intMoney: Money = 10

// 通過浮點數字面量初始化
let floatMoney: Money = 10.1

// 通過字符串字面量初始化
let strMoney: Money = "10.2"

// 通過布爾值初始化
let boolMoney: Money = true

2、定義Book類型,實現通過字典字面量、數組字面量、nil字面量初始化Book實例:
//: Playground - noun: a place where people can play

import Foundation

struct Book {

    public var id: Int
    public var name: String

    init(id: Int, name: String = "unnamed") {
        self.id = id
        self.name = name
    }
}

// 實現CustomStringConvertible協議,提供description方法
extension Book: CustomStringConvertible {
    public var description: String {
        return "id:\(id)\nname:《\(name)》"
    }
}

// 實現ExpressibleByDictionaryLiteral字面量協議
extension Book: ExpressibleByDictionaryLiteral {
    typealias Key = String
    typealias Value = Any

    public init(dictionaryLiteral elements: (Key, Value)...) {
        var dictionary = [Key: Value](minimumCapacity: elements.count)
        for (k, v) in elements {
            dictionary[k] = v
        }

        let id = (dictionary["id"] as? Int) ?? 0
        let name = (dictionary["name"] as? String) ?? "unnamed"

        self.init(id: id, name: name)
    }
}

// 實現ExpressibleByArrayLiteral字面量協議
extension Book: ExpressibleByArrayLiteral {
    typealias ArrayLiteralElement = Any

    public init(arrayLiteral elements: ArrayLiteralElement...) {
        var id: Int = 0
        if let eId = elements.first as? Int {
            id = eId
        }

        var name = "unnamed"
        if let eName = elements[1] as? String {
            name = eName
        }

        self.init(id: id, name: name)
    }
}

// 實現ExpressibleByNilLiteral字面量協議
extension Book: ExpressibleByNilLiteral {
    public init(nilLiteral: ()) {
        self.init()
    }
}

// 通過字典字面量初始化
let dictBook: Book = ["id": 100, "name": "Love is Magic"]
print("\(dictBook)\n")

// 通過數組字面量初始化
let arrayBook: Book = [101, "World is word"]
print("\(arrayBook)\n")

// 通過nil字面量初始化
let nilBook: Book = nil
print("\(nilBook)\n")

四、關于 'not expressible by any literal

enum' Error

當你使用自定義數據類型定義枚舉時,可能會遇到以下類似錯誤:

raw type 'XX_TYPE' is not expressible by any literal
enum XX_ENUM: XX_TYPE

這是說你的自定義數據類型沒有實現字面量協議。然而需要注意的是,enum目前支持的字面量協議是有限制的,其目前只支持以下幾個字面量協議:

  • ExpressibleByIntegerLiteral
  • ExpressibleByFloatLiteral
  • ExpressibleByStringLiteral

也就是說,若你的自定義數據類型實現的字面量協議沒有包含上面中的一個,就會得到此種錯誤。具體示例如下:

//: Playground - noun: a place where people can play

import Foundation

struct StockType {
    var number: Int
}

// 實現CustomStringConvertible協議,提供description方法
extension StockType: CustomStringConvertible {
    public var description: String {
        return "Stock Number:\(number)"
    }
}

// 實現Equatable協議,提供==方法
extension StockType: Equatable {
    public static func ==(lhs: StockType, rhs: StockType) -> Bool {
        return lhs.number == rhs.number
    }
}

// 實現ExpressibleByDictionaryLiteral字面量協議
extension StockType: ExpressibleByDictionaryLiteral {
    typealias Key = String
    typealias Value = Any

    public init(dictionaryLiteral elements: (Key, Value)...) {
        var dictionary = [Key: Value](minimumCapacity: elements.count)
        for (k, v) in elements {
            dictionary[k] = v
        }

        let number = (dictionary["number"] as? Int) ?? 0

        self.init(number: number)
    }
}

// 實現ExpressibleByIntegerLiteral字面量協議
extension StockType: ExpressibleByIntegerLiteral {
    public init(integerLiteral value: IntegerLiteralType) {
        self.init(number: value)
    }
}

/*
 若StockType沒有實現 ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral、ExpressibleByStringLiteral中的一個,會報錯誤:error: raw type 'StockType' is not expressible by any literal
 */
// 你可以嘗試去掉ExpressibleByIntegerLiteral的實現,看看編譯器報的錯誤
enum Stock: StockType {
    case apple = 1001
    case google = 1002
}

let appleStock = Stock.apple.rawValue
print("\(appleStock)")

上述例子中,定義了StockType數據類型和Stock枚舉類型。若StockType去掉ExpressibleByIntegerLiteral字面量的協議的實現,將會獲得上述的編譯錯誤。

資料參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容