Swift进阶-泛型

Swift进阶-类与结构体
Swift-函数派发
Swift进阶-属性
Swift进阶-指针
Swift进阶-内存管理
Swift进阶-TargetClassMetadata和TargetStructMetadata数据结构源码分析
Swift进阶-Mirror解析
Swift进阶-闭包
Swift进阶-协议
Swift进阶-泛型
Swift进阶-String源码解析
Swift进阶-Array源码解析

为什么会有泛型?

func multiNumInt(_ x: Int, _ y: Int) -> Int { 
    return x * y 
}

func multiNumDouble(_ x: Double, _ y: Double) -> Double { 
    return x * y 
}

我们发现, multiNumInt(::) 、 multiNumDouble(::) 函数体是一样的。唯一的区别是它们接收值类型不同( Int 、 Double )。
这个时候我们想找到一个可以计算任意类型值的函数怎么办?泛型正是能让我们写出这样函数的语法。

一、泛型语法

首先我我们要指定一个占位符 T ,紧挨着写在函数名后面的 一对尖括号(当前我们这个 T 要遵循 FloatingPoint 协议,计算乘积所必须);其次我们就可以使用 T 来替换任意定义的函数形式参数。

  • 举例一:函数的泛型
func multiNum<T: FloatingPoint>(_ x: T, _ y: T) -> T { 
    return x * y 
}
  • 举例二:类型的泛型
struct Stack {
    private var items = [Int]()
    
    mutating func push(_ item: Int) {
        items.append(item)
    }

    mutating func pop() -> Int? {
        if items.isEmpty { return nil }
        return items.removeLast()
    }
}

这是一个标准的非泛型版的数据结构,如果我们想要改造一下怎么做:

struct Stack<Element> {
    private var items = [Element]()
    
    mutating func push(_ item: Element){
        items.append(item)
    }
    
    mutating func pop() -> Element? {
        if items.isEmpty { return nil }
        return items.removeLast()
    }
}
  • 举例三: 协议的泛型

基本上和栈的数据结构相关都有相同的操作,这个时候我们免不了抽取相同的行为定一个协议:

protocol StackProtocol {
    var itemCount: Int{ get }
    
    mutating func pop() -> Int?
    func index(of index: Int) -> Int
}

定义协议的时候需要指明当前类型,那么能不能给 Protocol 也上一个泛型?系统提示 Protocol 不支持泛型参数,需要我们使用关联类型来代替。

protocol StackProtocol {
    associatedtype Item
    
    var itemCount: Item{ get }
    
    mutating func pop() -> Item?
    func index(of index: Int) -> Item
}
  • 案例四: 关联类型给协议中用到的类型一个占位符名称
    协议定义中的相关类型我们都可以用这个占位符替代,等到真正实现协议的时候在去确定当前占位符的类型,比如下面的代码:
protocol StackProtocol {
    associatedtype Item
    var itemCount: Int{ get }
    
    mutating func pop() -> Item?
    func index(of index: Int) -> Item
}
    

struct Stack: StackProtocol {
    typealias Item = Int
    
    private var items = [Item]()
    var itemCount: Int{
        get{
            return items.count
        }
    }
    
    mutating func push(_ item: Item) {
        items.append(item)
    }
    
    mutating func pop() -> Item? {
        if items.isEmpty { return nil }
        return items.removeLast()
    }
    
    func index(of index: Int) -> Item {
        return items[index]
    }
}
  • 案例五:协议继承声明

同样的我们也可以给当前的关联类型添加约束,比如我们要求 Item 必须都要遵循 FixWidthInteger:

protocol StackProtocol {
    associatedtype Item: FixedWidthInteger
    
    var itemCount: Item{ get }
    
    mutating func pop() -> Item?
    func index(of index: Int) -> Item
}

当然我们也可以直接在约束中使用协议

protocol EvenProtocol: StackProtocol {
    associatedtype Even: EvenProtocol where Even.Item == Item
    
    func pushEven(_ item: Int) -> Even
}

在这个协议里, Even 是一个关联类型,就像上边例子中 StackProtocolItem 类型一样。
Even 拥有两个约束:
1.它必须遵循 EvenProtocol协议(就是当前定义的协议);
2.以及它的 Item 类型必须是和容器里的 Item 类型相同。
Item 的约束是一个 where 分句。

protocol StackProtocol {
    associatedtype Item: FixedWidthInteger
    
    var itemCount: Item{ get }
    
    mutating func pop() -> Item?
    func index(of index: Int) -> Item
}

protocol EvenProtocol: StackProtocol {
    associatedtype Even: EvenProtocol where Even.Item == Item
    
    func pushEven(_ item: Int) -> Even
}


struct Stack: StackProtocol {
    typealias Item = Int
    
    private var items = [Item]()
    var itemCount: Int{
        get{
            return items.count
        }
    }
    
    mutating func push(_ item: Item) {
        items.append(item)
    }
    
    mutating func pop() -> Item? {
        if items.isEmpty { return nil }
        return items.removeLast()
    }
    
    func index(of index: Int) -> Item {
        return items[index]
    }
}

extension Stack: EvenProtocol {
    func pushEven(_ item: Int) -> Stack {
        var result = Stack()
        if item % 2 == 0 {
            result.push(item)
        }
        return result
    }
}

在上面的例子中我们出现了一个where 分句,泛型 Where 分句要求了关联类型必须遵循指定的协议,或者指定的类型形式参数和关联类型必须相同。
泛型 Where分句以 Where 关键字开头,后接关联类型的约束或类型和关联类型一致的关系。

  • 案例六:函数泛型约束
protocol StackProtocol {
    associatedtype Item: FixedWidthInteger
    
    var itemCount: Item { get }
    
    mutating func pop() -> Item?
    func index(of index: Int) -> Item
}

func compare<T1: StackProtocol, T2: StackProtocol>(_ stack1: T1, _ stack2: T2) -> Bool {
    guard stack1.itemCount == stack2.itemCount else { return false }
    
    for i in 0..<stack1.itemCount {
        if stack1.index(of: Int(i)) != stack2.index(of: Int(i)) {
            return false
        }
    }
    return true
}

这个函数有两个形式参数, stack1stack2stack1 形式参数是 T1 类型, stack2 形式参数是 T2 类型。 T1T2 是两个容器类型的类型形式参数,它们的类型在调用函数时决定。

下面是函数的两个类型形式参数上设置的要求:
1、 T1 必须遵循 StackProtocol 协议(写作 T1: StackProtocol );
2、 T2 也必须遵循 StackProtocol 协议(写作 C2: StackProtocol );
3、 T1 的 Item 必须和 T2 的 Item 相同(写作 T1.Item == C2.Item);
4、 T1 的 Item 必须遵循 Equatable 协议(写作 T1.ItemType: Equatable )。

前两个要求定义在了函数的类型形式参数列表里,后两个要求定义在了函数的泛型 Where 分句中。

这些要求意味着:
1、 stack1 是一个 T1 类型的容器;
2、 stack2 是一个 T2 类型的容器;
3、 stack1 和 stack2 中的元素类型相同

二、类型擦除

给一个类型耦合的例子:

// 定义数据拉取协议
protocol DataFetch {
    associatedtype DataType

    func fetch(completion: ((Result<DataType, Error>)->Void))
}


// 数据模型
struct User {
    let userId: Int
    let name: String
}

// 定义数据拉取工具类 - 并且确定拉取后通过闭包返回User模型
struct UserDataFetch: DataFetch {
    
    typealias DataType = User
    
    // 类似网路网络请求,获取数据
    func fetch(completion: ((Result<DataType, Error>) -> Void)) {
        let user = User(userId: 1001, name: "安安")
        completion(.success(user))
    }
}

接下来看看ViewController会耦合的地方:

class MyViewController {
    let dataFetch: UserDataFetch
    
    init(_ dataFetch: UserDataFetch) {
        self.dataFetch = dataFetch
    }
}

可以看到ViewController里面声明了一个UserDataFetch实例,那就会出现一种情况是那如果我又有一个VipUserDataFetch类型呢,那就又得嵌套进了ViewController了,那就会显得臃肿且耦合,那如果我们声明成DataFetch协议类型呢?

因为我们声明是一个泛型协议,会报错的:

想要解决上面的耦合性,又想VC传入的时候使用泛型协议且又想绕过编译器的检查,可以抽象出一层中间层AnyDataFetch通过依赖注入的方式把协议的具体类型传递进来了,并让其做一个泛型约束:

  • 这里我们定义了一个中间层结构体 AnyDataFetchAnyDataFetch 实现了 DataFetch的所有方法。
  • AnyDataFetch 的初始化过程中,实现协议的类型会被当做参数传入(依赖注入)
  • AnyDataFetch 实现的具体协议方法 fetch 中,再转发实现协议的抽象类型。
// 定义一个中间层处理vc数据接收,因为vc接收的不一定只有 UserData 获取的数据,也可以VipUserData等等...
struct AnyDataFetch<T>: DataFetch {
    typealias DataType = T
    
    private let _fetch: ((Result<T, Error>) -> Void) -> Void
    
    init<U: DataFetch>(_ fetchable: U) where U.DataType == T {
        self._fetch = fetchable.fetch
    }
    
    func fetch(completion: ((Result<T, Error>) -> Void)) {
        _fetch(completion)
    }
}

class MyViewController {
    let dataFetch: AnyDataFetch<User>

    init(_ dataFetch: AnyDataFetch<User>) {
        self.dataFetch = dataFetch
    }
}


let userData = UserDataFetch()
let anyDataFetch = AnyDataFetch<User>(userData)
let myVC = MyViewController(anyDataFetch)
myVC.dataFetch.fetch { result in
    switch result {
    case .success(let user):
        print(user.name)
    case .failure(let error):
        print(error)
    }
}

这里可能大家看不出有什么区别,这样做的好处就是我们不用知道当前请求的具体类型是什么, 也就意味着如果我改变了之后,我还有一个VipDataFetch遵循了DataFetch协议:

struct VipDataFetch: DataFetch {

    typealias DataType = User

    func fetch(completion: ((Result<DataType, Error>) -> Void)) {
        let user = User(userId: 0001, name: "VIP")
        completion(.success(user))
    }
}

let vipDataFetch = VipDataFetch()
let anyDataFetch1 = AnyDataFetch<User>(vipDataFetch)
let vc = MyViewController(anyDataFetch1)
vc.dataFetch.fetch { (result) in
    switch result {
        case .success(let user):
        print(user.name)
        case .failure(let error):
        print(error)
    }
}

对于VC来说压根儿不关心具体的DataFetch类型了,关注点只放在model上。
系统上对于类型擦除的运用比如:AnyCollection、AnySequence

AnySequence使用案例

假设你有这样一个需求,你需要迭代你的自定义属性 User ;此刻对于User 来说,我们应该要做的是实现 Sequence 协议:

struct User: Sequence {
    var userId: Int
    var name: String
    
    func makeIterator() -> CustomIterator {
        return CustomIterator(obj: self)
    }
}

struct CustomIterator: IteratorProtocol {
    var children: Mirror.Children
    
    init(obj: Any) {
        children = Mirror(reflecting: obj).children
    }
    
    mutating func next() -> String? {
        guard let child = children.popFirst() else { return nil }
        return "\(child.label ?? "无label") is \(child.value)"
    }
}

那这个时候,对于我们当前的另一个页面的 VIP 用户数据,也需要遍历,对于 VIPUSer 来说他们的行为是一致的,所以这里我们希望抽象出一个统一的协议:

struct VIP: CustomDataSeuqence{
    var vipdate: String = "20221226"
    var viplevel: Int = 18
    var vipName: String = "林林"
}


protocol CustomDataSeuqence: Sequence {}
extension CustomDataSeuqence {
    func makeIterator() -> CustomIterator {
        return CustomIterator(obj: self)
    }
}

接下来比如我们要做一个社群功能,需要当前同一个社群当中的用户进行遍历,如果是VIP,需要特殊显示一些效果。
那么这里的 Users 应该定义成什么类型呢?定义成 Any 类型,这个时候对于当前页面来说就需要讲 Any 的类型强转成 CustomDataSeuqence ,可以,但是有点牵强。这个时候我们就可以使用 AnySeuqence

let user = User(userId: 1001, name: "安安")
let vip = VIP()

let users: [AnySequence<String>] = [AnySequence(user), AnySequence(vip)]

for user in users{
    // print(user)
    for item in user {
        print(item)
    }
}

打印结果:

userId is 1001
name is 安安
vipdate is 20221226
viplevel is 18
vipName is 林林

这个时候 AnySequence 就将具体的 Sequence 类型隐藏了,调用者只知道数组中的元素是一个可以迭代输出字符串类型的序列。

三、泛型的底层原理

func testGenric<T>(_ value: T) -> T {
    let tmp = value
    return value
}

testGenric(10)

对于编译器来说,它不知道T到底是什么具体的类型(可能是值类型,也可能是引用类型),那编译器就不知道 tmp / value 到底分配多少内存空间、步长是多少、对齐的字段是多少...只有当真正传递参数才知道真实类型对吧。
tmp在方法栈占据内存空间编译器是怎么决断的呢?

来看看上面代码编译成IR代码进行分析:

main
testGenric

可以看到在调用testGenric时候可以看到一个值见证表valueWitnesses,并且可以看出到initializeWithCopy、destroy等函数都是从值见证表来的,所以泛型是通过ValueWitnessTable去管理内存的。

ValueWitnessTable结构体

截图是ValueWitnessTable的数据结构,前面几个i8*其实就是void *代表内存管理函数,剩下的就是size、stride、flags、extraInhabitantCount,把数据结构还原一下

ValueWitnessTable的数据结构:

// 弱引用表
struct ValueWitnessTable {
    var unknow1: UnsafeRawPointer
    var unknow2: UnsafeRawPointer
    var unknow3: UnsafeRawPointer
    var unknow4: UnsafeRawPointer
    var unknow5: UnsafeRawPointer
    var unknow6: UnsafeRawPointer
    var unknow7: UnsafeRawPointer
    var unknow8: UnsafeRawPointer
    var size: Int
    var stride: Int
    var flags: UInt32
    var extraInhabitantCount: UInt32
}

不管testGenric函数的参数传递的是值/引用类型,不管什么类型,总之它的metadata一定有ValueWitnessTable!并且保存着这个类型的size、stride、flags、extraInhabitantCount还有一些内存管理函数等信息。

尝试还原一下上面分析的结构体对不对,举一个案例呗:

struct Teacher {
    var age = 10
}

// 所有类型的最终基类 Metadata
struct TargetMetadata {
    var kind: Int // kind用于区分类型的
}

ps: 不懂为啥是TargetMetadata可以看我之前f的分享的文章

let ptr = unsafeBitCast(Teacher.self as Any.Type, to: UnsafeMutablePointer<TargetMetadata>.self)
let valueWitnessTable = UnsafeRawPointer(ptr).advanced(by: -MemoryLayout<UnsafeRawPointer>.size).assumingMemoryBound(to: UnsafeMutablePointer<ValueWitnessTable>.self).pointee
print(valueWitnessTable.pointee.size) // 8 
// 如果给 Teacher 添加一个属性 var age1 = 20,则打印的size就是 16


print(valueWitnessTable.pointee.unknow1) // 0x00007ff81b935400
print(valueWitnessTable.pointee.unknow2) // 0x00007ff81b935410
print(valueWitnessTable.pointee.unknow3) // 0x00007ff81b935420
print(valueWitnessTable.pointee.unknow4)
print(valueWitnessTable.pointee.unknow5)
print(valueWitnessTable.pointee.unknow6)
print(valueWitnessTable.pointee.unknow7)
print(valueWitnessTable.pointee.unknow8)
print("end")

ps: 为什么ValueWitnessTable是在TargetMetadata向前移动?

在IR代码中就能分析出,ValueWitnessTable的位置是在TargetMetadata的向前移动-1的位置。

来看看unknow1打印的是什么?

image.png

得到函数名,可以自行打印最后恢复ValueWitnessTable的数据结构:

// 弱引用表
struct ValueWitnessTable {
    var initializeBufferWithCopyOfBuffer: UnsafeRawPointer
    var destroy: UnsafeRawPointer
    var initializeWithCopy: UnsafeRawPointer
    var assignWithCopy: UnsafeRawPointer
    var initializeWithTake: UnsafeRawPointer
    var assignWithTake: UnsafeRawPointer
    var getEnumTagSinglePayload: UnsafeRawPointer
    var storeEnumTagSinglePayload: UnsafeRawPointer
    var size: Int
    var stride: Int
    var flags: UInt32
    var extraInhabitantCount: UInt32
}

在来对比刚才testGenric 编译后的IR代码里的,如果我们上面的结论:不管什么类型它的metadata一定有ValueWitnessTable是对的,那一定可以在IR上找到上面结构体里还原出来的函数。

恰好能对应上面还原的方法名:

总结:

  • 泛型是通过ValueWitnessTable (简称VWT 是由编译器产生的) 来进行内存管理,VWT存储了size、stride、alignment和一些内存管理函数等;
  • 对于值类型来说,实际上是通过内存的拷贝;copy/move等操作;
  • 对于引用类型来说,就是堆区内存块的引用,通过引用计数的方式。
如果传递的参数是泛型呢?会发生什么变化?

来看看下面这个案例

func makeIncrementer() -> (Int) -> Int {
    var runningTotal = 10
    func incrementer(mount: Int) -> Int {
        runningTotal += mount
        return runningTotal
    }
    return incrementer
}

func genric<T>(t: T) {    
}

let increment = makeIncrementer()
genric(t: increment)

编译成IR代码,找到mian函数:

define i32 @main(i32 %0, i8** %1) #0 {
entry:
  %2 = alloca %swift.function, align 8
  %3 = bitcast i8** %1 to i8*

  // 调用makeIncrementer,生成闭包
  %4 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerS2icyF"()
  %5 = extractvalue { i8*, %swift.refcounted* } %4, 0
  %6 = extractvalue { i8*, %swift.refcounted* } %4, 1

  // 将指针存储到increment变量里
  store i8* %5, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main9incrementyS2icvp", i32 0, i32 0), align 8
  // 将捕获的变量存储到increment变量里
  store %swift.refcounted* %6, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main9incrementyS2icvp", i32 0, i32 1), align 8

  // 把function类型转换成void *
  %7 = bitcast %swift.function* %2 to i8*
  call void @llvm.lifetime.start.p0i8(i64 16, i8* %7)
  // 从increment变量取出指针和捕获的变量
  %8 = load i8*, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main9incrementyS2icvp", i32 0, i32 0), align 8
  %9 = load %swift.refcounted*, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main9incrementyS2icvp", i32 0, i32 1), align 8

  // 一些内存管理的函数
  %10 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %9) #3

  // swift_allocObject创建了堆区的内存空间
  %11 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 32, i64 7) #3
  %12 = bitcast %swift.refcounted* %11 to <{ %swift.refcounted, %swift.function }>*

  // 取出第一个成员是function这个结构体
  %13 = getelementptr inbounds <{ %swift.refcounted, %swift.function }>, <{ %swift.refcounted, %swift.function }>* %12, i32 0, i32 1
  // 从function这个结构体取出函数地址
  %.fn = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 0
  store i8* %8, i8** %.fn, align 8
  %.data = getelementptr inbounds %swift.function, %swift.function* %13, i32 0, i32 1
  store %swift.refcounted* %9, %swift.refcounted** %.data, align 8
  %.fn1 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 0
  store i8* bitcast (void (%TSi*, %TSi*, %swift.refcounted*)* @"$sS2iIegyd_S2iIegnr_TRTA" to i8*), i8** %.fn1, align 8

  %.data2 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
  store %swift.refcounted* %11, %swift.refcounted** %.data2, align 8
  %14 = bitcast %swift.function* %2 to %swift.opaque*
  // 后面又对这个function又做了一层重新的抽象
  %15 = call %swift.type* @__swift_instantiateConcreteTypeFromMangledName({ i32, i32 }* @"$sS2icMD") #10
  call swiftcc void @"$s4main6genric1tyx_tlF"(%swift.opaque* noalias nocapture %14, %swift.type* %15)
  %.data3 = getelementptr inbounds %swift.function, %swift.function* %2, i32 0, i32 1
  %16 = load %swift.refcounted*, %swift.refcounted** %.data3, align 8
  call void @swift_release(%swift.refcounted* %16) #3
  %17 = bitcast %swift.function* %2 to i8*
  call void @llvm.lifetime.end.p0i8(i64 16, i8* %17)
  ret i32 0
}

可以看到func genric<T>(t: T) 当泛型参数接收一个闭包/函数时,会对这个闭包/函数原有数据结构上又抽象封装了一层。因为我们传入可能是是函数,也可能传入的是闭包表达式,编译器不知道呀,它就和协议设计一样不知道谁遵循了协议,所以它就抽象了一层中间层为了泛型管理统一区分类型。

当去调用的时候,是通过这个抽象出来的中间层来取调用,如果不调用而用来传值的话,依旧是通过上面的ValueWitnessTable进行内存管理的。(注意:函数/闭包也是引用类型)。

func genric<T>(t: T) {    
    // VWT管理内存
    let tmp = t
    // 函数调用,抽象中间层统一管理函数/闭包
    var t1 = t as! (Int) -> Int
    t1()
}

原本对于闭包的数据结构是这样的:(想了解可点击 函数/闭包数据结构)

// 闭包的实质
struct ClosureData<T> {
    var ptr: UnsafeRawPointer // 闭包地址
    var box: UnsafePointer<T> // Box
}

// 捕获单个外部变量的时候Box
struct Box<T> {
    var heapObject: HeapObject // 实例对象的内存地址
    var value: T
}

// 捕获多个外部变量的时候Box
//struct Box<T1, T2> {
//    var object: HeapObject  // 把value1、value2统一当成object的属性
//    var value1: UnsafePointer<T1>
//    var value2: T2
//    var value3.......
//}

struct HeapObject {
    var metadata: UnsafeRawPointer
    var refcount1: UInt32
    var refcount2: UInt32
}

泛型参数接收闭包/函数后对其进行调用,又抽象一层封装的数据结构:

// 闭包作为泛型参数,又包装了一层
struct ReabstractionThunkContext<Context> {
    var heapObject: HeapObject
    var function: ClosureData<Context>
}

验证数据结构:

func makeIncrementer() -> (Int) -> Int {
    var runningTotal = 10
    func incrementer(mount: Int) -> Int {
        runningTotal += mount
        return runningTotal
    }
    return incrementer
}

func genric<T>(t: T) {
    let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
    ptr.initialize(to: t)
    
    defer {
        ptr.deinitialize(count: 1)
        ptr.deallocate()
    }
    
    let closure_ptr = ptr.withMemoryRebound(to: ClosureData<ReabstractionThunkContext<Box<Int>>>.self, capacity: 1) { $0 }
    let ctx = closure_ptr.pointee.box.pointee.function.box
    print(ctx.pointee.value) // 10    得到捕获的外部变量的值
}

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

推荐阅读更多精彩内容

  • 1. 前言 泛型代码让你能根据你所定义的要求写出可以用于任何类型的灵活的、可复用的函数。你可以编写出可复用、意图表...
    搬运工iOS橙阅读 651评论 0 1
  • swift进阶总汇[//www.greatytc.com/p/c00fa675d7d5] 本文主要介绍泛...
    iOS鑫阅读 1,294评论 0 7
  • swift 进阶之路:学习大纲[//www.greatytc.com/p/115367c3eefd] 本...
    欧德尔丶胡阅读 303评论 0 1
  • 本文主要介绍泛型及其底层原理 泛型 泛型主要用于解决代码的抽象能力 + 代码的复用性 例如下面的例子,其中的T就是...
    辉辉岁月阅读 420评论 0 0
  • Swift语言有很多强大的特性,泛型编程(generic programming)就是其中之一,我们也可以将其简称...
    flionel阅读 3,639评论 0 6