OC與JS交互

一、iOS7 之前

1. OC 調(diào)用 JS

// 在 iOS7 之前,OC 調(diào)用 JS 只有一種方法,使用 UIWebView 的 stringByEvaluatingJavaScriptFromString:,因?yàn)樯婕暗?UI 更新,所以該方法只能在主線程中執(zhí)行肪笋,另外, stringByEvaluatingJavaScriptFromString 是同步執(zhí)行 JS 代碼度迂,即會(huì)阻塞到該 JS 執(zhí)行完畢藤乙,才繼續(xù)執(zhí)行接下來(lái)的代碼。

dispatch_async(dispatch_get_main_queue(), ^{
    NSString *jsString = [NSString stringWithFormat:@"alert(\"提示彈框\")"];
    [webView stringByEvaluatingJavaScriptFromString:jsString];
});

2. JS 調(diào)用 OC

// 在 iOS7 之前惭墓,JS 調(diào)用 OC 主要是通過(guò)攔截 URL 請(qǐng)求坛梁,即 JS 發(fā)送一個(gè)偽 URL 請(qǐng)求,通過(guò) webView 的代理方法進(jìn)行監(jiān)聽(tīng)腊凶,根據(jù) JS 與 OC 約定好的協(xié)議進(jìn)行攔截划咐,然后根據(jù) URL 中的 path、query 等進(jìn)行相應(yīng)的處理钧萍。

// 主要通過(guò) UIWebViewDelegate 中的 webView:shouldStartLoadWithRequest:navigationType: 方法攔截

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
    if([request.URL.scheme isEqualToString:@"js2oc"]) {
        // oc 進(jìn)行相應(yīng)的處理操作
        return NO;
    }
    return YES;
}

二褐缠、iOS7 之后 (JavaScriptCore)

參考

iOS7 之后,蘋果官方引入了 JavaScriptCore 框架风瘦,使得 OC 可以在脫離 webView 的情況下直接運(yùn)行 JS队魏,而且,可以插入自定義 OC 對(duì)象到 JavaScript 環(huán)境中。

JavaScriptCore 框架中主要有以下幾個(gè)類:

JSContext: 主要提供在 OC 中執(zhí)行 Java Script 代碼的環(huán)境胡桨,管理 Java Script Object 生命周期官帘,每個(gè) JSValue 都與 JSContext 強(qiáng)關(guān)聯(lián),只要 JSValue 存在昧谊,JSContext 就保持引用刽虹,知道所有 JSValue 都被釋放,JSContext 才有可能被釋放呢诬。 一個(gè) JSContext 是一個(gè)全局環(huán)境的實(shí)例涌哲。

JSValue: 是 JS value(JS 變量和方法) 的封裝,主要用于 JS 對(duì)象 與 OC 對(duì)象互相轉(zhuǎn)換尚镰。每個(gè) JSValue 都和 JSContext 相關(guān)聯(lián)并且強(qiáng)引用 JSContext阀圾。

JSManagedValue: 是 JS 和 OC 對(duì)象的內(nèi)存管理輔助對(duì)象,主要用來(lái)保存 JSValue钓猬,從而解決 OC 對(duì)象存儲(chǔ) JSValue 導(dǎo)致循環(huán)引用問(wèn)題。JS 內(nèi)存管理是垃圾回收機(jī)制撩独,其中所有對(duì)象都是強(qiáng)引用敞曹,但是我們不必?fù)?dān)心循環(huán)引用,因?yàn)槔厥諘?huì)打破這種強(qiáng)引用综膀;OC 是引用計(jì)數(shù)機(jī)制澳迫。JSValue 強(qiáng)引用相關(guān) JSContext,把 OC 暴露給 JSContext剧劝,JSContext 強(qiáng)引用 OC橄登,如果 OC 再?gòu)?qiáng)引用 JSValue 對(duì)象,就會(huì)導(dǎo)致循環(huán)引用讥此,JSContext 釋放不了拢锹,內(nèi)存泄漏。

為了解決 OC 與 JSValue 和 JSContext 的循環(huán)引用萄喳,引入了 JSManagedValue卒稳。

NSManagedValue *managedValue = [NSManagedValue managedValueWithValue:jsValue];
// managedValue 相當(dāng)于弱引用 jsValue,如果 jsValue 指向 JSVirtualMachine 中 javascript value 被垃圾回收機(jī)制回收他巨,jsValue 會(huì)自動(dòng)設(shè)為 nil充坑。
[jsVirtualMachine addManagedReference:managedValue withOwner:self];
// 該方法將原生的引用來(lái)告知 jsVirtualMachine,只要這種引用鏈存在染突,jsVirtualMachine 就不會(huì)對(duì) managedValue.value 指向的 java script value 進(jìn)行垃圾回收捻爷。
[jsVirtualMachine removeManagedReference:managedValue withOwner:self];
// 該方法在 jsVirtualMachine 中去除原生引用鏈,然后 java script value 就可能會(huì)被垃圾回收份企。

JSVirtualMachine: JS 運(yùn)行的虛擬機(jī)也榄,有獨(dú)立的堆空間和垃圾回收機(jī)制。主要用于多線程并發(fā)執(zhí)行 JS 及 JS 與 OC 之間的內(nèi)存管理司志。

每個(gè) JSContext 屬于一個(gè) JSVirtualMachine手蝎,每個(gè) JSVirtualMachine 包含多個(gè) JSContext榕莺,所以 屬于同一個(gè) JSVirtualMachine 的 JSContext 可以互相傳值,因?yàn)楣灿孟嗤亩褩?媒椋煌?JSVirtualMachine 之間不能互相傳值钉鸯。

如果想并發(fā)執(zhí)行 JS,需要采用多個(gè) JSVirtualMachine邮辽,每個(gè) JSVirtualMachine 對(duì)應(yīng)一個(gè)線程唠雕,同一個(gè) JSVirtualMachine 中,只能串行執(zhí)行 JS吨述,當(dāng)執(zhí)行一個(gè) JS 時(shí)岩睁,其他的需要等待。

JSExport: 是一個(gè)協(xié)議揣云,這個(gè)協(xié)議將原生對(duì)象的屬性捕儒、方法暴露給 JavaScript,使得 JavaScript 可以直接調(diào)用 OC 對(duì)象的方法邓夕、屬性刘莹。遵守 JSExport 協(xié)議,就可以定義我們自己的協(xié)議焚刚,在協(xié)議中聲明的 API 都會(huì)暴露在 JS 中点弯。
如果 JS 想調(diào)用 OC 對(duì)象的方法,只要使 OC 對(duì)象實(shí)現(xiàn)這個(gè)協(xié)議矿咕,并且將這個(gè) OC 對(duì)象實(shí)例綁定到 JS抢肛。

1. 利用 JSContext 和 JSValue 實(shí)現(xiàn) JS 與 OC 交互

HTML

<html>
    <head>
        <title>JS_OC</title>
    </head>
    <body>
    <h1>發(fā)送偽URL請(qǐng)求</h1>
        <div style="margin-top: 10px">
            <input type="button" value="Call OC With URL" onclick="callOC()">
        </div>
    <h3> JS Call OC Wth JavaScriptCore</h3>
        <div style="margin-top: 20px">
            <input type="button" value="Call OC System Camera" onclick="callOCSystemCamera()">
        </div>
        <div style="margin-top: 10px">
            <input type="button" value="Call OC Alert" onclick="showOCAlertMsg('js title','js msg')">
        </div>
    </body>
    <script>
        function callOC(){
            window.location.href = 'js2oc://callOC?p1=1&p2=2';
        }
    </script>
    <script type="text/javascript">
        function showJSAlertMsg(msg){
            alert(msg);
        }
    </script>
</html>

UIWebView 加載完成后,獲取 JS 的運(yùn)行運(yùn)行環(huán)境 - JSContext碳柱。

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
}

OC 調(diào)用 JS

JSValue *jsValue = [self.jsContext evaluateScript:@"oc_call_js_func"];
[jsValue callWithArguments:@[args,...]];

JS 調(diào)用 OC

// 即為 JS 調(diào)用 OC 的函數(shù)指定相應(yīng)的 block
self.jsContext[@"js_call_oc_func"] = ^(args,...){
    // 主線程執(zhí)行 native UI 操作
}

2. 利用 JSExport 實(shí)現(xiàn) JS 與 OC 交互

HTML

<html>
    <head>
        <title>JS_OC</title>
    </head>
    <body>
    <h1>發(fā)送偽URL請(qǐng)求</h1>
        <div style="margin-top: 10px">
            <input type="button" value="Call OC With URL" onclick="callOC()">
        </div>
    <h3> JS Call OC Wth JavaScriptCore</h3>
        <div style="margin-top: 20px">
            <input type="button" value="Call OC System Camera" onclick="OCModel.callOCSystemCamera()">
        </div>
        <div style="margin-top: 10px">
            <input type="button" value="Call OC Alert" onclick="OCModel.showOCAlertMsg('js title','js msg')">
        </div>
    </body>
    <script>
        function callOC(){
            window.location.href = 'js2oc://callOC?p1=1&p2=2';
        }
    </script>
    <script type="text/javascript">
        function showJSAlertMsg(msg){
            alert(msg);
        }
    </script>
</html>

由 HTML 文件可以看出來(lái)捡絮,JS 不是直接調(diào)用某一方法,而是調(diào)用某個(gè)對(duì)象 OCModel 的方法莲镣,只要?jiǎng)?chuàng)建一個(gè) OC 對(duì)象 OCModel 并讓他實(shí)現(xiàn) JS 要調(diào)用的方法锦援,然后將它綁定到 JS 即可。

聲明一個(gè) JSExport 協(xié)議剥悟,并在其中聲明 JS 調(diào)用 OC 的那些方法:

#import <JavaScriptCore/JavaScriptCore.h>

@protocol JSExportProtocol <JSExport>

- (void)callOCSystemCamera;
- (void)showOCAlertMsg:(NSString *)msg;

@end

指定類實(shí)現(xiàn)上面聲明的協(xié)議:

@interface OCModel : NSObject <JSExportProtocol>

@end

@implementaion OCModel

- (void)callOCSystemCamera {
    // 主線程操作
}

- (void)showOCAlertMsg:(NSString *)msg {
    // 主線程操作
}

@end

將上述類實(shí)例綁定到 JSContext 中:

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    JSContext *jsContext. = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    jsContext[@"OCModel"] = [OCModel new];
}

然后 JS 就可以通過(guò) JSExport 協(xié)議調(diào)用 OC 的方法了灵寺。

注:JavaScriptCore 中,JS 是在子線程中調(diào)用 OC 方法区岗,如果 OC 方法中有 UI 相關(guān)操作略板,需要在主線程中執(zhí)行。
用 JavaScriptCore 進(jìn)行 OC 與 JS 交互慈缔,又一個(gè)顯著的缺點(diǎn):只有 html 加載完畢后叮称,OC 才能調(diào)用 JS 成功

三、WKWebView

iOS8以后,蘋果推出了新框架 WebKit瓤檐,提供了替換 UIWebView 的組件 WKWebView赂韵。WKWebView 在性能、穩(wěn)定性和功能方面都有很大的提升挠蛉,最顯著的優(yōu)點(diǎn)就是占用的內(nèi)存大幅減少祭示。

WebKit 將 UIWebView 和 UIWebViewDelegate 重構(gòu)為14個(gè)類和3個(gè)協(xié)議。具體參考

WKWebView: 用于顯示 web 內(nèi)容谴古。

WKWebViewConfiguration: 用于在初始化 WKWebView 時(shí)质涛,指定其設(shè)置信息。

WKPreferences: 指定 WKWebView 的偏好設(shè)置掰担。

WKScriptMessage: WKWebView 向 native 發(fā)送的消息汇陆。

WKUserScript: 注入 web view 的用戶腳本。

WKUserContentController: 主要用于向 web view 注入腳本和指定 web view 發(fā)送消息的接收處理(指定 JS 調(diào)用 OC 的實(shí)現(xiàn)代碼)带饱。

UINavigation: 加載 web view 時(shí)返回的對(duì)象毡代,主要用于跟蹤 web view 加載進(jìn)程。

WKProcessPool勺疼、WKBackForwardList教寂、WKBackForwardListItem等。

WKNavigationDelegate: 協(xié)議恢口,主要用于處理 web view 的加載和跳轉(zhuǎn)孝宗。

WKUIDelegate: 協(xié)議穷躁,主要用于處理 JS 腳本耕肩,以及將 JS 的確認(rèn)、警告等對(duì)話框用 native 表示问潭。

WKScriptMessageHandler: 協(xié)議猿诸,主要用于接收、處理 web view 發(fā)送的消息狡忙。

1. 創(chuàng)建 WKWebView

// 初始化配置對(duì)象
WKWebviewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// 初始化偏好設(shè)置
config.preferences = [[WKPreferences alloc] init];
// 指定最小字體梳虽,默認(rèn)是 0
config.preferences.minimumFontSize = 10;
// 是否支持 javascript
config.preferences.javaScriptEnable = YES;
// javascript 不通過(guò)用戶交互是否可以自動(dòng)打開(kāi)窗口
config.preferences.javaScriptCanOpenWindowsAutomatically = YES;
// 創(chuàng)建 web view
WXWebView *webView = [[WKWebView alloc] initWithFrame:frame configuration:config];
webView.navigationDelegate = self;
webView.UIDelegate = self;
[webView loadRequest:urlRequest];
// 向 web view 中注入用戶腳本,可以通過(guò)該方法將 native 中的方法轉(zhuǎn)換為 JS 函數(shù)灾茁,比如窜觉,獲取 app 版本號(hào)等。
[webView.config.userContentController addUserScript:userScript];
// 指定 web view 發(fā)送消息的接收者(要及時(shí)執(zhí)行 removeScriptMessageHandler:name 方法移除接收者北专,否者會(huì)循環(huán)引用而內(nèi)存泄漏)
[webView.config.userContentController addScriptMessageHandler:self name:@"msgName"];
[self.view addSubview:webView];

2. JS 調(diào)用 OC

WKWebView 主要通過(guò)向 native 發(fā)送消息來(lái)調(diào)用 native 方法禀挫, native 根據(jù)接收到的消息進(jìn)行相應(yīng)的處理

// WKWebView 中 JS 發(fā)送消息
function clickAction() {
    window.webkit.messageHandlers.msgName.postMessage(messageBody);
}

// native 主要通過(guò) WKScriptMessageHandler 協(xié)議來(lái)接收消息,并進(jìn)行處理
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if([message.name isEqualToString:@"msgName"]) {
        // native action
    }else {
        // ...
    }
}

3. OC 調(diào)用 JS

[webView evaluateJavaScript:jsString completionHandler^(id result, NSError *error){
    // ...
}];
// 使用該方法執(zhí)行 JS 腳本拓颓,或者直接執(zhí)行 webView 暴露出來(lái)的全局函數(shù)语婴,通常是后者。

4. WKUIDelegate 協(xié)議實(shí)現(xiàn)

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void(^)(void))completionHandler {
    // 使用 UIAlertViewController 將 JS Alert 轉(zhuǎn)換為 native alert
}

- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
    // 將 JS 確認(rèn)框轉(zhuǎn)換為 native 框。
}

//...其他的協(xié)議方法

5. WKNavigationDelegate 協(xié)議實(shí)現(xiàn)

// web view 開(kāi)始接收 web content 時(shí)調(diào)用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;

// 開(kāi)始加載 web content 時(shí)調(diào)用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation; 

// 當(dāng)需要進(jìn)行 server 重定向時(shí)調(diào)用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;

// 當(dāng) web 需要進(jìn)行驗(yàn)證時(shí)調(diào)用
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

// web view 跳轉(zhuǎn)失敗時(shí)調(diào)用
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation 
  withError:(NSError *)error;
  
// web view 加載失敗時(shí)調(diào)用 
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation 
  withError:(NSError *)error;
  
// web view 跳轉(zhuǎn)結(jié)束時(shí)調(diào)用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;

// web view 處理終止時(shí)調(diào)用
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView;

// web view 是否允許跳轉(zhuǎn)砰左,比如點(diǎn)擊某個(gè)超鏈接時(shí)觸發(fā)匿醒,可以根據(jù)情況允許或者取消
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

// 已經(jīng)知道響應(yīng)結(jié)果,是否允許跳轉(zhuǎn)
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;

使用 WKWebView 問(wèn)題及解決方案

四缠导、第三方庫(kù)(WebViewJavascriptBridge)

Github地址

WebViewJavascriptBridge 也是通過(guò) URL 攔截來(lái)實(shí)現(xiàn) JS 與 OC 的交互廉羔,而且同時(shí)支持 UIWebView、WKWebView酬核。

優(yōu)點(diǎn):

html 加載時(shí)蜜另,只要 JS 代碼被運(yùn)行就可以進(jìn)行交互,不需等待 html 加載完畢才能交互嫡意。

iOS 與 Android 都有一套對(duì)應(yīng)的庫(kù)举瑰,這樣 H5 只需要統(tǒng)一一套就行了。

缺點(diǎn):

需要在 html 中加入固定的 JS 代碼片段蔬螟。

1. JS 處理

主要包括兩個(gè)部分此迅,固定聲明代碼、注冊(cè) OC 需要調(diào)用的 JS 函數(shù) 和 JS 調(diào)用 OC 方法入口聲明旧巾。

<!-- 聲明交互 固定代碼 -->
function setupWebViewJavascriptBridge(callback) {
    if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
    if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
    window.WVJBCallbacks = [callback];
    var WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    WVJBIframe.src = 'https://__bridge_loaded__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

<!-- 處理交互  方法名要和 iOS 內(nèi)定義的對(duì)應(yīng) -->
setupWebViewJavascriptBridge(function(bridge) {

    <!-- 注冊(cè) OC 調(diào)用的 JS 函數(shù) -->
    bridge.registerHandler('OC2JS', function(data, responseCallback) {
        //處理 OC 給的傳參
        alert('OC 請(qǐng)求 JS  傳值參數(shù)是:'+data)                               
        var responseData = { 'result':'handle success' }
        // 將處理結(jié)果回傳給 OC
        responseCallback(responseData)
    })

    var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
    callbackButton.innerHTML = '點(diǎn)擊我耸序,我會(huì)調(diào)用 OC 的方法'
    callbackButton.onclick = function(e) {
        e.preventDefault()                                 
        <!--JS 調(diào)用 OC -->
        bridge.callHandler('loginAction', {'userId':'zhangsan','name': 'HeHe'}, function(response) {
             // 處理 OC 回傳的數(shù)據(jù)
             alert('收到 OC 的回調(diào):'+response)
        })
    }
})

2. OC 處理

OC 中主要也是注冊(cè) JS 調(diào)用的 OC 方法,和 聲明 OC 調(diào)用 JS 方法入口鲁猩。

_bridge = [WebViewJavascriptBridge bridgeForWebView:_webView];
[_bridge setWebViewDelegate:self];

// 聲明 JS 調(diào)用的 OC 方法
[_bridge.registerHandler:@"JS2OC" handler:^(id data, WVJBResponseCallback responseCallback){坎怪、
    // 對(duì) JS 傳過(guò)來(lái)的 data 進(jìn)行處理
    // 將處理結(jié)果回傳給 JS
    responseCallback(data);
}];

// 調(diào)用 JS
_bridge.callHandler:@"OC2JS" data:nil responseCallback:^(id responseData) {
    // 處理 JS 回傳數(shù)據(jù)
}

3. WebViewJavascriptBridge 實(shí)現(xiàn)原理

分別在 OC 環(huán)境和 JS 環(huán)境都保存一個(gè) bridge 對(duì)象,里面維持著 requestId廓握、callbackId 以及每個(gè)Id對(duì)應(yīng)的具體實(shí)現(xiàn)搅窿。

OC 通過(guò) JS 環(huán)境的 window.WebViewJavascriptBridge 對(duì)象找到具體的方法,然后執(zhí)行隙券。

JS 通過(guò)改變 iframe 的 src 來(lái)喚起 webview 的代理方法 webView:(WKWebView* )webView decidePolicyForNavigationAction:(WKNavigationAction* )navigationAcion decisionHandler:(void(^)(WKNavigationActionPolicy))decisionHandler 或者 UIWebView 對(duì)應(yīng)的代理方法男应,從而實(shí)現(xiàn)把 JS 消息發(fā)送給 OC。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末娱仔,一起剝皮案震驚了整個(gè)濱河市沐飘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牲迫,老刑警劉巖耐朴,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異盹憎,居然都是意外死亡筛峭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門脚乡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蜒滩,“玉大人滨达,你說(shuō)我怎么就攤上這事「┘瑁” “怎么了捡遍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)竹握。 經(jīng)常有香客問(wèn)我画株,道長(zhǎng),這世上最難降的妖魔是什么啦辐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任谓传,我火速辦了婚禮,結(jié)果婚禮上芹关,老公的妹妹穿的比我還像新娘续挟。我一直安慰自己,他們只是感情好侥衬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布诗祸。 她就那樣靜靜地躺著,像睡著了一般轴总。 火紅的嫁衣襯著肌膚如雪直颅。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天怀樟,我揣著相機(jī)與錄音功偿,去河邊找鬼。 笑死往堡,一個(gè)胖子當(dāng)著我的面吹牛械荷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播投蝉,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼养葵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼征堪!你這毒婦竟也來(lái)了瘩缆?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤佃蚜,失蹤者是張志新(化名)和其女友劉穎庸娱,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體谐算,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熟尉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洲脂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斤儿。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剧包,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出往果,到底是詐尸還是另有隱情疆液,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布陕贮,位于F島的核電站堕油,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏肮之。R本人自食惡果不足惜掉缺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望戈擒。 院中可真熱鬧眶明,春花似錦、人聲如沸筐高。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凯傲。三九已至犬辰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冰单,已是汗流浹背幌缝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诫欠,地道東北人涵卵。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像荒叼,于是被迫代替她去往敵國(guó)和親轿偎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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