剖析OC與JS交互

隨著H5技術的興起阅束,在iOS開發(fā)過程中,難免會遇到原生應用需要和H5頁面交互的問題股毫。其中會涉及方法調用及參數(shù)傳值等場景茴丰。

iOS原生應用和web頁面的交互大致上有這幾種方法:
1)URL攔截協(xié)議;(兼容iOS6及以下時可考慮姐刁,本文介紹)
2)第三方框架WebViewJavaScriptBridge芥牌;(本文不介紹)
3)iOS7之后的JavaScriptCore;(推薦聂使、本文重點介紹)
4)iOS8之后的WKWebView壁拉;(強烈推薦、本文重點介紹)

從使用場景上可分為OC調用JS柏靶、JS調用OC兩種形式弃理,下面分別討論之:

一、OC調用JS

JS 調用OC 方法后宿礁,有的操作可能需要將結果返回給JS案铺。這時候就是OC 調用JS 方法的場景。

方式一:使用UIWebView的方法

- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')",@"這是JS中alert彈出的message"];
[_webView stringByEvaluatingJavaScriptFromString:jsStr];
注意:這是一同步方法梆靖,可能會阻塞UI控汉,如果要執(zhí)行的js方法比較耗時,會造成界面卡頓返吻,比如姑子,js彈出alert后會阻塞UI界面,等待用戶操作響應测僵,而同時stringByEvaluatingJavaScriptFromString方法也會等待js執(zhí)行完畢后返回街佑。這就造成了死鎖谢翎。官方推薦使用WKWebView的evaluateJavaScript:completionHandler:代替這個方法。

還比如UIWebView的一些常用操作:

// 獲取當前頁面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"];
// 獲取當前頁面的url
NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];

方式二沐旨、使用JavaScriptCore的方法(iOS7.0之后)

1森逮、使用JSContent的方法:- (JSValue *)evaluateScript:(NSString *)script;

JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *textJS = @"showAlert('這是JS中alert彈出的message')";
[context evaluateScript:textJS];
//帶參數(shù)
NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];[[JSContext currentContext] evaluateScript:jsStr];

2、使用JSValue的方法:- (JSValue *)callWithArguments:(NSArray *)arguments;

JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];[context[@"payResult"] callWithArguments:@[@"支付成功"]];


方式三磁携、使用WKWebView(iOS8之后)

使用WKWebView的evaluateJavaScript:completionHandler:方法褒侧,解決了使用UIWebView的stringByEvaluatingJavaScriptFromString方法易造成死鎖的問題。(官方推薦)

// 將分享結果返回給js
NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
? ? ?NSLog(@"%@----%@",result, error);
}];

說明:evaluateJavaScript:completionHandler:沒有返回值谊迄,JS 執(zhí)行成功還是失敗都會在completionHandler 中返回闷供。所以使用這個API 就可以避免執(zhí)行耗時的JS,或者alert 導致界面卡住的問題统诺。

二歪脏、JS調用OC

方式一、攔截URL

使用攔截URL方式又可細分為使用UIWebView攔截URL方式和使用WKWebView攔截URL方式粮呢。

1)使用UIWebView攔截URL:

UIWebView的代理方法shouldStartLoadWithRequest會攔截到每一個鏈接的Request婿失。當return YES時webView 就會加載這個鏈接;return NO時webView 就不會加載這個鏈接鬼贱,我們可以在該方法中根據(jù)scheme做不同處理移怯。

相關測試js代碼如下:(僅截取部分核心代碼)

function loadURL(url) {
var iFrame;
iFrame = document.createElement("iframe");
iFrame.setAttribute("src", url);
iFrame.setAttribute("style", "display:none;");
iFrame.setAttribute("height", "0px");
iFrame.setAttribute("width", "0px");
iFrame.setAttribute("frameborder", "0");
document.body.appendChild(iFrame);
// 發(fā)起請求后這個iFrame就沒用了,所以把它從dom上移除掉
iFrame.parentNode.removeChild(iFrame);
iFrame = null;
}
function payClick() {
loadURL("haleyAction://payAction?order_no=112233445566&channel=chris&amount=99&subject=測試支付");
}
function payResult(pay_no,pay_channel,pay_amount,pay_subject) {
var content = pay_no + ", " + pay_channel + ", " + pay_amount + ", " + pay_subject ;
asyncAlert(content);
document.getElementById("returnValue").value = content;
}
//設置一延遲方法这难,解決stringByEvaluatingJavaScriptFromString同步死鎖
function asyncAlert(content) {
setTimeout(function(){
alert(content);
},1);
}
<input type="button" value = "支付" onclick = "payClick()" />

在UIWebView的代理方法中舟误,攔截URL通過scheme做不同處理:

#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *URL = request.URL;
NSString *scheme = [URL scheme];
if ([scheme isEqualToString:@"haleyaction"]) {
[self handleCustomAction:URL];
return NO;
}
return YES;
}

在此處,根據(jù)URL的不同host做響應不同處理姻乓。

- (void)handleCustomAction:(NSURL *)URL
{
NSString *host = [URL host];
if ([host isEqualToString:@"scanClick"]) {
? ? ?NSLog(@"掃一掃");
}??else if ([host isEqualToString:@"payAction"]) {
? ? [self payAction:URL];
}?
}

OC中的處理嵌溢,并將處理結果回傳至JS中(OC執(zhí)行js方法,以參數(shù)形式回傳結果)蹋岩。

- (void)payAction:(NSURL *)URL
{
NSArray *params =[URL.query componentsSeparatedByString:@"&"];
NSMutableDictionary *tempDic = [NSMutableDictionary dictionary];
for (NSString *paramStr in params) {
NSArray *dicArray = [paramStr componentsSeparatedByString:@"="];
if (dicArray.count > 1) {
NSString *decodeValue = [dicArray[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[tempDic setObject:decodeValue forKey:dicArray[0]];
}
}
NSString *orderNo = [tempDic objectForKey:@"order_no"];
long long amount = [[tempDic objectForKey:@"amount"] longLongValue];
NSString *subject = [tempDic objectForKey:@"subject"];
NSString *channel = [tempDic objectForKey:@"channel"];
// 支付操作
// 將支付結果返回給js(參數(shù)拼接赖草,當參數(shù)不是字符串時,不要加單引號'',如amount)
NSString *jsStr = [NSString stringWithFormat:@"payResult('%@', '%@', %lu, '%@')", orderNo, channel, (unsigned long)amount, subject];}
[self.webView stringByEvaluatingJavaScriptFromString:jsStr];
}

附注:js調用OC方法需要傳參數(shù)時剪个,可使用如下方法:

js中代碼:
function shareClick() {
loadURL("haleyAction://shareClick?title=測試分享的標題&content=測試分享的內容&url=http://www.baidu.com");
}

在OC方法中獲取參數(shù)秧骑,因所有的參數(shù)都在URL的query中,可先通過&將字符串拆分扣囊,再通過=把參數(shù)拆分成key和value乎折。

- (void)share:(NSURL *)URL
{
NSArray *params =[URL.query componentsSeparatedByString:@"&"];
NSMutableDictionary *tempDic = [NSMutableDictionary dictionary];
for (NSString *paramStr in params) {
? ? ? ?NSArray *dicArray = [paramStr componentsSeparatedByString:@"="];
? ? ? ?if (dicArray.count > 1) {
? ? ? ? ? ? NSString *decodeValue = [dicArray[1]
? ? ? ? ? ? ? ?stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
? ? ? ? ? ?[tempDic setObject:decodeValue forKey:dicArray[0]];
? ? ? ?}
}
NSString *title = [tempDic objectForKey:@"title"];
NSString *content = [tempDic objectForKey:@"content"];
NSString *url = [tempDic objectForKey:@"url"];
// 在這里執(zhí)行分享的操作
// 將分享結果返回給js
NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
[self.webView stringByEvaluatingJavaScriptFromString:jsStr];
}

附注:以下代碼可以往HMTL的JS環(huán)境中插入全局變量、JS方法等
[webView stringByEvaluatingJavaScriptFromString:@"var arr = [3, 4, 'abc'];"];

2)使用WKWebView攔截URL(iOS8)(但更推薦使用下面介紹的方式三)

通過WKWebView的代理(WKNavigationDelegate)方法實現(xiàn)攔截URL(需要實現(xiàn)WKNavigationDelegate代理協(xié)議):

#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *URL = navigationAction.request.URL;
NSString *scheme = [URL scheme];
if ([scheme isEqualToString:@"haleyaction"]) {
? ? ?[self handleCustomAction:URL];
? ? ?decisionHandler(WKNavigationActionPolicyCancel);
? ? ?return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}

說明:WKNavigationActionPolicyCancel代表取消加載侵歇,相當于UIWebView的代理方法return NO的情況骂澄;WKNavigationActionPolicyAllow代表允許加載,相當于UIWebView的代理方法中 return YES的情況惕虑。
關于[self handleCustomAction:URL];的具體實現(xiàn)同上面使用UIWebView攔截URL的情形坟冲,此不贅述磨镶。

注意:在WKWebView中要使用alert等彈窗操作,需要實現(xiàn)代理WKUIDelegate中相應方法健提,自己定義彈窗琳猫,否則alert彈不出。

#pragma mark - WKUIDelegate
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提醒" message:message preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(); ?//此block一定得調用矩桂,否則alert彈不出沸移,調用位置隨意
}]];
[self presentViewController:alert animated:YES completion:nil];
}

方式二、使用JavaScriptCore

JavaScriptCore是iOS7開始引入的js框架侄榴。JavaScriptCore中主要的類有5個,下面分別介紹:1网沾、JSContext:
JSContext是為JavaScript的執(zhí)行提供運行環(huán)境癞蚕,所有的JavaScript的執(zhí)行都必須在JSContext環(huán)境中。常見的方法有:

//創(chuàng)建JSContext
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 運行一段js代碼辉哥,輸出結果為JSValue類型
- (JSValue *)evaluateScript:(NSString *)script;
// 獲取當前正在運行的JavaScript上下文環(huán)境
+ (JSContext *)currentContext;
// 獲取當前被調用方法的參數(shù)
+ (NSArray *)currentArguments;

2桦山、JSValue:
JavaScript中的變量和方法。JSValue都是通過JSContext返回或者創(chuàng)建的醋旦,并沒有構造方法恒水。JSValue包含了每一個JavaScript類型的值,通過JSValue可以將Objective-C中的類型轉換為JavaScript中的類型饲齐,也可以將JavaScript中的類型轉換為Objective-C中的類型钉凌。JSValue可以說是JavaScript和Object-C之間互換的橋梁。每個JSValue都和JSContext相關聯(lián)并且強引用context捂人,當與某JSContext對象關聯(lián)的所有JSValue釋放后御雕,JSContext也會被釋放。

//示例:在OC中往JS環(huán)境中添加一個變量(如一數(shù)組arr)
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[context evaluateScript:@"var arr = [3, 4, 'abc'];"];
//取出arr變量
JSValue *jsArr = context[@"arr"];
//轉換成NSArray
NSArray *nsArr=[jsArr toArray];
NSLog(@"NSArray: %@", nsArr);
//常見其他轉換方法
- (int32_t)toInt32;
- (NSArray *)toArray;
- (NSString *)toString;

3滥搭、JSVirtualMachine:
JS運行的虛擬機酸纲,代表一個獨立的JavaScript對象空間,并為其執(zhí)行提供資源瑟匆。

4闽坡、JSManagedValue:
JS和OC對象的內存管理輔助對象,主要用來保存JSValue對象愁溜,解決OC對象中存儲js的值導致的循環(huán)引用問題疾嗅。

5、JSExport:
JS調用OC中的方法和屬性都寫在繼承自JSExport的協(xié)議當中祝谚,如此一來宪迟,這些方法和屬性會自動提供給JavaScript。

讓JSContext訪問我們的本地OC代碼的方式主要有兩種:block 和JSExport協(xié)議交惯,以下主要介紹block方式次泽。當一個 Objective-C block 被賦給JSContext里的一個標識符(如下面的@"share")穿仪,JavaScriptCore 會自動的把 block 封裝在 JavaScript 函數(shù)里。這使得在 JavaScript 中可以簡單的使用 Foundation 和 Cocoa 類意荤,所有的橋接都為你做好了啊片。

JS調用OC示例代碼:

//html中代碼變得更簡潔
function shareClick() {
? ? ? ?share('測試分享的標題','測試分享的內容','url=http://www.baidu.com');
}

在UIWebView代理方法中- (void)webViewDidFinishLoad:(UIWebView *)webView添加JS要調用的OC方法:

#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSLog(@"webViewDidFinishLoad");
[self addCustomActions]; ? //js要調用的oc方法
}

oc方法代碼:

- (void)addCustomActions
{
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[self addShareWithContext:context];
[self addPayActionWithContext:context];
//其他js要調用的方法...
}

具體實現(xiàn):(模塊獨立開,方便清晰定位)

- (void)addShareWithContext:(JSContext *)context {
__weak typeof(self) weakSelf = self;
context[@"share"] = ^() {
? ? NSArray *args = [JSContext currentArguments];
? ? if (args.count < 3) {
? ? return ;
? ? }
? ? NSString *title = [args[0] toString];
? ? NSString *content = [args[1] toString];
? ? NSString *url = [args[2] toString];
? ? // 在這里執(zhí)行分享的操作...
? ? // 將分享結果返回給js
? ? NSString *jsStr = [NSString ? ? ?stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
? ? [[JSContext currentContext] evaluateScript:jsStr];
? };
}

注意:
1)獲取js運行環(huán)境JSContext的時機:一般在webViewDidFinishLoad中獲取

self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

2)線程問題:js調用OC代碼玖像,是在子線程紫谷。而更新OC中的UI(如彈出OC原生Alert),需要回到主線程捐寥。
3)循環(huán)引用問題:無論是把Block傳給JSContext對象讓其變成JavaScript方法笤昨,還是把它賦給exceptionHandler屬性,在Block內都不要直接使用其外部定義的JSContext對象或者JSValue握恳,應該將其當做參數(shù)傳入到Block中瞒窒,或者通過JSContext的類方法+ (JSContext *)currentContext;來獲得。否則會造成循環(huán)引用使得內存無法被正確釋放乡洼。

附加:
1)通過JSValue獲取JavaScript對象上的屬性崇裁。

JSValue會自動延展數(shù)組大小。并且通過JSValue還可以獲取JavaScript對象上的屬性束昵,比如例子中通過"length"就獲取到了JavaScript數(shù)組arr的長度(元素個數(shù))拔稳。

JSContext*context=[[JSContext alloc]init];
[context evaluateScript:@"var arr = [21, 7 , 'iderzheng.com'];"];
JSValue*jsArr=context[@"arr"];? // Get array from JSContext
NSLog(@"JS Array: %@;? ? Length: %@", jsArr, jsArr[@"length"]);
jsArr[1]=@"blog";? // Use JSValue as array
jsArr[7]=@7;
NSLog(@"JS Array: %@;? ? Length: %d", jsArr,[jsArr[@"length"]toInt32]);
NSArray *nsArr=[jsArr toArray];
NSLog(@"NSArray: %@", nsArr);
//Output:
//? JS Array: 21,7,iderzheng.com? ? Length: 3
//? JS Array: 21,blog,iderzheng.com,,,,,7? ? Length: 8
//? NSArray: (
//? 21,
//? blog,
//? "iderzheng.com",
//? "<null>",
//? "<null>",
//? "<null>",
//? "<null>",
//? 7
//? )

2)異常處理

在JSContext中執(zhí)行的JavaScript如果出現(xiàn)異常,只會被JSContext捕獲并存儲在exception屬性上锹雏,而不會向外拋出巴比。正確做法是:給JSContext對象設置exceptionHandler屬性,它接受的是^(JSContext *context, JSValue *exceptionValue)形式的Block逼侦。其默認值就是將傳入的exceptionValue賦給傳入的context的exception屬性匿辩。

JSContext *jsContext = [[JSContext alloc] init];
//捕獲運行js腳本的錯誤信息
jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
/*此處exceptionValue賦值給傳入的context的exception屬性,而不是賦給外部創(chuàng)建的 ?jsContext對象榛丢,這樣做是為了避免循環(huán)引用*/
? ? ? context.exception = exceptionValue;?
? ? ? NSLog(@"異常信息:%@", exceptionValue);
};


方式三铲球、使用WKWebView的addScriptMessageHandler

WKWebView是iOS8開始引入的UIWebView的增強版,無論是內存晰赞、性能還是使用靈活性上都要優(yōu)越于UIWebView稼病,是官方推出的UIWebView的替代品。

WKWebView->configuration(WKWebViewConfiguration)->userContentController(WKUserContentController)->addScriptMessageHandler:name:

WKWebView正是使用addScriptMessageHandler實現(xiàn)js調用原生OC方法掖鱼。而要使用此功能然走,必須實現(xiàn)WKScriptMessageHandler協(xié)議。該協(xié)議僅有一個@required方法:
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
其中戏挡,message.name即為消息發(fā)送者芍瑞,以指示發(fā)送的是哪個消息,可理解為js要調的oc方法名稱褐墅。
1)創(chuàng)建WKWebView:

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKPreferences *preferences = [WKPreferences new];
preferences.javaScriptCanOpenWindowsAutomatically = YES;
preferences.minimumFontSize = 40.0;
configuration.preferences = preferences;
self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];

2)并配置js調用的oc方法(addScriptMessageHandler:name:)

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"Share"];
}

為避免循環(huán)引用導致的控制器不能釋放問題拆檬,一般的洪己,在viewWillDisappear方法中需要移除相應的scriptMessageHandler:

- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// 移除scriptMessageHandler
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"Share"];
}

3)實現(xiàn)WKScriptMessageHandler協(xié)議方法

#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if ([message.name isEqualToString:@"Share"]) {
? ? ?[self shareWithParams:message.body]; //js需要傳參數(shù)給oc
} else if ([message.name isEqualToString:@"Location"]) {
? ? ?[self getLocation]; ?//無參
}
}

說明:可根據(jù)message.name 來區(qū)分js要執(zhí)行的不同oc方法,而message.body 中存放著JS 要給OC 傳的參數(shù)竟贯。注意message.body允許的參數(shù)類型為:
Allowed types are ?NSNumber,NSString,NSDate,NSArray,NSDictionary, and?NSNull.

4)解析參數(shù)答捕,oc方法的具體實現(xiàn)

- (void)shareWithParams:(NSDictionary *)tempDic
{
if (![tempDic isKindOfClass:[NSDictionary class]]) {
? ? ?return;
}
NSString *title = [tempDic objectForKey:@"title"];
NSString *content = [tempDic objectForKey:@"content"];
NSString *url = [tempDic objectForKey:@"url"];
// 在這里執(zhí)行分享的操作
// 將分享結果返回給js
NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
? ? ? NSLog(@"%@----%@",result, error);
}];
}

5)js中的代碼

<input type = "button" value = "分享" onclick = "shareClick()" />
function shareClick() {
//此處傳遞的參數(shù)是一字典
window.webkit.messageHandlers.Share.postMessage({title:'測試分享的標題',content:'測試分享的內容',url:'http://m.rblcmall.com/share/openShare.htm?share_uuid=shdfxdfdsfsdfs&share_url=http://m.rblcmall.com/store_index_32787.htm&imagePath=http://c.hiphotos.baidu.com/image/pic/item/f3d3572c11dfa9ec78e256df60d0f703908fc12e.jpg'});
}

附加:WKWebView提供了estimatedProgress屬性代表當前網(wǎng)頁加載進度,可通過KVO監(jiān)聽此屬性屑那,實現(xiàn)進度計算拱镐。示例代碼如下:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == self.webView && [keyPath isEqualToString:@"estimatedProgress"]) {
CGFloat newprogress = [[change objectForKey:NSKeyValueChangeNewKey] doubleValue];
if (newprogress == 1) {
? ? [self.progressView setProgress:1.0 animated:YES];
? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * ? ? ? ?NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
? ? self.progressView.hidden = YES;
? ? [self.progressView setProgress:0 animated:NO];
});
}else {
? ? self.progressView.hidden = NO;
? ? [self.progressView setProgress:newprogress animated:YES];
}
}
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市持际,隨后出現(xiàn)的幾起案子沃琅,更是在濱河造成了極大的恐慌,老刑警劉巖选酗,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阵难,死亡現(xiàn)場離奇詭異,居然都是意外死亡芒填,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門空繁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來殿衰,“玉大人,你說我怎么就攤上這事盛泡∶葡椋” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵傲诵,是天一觀的道長凯砍。 經(jīng)常有香客問我,道長拴竹,這世上最難降的妖魔是什么悟衩? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮栓拜,結果婚禮上座泳,老公的妹妹穿的比我還像新娘。我一直安慰自己幕与,他們只是感情好挑势,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啦鸣,像睡著了一般潮饱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诫给,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天香拉,我揣著相機與錄音啦扬,去河邊找鬼。 笑死缕溉,一個胖子當著我的面吹牛考传,可吹牛的內容都是我干的。 我是一名探鬼主播证鸥,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼僚楞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枉层?” 一聲冷哼從身側響起泉褐,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鸟蜡,沒想到半個月后膜赃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡揉忘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年跳座,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泣矛。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡疲眷,死狀恐怖,靈堂內的尸體忽然破棺而出您朽,到底是詐尸還是另有隱情狂丝,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布哗总,位于F島的核電站几颜,受9級特大地震影響,放射性物質發(fā)生泄漏讯屈。R本人自食惡果不足惜蛋哭,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耻煤。 院中可真熱鬧具壮,春花似錦、人聲如沸哈蝇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炮赦。三九已至怜跑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背性芬。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工峡眶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人植锉。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓辫樱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俊庇。 傳聞我的和親對象是個殘疾皇子狮暑,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內容