swift简单总结(三十二)—— 协议

版本记录

版本号 时间
V1.0 2017.08.01

前言

我是swift2.0的时候开始接触的,记得那时候还不是很稳定,公司的项目也都是用oc做的,并不对swift很重视,我自己学了一段时间,到现在swift3.0+已经出来了,自己平时也不写,忘记的也差不多了,正好项目这段时间已经上线了,不是很忙,我就可以每天总结一点了,希望对自己对大家有所帮助。在总结的时候我会对比oc进行说明,有代码的我会给出相关比对代码。
1. swift简单总结(一)—— 数据简单值和类型转换
2. swift简单总结(二)—— 简单值和控制流
3. swift简单总结(三)—— 循环控制和函数
4. swift简单总结(四)—— 函数和类
5. swift简单总结(五)—— 枚举和结构体
6. swift简单总结(六)—— 协议扩展与泛型
7. swift简单总结(七)—— 数据类型
8. swift简单总结(八)—— 别名、布尔值与元组
9. swift简单总结(九)—— 可选值和断言
10. swift简单总结(十)—— 运算符
11. swift简单总结(十一)—— 字符串和字符
12. swift简单总结(十二)—— 集合类型之数组
13. swift简单总结(十三)—— 集合类型之字典
14. swift简单总结(十四)—— 控制流
15. swift简单总结(十五)—— 控制转移语句
16. swift简单总结(十六)—— 函数
17. swift简单总结(十七)—— 闭包(Closures)
18. swift简单总结(十八)—— 枚举
19. swift简单总结(十九)—— 类和结构体
20. swift简单总结(二十)—— 属性
21. swift简单总结(二十一)—— 方法
22. swift简单总结(二十二)—— 下标脚本
23. swift简单总结(二十三)—— 继承
24. swift简单总结(二十四)—— 构造过程
25. swift简单总结(二十五)—— 构造过程
26. swift简单总结(二十六)—— 析构过程
27. swift简单总结(二十七)—— 自动引用计数
28. swift简单总结(二十八)—— 可选链
29. swift简单总结(二十九)—— 类型转换
30.swift简单总结(三十)—— 嵌套类型
31.swift简单总结(三十一)—— 扩展

协议

OC中就有协议,比如下面几个:

@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>

@optional

// Display customization

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);
- (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section NS_AVAILABLE_IOS(6_0);

// Variable height support

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;

同样,在swift中也存在协议。

协议protocol用于定义完成某项任务或功能所必须的方法和属性,协议实际上并不提供这些功能或任务的具体实现implementation,而只是用来描述这些实现应该是什么样的。类、结构体和枚举通过提供协议所要求的方法,属性的具体实现来采用adopt协议,任意能够满足协议要求的类型被称为协议的遵循者

协议可以要求其遵循者提供特定的属性,实例方法、类方法、操作符或下标脚本等。

下面主要从这几个方面说一下协议。

  • 协议的语法 Protocol Syntax
  • 对属性的规定Property Requirements
  • 对方法的规定 Method Requirements
  • 对突变方法的规定 Mutating Method Requirements
  • 对构造器的规定Initializer Requirements
  • 协议类型Protocol as Types
  • 委托代理模式Delegation
  • 在扩展中添加协议成员 Adding Protocol Conformance with an Extension
  • 通过扩展补充协议声明 Declaring Protocol Adoption with an Extension
  • 集合中的协议类型Collections of Protocol Types
  • 协议的继承Protocol Inheritance
  • 类专属协议Class - Only Protocol
  • 协议合成 Protocol Composition
  • 检验协议的一致性Checking for Protocol Conformance
  • 对可选协议的规定 Optional Protocol Requirements

协议的语法

协议的定义方式与类、结构体、枚举的定义非常类似,如下所示。

protocol SomeProtocol {
    //协议内容
}

在类型名称后面加上协议名称,中间加上冒号:分割即可实现协议,实现多个协议时,各协议之间用逗号,分割,如下所示。

struct SomeStructure : FirstProtocol, AnotherProtocol{
    //结构体内容
}

如果一个类在含有父类的同时也采用了协议,应当把父类放在所有的协议之前,如下所示。

class someClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    //类的内容
}

对属性的规定

协议可以规定其遵循者提供特定名称与类型的实例属性 instance property类属性 type property,而不管其是存储型属性stored property还是计算型属性calculate property

还要注意:

  • 如果协议要求属性是可读写的,那么这个属性不能是常量存储型属性或只读计算型属性。
  • 如果协议要求属性是只读的gettable,那么计算型属性或存储型属性都能满足协议对属性的规定,即使为只读属性实现了写方法也依然有效settable

协议中的属性经常被加以var前缀声明其为变量属性,在声明后加上{set get}来表示属性是可读写的,只读的属性则写作{get},如下所示。

protocol SomeProtocol {
    var mustBeSettable : Int{
        get set
    }
    var doesNotNeedToBeSettable : Int = {
        get
    }
}

通常,在协议的定义中使用class前缀表示该属性为类成员;在枚举和结构体实现协议中,需要使用static关键字作为前缀。

protocol SomeProtocol {
    class var someTypeProperty : Int{
        get set
    }
}

下面的是一个含有一个实例属性要求的协议。

protocol SomeProtocol {
    var someString : String{
        get
    }
}

下面就是一个实现了SomeProtocol协议的简单结构体。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        let john = Person(someString: "John")
        print(john)
    }
}

struct Person : SomeProtocol{
    var someString : String
}

protocol SomeProtocol {
    var someString : String{
        get
    }
}

下面看输出结果

Person(someString: "John")

下面在看一下复杂的例子。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        var starship = StarShip(name: "Enterprise", prefix: "USS")
        print(starship.someString)
    }
}

class StarShip : SomeProtocol {
    var prefix : String?
    var name : String
    init(name : String, prefix : String? = nil) {
        self.name = name
        self.prefix = prefix
    }
    var someString: String{
        return (prefix != nil ? prefix! + "" : "") + name
    }
}

下面看输出结果

USSEnterprise

对方法的规定

协议可以要求其遵循者实现某些指定的实例方法或类方法,这些方法作为协议一部分,像普通的方法一样清晰的放在协议的定义中,而不需要大括号和方法体。

注意:协议中的方法支持变长参数variadic parameter,不支持参数默认值default value

协议中类方法的定义与类属性的定义相似,在协议定义的方法前置class关键字来表示,当在枚举或结构体实现类方法时,需要使用static关键字来代替。

protocol SomeProtocol {
    class func someTypeMethod()
}

如下所示,定义了含有一个实例方法的协议。

protocol RandomNumberGenerator {
    func random() -> Double
}

下面我们看一个类,遵循了RandomNumberGenerator协议的类,该类实现了一个叫做线性同余生成器的伪随机数算法。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        let generator = LinearCongruentialGenerator()
        let value = generator.random()
        print(value)
    }
}

class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
        return lastRandom / m
    }
}

下面我们看一下输出结果

0.37464991998171

对突变方法的规定

有时候不得不在方法中更改实例的所属类型,在基于值类型 value types(结构体、枚举)的实例方法中,将mutating关键字作为函数的前缀,写在func之前,表示可以在该方法中修改实例及其属性的所属类型。

如果协议中的实例方法打算改变其遵循者实例的类型,那么在协议定义时需要在方法前加上mutating关键字,才能是结构体和枚举来采用并满足协议中对方法的规定。

注意:用类实现协议中的mutating方法时,不用写mutating关键字;用结构体、枚举实现协议中的mutating方法时必须写mutating关键字。

先看一下下面的协议

protocol Togglable {
    mutating func toggle()
}

下面调用一下

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        var lightSwitch = LightSwitch.off
        lightSwitch.toggle()
        print(lightSwitch)
    }
}

enum LightSwitch : Togglable{
    case off, on
    mutating func toggle() {
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}

下面看输出结果

on

对构造器的规定

协议可以要求它的遵循类型实现特定的构造器,你可以像书写普通的构造器那样,在协议的定义里写下构造器的需求,但不需要写花括号和构造器实体。

protocol SomeProtocol {
    init(someParameter : Int)
}

协议构造器规定在类中的实现,你可以在遵循该协议的类中实现构造器,并指定其为类的特定构造器或者便携构造器,在这两种情况下你都必须给构造器实现标上required修饰符。

class SomeClass : SomeProtocol{
    required init(someParameter: Int) {
        //构造器实现
    }
}

使用required修饰符可以保证,所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。

注意:如果类已经被final修饰符所标示,你就不需要在协议构造器规定的实现中使用required修饰符,因为final类不能有子类。

如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示为requiredoverride修饰符。

看一下简单的例子。

class someSubClass : SomeSuperClass, SomeProtocol{
    required override init() {
        //构造器实现
    }
}

class SomeSuperClass {
    init() {
        //协议定义
    }}

protocol SomeProtocol {
    init()
}

协议类型

尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。

主要有下面几种情况:

  • 协议类型作为函数、方法或构造器中的参数类型或返回值类型
  • 协议类型个作为常量、变量或属性的类型
  • 协议类型作为数组、字典或其他容器中的元素类型

注意:协议是一种类型,因为协议类型的名称应与其他类型Int、Double、String的写法相同,驼峰命名法。

下面是一个简单的例子。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
        for _ in 1...5{
            print("random roll is \(d6.roll())")
        }
    }
}

class Dice{
    let sides : Int
    let generator : RandomNumberGenerator
    init(sides : Int, generator : RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    
    func roll() -> Int{
        return Int(generator.random() * Double(sides)) + 1
    }
}

class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
        return lastRandom / m
    }
}

protocol RandomNumberGenerator {
    func random() -> Double
}

下面看输出结果

random roll is 3
random roll is 5
random roll is 4
random roll is 5
random roll is 4

委托代理模式

委托是一种设计模式,允许类或结构体将一些需要它们负责的功能交由委托给其他的类型的实例。委托模式的实现很简单,定义协议来封装那些需要被委托的函数和方法,使其遵循者拥有这些被委托的函数和方法。

委托模式可以用来响应特定的动作或接收外部数据源提供的数据,而无需要知道外部数据源所属类型。


在扩展中添加协议成员

可以通过扩展(Extension)来扩展已存在类型,扩展可以为已存在的类型添加属性、方法、下标脚本、协议等成员。

注意:通过扩展已存在的类型遵循协议时,该类型的所有实例也会随之添加协议中的方法。

下面看一下代码。

protocol TextRepresentable {
    func asText() -> String
}

上面协议TextRepresentable中包含一个方法asText

通过扩展为上面类Dice类遵循这个协议TextRepresentable

extension Dice : TextRepresentable{
    func  asText() -> String {
        return "A \(sides) - sided dice"
    }
}

从现在开始,Dice类型实例可被当作TextRepresentable类型。


通过扩展补充协议声明

当一个类型已经实现了协议中的所有要求,却没有声明时,可以通过扩展来补充协议声明。

struct Hamster {
    var name : String
    
    func asText() -> String {
        return "A hamster named \(name)"
    }
}
extension Hamster : TextRepresentable{}

从现在起,Hamster的实例可以作为TextRepresentable类型使用。

下面我们调用一下

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        let simonTheHamster = Hamster(name: "Simon")
        let somethingTextRepresentable : TextRepresentable = simonTheHamster
        print(somethingTextRepresentable.asText())
    }
}

下面我们看一下输出结果

A hamster named Simon

注意:即使满足了协议的所有要求,类型也不会自动转变,因此你必须为它做出明显的协议声明。


集合中的协议类型

协议类型可以被集合使用,表示集合中的元素均为协议类型。

可以如下定义

let things : [TextRepresentable] = [game, d12, simonTheHamster]

如下所示,things数组可以被直接遍历,并调用其中元素的asText()函数。每个成员thing被当做是TextRepresentable类型,因此能够且仅能调用asText()方法。


协议的继承

协议能够继承一到多个其他协议,语法与类的继承相似,多个协议之间用逗号,分隔。

下面我们看一下是如何定义的。

protocol InheritingProtocol : SomeProtocol, AnotherProtocol {
    //协议定义
}

下面看一个简单的例子。

protocol PrettyTextRepresentable : TextRepresentable {
    func asPrettyText() -> String
}

PrettyTextRepresentable协议继承了TextRepresentable,遵循PrettyTextRepresentable协议的同时,也需要遵循TextRepresentable协议。


类专属协议

你可以在协议的继承列表中,通过添加class关键字,限制协议只能适配到类class类型,(结构体和枚举不能遵循该协议),该class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。

下面看一下定义方法。

protocol SomeClassOnlyProtocol : class,  SomeInheritedProtocol{
    //class - only protocol definition goes here
}

当协议需求定义的行为,要求或假设它的遵循类型必须是引用语义而非值语义时,应该采用类专属协议。


协议合成

一个协议可以由多个协议采用protocol <SomeProtocol, AnotherProtocol>这样的格式进行组合,称为协议合成(protocol composition)

下面来个例子。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        let birthdayPerson = Person(name: "Allen", age: 20)
        wishHappyBirthday(celebrator: birthdayPerson)
    }
}

protocol Named {
    var name : String {
        get
    }
}

protocol Aged {
    var age : Int{
        get
    }
}

struct Person : Named, Aged {
    var name : String
    var age : Int
}

func wishHappyBirthday(celebrator : protocol<Named, Aged>){
    print("Happy birthday \(celebrator.name) - you are \(celebrator.age)!")
}

wishHappyBirthday函数的形参celebrator的类型为protocol<Named, Aged>,可以传入任意遵循这两个协议的类型的实例。


检验协议的一致性

使用isas操作符来检查协议的一致性或转化协议类型。

  • is操作符用来检查实例是否遵循了某个协议
  • as?返回一个可选值,当实例遵循协议时,返回该协议类型,否则返回nil
  • as用以强制向下转型

下面看一下

@objc protocol HasArea{
    var area : Double {
        get
    }
}

注意@objc用来表示协议是可选的,也可以用来表示暴露给OC的代码,@objc型协议只对类有效,因此只能在类中检查协议的一致性。

class CirCle : HasArea{
    let pi = 3.1415927
    var radius : Double
    var area : Double{
        return pi * radius * radius
    }
    
    init(radius : Double) {
        self.radius = radius
    }
}

class Country : HasArea{
    var area : Double
    init(area : Double) {
        self.area = area
    }
}

如下所示,Animal是一个没有实现HasArea协议的类。

class Animal{
    var legs : Int
    init(legs : Int) {
        self.legs = legs
    }
}

CirCleCountryAnimal没有一个相同的基类,因而采用AnyObject类型的数组来装载在它们的实例,如下所示:

let objects : [AnyObject] = [
    CirCle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]

下面看一下数组遍历

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        let objects : [AnyObject] = [
            CirCle(radius: 2.0),
            Country(area: 243_610),
            Animal(legs: 4)
        ]
        
        for object in objects {
            if let objectWithArea = object as? HasArea{
                print("Area is \(objectWithArea.area)")
            }
            else {
                print("Something that does not have an area")
            }
        }
    }
}

下面我们看一下输出结果

Area is 12.5663708
Area is 243610.0
Something that does not have an area

objects数组中元素的类型并不会因为向下转型而改变,它们仍然是CirCleCountryAnimal类型,然而,当它们被赋值给objectWithArea常量时,则只被视为HasArea类型,因此只有area属性能够被访问。


对可选协议的规定

可选协议含有可选成员,其遵循者可以选择是否实现这些成员,在协议中使用@optional关键字作为前缀来定义可选成员。这一点和OC中是一样的。

可选协议在调用时使用可选链

注意:可选协议只能在含有@objc前缀的协议中生效,且@objc的协议只能被遵循。

@objc protocol CounterDataSource{
    @objc optional func incrementForCount(count : Int) -> Int
    @objc optional var fixedIncrement : Int{
        get
    }
}

CounterDataSource含有incrementForCount可选方法和fixedIncrement可选属性。

下面看一个简单代码

class Counter{
    var count = 0
    var dataSource : CounterDataSource?
    func increment(){
        if let amount = dataSource?.incrementForCount?(count: count) {
            count += amount
        }
        else if let amount = dataSource?.fixedIncrement
        {
            count += amount
        }
    }
}

count属性用于存储当前的值,increment方法用来为count赋值,increment方法通过可选链,尝试从两种可选成员中获取count

  • 由于dataSource可能为nil,因此,在dataSource后面加上了?标记表明只在dataSource非空的时候才能调用incrementForCount方法。
  • 即使dataSource存在,但是也无法保证其是否实现了incrementForCount方法,因此在incrementForCount方法后面也加上?标记。
  • incrementForCount不能被调用时,尝试使用可选属性fixedIncrement来代替。

下面看一个简单例子。

class ThreeSource : CounterDataSource{
    let fixedIncrement = 3
}

使用ThreeSource作为数据源实例化一个Counter

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        var counter = Counter()
        counter.dataSource = ThreeSource()
        for _ in 1...4 {
            counter.increment()
            print(counter.count)
        }
    }
}

下面看输出结果

3
6
9
12

在看一下下面的例子。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        var counter = Counter()
        counter.count = -4
        counter.dataSource = TowardsZeroSource()
        for _ in 1...5 {
            counter.increment()
            print(counter.count)
        }
    }
}

class TowardsZeroSource : CounterDataSource{
    func incrementForCount(count : Int) -> Int {
        if count == 0 {
            return 0
        }
        else if count < 0 {
            return 1
        }
        else {
            return -1
        }
    }
}

下面看输出结果

-3
-2
-1
0
0

后记

未完,待续~~~

布加迪
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容