Swift-06:反射Mirror

本文主要介紹Mirror的使用以及使用Mirror進(jìn)行JSON解析的錯(cuò)誤處理

反射Mirror

反射:是指可以動(dòng)態(tài)獲取類型荒适、成員信息绸硕,在運(yùn)行時(shí)可以調(diào)用方法琼讽、屬性等行為的特性徘熔,

  • 在上面的分析中,我們已經(jīng)知道霉猛,對(duì)于一個(gè)純swift類來(lái)說(shuō),并不支持直接像OC runtime那樣的操作

  • 但是swift標(biāo)準(zhǔn)庫(kù)依舊提供了反射機(jī)制珠闰,用來(lái)訪問(wèn)成員信息惜浅,即Mirror

一般使用

class CJLTeacher: NSObject {
    var age: Int = 18
}
let mirror = Mirror(reflecting: CJLTeacher.self)
for pro in mirror.children{
    print("\(pro.label): \(pro.value)")
}

  • 運(yùn)行上面代碼,發(fā)現(xiàn)沒(méi)有任何打印伏嗜,為什么坛悉?是因?yàn)?code>Mirror中傳入的參數(shù)不對(duì),應(yīng)該是傳入實(shí)例對(duì)象承绸,修改如下
class CJLTeacher: NSObject {
    var age: Int = 18
}
var t = CJLTeacher()
//傳入t也可以
let mirror = Mirror(reflecting: t.self)
for pro in mirror.children{
    print("\(pro.label): \(pro.value)")
}

image

查看Mirror定義

  • 進(jìn)入Mirror初始化方法裸影,發(fā)現(xiàn)傳入的類型是Any,則可以直接傳t
public init(reflecting subject: Any)

  • 進(jìn)入children
public let children: Mirror.Children
??
//進(jìn)入Children军熏,發(fā)現(xiàn)是一個(gè)AnyCollection轩猩,接收一個(gè)泛型
public typealias Children = AnyCollection<Mirror.Child>
??
//進(jìn)入Child,發(fā)現(xiàn)是一個(gè)元組類型,由可選的標(biāo)簽和值構(gòu)成均践,
public typealias Child = (label: String?, value: Any)

這也是為什么能夠通過(guò)label晤锹、value打印的原因。即可以在編譯時(shí)期且不用知道任何類型信息情況下彤委,在Child的值上用Mirror遍歷整個(gè)對(duì)象的層級(jí)視圖

JSON解析

根據(jù)Mirror的這個(gè)特性鞭铆,我們思考下,可以通過(guò)Mirror做什么焦影?首先想到的是JSON解析车遂,如下所示,我們定義了一個(gè)CJLTeacher類斯辰,然后通過(guò)一個(gè)test方法來(lái)解析t

class CJLTeacher {
    var age = 18
    var name = "CJL"
}

<!--JSON解析-->
func test(_ obj: Any) -> Any{
    let mirror = Mirror(reflecting: obj)
    //判斷條件 - 遞歸終止條件
    guard !mirror.children.isEmpty else {
        return obj
    }
    //字典
    var keyValue: [String: Any] = [:]
    //遍歷
    for children in mirror.children {
        if let keyName = children.label {
            //遞歸調(diào)用
            keyValue[keyName] = test(children.value)
        }else{
            print("children.label 為空")
        }
    }
    return keyValue
}

<!--使用-->
var t = CJLTeacher()
print(test(t))

代碼的打印結(jié)果如下舶担,打印出了key-value

image

JSON解析封裝
如果我們想大規(guī)模的使用上述的JSON解析,上面只是針對(duì)CJLTeacher的JSON解析椒涯,所以柄沮,為了方便其他類使用,可以將JSON解析抽取成一個(gè)協(xié)議废岂,然后提供一個(gè)默認(rèn)實(shí)現(xiàn)祖搓,讓類遵守協(xié)議

//1、定義一個(gè)JSON解析協(xié)議
protocol CustomJSONMap {
    func jsonMap() -> Any
}
//2湖苞、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
    func jsonMap() -> Any{
        let mirror = Mirror(reflecting: self)
        //遞歸終止條件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典拯欧,用于存儲(chǔ)json數(shù)據(jù)
        var keyValue: [String: Any] = [:]
        //遍歷
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //遞歸
                    keyValue[keyName] = value.jsonMap()
                }else{
                    print("key是nil")
                }
            }else{
                print("當(dāng)前-\(children.value)-沒(méi)有遵守協(xié)議")
            }
        }
        return keyValue
    }
}

//3、讓類遵守協(xié)議(注意:類中屬性的類型也需要遵守協(xié)議财骨,否則無(wú)法解析)
class CJLTeacher: CustomJSONMap {
    var age = 18
    var name = "CJL"
}

//使用
var t = CJLTeacher()
print(t.jsonMap())

【問(wèn)題】:運(yùn)行代碼發(fā)現(xiàn)镐作,并不是我們想要的結(jié)果,原因是因?yàn)镃JLTeacher中屬性的類型也需要遵守CustomJSONMap協(xié)議

image

【修改】:所以在原有基礎(chǔ)上增加以下代碼

//Int、String遵守協(xié)議
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}

修改后的運(yùn)行結(jié)果如下

image

錯(cuò)誤處理

為了讓我們自己封裝的JSON解析更好用隆箩,除了對(duì)正常返回的處理该贾,還需要對(duì)其中的錯(cuò)誤進(jìn)行處理,在上面的封裝中捌臊,我們目前采用的是print打印的杨蛋,這樣并不規(guī)范,也不好維護(hù)及管理理澎。那么如何在swift中正確的表達(dá)錯(cuò)誤呢逞力?

首先,Swift中糠爬,提供了Error協(xié)議來(lái)標(biāo)識(shí)當(dāng)前應(yīng)用程序發(fā)生錯(cuò)誤的情況寇荧,其中Error的定義如下

public protocol Error {
}

Error是一個(gè)空協(xié)議,其中沒(méi)有任何實(shí)現(xiàn)执隧,這也就意味著你可以遵守該協(xié)議揩抡,然后自定義錯(cuò)誤類型户侥。所以不管是我們的struct、Class捅膘、enum添祸,我們都可以遵循這個(gè)Error來(lái)表示一個(gè)錯(cuò)誤

所以接下來(lái),對(duì)我們上面封裝的JSON解析修改其中的錯(cuò)誤處理

  • 定義一個(gè)JSONMapError錯(cuò)誤枚舉寻仗,將默認(rèn)實(shí)現(xiàn)的print替換成枚舉類型
//定義錯(cuò)誤類型
enum JSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

//1刃泌、定義一個(gè)JSON解析協(xié)議
protocol CustomJSONMap {
    func jsonMap() -> Any
}
//2、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
    func jsonMap() -> Any{
        let mirror = Mirror(reflecting: self)
        //遞歸終止條件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典署尤,用于存儲(chǔ)json數(shù)據(jù)
        var keyValue: [String: Any] = [:]
        //遍歷
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //遞歸
                    keyValue[keyName] = value.jsonMap()
                }else{
                    return JSONMapError.emptyKey
                }
            }else{
                return JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

  • 但是這里有一個(gè)問(wèn)題耙替,jsonMap方法的返回值是Any,我們無(wú)法區(qū)分返回的是錯(cuò)誤還是json數(shù)據(jù)曹体,那么該如何區(qū)分呢俗扇?即如何拋出錯(cuò)誤呢?在這里可以通過(guò)throw關(guān)鍵字(即將Demo中原本return的錯(cuò)誤箕别,改成throw拋出)
//2铜幽、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
    func jsonMap() -> Any{
        let mirror = Mirror(reflecting: self)
        //遞歸終止條件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存儲(chǔ)json數(shù)據(jù)
        var keyValue: [String: Any] = [:]
        //遍歷
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //遞歸
                    keyValue[keyName] = value.jsonMap()
                }else{
                    throw JSONMapError.emptyKey
                }
            }else{
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

  • 發(fā)現(xiàn)改成throw拋出錯(cuò)誤后串稀,編譯器提示有錯(cuò)除抛,其原因是因?yàn)榉椒ú](méi)有聲明成throws

    image
  • 所以還需要在方法的返回值箭頭前增加throws(表示將方法中錯(cuò)誤拋出),修改后的默認(rèn)實(shí)現(xiàn)代碼如下所示

//1母截、定義一個(gè)JSON解析協(xié)議
protocol CustomJSONMap {
    func jsonMap() throws-> Any
}
//2到忽、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
    func jsonMap() throws -> Any{
        let mirror = Mirror(reflecting: self)
        //遞歸終止條件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存儲(chǔ)json數(shù)據(jù)
        var keyValue: [String: Any] = [:]
        //遍歷
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //遞歸
                    keyValue[keyName] = value.jsonMap()
                }else{
                    throw JSONMapError.emptyKey
                }
            }else{
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

  • 由于我們?cè)趈sonMap方法中遞歸調(diào)用了自己清寇,所以還需要在遞歸調(diào)用前增加 try 關(guān)鍵字
//2喘漏、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
    func jsonMap() throws -> Any{
        let mirror = Mirror(reflecting: self)
        //遞歸終止條件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典,用于存儲(chǔ)json數(shù)據(jù)
        var keyValue: [String: Any] = [:]
        //遍歷
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //遞歸
                    keyValue[keyName] = try value.jsonMap()
                }else{
                    throw JSONMapError.emptyKey
                }
            }else{
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

<!--使用時(shí)需要加上try-->
var t = CJLTeacher()
print(try t.jsonMap())

到此华烟,一個(gè)完整的錯(cuò)誤表達(dá)方式就完成了

swift中錯(cuò)誤處理的方式

swift中錯(cuò)誤處理的方式主要有以下兩種:

  • 【方式一】:使用try關(guān)鍵字翩迈,是最簡(jiǎn)便的,即甩鍋盔夜,將這個(gè)拋出給別人(向上拋出帽馋,拋給上層函數(shù))。但是在使用時(shí)比吭,需要注意以下兩點(diǎn):
    • try? 返回一個(gè)可選類型,只有兩種結(jié)果:

      • 要么成功姨涡,返回具體的字典值

      • 要么錯(cuò)誤衩藤,但并不關(guān)心是哪種錯(cuò)誤,統(tǒng)一返回nil

    • try! 表示你對(duì)這段代碼有絕對(duì)的自信涛漂,這行代碼絕對(duì)不會(huì)發(fā)生錯(cuò)誤

  • 【方式二】:使用do...catch

【方式一】try

通過(guò)try來(lái)處理JSON解析的錯(cuò)誤

//CJLTeacher中定義一個(gè)height屬性赏表,并未遵守協(xié)議
class CJLTeacher: CustomJSONMap {
    var age = 18
    var name = "CJL"
    var height = 1.85
}

/*****1检诗、try? 示例*****/
var t = CJLTeacher()
print(try? t.jsonMap())

/*****打印結(jié)果*****/
nil

/*****2、try! 示例*****/
var t = CJLTeacher()
print(try! t.jsonMap())

/*****打印結(jié)果*****/
Fatal error: 'try!' expression unexpectedly raised an error: _5_MirrorAndError.JSONMapError.notConformProtocol: file _5_MirrorAndError/main.swift, line 90
2020-12-20 18:27:28.305112+0800 05-MirrorAndError[18642:1408258] Fatal error: 'try!' expression unexpectedly raised an error: _5_MirrorAndError.JSONMapError.notConformProtocol: file _5_MirrorAndError/main.swift, line 90

<!--3瓢剿、如果直接使用try逢慌,是向上拋出-->
var t = CJLTeacher()
try t.jsonMap()

/*****打印結(jié)果*****/
Fatal error: Error raised at top level: _5_MirrorAndError.JSONMapError.notConformProtocol: file Swift/ErrorType.swift, line 200
2020-12-20 18:31:24.837476+0800 05-MirrorAndError[18662:1410970] Fatal error: Error raised at top level: _5_MirrorAndError.JSONMapError.notConformProtocol: file Swift/ErrorType.swift, line 200

從上面可以知道,錯(cuò)誤是向上拋出的间狂,即拋給了上層函數(shù)攻泼,如果上層函數(shù)也不處理,則直接拋給main鉴象,main沒(méi)有辦法處理忙菠,則直接報(bào)錯(cuò),例如下面的例子

//使用
var t = CJLTeacher()

func test() throws -> Any{
    try t.jsonMap()
}
try test()

其運(yùn)行結(jié)果如下

image

方式二:do-catch

通過(guò)do-catch來(lái)處理JSON解析的錯(cuò)誤

var t = CJLTeacher()
do{
    try t.jsonMap()
}catch{
    print(error)
}

運(yùn)行結(jié)果如下

image

LocalError協(xié)議

如果只是用Error還不足以表達(dá)更詳盡的錯(cuò)誤信息纺弊,可以使用LocalizedError協(xié)議牛欢,其定義如下

public protocol LocalizedError : Error {

    /// A localized message describing what error occurred.錯(cuò)誤描述
    var errorDescription: String? { get }

    /// A localized message describing the reason for the failure.失敗原因
    var failureReason: String? { get }

    /// A localized message describing how one might recover from the failure.建議
    var recoverySuggestion: String? { get }

    /// A localized message providing "help" text if the user requests help.幫助
    var helpAnchor: String? { get }
}

  • 繼續(xù)修改上面的JSON解析,對(duì)JSONMapError枚舉增加一個(gè)擴(kuò)展淆游,遵守LocalizedError協(xié)議傍睹,可以打印更詳細(xì)的錯(cuò)誤信息
//定義更詳細(xì)的錯(cuò)誤信息
extension JSONMapError: LocalizedError{
    var errorDescription: String?{
        switch self {
        case .emptyKey:
            return "key為空"
        case .notConformProtocol:
            return "沒(méi)有遵守協(xié)議"
        }
    }
}

<!--使用-->
var t = CJLTeacher()
do{
    try t.jsonMap()
}catch{
    print(error.localizedDescription)
}

運(yùn)行結(jié)果如下

image

CustomNSError協(xié)議

CustomNSError相當(dāng)于OC中的NSError,其定義如下犹菱,有三個(gè)默認(rèn)屬性

public protocol CustomNSError : Error {

    /// The domain of the error.
    static var errorDomain: String { get }

    /// The error code within the given domain.
    var errorCode: Int { get }

    /// The user-info dictionary.
    var errorUserInfo: [String : Any] { get }
}

  • 繼續(xù)修改JSON解析中的JSONMapError拾稳,讓其遵守CustomNSError協(xié)議,如下所示
//CustomNSError相當(dāng)于NSError
extension JSONMapError: CustomNSError{
    var errorCode: Int{
        switch self {
        case .emptyKey:
            return 404
        case .notConformProtocol:
            return 504
        }
    }
}

<!--使用-->
var t = CJLTeacher()
do{
    try t.jsonMap()
}catch{
    print((error as? JSONMapError)?.errorCode)
}

運(yùn)行結(jié)果如下

image

總結(jié)

  • Error是swift中錯(cuò)誤類型的基本協(xié)議已亥,其中LocalizedError熊赖、CustomNSError都遵守Error

  • 如果在方法中,想要同時(shí)返回正常值錯(cuò)誤虑椎,需要通過(guò)throw返回錯(cuò)誤震鹉,并且在方法返回值的箭頭前面加throws關(guān)鍵字,再調(diào)用方法時(shí)捆姜,還需要加上try關(guān)鍵字传趾,或者使用do-catch,使用try時(shí)泥技,有以下兩點(diǎn)需要注意:

    • try? 返回的是一個(gè)可選類型浆兰,要么成功,返回正常值珊豹,要么失敗簸呈,返回nil

    • try! 表示你對(duì)自己的代碼非常自信,絕對(duì)不會(huì)發(fā)生錯(cuò)誤店茶,一旦發(fā)生錯(cuò)誤蜕便,就會(huì)崩潰

    • 使用建議:建議使用try?,而不是try!

最后附上完整的自定義JSON解析代碼

//定義錯(cuò)誤類型
enum JSONMapError: Error{
    case emptyKey
    case notConformProtocol
}

//定義更詳細(xì)的錯(cuò)誤信息
extension JSONMapError: LocalizedError{
    var errorDescription: String?{
        switch self {
        case .emptyKey:
            return "key為空"
        case .notConformProtocol:
            return "沒(méi)有遵守協(xié)議"
        }
    }
}

//CustomNSError相當(dāng)于NSError
extension JSONMapError: CustomNSError{
    var errorCode: Int{
        switch self {
        case .emptyKey:
            return 404
        case .notConformProtocol:
            return 504
        }
    }
}

//1贩幻、定義一個(gè)JSON解析協(xié)議
protocol CustomJSONMap {
    func jsonMap() throws-> Any
}
//2轿腺、提供一個(gè)默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
    func jsonMap() throws -> Any{
        let mirror = Mirror(reflecting: self)
        //遞歸終止條件
        guard !mirror.children.isEmpty else {
            return self
        }
        //字典两嘴,用于存儲(chǔ)json數(shù)據(jù)
        var keyValue: [String: Any] = [:]
        //遍歷
        for children in mirror.children {
            if let value = children.value as? CustomJSONMap {
                if let keyName = children.label {
                    //遞歸
                    keyValue[keyName] = try value.jsonMap()
                }else{
                    throw JSONMapError.emptyKey
                }
            }else{
                throw JSONMapError.notConformProtocol
            }
        }
        return keyValue
    }
}

extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}

//3、讓類遵守協(xié)議(注意:類中屬性的類型也需要遵守協(xié)議族壳,否則無(wú)法解析)
class CJLTeacher: CustomJSONMap {
    var age = 18
    var name = "CJL"
    var height = 1.85
}

//使用
var t = CJLTeacher()

do{
    try t.jsonMap()
}catch{
    print((error as? JSONMapError)?.errorCode)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末憔辫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子仿荆,更是在濱河造成了極大的恐慌贰您,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赖歌,死亡現(xiàn)場(chǎng)離奇詭異枉圃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)庐冯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門孽亲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人展父,你說(shuō)我怎么就攤上這事返劲。” “怎么了栖茉?”我有些...
    開(kāi)封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵篮绿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我吕漂,道長(zhǎng)亲配,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任惶凝,我火速辦了婚禮吼虎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘苍鲜。我一直安慰自己思灰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布混滔。 她就那樣靜靜地躺著洒疚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坯屿。 梳的紋絲不亂的頭發(fā)上油湖,一...
    開(kāi)封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音领跛,去河邊找鬼肺魁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛隔节,可吹牛的內(nèi)容都是我干的鹅经。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼怎诫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瘾晃!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起幻妓,我...
    開(kāi)封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蹦误,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后肉津,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體强胰,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年妹沙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了偶洋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡距糖,死狀恐怖玄窝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悍引,我是刑警寧澤恩脂,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站趣斤,受9級(jí)特大地震影響俩块,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浓领,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一玉凯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镊逝,春花似錦壮啊、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至座菠,卻和暖如春狸眼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浴滴。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工拓萌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人升略。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓微王,卻偏偏與公主長(zhǎng)得像屡限,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子炕倘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容