將Swift 運(yùn)用協(xié)議泛型封裝網(wǎng)絡(luò)層

一疏遏、前言

最近進(jìn)入新公司開展新項(xiàng)目,我發(fā)現(xiàn)公司項(xiàng)目的網(wǎng)絡(luò)層很 OC 戳寸,最讓人無法忍受的是數(shù)據(jù)解析是在網(wǎng)絡(luò)層之外的疫鹊,每一個(gè)數(shù)據(jù)模型都需要單獨(dú)寫解析代碼拆吆。趁著項(xiàng)目才開始锈拨,給大家講解Swift 運(yùn)用協(xié)議泛型封裝網(wǎng)絡(luò)層

(其實(shí)做為一個(gè)開發(fā)者奕枢,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要缝彬,這是一個(gè)我的iOS學(xué)習(xí)交流群783941081添寺,不管你是小白還是大牛歡迎入駐活尊,大家一起交流學(xué)習(xí))

二墩邀、Moya工具和Codable協(xié)議簡(jiǎn)介

這里只是展示一下 Moya 的基本使用方法和 Codable協(xié)議 的基本知識(shí)荔茬,如果對(duì)這兩塊感興趣慕蔚,讀者可以自行去搜索研究孔飒。

  1. Moya工具

在OC中,我們使用AFNetworking來進(jìn)行網(wǎng)絡(luò)請(qǐng)求园细,簡(jiǎn)潔方便狮崩。在swift中睦柴,我們使用Moya來進(jìn)行網(wǎng)絡(luò)請(qǐng)求坦敌,Moya封裝了Alamofire狱窘,可以更加方便的進(jìn)行網(wǎng)絡(luò)請(qǐng)求躬络。初次使用Moya,還是覺得稍稍有些不習(xí)慣馁菜。在這里,記錄下使用過程德撬。

let url = URL(string: "your url")!
Alamofire.request(url).response { (response) in
    // handle response
}

當(dāng)然讀者也會(huì)基于它進(jìn)行二次封裝,不會(huì)僅僅是上面代碼那么簡(jiǎn)單躲胳。

如果使用 Moya, 你首先做的不是直接請(qǐng)求蜓洪,而是根據(jù)項(xiàng)目模塊建立一個(gè)個(gè)文件定義接口。例如我喜歡根據(jù)模塊的功能取名 模塊名 + API坯苹,然后再在其中定義我們需要使用的接口隆檀,例:

import Foundation
import Moya

enum YourModuleAPI {
    case yourAPI1
    case yourAPI2(parameter: String)
}

extension YourModuleAPI: TargetType {
    var baseURL : URL {
        return URL(string: "your base url")!
    }
    
    var headers : [String : String]? {
        return "your header"
    }
   
    var path: String {
        switch self {
            case .yourAPI1:
                return "yourAPI1 path"
            case .yourAPI2:
                return "yourAPI2 path"
        }
    }
   
    var method: Moya.Method {
        switch self {
            case .yourAPI1:
                return .post
            default:
                return .get
        }
    }
   
    // 這里只是帶參數(shù)的網(wǎng)絡(luò)請(qǐng)求

    var task: Task {
        var parameters: [String: Any] = [:]
        switch self {
            case let .yourAPI1:
                parameters = [:]
            case let .yourAPI2(parameter):
                parameters = ["字段":parameter]
        }
        return .requestParameters(parameters: parameters,
                                    encoding: URLEncoding.default)
    }
   
    // 單元測(cè)試使用    
    var sampleData : Data {
        return Data()
    }
}

定義如上的文件后,你就可以使用如下方式進(jìn)行網(wǎng)絡(luò)請(qǐng)求:

MoyaProvider<YourModuleAPI>().request(YourModuleAPI.yourAPI1) { (result) in
    // handle result            
}
  1. Codable協(xié)議

自Swift4發(fā)布以來已有一段時(shí)間了粹湃,各種新特性為我們提供更加高效的開發(fā)效率恐仑,其中在Swift4中使用Codable協(xié)議進(jìn)行模型與json數(shù)據(jù)之間的映射提供更加便利的方式。在Swift3中孤钦,對(duì)于從服務(wù)器獲取到的json數(shù)據(jù)后觉鼻,我們要進(jìn)行一系列繁瑣的操作才能將數(shù)據(jù)完整的轉(zhuǎn)化成模型仇矾,舉個(gè)??粗合,我們從服務(wù)器獲取了一段這樣的json數(shù)據(jù):

{
    "student": {
        "name": "Jone",
        "age": 18,
        "finger": {
            "count": 10
        }
    }
}

然后我們用JSONSerialization來解析數(shù)據(jù)供屉,得到的是一個(gè)Any類型哗魂。當(dāng)我們要讀取count時(shí)就要采取以下操作:

let json = try! JSONSerialization.jsonObject(with: data, options: [])
if let jsonDic = json as? [String:Any] {
if let student = jsonDic["student"] as? [String:Any] {
        if let finger = student["finger"] as? [String:Int] {
 if let count = finger["count"] {
                print(count)
            }
        }
    }
}

在日常用Swift編寫代碼時(shí),就我而言,我喜歡使用SwiftyJSON或則ObjectMapper來進(jìn)行json轉(zhuǎn)模型,因?yàn)橄啾仍模褂眠@些第三方會(huì)給我們帶來更高的效率。于是在Swift4中,Apple官方就此提供了自己的方法,現(xiàn)在我們來了解其基本的用法。

三、Codable的簡(jiǎn)單使用

首先用狱,我們來對(duì)最簡(jiǎn)單的json數(shù)據(jù)進(jìn)行轉(zhuǎn)模型,現(xiàn)在我們有以下一組json數(shù)據(jù):

let res = """
{
    "name": "Jone",
    "age": 17
}
"""
let data = res.data(using: .utf8)!

然后我們定義一個(gè)Student結(jié)構(gòu)體作為數(shù)據(jù)的模型,并遵守Codable協(xié)議:

struct Student: Codable {
    let name: String
    let age: Int
}

而關(guān)于Codable協(xié)議的描述我們可以點(diǎn)進(jìn)去看一下:

public typealias Codable = Decodable & Encodable

public protocol Encodable {

 public func encode(to encoder: Encoder) throws
}
public protocol Decodable {
    public init(from decoder: Decoder) throws
}

其實(shí)就是遵守一個(gè)關(guān)于解碼的協(xié)議和一個(gè)關(guān)于編碼的協(xié)議埠偿,只要遵守這些協(xié)議才能進(jìn)行json與模型之間的編碼與解碼抖剿。

接下來我們就可以進(jìn)行講json解碼并映射成模型:

let decoder = JSONDecoder()
let stu = try! decoder.decode(Student.self, from: data)
print(stu) //Student(name: "Jone", age: 17)

然后甥温,我們可以將剛才得到的模型進(jìn)行編碼成json:

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted //輸出格式好看點(diǎn)
let data = try! encoder.encode(stu)

print(String(data: data, encoding: .utf8)!)
//{
//    "name" : "Jone",
//    "age" : 17
//}

就是這么簡(jiǎn)單~~~
這里對(duì)encode和decode使用try!是為了減少文章篇幅乃秀,正常使用時(shí)要對(duì)錯(cuò)誤進(jìn)行處理超凳,而常見的錯(cuò)誤會(huì)在第三篇講到

四创夜、分析和解決方案

4.1.1重復(fù)解析數(shù)據(jù)到模型

例如這里有兩個(gè)接口,一個(gè)是請(qǐng)求商品列表虫蝶,一個(gè)是請(qǐng)求商城首頁蝙泼。筆者以前是這樣寫的:


enum MallAPI {
    case getMallHome
    case getGoodsList
}
extension MallAPI: TargetType {
    // 略   
}

let mallProvider = MoyaProvider<MallAPI>()
mallProvider.request(MallAPI.getGoodsList) { (response) in
    // 將 response 解析成 Goods 模型數(shù)組用 success 閉包傳出去
}
mallProvider.request(MallAPI.getMallHome) { (response) in
    // 將 response 解析成 Home 模型用 success 閉包傳出去
}

以上是簡(jiǎn)化的實(shí)用場(chǎng)景才避,每一個(gè)網(wǎng)絡(luò)請(qǐng)求都會(huì)單獨(dú)的寫一次將返回的數(shù)據(jù)解析成數(shù)據(jù)模型或者數(shù)據(jù)模型數(shù)組舆驶。就算是將數(shù)據(jù)解析的功能封裝成一個(gè)單例工具類撬陵,也僅僅是稍稍好了一些蟋定。

筆者想要的是指定數(shù)據(jù)模型類型后抄淑,網(wǎng)絡(luò)層直接返回解析完成后的數(shù)據(jù)模型供我們使用郑原。

41..2 運(yùn)用泛型來解決

泛型就是用來解決上面這種問題的,
使用泛型創(chuàng)建一個(gè)網(wǎng)絡(luò)工具類,并給定泛型的條件約束:遵守 Codable 協(xié)議。


struct NetworkManager<T> where T: Codable {
}

這樣我們?cè)谑褂脮r(shí)逢捺,就可以指定需要解析的數(shù)據(jù)模型類型了谁鳍。


NetworkManager<Home>().reqest...
NetworkManager<Goods>().reqest...

細(xì)心的讀者會(huì)發(fā)現(xiàn)這和 Moya 初始化 MoyaProvider 類的使用方式一樣。

4.1.3使用Moya后劫瞳,如何將加載控制器和緩存封裝到網(wǎng)絡(luò)層

由于使用了 Moya 進(jìn)行再次封裝倘潜,每對(duì)代碼進(jìn)行一次封裝的代價(jià)就是自由度的犧牲。如何將加載控制器&緩存功能和 Moya 契合起來呢?

一個(gè)很簡(jiǎn)單的做法是在請(qǐng)求方法里添加是否顯示控制器和是否緩存布爾值參數(shù)志于′桃颍看著我的請(qǐng)求方法參數(shù)已經(jīng)5,6個(gè)恨憎,這個(gè)方案立馬被排除了蕊退。看著 Moya 的 TargetType 協(xié)議憔恳,給了我靈感瓤荔。

4.2.1 運(yùn)用協(xié)議來解決

既然 MallAPI 能遵守 TargetType 來實(shí)現(xiàn)配置網(wǎng)絡(luò)請(qǐng)求信息,那當(dāng)然也能遵守我們自己的協(xié)議來進(jìn)行一些配置钥组。

自定義一個(gè) Moya 的補(bǔ)充協(xié)議


protocol MoyaAddable {
    var cacheKey: String? { get }
    var isShowHud: Bool { get }
}

這樣 MallAPI 就需要遵守兩個(gè)協(xié)議了

extension MallAPI: TargetType, MoyaAddable {
    // 略   
}

五输硝、部分代碼展示和解析

完整的代碼,讀者可以到 Github 上去下載程梦。

5.1 封裝后的網(wǎng)絡(luò)請(qǐng)求

通過給定需要返回的數(shù)據(jù)類型点把,返回的 response 可以直接調(diào)取 dataList 屬性獲取解析后的 Goods 數(shù)據(jù)模型數(shù)組。錯(cuò)誤閉包里面也能直接通過 error.message 獲取報(bào)錯(cuò)信息屿附,然后根據(jù)業(yè)務(wù)需求選擇是否使用彈出框提示用戶郎逃。


NetworkManager<Goods>().requestListModel(MallAPI.getOrderList, 
completion: { (response) in
    let list = response?.dataList
    let page = response?.page
}) { (error) in
    if let msg = error.message else {
        print(msg)
    }
}

5.2 返回?cái)?shù)據(jù)的封裝

筆者公司服務(wù)端返回的數(shù)據(jù)結(jié)構(gòu)大致如下:

{
    "code": 0,
    "msg": "成功",
    "data": {
        "hasMore": false,
        "list": []
    }
}

出于目前業(yè)務(wù)和解析數(shù)據(jù)的考慮,筆者將返回的數(shù)據(jù)類型封裝成了兩類,同時(shí)也將解析的操作放在了里面挺份。

后面的請(qǐng)求方法也分成了兩個(gè)褒翰,這不是必要的,讀者可以根據(jù)自己的業(yè)務(wù)和喜好選擇匀泊。
請(qǐng)求列表接口返回的數(shù)據(jù)
請(qǐng)求普通接口返回的數(shù)據(jù)

class BaseResponse {
    var code: Int { ... } // 解析
    var message: String? { ... } // 解析
    var jsonData: Any? { ... } // 解析
   
    let json: [String : Any]
    init?(data: Any) {
        guard let temp = data as? [String : Any] else {
            return nil
        }
        self.json = temp
    }
   
    func json2Data(_ object: Any) -> Data? {
        return try? JSONSerialization.data(
        withJSONObject: object,
        options: [])
    }
}

class ListResponse<T>: BaseResponse where T: Codable {
    var dataList: [T]? { ... } // 解析
    var page: PageModel? { ... } // 解析
}

class ModelResponse<T>: BaseResponse where T: Codable {
    var data: T? { ... } // 解析
}

這樣我們直接返回相應(yīng)的封裝類對(duì)象就能獲取解析后的數(shù)據(jù)了优训。

5.3 錯(cuò)誤的封裝

網(wǎng)絡(luò)請(qǐng)求過程中,肯定有各種各樣的錯(cuò)誤各聘,這里使用了 Swift 語言的錯(cuò)誤機(jī)制揣非。

// 網(wǎng)絡(luò)錯(cuò)誤處理枚舉

public enum NetworkError: Error  {
    // 略...
    // 服務(wù)器返回的錯(cuò)誤
    case serverResponse(message: String?, code: Int)
}

extension NetworkError {
    var message: String? {
        switch self {
            case let .serverResponse(msg, _): return msg
            default: return nil
        }
    }
   
    var code: Int {
        switch self {
            case let .serverResponse(_, code): return code
            default: return -1
        }
    }
}

這里的擴(kuò)展很重要,它能幫我們?cè)谔幚礤e(cuò)誤時(shí)獲取錯(cuò)誤的 message 和 code.

5.4 請(qǐng)求網(wǎng)絡(luò)方法

最終請(qǐng)求的方法

private func request<R: TargetType & MoyaAddable>(
    _ type: R,
    test: Bool = false,
    progressBlock: ((Double) -> ())? = nil,
    modelCompletion: ((ModelResponse<T>?) -> ())? = nil,
    modelListCompletion: ((ListResponse<T>?) -> () )? = nil,
    error: @escaping (NetworkError) -> () )
    -> Cancellable?
{}

這里的 R 泛型是用來獲取 Moya 定義的接口躲因,指定了必須同時(shí)遵守 TargetType 和 MoyaAddable 協(xié)議早敬,其余的都是常規(guī)操作了忌傻。
和封裝的返回?cái)?shù)據(jù)一樣,這里也分了普通接口和列表接口搞监。

@discardableResult
func requestModel<R: TargetType & MoyaAddable>(
    _ type: R,
    test: Bool = false,
    progressBlock: ((Double) -> ())? = nil,
    completion: @escaping ((ModelResponse<T>?) -> ()),
    error: @escaping (NetworkError) -> () )
    -> Cancellable?
{
    return request(type,
                    test: test,
                    progressBlock: progressBlock,
                    modelCompletion: completion,
                    error: error)
}

@discardableResult
func requestListModel<R: TargetType & MoyaAddable>(
    _ type: R,
    test: Bool = false,
    completion: @escaping ((ListResponse<T>?) -> ()),
    error: @escaping (NetworkError) -> () )
    -> Cancellable?
{
    return request(type,
                    test: test,
                    modelListCompletion: completion,
                    error: error)
}

我綜合目前項(xiàng)目和 Codable 協(xié)議的坑點(diǎn)考慮,將這里寫得有點(diǎn)死板芯勘,萬一來個(gè)既是列表又有其他數(shù)據(jù)的就不適用了。不過到時(shí)候可以添加一個(gè)類似這種方法腺逛,將數(shù)據(jù)傳出去處理。

// Demo里沒有這個(gè)方法
func requestCustom<R: TargetType & MoyaAddable>(
    _ type: R,
    test: Bool = false,
    completion: (Response) -> ()) -> Cancellable? 
{
    // 略
}

5.5 緩存和加載控制器

想到添加 MoyaAddable 協(xié)議后衡怀,其他就沒什么困難的了棍矛,直接根據(jù) type 獲取接口定義文件中的配置做出相應(yīng)的操作就行了。

var cacheKey: String? {
    switch self {
        case .getGoodsList:
            return "cache goods key"
        default:
            return nil
    }
}

var isShowHud: Bool {
    switch self {
        case .getGoodsList:
            return true
        default:
            return false
    }
}

這就添加了 getGoodsList 接口請(qǐng)求中的兩個(gè)功能
請(qǐng)求返回?cái)?shù)據(jù)后會(huì)通過給定的緩存 Key 進(jìn)行緩存
網(wǎng)絡(luò)請(qǐng)求過程中自動(dòng)顯示和隱藏加載控制器抛杨。

如果讀者的加載控制器有不同的樣式够委,還可以添加一個(gè)加載控制器樣式的屬性。甚至緩存的方式是同步還是異步怖现,都可以通過這個(gè) MoyaAddable 添加茁帽。

// 緩存
private func cacheData<R: TargetType & MoyaAddable>(
    _ type: R,
    modelCompletion: ((Response<T>?) -> ())? = nil,
    modelListCompletion: ( (ListResponse<T>?) -> () )? = nil,
    model: (Response<T>?, ListResponse<T>?))
{
    guard let cacheKey = type.cacheKey else {
        return
    }
    if modelComletion != nil, let temp = model.0 {
        // 緩存
    }
    if modelListComletion != nil, let temp = model.1 {
        // 緩存
    }
}

加載控制器的顯示和隱藏使用的是 Moya 自帶的插件工具。

// 創(chuàng)建moya請(qǐng)求類
private func createProvider<T: TargetType & MoyaAddable>(
    type: T,
    test: Bool) 
    -> MoyaProvider<T> 
{
    let activityPlugin = NetworkActivityPlugin { (state, targetType) in
        switch state {
        case .began:
            DispatchQueue.main.async {
                if type.isShowHud {
                    SVProgressHUD.showLoading()
                }
                self.startStatusNetworkActivity()
            }
        case .ended:
            DispatchQueue.main.async {
                if type.isShowHud {
                    SVProgressHUD.dismiss()
                }
                self.stopStatusNetworkActivity()
            }
        }
    }
    let provider = MoyaProvider<T>(
        plugins: [activityPlugin,
        NetworkLoggerPlugin(verbose: false)])
    return provider
}

5.6 避免重復(fù)請(qǐng)求

定義一個(gè)數(shù)組來保存網(wǎng)絡(luò)請(qǐng)求的信息屈嗤,一個(gè)并行隊(duì)列使用 barrier 函數(shù)來保證數(shù)組元素添加和移除線程安全潘拨。

// 用來處理只請(qǐng)求一次的柵欄隊(duì)列
private let barrierQueue = DispatchQueue(label: "cn.tsingho.qingyun.NetworkManager",
attributes: .concurrent)
// 用來處理只請(qǐng)求一次的數(shù)組,保存請(qǐng)求的信息 唯一
private var fetchRequestKeys = [String]()

private func isSameRequest<R: TargetType & MoyaAddable>(_ type: R) -> Bool {
    switch type.task {
        case let .requestParameters(parameters, _):
            let key = type.path + parameters.description
            var result: Bool!
            barrierQueue.sync(flags: .barrier) {
                result = fetchRequestKeys.contains(key)
                if !result {
                    fetchRequestKeys.append(key)
                }
            }
            return result
        default:
            // 不會(huì)調(diào)用
            return false
    }
}

private func cleanRequest<R: TargetType & MoyaAddable>(_ type: R) {
    switch type.task {
        case let .requestParameters(parameters, _):
            let key = type.path + parameters.description
            barrierQueue.sync(flags: .barrier) {
                fetchRequestKeys.remove(key)
            }
        default:
            // 不會(huì)調(diào)用
            ()
    }
}

這種實(shí)現(xiàn)方式目前有一個(gè)小問題,多個(gè)界面使用同一接口饶号,并且參數(shù)也相同的話铁追,只會(huì)請(qǐng)求一次,不過這種情況還是極少的茫船,暫時(shí)沒遇到就沒有處理琅束。

六、后記

目前封裝的這個(gè)網(wǎng)絡(luò)層代碼有點(diǎn)強(qiáng)業(yè)務(wù)類型算谈,畢竟我的初衷就是給自己公司項(xiàng)目重新寫一個(gè)網(wǎng)絡(luò)層涩禀,因此可能不適用于某些情況。不過這里使用泛型和協(xié)議的方法是通用的然眼,讀者可以使用同樣的方式實(shí)現(xiàn)匹配自己項(xiàng)目的網(wǎng)絡(luò)層艾船。如果讀者有更好的建議,還希望評(píng)論出來一起討論罪治。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末丽声,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子觉义,更是在濱河造成了極大的恐慌雁社,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晒骇,死亡現(xiàn)場(chǎng)離奇詭異霉撵,居然都是意外死亡磺浙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門徒坡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撕氧,“玉大人,你說我怎么就攤上這事喇完÷啄啵” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵锦溪,是天一觀的道長(zhǎng)不脯。 經(jīng)常有香客問我,道長(zhǎng)刻诊,這世上最難降的妖魔是什么防楷? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮则涯,結(jié)果婚禮上复局,老公的妹妹穿的比我還像新娘。我一直安慰自己粟判,他們只是感情好亿昏,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著档礁,像睡著了一般龙优。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上事秀,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天彤断,我揣著相機(jī)與錄音,去河邊找鬼易迹。 笑死宰衙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的睹欲。 我是一名探鬼主播供炼,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼窘疮!你這毒婦竟也來了袋哼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤闸衫,失蹤者是張志新(化名)和其女友劉穎涛贯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔚出,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弟翘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年虫腋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稀余。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悦冀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出睛琳,到底是詐尸還是另有隱情盒蟆,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布师骗,位于F島的核電站茁影,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏丧凤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一步脓、第九天 我趴在偏房一處隱蔽的房頂上張望愿待。 院中可真熱鬧,春花似錦靴患、人聲如沸仍侥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽农渊。三九已至,卻和暖如春或颊,著一層夾襖步出監(jiān)牢的瞬間砸紊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工囱挑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留醉顽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓平挑,卻偏偏與公主長(zhǎng)得像游添,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子通熄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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