Swift3 改变收集

转自:
https://github.com/KQAppleDev/SwiftLearn
http://blog.csdn.net/qq_16437739/article/details/52819452
http://www.jianshu.com/p/460b5424942a
http://www.qingpingshan.com/rjbc/swift/155193.html

Swift 3 出来也有一阵子了,Swift 3做了很大的改动,逐渐脱离OC的影子。语法上很多对象去掉了NS开头,去掉了繁琐的命名。

本文主要讨论Swift 3中的一些坑和使用过程中的一些小技巧,排名无理由~~

转变为结构体的类

在之前的Swift版本中,苹果引入了String、Array和Dictionary这三个结构体来代替OC中的NSString、NSArray和NSDictionary这三个类,当然这三个OC类依然可以使用。但是在平时的开发使用中,Swift的这三个结构体使用起来更方便,大部分情况下效率更高。 在Swift3.0中,苹果又推出了以下新的结构体,原有OC类依然可以使用。并且可以相互转化。


新增结构体类型及对应的OC类型
常用系统提供单例类的获取方法Swift风格化
Swift 3.0 和 Swift 2.3 写法对比
Foundation框架部分类名去掉NS前缀

包括:UserDefaults、URL、NotificationCenter、Bundle、Timer、Thread、RunLoop


Swift 3.0 和 Swift 2.3 写法对比
BOOL属性的命名规则

在OC中,官方建议我们将BOOL属性的getter方法命名为isXXX,Swift中由于只是将原有OCUIKit框架进行Swift转换,所以这个规则在之前是Swift中并没有体现。
Swift3.0中,这个规则被再次应用,所有的BOOL类型都重新命名为isXXX,所以以后我们的自定义类中BOOL属性的命名也应体现这个规则。

private和fileprivate
  • private: 私有属性和方法,仅在当前类中可以访问,不包括分类;
  • fileprivate: 文件内私有属性和方法,仅在当前文件中可以访问,包括同一个文件中不同的类。
/// 以下所有的类都在同一个文件中
class Test: NSObject {
    // 只能在当前大括号内访问
    private var value: Int = 0
    // 只能在当前文件内访问
    fileprivate var value1: Int = 0
    
    // 只能在当前大括号内访问
    private func privatePractise() {
        value = 1
        value1 = 1
        fileprivatePractise()
        fileprivatePractise1()
        print("privatePractise方法被调用了")
    }
    // 只能在当前文件内访问
    fileprivate func fileprivatePractise() {
        privatePractise()
        fileprivatePractise()
        fileprivatePractise1()
        print("fileprivatePractise方法被调用了")
    }
}
public和open

在Swift2.3中,pubic有两层含义:

  1. 这个元素可以在其他作用域被访问
  2. 这个元素可以在其他作用域被继承或者override

继承是一件危险的事情。尤其对于一个framework或者module的设计者而言。在自身的module内,类或者属性对于作者而言是清晰的,能否被继承或者override都是可控的。但是对于使用它的人,作者有时会希望传达出这个类或者属性不应该被继承或者修改。这个对应的就是 final。

final的问题在于在标记之后,在任何地方都不能override。而对于lib的设计者而言,希望得到的是在module内可以被override,在被import到其他地方后其他用户使用的时候不能被override。
这就是 open产生的初衷。通过open和public标记区别一个元素在其他module中是只能被访问还是可以被override。

在Swift3.0中

  • public表示当前类、属性或者方法只能在当前module内被继承或者override,在当前module意外只能被访问;
  • open表示当前类、属性或者方法可以在任何地方被继承或者override;
  • final是一个辅助修饰词,表示当前类、属性或者方法在任何地方都只能被访问,不能被继承或者override;
    internal表示默认级别。
/// ModuleA: 
import UIKit 
/// 这个类在ModuleA的范围外是不能被继承的,只能被访问 
public class NonSubclassableParentClass: NSObject { 
    // 这个方法在ModuleA的范围外只能被访问,不能被override 
    public func test() { 
        print(“test”)
    } 

    //这是**错误**的写法,因为class已经不能被继承,
    //所以�它的方法的访问权限不能大于类的访问权限 
    open func bar() { 
        print(“bar”) 
    } 
    // 这个方法在任何地方都只能被访问,不能被override 
    public final func baz() { 
        print(“baz”) 
    } 
} 

/// 在ModuleA的范围外可以被继承 
open class SubclassableParentClass: NSObject { 
    // 这个属性在ModuleA的范围外只能被访问,不能被override 
    public var size: Int = 0 
    // 这个方法在ModuleA的范围外只能被访问,不能被override 
    public func foo() { 
        print(“foo”) 
    } 
    // 这个方法在任何地方都可以被override 
    open func baz() { 
        print(“baz”) 
    } 
    // 这个方法在任何地方都只能被访问,不能被override 
    public final func bar() { 
        print(“bar”) 
    } 
} 

/// 这个类在任何地方都不能被继承 
public final class FinalClass { 
}

Swfit3.0中,访问控制权限由高到低依次为:open、public、internal(默认)、fileprivate,private。

用stride 代替 C-style 循环

Swift 3 移除了 C 语言风格的 for 循环:

for var a = -6.28; a <= 6.28; a += 0.1 {
    print(a, separator: " ", terminator: " ")
}

采用stride

for a in stride(from: -6.28, to: 6.28, by: 0.1) { // from ..< to
    print(a, separator: " ", terminator: " ")
}

for x in stride(from: -6.28, through: 6.28, by: 0.1) {  // from ... to
    print(x, separator: " ", terminator: " ")
}
@discardableResult 消除返回值警告

在Swift 3中,如果方法的返回值没有处理xCode会报一个警告,如果在方法前加上 @discardableResult
不处理的时候就不会有警告了。也可以用
_ = xxx()
来消除警告。

浮点数取余数和除法

在Swift 3中 ,如果你声明一个 let m = 12.0
默认m是 Double, Double是不能和Float做运算的。 CGFloat在32位设备上是 Float32 在64位设备上是 Float64, 所以如果一个 Double 和一个 Float 做运算时先要转换类型的

let m = 12.0
let n:CGFloat = 19.0
let x = CGFloat(m)/n
let k = m.multiplied(by: Double(n)) // 乘法
let y = m.divided(by: Double(n)) // 除法

但是取余算法是不能作用于浮点型的,如果这样就会报错 CGFloat(m)%n
正确的做法是:

let z = CGFloat(m).truncatingRemainder(dividingBy: n) //取余 12.0
AnyObject、 Any

这两个类型都是Swift中很早就出现的类型,但是我们经常使用AnyObject,很少使用Any。
AnyObject类似于OC中的id类型,表示任意的class的实例对象,但是在Swift中,例如我们常见的String和Array都变为结构体了,
而且在Swift3.0中,更多的类型或者枚举被写为结构体了,AnyObject的适用范围变相被削弱了,
所以在Swift3.0的API中曾经许多AnyOjbect的类型被替换为Any了。

Swift 3.0 和 Swift 2.3 写法对比

之前整个项目基本只用 AnyObject 代表大多数实例,基本也不和 Any 有什么交集。因为Swift 2 针对 Int、 String 等结构体进行了转换,编译器会自动桥接为 NSNumber 和 NSString 这种对象类型,在swift3中 AnyObject 不能表示结构体了 。而 Any 可以代表 struct、 class、 func 等几乎所有类型。

这个改动导致项目很多地方都要随着改,而且大多数库也做了改变,如Alamofire的参数从 [String:AnyObject]? 变成 [String:Any]?

值得一提的是 Any 可以代表任何可空类型,不用指定 Any?

栗子:

let str:String? = "xwwa"
var param:[String:Any] = ["x":1,"code":str]
// ["code": Optional("xwwa"), "x": 1]

str 是一个 Optional 类型的,输出出来也是 Optional。因为我们以前的请求是需要在header中带参数的json机密,换成 Any 怎么都过不去,后来发现有 Optional 值。
这里写了个方法转化了下

func absArray(param:[String:Any])->[String:Any]{ 
    let res = param.map { (key,value) -> (String,Any?) in 
        let newValue = Mirror(reflecting: value) 
        if newValue.displayStyle == Mirror.DisplayStyle.optional{ 
            if let v = newValue.children.first?.value{
                return (key,v) 
            }else{ 
                return (key,nil) 
            }
        } return (key,value) 
    } 
    var newParam:[String:Any] = [:] 
    res.forEach { (key,v) in 
        newParam[key] = v 
    } 
    return newParam
}
print(absArray(param:param))  // ["code": "xwwa", "x": 1]

用了反射判断如果值是optional就取出他实际的值.

Swift 3中 Notification 使用方法

Swift 3.0 中NSNotification和Notification创建时,通知的name参数类型都变为“Notification.Name”类型


Paste_Image.png
extension Notification.Name { 
    static let kNoticeDemo = Notification.Name("xx.xx.ww.ss")
}

class DE{
    func test(){ 
        NotificationCenter.default.post(name:Notification.Name.kNoticeDemo , object: nil) 
        NotificationCenter.default.addObserver(self, selector: #selector(demo), name: Notification.Name.kNoticeDemo, object: nil)
        NotificationCenter.default.removeObserver(self, name: Notification.Name.kNoticeDemo, object: nil) 
    } 
    
    @objc func demo(){
   
    }
}
自定义操作符

在swift 2中自定义操作符

infix operator =~ {
    associativity none 
    precedence 130
}

现在在Swift 3中这样的话会报警告 Operator should no longer be declared with body;use a precedence group instead

自定义操作符 别名类型

// >>>操作符, 优先级别名
infix operator >>> : ATPrecedence
precedencegroup ATPrecedence { //定义运算符优先级ATPrecedence
    associativity: left 
    higherThan: AdditionPrecedence 
    lowerThan: MultiplicationPrecedence
}

直接指定操作符的类型,对这个类型进行定义,

  • associativity: left 表示左结合
  • higherThan 优先级高于 AdditionPrecedence 这个是加法的类型
  • lowerThan 优先级低于 MultiplicationPrecedence 乘除

这里给出常用类型对应的group

infix operator || : LogicalDisjunctionPrecedence
infix operator && : LogicalConjunctionPrecedence
infix operator < : ComparisonPrecedence
infix operator <= : ComparisonPrecedence
infix operator > : ComparisonPrecedence
infix operator >= : ComparisonPrecedence
infix operator == : ComparisonPrecedence
infix operator != : ComparisonPrecedence
infix operator === : ComparisonPrecedence
infix operator !== : ComparisonPrecedence
infix operator ~= : ComparisonPrecedence
infix operator ?? : NilCoalescingPrecedence
infix operator + : AdditionPrecedence
infix operator - : AdditionPrecedence
infix operator &+ : AdditionPrecedence
infix operator &- : AdditionPrecedence
infix operator | : AdditionPrecedence
infix operator ^ : AdditionPrecedence
infix operator * : MultiplicationPrecedence
infix operator / : MultiplicationPrecedence
infix operator % : MultiplicationPrecedence
infix operator &* : MultiplicationPrecedence
infix operator & : MultiplicationPrecedence
infix operator << : BitwiseShiftPrecedence
infix operator >> : BitwiseShiftPrecedence
infix operator ..< : RangeFormationPrecedence
infix operator ... : RangeFormationPrecedence
infix operator *= : AssignmentPrecedence
infix operator /= : AssignmentPrecedence
infix operator %= : AssignmentPrecedence
infix operator += : AssignmentPrecedence
infix operator -= : AssignmentPrecedence
infix operator <<= : AssignmentPrecedence
infix operator >>= : AssignmentPrecedence
infix operator &= : AssignmentPrecedence
infix operator ^= : AssignmentPrecedence
infix operator |= : AssignmentPrecedence
合理的使用异常处理,提高代码质量

在日常开发中,可能遇到很多特殊情况,使得程序不能继续执行下去。有的来自系统语法方面,有的是来自业务方面的。这时候可以使用自定义异常,在底层代码中不断throw 在最后一层中去处理。

struct ZError : Error { 
    let domain: String 
    let code: Int
}

func canThrow() throws{ 
    let age = 10 
    if a < 18{ 
        let error = ZError(domain: "xxx", code: 990) 
        throw error 
    }
}

do { 
    try canThrow()
} catch let error as ZError {
    print("Error: \(error.code) - \(error.domain)") // Error: 990 - xxx
}

是时候放弃前缀的扩展了
以前我们要给 UIView 扩展是这样的

extension UIView { 
    var zz_height:CGFloat{ 
        set(v){ 
            self.frame.size.height = v 
        } 

        get{ 
            return self.frame.size.height 
        } 
    }
}

这样在自己写的属性前面加一个前缀。但是Swift 3出来后更多的选择应该是这样的view.zz.height 。 以前 kingfisher是 imageView.kf_setImage 现在变成 imageView.kf.setImage 。 SnapKit 也改变成了 make.snp.left 之类的语法那么怎么写这样的扩展呢?

这里看了 KingFisher 的代码,给出他的解决方案。比如我们想写一个UIView的扩展。

// 写一个协议 定义一个只读的类型
public protocol UIViewCompatible { 
    associatedtype CompatableType 
    var zz: CompatableType { get }
}

public extension UIViewCompatible { 
    // 指定泛型类型为自身 , 自身是协议 谁实现了此协议就是谁了 
    public var zz: Auto<Self> {
        get { return Auto(self) } // 初始化 传入自己
        set { } 
    }
}

// Auto是一个接受一个泛型类型的结构体
public struct Auto<Base> {
    // 定义该泛型类型属性  
    public let base: Base 
    public init(_ base: Base) {
        self.base = base 
    }
}

// 写一个Auto的扩展 指定泛型类型是UIView 或者其子类
public extension Auto where Base:UIView { 
    var height:CGFloat{ 
        set(v){ 
            self.base.frame.size.height = v 
        } get{ 
            return self.base.frame.size.height 
        } 
    }
}

// 扩展 UIView 实现 UIViewCompatible 协议,就拥有了zz属性 zz又是Auto类型 Auto是用泛型实例化的 这个泛型就是UIView了
extension UIView : UIViewCompatible{
}
// 使用
view.zz.height

上面的注释已经尽量详细的解释了这段代码,hope you can understand !

GCD 的改变

swift 3彻底改变了GCD的使用方式,这里给出日常最基本的几个

你不需要在去用 dispatch_get_main_queue ( ) 来获取主线程,而是 DispatchQueue . main ,那么要放到主线程的代码怎么执行呢?只需要在线程后边使用 . async { } 即可,也就是说,大概是这样:

DispatchQueue.main.async {
    print("这里在主线程执行")
}

优先级

  • DISPATCH_QUEUE_PRIORITY_HIGH: .userInitiated
  • DISPATCH_QUEUE_PRIORITY_DEFAULT: .default
  • DISPATCH_QUEUE_PRIORITY_LOW: .utility
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND: .background
//global 中设置优先级 不设置默认是 default
DispatchQueue.global(qos: .userInitiated).async {
    print("设置优先级")
}

创建一个队列

let queue = DispatchQueue(label: "im.demo.test")

也可以指定优先级和队列

let highQueue = DispatchQueue(
    label: "high.demo.test.queue",
    qos: DispatchQoS.background, 
    attributes: DispatchQueue.Attributes.concurrent,
    autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit,
    target: nil
)

highQueue.async { 
    print("ceshi")
}

3秒后执行

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3.0) { 
    print("after")
}
根据View查找VC

如果你在一个 UITableViewCell 或者 cell上自定义的一个view上想使用这个view所在的vc怎么办? 代理 ? 层层引用 ? NO ! 一个扩展解决。
一个UIView的扩展

// 查找vc
func responderViewController() -> UIViewController { 
    var responder: UIResponder! = nil 
    var next = self.superview 
    while next != nil { 
        responder = next?.next 
        if (responder!.isKind(of: UIViewController.self)){ 
            return (responder as! UIViewController) 
        } 
        next = next?.superview 
    } 
    return (responder as! UIViewController)
}

记得写在扩展中哦,加上前面的技巧 。不论你在哪个view中。只需要这样let vc = view.zz.responderViewController() 就能拿到所处的vc了。
View中的第一响应者
又是一个View的扩展也很好用,上代码

func findFirstResponder()->UIView?{ 
    if self.isFirstResponder{ 
        return self 
    } 
    for subView in self.subviews{ 
        let view = subView.findFirstResponder() 
        if view != nil { 
            return view 
        } 
    } 
    return nil
}

用法同上,这个东西能干啥呢?
利用这个可以在 NSNotification.Name.UIKeyboardWillShow 通知通知中拿到第一响应者,如果第一响应者是UITextfield,可以算出局底下的距离,给挡墙view做变换。避免被覆盖。而且这个东西可以写在协议的默认实现中或者
写在一个基类中公用。本文为杂谈不细说了,点到为止咯!

UIViewController 返回是否显示状态栏的方法变化
控制器方法改为属性
控制器方法改为属性

获取string的字符串长度方法的改变


获取字符串长度参数改变
获取沙盒指定文件夹路径的方法变化

获取文件路径统一交给FileManager来管理


获取沙盒路径参数改变
Swift3.0中selecter的Swift风格化

在Swift2.2中,当我们为一个按钮添加点击事件时常常这样写:


Swift 2.3 中 Selector写法

Swift 2.3 中 Selector写法

在Swift2.2更新到Swift2.3后可以看到警告告诉我们这是一个OC风格的写法,建议改为Swift风格的写法。在Swift3.0中两种写法依然都可以使用,但是建议统一写为Swift风格的,因为你不知道什么时候OC风格的就不被允许了。


Swift 3.0 中 Selector写法
Swift3中字符串处理的变化
//字符串的索引
str = "Hello, Swift"
//str.characters[0] //这是错误的
let startIndex = str.startIndex //str字符串的起始Index, 注意它是Index类型的, 并不是Int类型.
str[startIndex] //"H"
//str[startIndex.advancedBy(5)] //"," //swift2
str[str.index(startIndex, offsetBy: 5)] //"," //swift3

//let spaceIndex = startIndex.advancedBy(6) //6 //swift2
let spaceIndex = str.index(startIndex, offsetBy: 6) //6 //swift3
spaceIndex //6
//str[spaceIndex.predecessor()]  //"," //spaceIndex前一个字符. //swift2
str[str.index(before: spaceIndex)]  //"," //spaceIndex前一个字符. //swift3
//str[spaceIndex.successor()] //"S" spaceIndex后一个字符.
str[str.index(after: spaceIndex)]//"S" spaceIndex后一个字符.

let endIndex = str.endIndex //12 最后一个字符的下标.
//str[endIndex] //下标越界了
//str[endIndex.predecessor()] //用endIndex.predecessor()来表示最后一个字符. //swift2
str[str.index(before: endIndex)] //swift3

str[startIndex..<spaceIndex] //"Hello,"
//let range = startIndex..<spaceIndex.predecessor() //圈定str字符串中的某个区间. //swift2
let range = startIndex..<str.index(before: spaceIndex) // swift3

//str.replaceRange(range, with: "Hi") //用 "Hi" 替换range中的字符串. //swift2
str.replaceSubrange(range, with: "Hi") //swift3

//str.appendContentsOf("123") // "Hi, Swift123" /swift2
str.append("123") //swift3

//str.insert("?", atIndex: str.endIndex) //"Hi, Swift123" //swift2
str.insert("?", at: str.endIndex) //swfit3

//str.removeAtIndex(str.endIndex.predecessor()) //"?" //swift2
str.remove(at: str.index(before: str.endIndex)) //swift3

str //"Hi, Swift123"
//str.removeRange(str.endIndex.advancedBy(-2)..<str.endIndex) //"Hi, Swift1" 删除后两个字符. //swift2
str.removeSubrange(str.index(str.endIndex, offsetBy: -2)..<str.endIndex) //swift3

//str.uppercaseString  //"HI, SWIFT1" //swift2
str.uppercased() //swift3

//str.lowercaseString  //"hi, swift1" //swift2
str.lowercased() //swift3

//str.capitalizedString //"Hi, Swift1" 单词首字母大写 方法 //swift2
str.capitalized //swift3

//str.containsString("Hello") //false //swift2
str.contains("Hello") //swift3

str.hasPrefix("Hi")  //true 是否以"Hi"开头
str.hasSuffix("ft1") //true 是否以"ft1"结束

let p1 = "one third is \(1.0/3.0)" //"one third is 0.333333333333333"
// 使用OC中的NSString 格式化保留两位小数, 并使用as强制转换为Swift的String类型.
let p2:String = NSString(format: "one third is %.2f", 1.0/3.0) as String

var p3:NSString = "one third is 0.33" //声明并赋值一个NSString字符串变量.
//p3.substringFromIndex(4) //NSString截取字符从第4个字符开始. //swift2
p3.substring(from: 4) //swift3

//p3.substringToIndex(3)  //NSString截取字符到第3个字符. //swift2
p3.substring(to: 3) //swift3

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

推荐阅读更多精彩内容