iOS 中的 JS

最近主要在研究 iOS 中的 JS 這一塊內(nèi)容坦袍,本文打算從 為什么不能單純地搞前端十厢、JSCore 的原理和通信機制、OC 底層 Runtime 原理捂齐、如何通過 JS 任意修改 iOS 的運行結(jié)果 這 4 部分來闡述蛮放,旨在讓前端和 iOS 開發(fā)同學(xué)更加了解跨端開發(fā)的原理,同時了解他倆結(jié)合起來做哪些意想不到的事情奠宜。

為什么不能單純地搞前端包颁?

畢業(yè)工作以來,經(jīng)歷過移動端 H5 React 到 RN 開發(fā)压真,到去年的 Weex 開發(fā)和最近的 iOS 開發(fā)娩嚼,越來越發(fā)現(xiàn):

僅靠前端技術(shù)難以滿足移動端的用戶需求或體驗要求

為什么只弄前端會效果不好?

可能 H5 同學(xué)很有感觸滴肿,比如需要做一個端上 H5 照片上傳功能岳悟,通過 JS 去實現(xiàn)往往效果會大打折扣,也很難達到業(yè)務(wù)方需要的順滑體驗嘴高,要是此時 Native 同學(xué)說我寫好了一個 Bridge竿音,只需在客戶端里用 JS 調(diào)用Bridge.uploadImg()這個方法就可直接用 Native 的上傳功能,聽到這句話你肯定會長舒一口氣輕松地去寫代碼了拴驮。

還有將端上 Webview 由 UIWebView 更換成 WKWebView,起到的效果也會比自己優(yōu)化很久的 H5 順滑滾動來得快和好柴信。

那么潮流前端同學(xué)一般怎么弄移動端需求呢套啤?

他們一般會借 Native 端的能力,譬如用 Weex 或者 RN 來開發(fā)頁面,讓其也有 Native 效果潜沦,假如有頁面在微信或者支付寶萄涯,還可以升級成小程序變成內(nèi)置程序一樣,客戶端中還可以借助端上容器優(yōu)化這一塊唆鸡,讓其可以離線我們的 H5 頁面涝影,并通過橋提供很多 Native 功能來拓展能力。

這里借力的的橋梁其實就是 Bridge争占,讓兩者不在是一個孤島燃逻,而是相互助力,我理解它可以做這些事情:

<figure>
image

</figure>

接下來通過JSCore 的原理和通信機制這一小節(jié)給出上述的解決方案臂痕。

JSCore 的原理和通信機制

JSCore 是什么伯襟?

大家都知道瀏覽器內(nèi)核的模塊主要是由渲染引擎JS 引擎組成,其中 JSCore 就是一種 JS 引擎

Apple 通過將 WebKit 的 JS 引擎用 OC 封裝握童,提供了一套 JS 運行環(huán)境以及 Native 與 JS 數(shù)據(jù)類型之間的轉(zhuǎn)換橋梁姆怪,常用于 OC 和 JS 代碼之間的相互調(diào)用,這也意味著他可以脫離渲染單獨去執(zhí)行 JS澡绩。

JSCore 主要包括如下這些 classes稽揭、協(xié)議、類結(jié)構(gòu):

<figure>
image

</figure>

JSCore 如何運行呢肥卡?

可以通過如下這張 JSCore 的框架結(jié)構(gòu)圖和上述描述來看懂各個模塊是怎么運行的淀衣。

<figure>
image

</figure>

從上圖我們可以看到一個這樣的過程:

在 Native 應(yīng)用中我們可以開啟多個線程來異步執(zhí)行我們不同的需求,也就意味著我們可創(chuàng)建多個 JSVirtualMachine 虛擬機(運行資源提供者)召调,同時相互隔離不影響膨桥,這樣我們就可以并行地執(zhí)行不同 JS 任務(wù)

在一個 JSVirtualMachine 中還可以關(guān)聯(lián)多個 JSContext (JS 執(zhí)行環(huán)境上下文)唠叛,并通過 JSValue(值對象) 來和 Native 進行數(shù)據(jù)傳遞通信只嚣,同時可以通過 JSExport (協(xié)議) ,將 Native 中遵守此解析的類的方法和屬性轉(zhuǎn)換為 JS 的接口供其調(diào)用艺沼。

JS 和 OC 數(shù)據(jù)類型互換

從上小節(jié)册舞,可以知道 JSValue 可以用來讓 JS 和 OC 之間無障礙的數(shù)據(jù)轉(zhuǎn)換,主要原理是 JSValue 上面提供了如下方法障般,便于雙方各種類型進行轉(zhuǎn)換调鲸。

<figure>
image

</figure>

在 iOS 里面執(zhí)行 JS 代碼

我們可以通過evaluateScript在 JSCore 中執(zhí)行一段 JS 腳本,利用這個特性我們可以來做一些多端邏輯統(tǒng)一的事情挽荡。

// 執(zhí)行一段 JavaScript 腳本
  - (JSValue *)evaluateScript:(NSString *)script;

比如業(yè)務(wù)中 3 端(iOS藐石、Android、H5)有一段相當(dāng)復(fù)雜的但原理一樣算價邏輯定拟,一般做法是 3 端用各自語言自己寫一套于微,這樣做不但麻煩、效率低而且邏輯不一定統(tǒng)一,同時用 OC 去實現(xiàn)復(fù)雜計算邏輯也沒有 JS 這么靈活高效株依。

這里就可以利用執(zhí)行 JS 代碼這個特性驱证,將這個邏輯抽成一個 JS 方法,只需要傳入特定的入?yún)⒘低螅苯臃祷貎r格抹锄,這樣的話,3 端可以同時使用這個邏輯荠藤,還可以放到遠(yuǎn)端進行動態(tài)更新維護伙单。

大概這樣實現(xiàn):

// 在 iOS 里面執(zhí)行 JS
  JSContext *jsContext = [[JSContext alloc] init];
  [jsContext evaluateScript:@"var num = 500"];
  [jsContext evaluateScript:@"var computePrice = function(value)
                             { return value * 2 }"];
  JSValue *value = [jsContext evaluateScript:@"computePrice(num)"];
  int  intVal = [value  toInt32];
  NSLog(@"計算結(jié)果為 %d", intVal);

運算結(jié)果為:

2018-03-16 20:20:28.006282+0800 JSCoreDemo[4858:196086]
========在 iOS 里面執(zhí)行 JS 代碼========
2018-03-16 20:20:28.006517+0800 JSCoreDemo[4858:196086] 
計算結(jié)果為 1000

我認(rèn)為還可以在正則校驗動畫函數(shù)商源、3D 渲染建模等這些數(shù)據(jù)計算方面來使用它车份。

在 iOS 里面調(diào)用 JS 中方法

說完在 iOS 中執(zhí)行 JS 代碼,接下來給大家介紹下牡彻,如何在 iOS 中調(diào)用 H5 中的 JS 方法扫沼。

比如我們 H5 中有一個全局方法叫做 nativeCallJS,我們可以通過執(zhí)行環(huán)境的上下文 jsContext[@"nativeCallJS"] 獲取該方法并進行調(diào)用庄吼,類似這樣:

// Html 中有一個 JS 全局方法
  <script type="text/javascript">
    var nativeCallJS = function (parameter) {
      alert(parameter);
    };
  </script>
// 在 iOS 運行 JS 方法
JSContext *jsContext = [webView valueForKeyPath:@“documentView.webView.mainFrame.javaScriptContext”];
JSValue *jsMethod = jsContext[@"nativeCallJS"];
jsMethod callWithArguments:@[ @"Hello JS, I am iOS" ]];

最終我們的運行結(jié)果就可以看到 Native 執(zhí)行到了 H5 的 Alter 彈層:

<figure>
image

</figure>

利用這個特性我們可以讓 iOS 獲取到一些 H5 的信息來處理一些他想處理的東西缎除,譬如先將信息在全局中暴露出來,通過調(diào)用方法獲取到 使用的版本號总寻、運行的環(huán)境信息器罐、端主動處理邏輯(清除緩存、控制運行)等這些事情渐行。

在 JS 里面調(diào)用 iOS 中方法

其實對于前端同學(xué)使用得最多的應(yīng)該是這個轰坊,通過 JS 調(diào)用端上能力來彌補 H5 上的不足

這里需要和@"documentView.webView.mainFrame.javaScriptContext"這個 webview 相關(guān)特性結(jié)合起來祟印,將 H5 調(diào)用的方法用 Block 以jsCallNative(調(diào)用方法名)為名傳遞給 JSCore 上下文肴沫。

比如我們 H5 中有一個按鈕的點擊回調(diào)是去調(diào)用客戶端的一個方法,并在方法中輸出傳入?yún)?shù)蕴忆,大致是這樣實現(xiàn):

// Html中按鈕點擊調(diào)用一個OC方法
  <button type="button"
      onclick="jsCallNative('Hello iOS', 'I am JS');">調(diào)用OC代碼</button>
//Block 以”jsCallNative"為名傳遞給JavaScript上下文
  JSContext *jsContext = [webView valueForKeyPath:
            @"documentView.webView.mainFrame.javaScriptContext"];
   jsContext[@"jsCallNative"] = ^() {
       NSArray *args = [JSContext currentArguments];
       for (JSValue *obj in args) {
           NSLog(@"%@", obj);
       }
    };

最終輸出是這樣:

2018-03-16 20:51:25.590749+0800 JSCoreDemo[4970:219245] ========在 JS 里面調(diào)用 iOS 中方法========
2018-03-16 20:51:25.591155+0800 JSCoreDemo[4970:219245] Hello iOS
2018-03-16 20:51:25.591370+0800 JSCoreDemo[4970:219245] I am JS

這個特性真正讓 H5 可以享受到很多端上的特性颤芬,比如Native 方式的跳轉(zhuǎn)、Native 底層能力(震動套鹅、錄音站蝠、拍照)、掃碼卓鹿、獲取設(shè)備信息菱魔、分享、設(shè)置導(dǎo)航欄减牺、調(diào)用 Native 封裝組件等這些功能豌习,此處大家可以聯(lián)系 Hybrid 開發(fā)模式存谎。

通過 JSExport 暴露 iOS 方法屬性給 JS

這個特性可能 H5 的同學(xué)不是很清楚拔疚,但是對于 Native 同學(xué)肥隆,我認(rèn)為非常有用。

通過 JSExport 可以很方便地將 iOS 對象的屬性方法暴露給 JS 環(huán)境稚失,讓其使用起來像 JS 對象一樣方便栋艳。

比如我們 OC 中有一個 Person 的類,包含兩個屬性和一個方法句各,此處通過讓fullName方法使用 JSExport 協(xié)議暴露出去吸占,這樣在 JS 中是可以直接去調(diào)用的。

@protocol PersonProtocol <JSExport>
- (NSString *)fullName;
@end

@interface Person : NSObject <PersonProtocol>
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@end

@implementation Person
@synthesize firstName, lastName;
(NSString *)fullName {
  return [NSString stringWithFormat:@"%@ %@",self.firstName, self.lastName];
}
@end
// 通過 JSExport 暴露 iOS 方法屬性給 JS
Person *person = [[Person alloc] init];
jsContext[@"p"] = person;
person.firstName = @"Fei";
person.lastName = @"Zhu";
NSLog(@"========通過 JSExport 暴露 iOS 方法屬性給 JS========");
[jsContext evaluateScript:@"log(p.fullName());"];
[jsContext evaluateScript:@"log(p.firstName);"];

最終運行結(jié)果為:

2018-03-16 20:51:17.437688+0800 JSCoreDemo[4970:219193] ========通過 JSExport 暴露 iOS 方法屬性給 JS========
2018-03-16 20:51:17.438100+0800 JSCoreDemo[4970:219193] Fei Zhu
2018-03-16 20:51:17.438388+0800 JSCoreDemo[4970:219193] undefined

為什么p.firstName運行后是undefined呢凿宾? 因為在這里沒有將其暴露到 Native 環(huán)境中矾屯,所以就獲取不到了。

這里我們可以利用的更多的是在編程便捷性上面初厚,讓 OC 和 JS 直接可以相互調(diào)用件蚕。

在 iOS 里面處理 JS 異常

稍微成熟一點的公司的前端頁面都會有運行異常監(jiān)控系統(tǒng),發(fā)現(xiàn) JS 執(zhí)行異巢蹋可以直接通知開發(fā)以防止線上事故的發(fā)生排作。

通過 JSCore 中的 exceptionHandler 可以很好的解決這個問題,當(dāng) JS 運行異常時候亚情,會回調(diào) JSContext 的 exceptionHandler 中設(shè)置的 Block妄痪,這樣我們可以在 Block 回調(diào)里面將我們的錯誤上傳到監(jiān)控平臺。

比如這個例子楞件,我運行一個返回a+1的函數(shù)衫生,平時我們在 Chrome console 可以看到報錯Can't find variable: a,這里運行也會一樣:

// 當(dāng)JavaScript運行時出現(xiàn)異常
   // 會回調(diào)JSContext的exceptionHandler中設(shè)置的Block  

   JSContext *jsContext = [[JSContext alloc] init];

   jsContext.exceptionHandler = ^(JSContext *context, JSValue  *exception) {
        NSLog(@"JS Error: %@", exception);
    };

  [jsContext evaluateScript:@"(function errTest(){ return a+1; })();"];

最后輸出報錯為:

2018-03-17 11:28:07.248778+0800 JSCoreDemo[15007:632219]
========在iOS里面處理 JS 異常========
2018-03-17 11:28:07.252255+0800 JSCoreDemo[15007:632219] 
JS Error: ReferenceError: Can't find variable: a

JS 和端相互通信

最近給 Weex 提交了一個《More enhanced about <web> component》 的 PR土浸,大概就是利用上述思路罪针,通過實現(xiàn) W3C 的 MessageEvent 規(guī)范來讓組件和 Weex 之間可以進行互相通信,同時通過 loadHTMLString 直接來渲染傳入的 html 源碼功能栅迄。

具體實現(xiàn)為:

<figure>
image

</figure>

具體思路和 Demo 可見 [WEEX-233][iOS]

JSPatch

讓我們更深一步來思考上述思路可否再次進行擴展站故,能否通過 JS 直接來干預(yù) iOS 代碼的運行呢?答案是可以的毅舆,下面我想整理一下我對 JSPatch 的理解西篓。

假如 iOS 想不發(fā)版改 bug

假如線上 APP 有一段代碼出現(xiàn) bug 導(dǎo)致 crash,可能 Native crash 會比 H5 問題嚴(yán)重很多憋活,前者可以立馬發(fā)布岂津,后者可能需要修改好提交 Apple 商店數(shù)日才上線,然后可能更新率還上不去悦即,很麻煩的吮成。

這里就可以通過 JSPatch 這種類似的方案橱乱,下發(fā)一段代碼覆蓋掉原來有問題的方法,這樣可以很快修復(fù)這個 bug粱甫。

可以通過一個簡單的例子來看上述過程泳叠。

用 OC 寫了一個藍色的Hello World, 我們可以通過下發(fā)一段 JS 代碼將原來藍色的字體修改成紅色并修改文字,將 JS 代碼下發(fā)代碼刪除后茶宵,又可以恢復(fù)原來的藍色Hello World

<figure>
image

</figure>

主要代碼大致如下:

// 一段顯示藍色 Hello World 的 OC 代碼
  @implementation ViewController
  - (void)viewDidLoad {
      [super viewDidLoad];
      [self simpleTest];
  }
  - (void)simpleTest {
      self.label.text = @"Hello World";
      self.label.textColor = [UIColor blueColor];
  }
@end

 // 一段符合 JSPatch 規(guī)則的JS覆蓋代碼
 require('UIColor');
 defineClass('ViewController', { simpleTest : function() {
   self.label().setText("你的藍色 Hello World 被我改成紅色了");

   var red = UIColor.redColor();
   self.label().setTextColor(red);
 },
})

這里是如何做到的呢危纫?首先需要介紹下 JSPatch:

JSPatch 是一個 iOS 動態(tài)更新框架,通過引入 JSCore乌庶,就可以使用 JS 調(diào)用任何原生接口种蝶,可以為項目動態(tài)更新模塊、替換原生代碼動態(tài)修復(fù) Bug瞒大。

也即 JS 傳遞字符串給 OC螃征,OC 通過 Runtime 接口調(diào)用和替換 OC 方法。

為什么可以通過 JS 調(diào)用任何原生接口呢透敌? 首先可以了解下 OC 底層 Runtime 的原理盯滚。

Runtime

OC 語言中大概 95% 都是 C 相關(guān)的寫法,為何當(dāng)時蘋果不直接使用 C 來寫 iOS 呢拙泽?其中一個很大的原因就是 OC 的動態(tài)性淌山,有一個很強大的 Runtime (一套 C 語言的 API,底層基于它來實現(xiàn))顾瞻,核心是消息分發(fā)泼疑,Runtime 會根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)

也許上述會比較生澀荷荤,簡單說就是OC 方法的實現(xiàn)和調(diào)用指針的關(guān)系是在運行時才決定的退渗,而非編譯期,這樣的話蕴纳,我們可以在運行期做些事情更改原來的實現(xiàn)会油,達到熱修復(fù)的目的。

OC 方法的調(diào)用不像 JS 這種語言古毛,直接array.push(foo)函數(shù)調(diào)用即可翻翩,他是通過消息機制來進行調(diào)用的,比如如下這個將foo插入到數(shù)組中的第 5 位:

[array insertObject:foo atIndex:5];

在底層比這個實現(xiàn)更加生澀稻薇,他通過objc_msgSend這個方法將消息搭配選擇器進行發(fā)送出去:

objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

運行時發(fā)消息給對象嫂冻,消息是如何映射到方法的 ?

簡單來說就是塞椎,一個對象的 class 保存了方法列表桨仿,是一個字典,key 為 selectors案狠,IMPs 為 value服傍,一個 IMP 是指向方法在內(nèi)存中的實現(xiàn)钱雷,selector 和 IMP 之間的關(guān)系是在運行時才決定的,非編譯時吹零。

- (id)doSomethingWithInt:(int)aInt{}
id doSomethingWithInt(id self, SEL _cmd, int aInt){}

通過看了下 Runtime 的源碼罩抗,發(fā)現(xiàn)有如下這些常用的方法:

<figure>
image

</figure>

通過上述這些方法就可以做很多意想不到的事情,比如動態(tài)的變量控制瘪校、動態(tài)給一個對象增加方法澄暮、可以把消息轉(zhuǎn)發(fā)給想要的對象名段、甚至可以動態(tài)交換兩個方法的實現(xiàn)阱扬。

JSPatch && Runtime

正是由于 OC 語言的動態(tài)性,上所有方法的調(diào)用/類的生成都通過 OC Runtime 在運行時進行伸辟,可通過類名稱和方法名的字符串獲取該類和該方法麻惶,并實例化和調(diào)用:

Class class = NSClassFromString("UIViewController");
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString("viewDidLoad");
[viewController performSelector:selector];

也可以替換某個類的方法為新的實現(xiàn):

static void newViewDidLoad(id slf, SEL sel) {}
class_replaceMethod(class, selector, newViewDidLoad, @"");

還可以新注冊一個類,為類添加方法

Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);

JSPatch 正是利用如上這些好的特性來實現(xiàn)他的熱修復(fù)功能信夫。

JSPatch 中 JS 如何調(diào)用 OC

此處 JSPatch 中的 JS 是如何和任意修改 OC 代碼聯(lián)系起來的呢窃蹋?大概原理如下:

1.JSPatch 在實現(xiàn)中是通過 Require 調(diào)用,在 JS 全局作用域上創(chuàng)建一個同名變量静稻,變量指向一個對象警没,對象屬性 __clsName 保存類名,同時表明這個對象是一個 OC Class振湾,通過調(diào)用require(“UIView")杀迹,我們就可以使用UIView去調(diào)用他上面對應(yīng)方法了。

UIView = require(“UIView");

var _require = function(clsName) {
  if (!global[clsName]) {
    global[clsName] = {__clsName: clsName}
  }
  return global[clsName]
}

2.在 JSCore 執(zhí)行前押搪,OC 方法的調(diào)用均通過新增 Object(JS) 原型方法__c(methodName)完成調(diào)用树酪,假如直接調(diào)用的話,需要 JS 遍歷當(dāng)前類的所有方法大州,還要循環(huán)找父類的方法直到頂層续语,無疑是很耗時的,通過__c()元函數(shù)的唯一性厦画,可以每次調(diào)用它時候疮茄,轉(zhuǎn)發(fā)給一個指定函數(shù)去執(zhí)行,就很優(yōu)雅了根暑。

<figure>
image

</figure>

Object.prototype.__c = function(methodName) {
return function(){
  var args = Array.prototype.slice.call(arguments)
  return _methodFunc(self.__obj, self.__clsName, methodName,
    args, self.__isSuper)
  }
  }

3.處理好 JS 接口問題后力试,接下來只需要借助前面 JSCore 的知識就可以做到 JS 和 OC 之間的消息互傳了,也即在在_c 函數(shù)中通過 JSContex 建立的橋接函數(shù)购裙,用 Runtime 接口調(diào)用相應(yīng)方法完成調(diào)用

  • JS 中的轉(zhuǎn)發(fā)
var _methodFunc = function(instance, clsName, methodName, args,  isSuper) {
     ....
     var ret =  _OC_callC(clsName, selectorName, args)
     return _formatOCToJS(ret)
  }

  • OC 中的處理
context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) {
  return callSelector(className, selectorName, arguments, nil, NO);
 };

感觸

上面大概就是如何通過 JS 任意修改 OC 運行結(jié)果的一個原理懂版,雖然 JSPatch 大部分功能被蘋果禁用了,但是其中 JS 操作 OC 的思路真的很棒躏率。

《iOS 中的 JS》 其實是在上周團隊象聲匯的一個分享躯畴,將其進行整理成文章民鼓,分享給對跨端感興趣的同學(xué),同時盡量用接地氣的方式蓬抄,希望只有客戶端基礎(chǔ)或者前端基礎(chǔ)的同學(xué)也可以看懂丰嘉,同時也可以 Clone 上文中 Demo 源碼 來進行測試。

由于我還處在 iOS 補基礎(chǔ)階段嚷缭,可能有些地方理解不到位饮亏,歡迎一起討論。
轉(zhuǎn)載:
https://zhuanlan.zhihu.com/p/34646281

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阅爽,一起剝皮案震驚了整個濱河市路幸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌付翁,老刑警劉巖简肴,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異百侧,居然都是意外死亡砰识,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門佣渴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辫狼,“玉大人,你說我怎么就攤上這事辛润∨虼Γ” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵频蛔,是天一觀的道長灵迫。 經(jīng)常有香客問我,道長晦溪,這世上最難降的妖魔是什么瀑粥? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮三圆,結(jié)果婚禮上狞换,老公的妹妹穿的比我還像新娘。我一直安慰自己舟肉,他們只是感情好修噪,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著路媚,像睡著了一般黄琼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上整慎,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天脏款,我揣著相機與錄音围苫,去河邊找鬼。 笑死撤师,一個胖子當(dāng)著我的面吹牛剂府,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播剃盾,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼腺占,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了痒谴?” 一聲冷哼從身側(cè)響起衰伯,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闰歪,沒想到半個月后嚎研,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡库倘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了论矾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片教翩。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贪壳,靈堂內(nèi)的尸體忽然破棺而出饱亿,到底是詐尸還是另有隱情,我是刑警寧澤闰靴,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布彪笼,位于F島的核電站,受9級特大地震影響蚂且,放射性物質(zhì)發(fā)生泄漏配猫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一杏死、第九天 我趴在偏房一處隱蔽的房頂上張望泵肄。 院中可真熱鬧,春花似錦淑翼、人聲如沸腐巢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冯丙。三九已至,卻和暖如春遭京,著一層夾襖步出監(jiān)牢的瞬間胃惜,已是汗流浹背风宁。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛹疯,地道東北人戒财。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像捺弦,于是被迫代替她去往敵國和親饮寞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

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

  • 用到的組件 1列吼、通過CocoaPods安裝 2幽崩、第三方類庫安裝 3、第三方服務(wù) 友盟社會化分享組件 友盟用戶反饋 ...
    SunnyLeong閱讀 14,625評論 1 180
  • 今天心情有點煩躁寞钥,可能是看了產(chǎn)婦跳樓的新聞慌申,突然有點后怕,生孩子到底有多痛理郑,上次檢查是臀位蹄溉,之前一直都是頭位,可能...
    小佳小小閱讀 132評論 0 0
  • 秋雨過后您炉,樹葉兒慢慢地黃了柒爵,花草逐漸凋零,惟有傲霜的菊花被秋雨喚醒赚爵。一盆盆形態(tài)各異的菊花不時從庭院探出頭來棉胀,金黃的...
    清逸幽蘭閱讀 875評論 2 7
  • 斯里蘭卡,一個小島國家冀膝,印度洋上的一滴眼淚唁奢,熱帶雨林氣候,風(fēng)景很好窝剖,溫度其實并沒有我想象中的那么高麻掸,但是太陽卻是很...
    我愛大大大太陽_aaf5閱讀 265評論 0 0