[iOS] 七七八八的小姿势(4)

目录:

  1. Web和客户端如何交互
  2. DeepLink
  3. Https抓包内容可见控制
  4. KVOController的简易使用
  5. 一道偶尔看到的面试题
  6. TCP优化
  7. 重复点击问题

1. Web和客户端如何交互

首先如何自己搭建一个本地网页:https://blog.csdn.net/u011456337/article/details/50704331/

关于如何实现web调用iOS以及iOS调用web可以参考:https://blog.csdn.net/dolacmeng/article/details/79623708

大概实现方式有四种:

  1. 拦截url(适用于UIWebView和WKWebView)
  2. JavaScriptCore(只适用于UIWebView,iOS7+)
  3. WKScriptMessageHandler(只适用于WKWebView,iOS8+)
  4. WebViewJavascriptBridge(适用于UIWebView和WKWebView,属于第三方框架)

鉴于WKWebView比UIWebView好很多,并且我们项目中用的是WKScriptMessageHandler,这里就只演示第三种啦~ 关于WKWebView的使用可以参考https://blog.csdn.net/u013983033/article/details/84027078

下面正式搞起来~ 先按照上面的方式自己搭一个node服务器,只要有一个页面就行,下面是index.html文件,放入webapp文件夹下:

<!DOCTYPE html>
<html>
<head>
<script>
function changePtext() {
    document.getElementById("demo").innerHTML = "Changed";
}

function sendRequestToIOS() {
    window.webkit.messageHandlers.changeText.postMessage(null);
}
</script>
</head>

<body>
<h2>Head JavaScript</h2>
<p id="demo">A paragraph</p>
<button type="button" onclick="sendRequestToIOS()">Try change text</button>
</body>
</html>

然后就可以打开页面:http://localhost:3000/index.html啦~

主页

这里的window.webkit.messageHandlers.changeText.postMessage(null);其实就是web调用了手机侧的changeText方法,注意哦,这里的postMessage(null)如果没有参数也必须写null,不可以postMessage()哦!

然后客户端需要一个webview以及处理js调用客户端的方法:

#import <WebKit/WebKit.h>

#import "WebInteractionViewController.h"

@interface WebInteractionViewController () <WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler>

@property (strong, nonatomic) WKWebView *webView;

@end

@implementation WebInteractionViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSString * urlS = [NSString stringWithFormat:@"http://127.0.0.1:3000/index.html"];
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlS]]];
}

- (WKWebView *)webView {
    if (!_webView) {
         // 进行配置控制器
        WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
         // 实例化对象
        configuration.userContentController = [WKUserContentController new];
         // 调用JS方法
        [configuration.userContentController addScriptMessageHandler:self name:@"changeText"];
        // 进行偏好设置
        WKPreferences *preferences = [WKPreferences new];
        preferences.javaScriptEnabled = YES;
        preferences.javaScriptCanOpenWindowsAutomatically = YES;
        preferences.minimumFontSize = 40.0;
        configuration.preferences = preferences;
    
        _webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
        _webView.navigationDelegate = self;
        _webView.opaque = NO;
        _webView.backgroundColor = [UIColor whiteColor];
        if (@available(ios 11.0,*)){ _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;}
        [self.view addSubview:_webView];
     }
     return _webView;
}

#pragma mark - WKScriptMessageHandler

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"changeText"]) {
        [self.webView evaluateJavaScript:@"changePtext()" completionHandler:nil];
        return;
    }
}

#pragma mark - WKNavigationDelegate

// 页面开始加载时调用
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
    NSLog(@"页面开始加载时调用");
    
}

// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation{
    NSLog(@"当内容开始返回时调用");
}

// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{//这里修改导航栏的标题,动态改变
    NSLog(@"页面加载完成之后调用");
    
    
}
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation{
    NSLog(@"页面加载失败时调用");
}

// 接收到服务器跳转请求之后再执行
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation{
    NSLog(@"接收到服务器跳转请求之后再执行");
}

// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    NSLog(@"在收到响应后,决定是否跳转");
    NSLog(@"%@",navigationResponse);
    WKNavigationResponsePolicy actionPolicy = WKNavigationResponsePolicyAllow;
    //这句是必须加上的,不然会异常
    decisionHandler(actionPolicy);
}

// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    NSLog(@"在发送请求之前,决定是否跳转");
    decisionHandler(WKNavigationActionPolicyAllow); // 必须实现 加载
}

#pragma mark - WKUIDelegate

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
}

//弹出一个输入框(与JS交互的)
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler{
}

@end

代码中的[configuration.userContentController addScriptMessageHandler:self name:@"changeText"];就是设置了当web调用了手机侧的changeText方法的时候,self会去处理,处理的方式就是WKScriptMessageHandler的- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message方法会被调用。

didReceiveScriptMessage里面我们可以拿到message也就是调用的方法名,然后通过判断不同的方法名执行不同的操作,这个例子里面我让收到changeText以后就调用web的方法,通过最简单的[self.webView evaluateJavaScript:@"changePtext()" completionHandler:nil];即可。

上面就实现了点击一下网页中的button,button会触发发changeText消息给客户端,客户端收到以后调用了webview的jschangePtext()来改变文字,注意调用JS的时候也要写括号吼~


2. DeepLink

Deeplink,简单讲,就是你在手机上点击一个链接之后,可以直接链接到app内部的某个页面,而不是app正常打开时显示的首页。

这项技术主要是为了方便广告跳转而产生的,最大的例子就是淘宝天猫京东等购物APP。在第三方APP中点击广告链接直接跳转到对应的客户端的商品的详情中。

可参考:https://blog.csdn.net/Keep_Dream/article/details/56842806

① 如何打开别人的客户端

这里先以taobao为例看如何做到点击自己的app里面的一个按钮打开淘宝吧~

首先你需要在阿里的平台注册为开发者,并且添加你自己的应用,拿到API Key。https://console.baichuan.taobao.com/applications.htm?spm=0.0.0.0#create

API Key

然后需要在客户端加两个配置,缺一不可哦!首先在如下位置添加 URL Type:

image

其中 identifier 写为 taobao 字样(自定义),URL Scheme 中填写的格式是 tbopen{AppKey},就是在阿里百川上申请的 App 对应的 AppKey。

由于 iOS 限制了APP打开类型,所以需要在 info.plist 中添加LSApplicationQueriesSchemes,在其中添加和 URL Types 中一致的 taobao 字符串即可,需要其中的注意元素类型:

image

然后就可以写代码啦~

- (IBAction)turnToTaobao:(id)sender {
    NSString *urlString = @"taobao://s.taobao.com/?q=iphone";
    if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:urlString]]) {
        //若安装了需要跳转的app->跳转到APP
        NSURL * url = [NSURL URLWithString:[urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
        [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
    }else{
        //若未安装需要跳转的app->跳转到APP的下载界面,这里用了淘宝ipad哈
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"itms-apps://itunes.apple.com/cn/app/appname/id438865278"] options:@{} completionHandler:nil];
        //或者直接显示web端的页面
    }
}
② 如何让其他app跳转至自己app

可参考:http://www.cocoachina.com/articles/31815

这个就比较简单啦,我们只要给自己的app配置一下url schema:


设置schema

这里identifier需要填写bundle id,然后schema就是我们打开app的前缀,例如taobao://,可以自定义的。

然后在safari里面打开[schema://identifier] 即可跳转至我们的app,例如ex1://xxx.Example1

并且在app被打开的时候,会回调:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    NSLog(@"openUrl: %@", url);
    return YES;
}

在app被打开的时候就会输出:

2020-01-12 15:19:45.547004+0800 Example1[3277:626851] openUrl: ex1://xxx.Example1

我们也就可以通过检查url来打开不同的页面啦~

最后有篇木有看懂但感觉很厉害的文章也推荐一下~//www.greatytc.com/p/43f8a81dd8ca


3. Https抓包内容可见控制

可参考://www.greatytc.com/p/833c560a8470//www.greatytc.com/p/4682aecf162d//www.greatytc.com/p/23545f8d36d2

关于Charles如何实现抓包的可以参考我之前的网络篇~ 今天只是聊一下关于Https内容加密。

按理说如果使用Charles可以抓到所有HTTP的请求,但这周测试小帅哥问我为什么xcode的debug包可以,但release包就不能看到请求内容,而Jenkins的release包可以呢?

后来优秀的小哥哥在Jenkins上找到了一段代码类似:

# 禁用SSL Pinning
perl -i -pe 's/shouldConfigPinnedCertificatesForRequest\:\(Request \*\)request {/shouldConfigPinnedCertificatesForRequest\:\(Request \*\)request { return NO;/' Source/……/NetworkSecurityPolicyPlugin.m

这里其实就是替换了NetworkSecurityPolicyPlugin.m文件里面的shouldConfigPinnedCertificatesForRequest,关于perl可以参考:https://blog.csdn.net/sdustliyang/article/details/7578730

所以原来Jenkins的release包和Xcode的shouldConfigPinnedCertificatesForRequest的实现是不同的,然后我看了下shouldConfigPinnedCertificatesForRequest的调用,如果设为NO,request.securityPolicy = [AFSecurityPolicy defaultPolicy];

+ (instancetype)defaultPolicy {
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    securityPolicy.SSLPinningMode = AFSSLPinningModeNone;

    return securityPolicy;
}

那么SSLPinningMode是啥呢?

SSL Pinning,即SSL证书绑定。通过SSL证书绑定来验证服务器身份,防止应用被抓包。

AFSecurityPolicy是AFNetworking中网络通信安全策略模块。它提供三种SSL Pinning Mode:

 enum {
 AFSSLPinningModeNone,
 AFSSLPinningModePublicKey,
 AFSSLPinningModeCertificate,
 }

 `AFSSLPinningModeNone`
 Do not used pinned certificates to validate servers.

 `AFSSLPinningModePublicKey`
 Validate host certificates against public keys of pinned certificates.

 `AFSSLPinningModeCertificate`
 Validate host certificates against pinned certificates.

判断证书是不是要信任就是下图紫色的部分,所以如果设置AFSSLPinningMode为AFSSLPinningModeNone,客户端就会信任Charles的证书;反正如果不是none,那么会和本地的公钥对比(AFSSLPinningModePublicKey)或者全部对比(AFSSLPinningModeCertificate),此时就使得Charles的假证书没有被信任,于是也就无法解析加密请求了哦。

Charles抓https原理

4. KVOController的简易使用

我之前用KVOController的时候为了保证可以持续监听,就把KVOController作为一个属性存给了VC,如果没有强引用其实监听不会被触发哦!

但其实FB提供了一个category专门用于获取KVOController的NSObject+FBKVOController,所以我们可以用category替代属性KVOController:

#import <Foundation/Foundation.h>

#import "FBKVOController.h"

@interface NSObject (FBKVOController)

@property (nonatomic, strong) FBKVOController *KVOController;

@property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;

@end

它提供懒加载的FBKVOController,并且还提供了KVOControllerNonRetaining,这个controller是没有强持有被观察者的,防止被观察者自身持有controller,controller又持有了被观察者形成了retain cycle。

它里面的实现就是通过init传入retainObserved为no来做的:

- (FBKVOController *)KVOControllerNonRetaining
{
  id controller = objc_getAssociatedObject(self, NSObjectKVOControllerNonRetainingKey);
  
  if (nil == controller) {
    controller = [[FBKVOController alloc] initWithObserver:self retainObserved:NO];
    self.KVOControllerNonRetaining = controller;
  }
  
  return controller;
}

虽然KVOControllerNonRetaining能够对observe中存入的参数弱引用来打破循环引用,但是自动解除观察者这个特性却变得无法实现。因为KVOController的MapTable弱引用observe,而弱引用的指针,会在dealloc方法走到时,已经变成nil。

即便我们在dealloc方法里面,使用[self.KVOControllerNonRetaining unobserveAll]; 依旧会崩溃,因为 unobserveAll也是去MapTable寻找保存的信息来做移除,弱引用的指针已经被释放,所以无法移除任何KVO。

上面这段是之前小哥哥给我看的为啥不能self.KVOControllerNonRetaining然后不removeObserver,但是对象的dealloc方法也就是我们覆写的部分其实是先于析构和清weak执行的,在我们的dealloc方法里面其实weak表啥的还没清呢哦,所以上面的part我是有一点疑惑的欢迎探讨


5. 一道偶尔看到的面试题

可参考://www.greatytc.com/p/f2a1518a42b8

题目是酱紫的:

@interface FJFPerson : NSObject
// name
@property (nonatomic, copy) NSString *name;
- (void)print;
@end

@implementation FJFPerson
- (void)print {
    NSLog(@"my name is %@", self.name);
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    id cls = [FJFPerson class];
    void *obj = &cls;
    [(__bridge id)obj print];
}

打印出来是神马呢?

my name is <WebInteractionViewController: 0x7fd91d70f5f0>

还记得NSObject的结构咩~ NSObject持有一个isa指针,指向它的class结构体。而上面的[FJFPerson class]就是class结构体,cls是指向[FJFPerson class]的指针,而obj是指向cls的指针。

有木有感觉和NSObject对象的指向方式很相似:


image

其实obj其实就是类似我们init alloc创建出来的对象指针,所以当我们调用[(__bridge id)obj print]的时候其实就是调用了对象的print方法,所以上面的代码不会crash也不会compile error。

然后就是为什么print出来的是vc了?

将print加一下self以及name的地址来看下:

@implementation FJFPerson
- (void)print {
    NSLog(@"self: %p", self);
    NSLog(@"self.name: %p", &_name);
    NSLog(@"my name is %@", self.name);
}
@end

输出:
2020-01-11 20:36:14.977915+0800 Example1[20066:923821] self: 0x7ffee3e83fd8
2020-01-11 20:36:14.978034+0800 Example1[20066:923821] self.name: 0x7ffee3e83fe0

所以指向_name的指针的地址 - 8 = self的地址。然后我们尝试改一下viewDidLoad的代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSString *str = @"11111";
    NSLog(@"str: %p", &str);
    
    id cls = [FJFPerson class];
    void *obj = &cls;
    [(__bridge id)obj print];
    
    NSLog(@"cls: %p", &cls);
    NSLog(@"obj: %p", &obj);
}

输出:
2020-01-11 20:58:27.162163+0800 Example1[20519:988180] str: 0x7ffedfd48fd8
2020-01-11 20:58:27.162467+0800 Example1[20519:988180] my name is 11111
2020-01-11 20:58:27.162522+0800 Example1[20519:988180] cls: 0x7ffedfd48fd0
2020-01-11 20:58:27.162596+0800 Example1[20519:988180] obj: 0x7ffedfd48fc8

注意哦,cls打印出来的是这个指针指向的地方,而&cls才是这个指针存在了哪里。字符串是类似的,str是这个字符串字面量的地址,而&str才是指向这个字面量的指针保存的地址。

有木有发现很神奇,str指针的地址 - 8 = cls指针保存的地址,并且cls指针的地址 - 8 = obj指针保存的地址。所以打印的时候会打出比cls指针+8的地址的指针所指向的地方,也就是str(11111)。

其实这个是因为函数调用采用栈的形式,栈的地址是从高地址到低地址。

所以其实这个问题最后打印出来的是VC就是因为VC恰好是比cls高8位地址,具体上面的文章分析了一下汇编代码,总结了下面的图,虽然高低顺序反了,地址也错了但是大意是对的QAQ:

image

6. TCP优化

可参考:https://blog.csdn.net/fred1653/article/details/51689617/

TCP三次握手完成后,客户端与服务器就可以通信了。客户端在发送ACK分组后就可以立即发送数据,服务器则必须等待接收到ACK分组后才能够发送数据。

三次握手带来的延迟使得每次创建一个新TCP连接都要付出巨大代价,所以这里是提升TCP应用性能的关键。

Google研究发现TCP三次握手是页面延迟时间的重要组成部分,所以他们提出了TFO:在TCP握手期间交换数据,这样可以减少一次RTT。根据测试数据,TFO可以减少15%的HTTP传输延迟,全页面的下载时间平均节省10%,最高可达40%。

TCP Fast Open 简称 TFO,其目的是缩短 TCP 三次握手的时间。通过加入 cookie,在握手阶段就可以传输数据包,从而将三次握手的延时降低到最低。比较适用于网络延时比较长的场景。参考://www.greatytc.com/p/24bcaa99bb02

  • 首次请求
    客户端发送 syn,并且字段里面请求 cookie (tfo request)
    服务端发送 syn+ack以及cookie
    客户端保存cookie并发送ack
    客户端发送数据

  • 后续请求
    客户端发送 syn、数据以及 cookie
    服务端验证 cookie 并发送 syn+ack
    服务端不必等客户端ack开始发送数据(在TFO cookie超时之前)
    客户端发送 ack


7. 重复点击问题

如果想让button在一定time(例如5秒)时间内不论调用多少次,只执行一次肿么破呢?其实就是用户可能连续点一个button,被触发好几次就不好了~

可以参考:https://blog.csdn.net/erice_e/article/details/72858720

  • 方案一:用bool判断是不是已经做完事情了

在点击以后先判断当前是不是已经做完了任务,如果木有就return,如果已经做完就可以开始下一次的干活,然后干完活以后设置bool为已经做完了的状态。

- (IBAction)buttonClicked:(UIbutton *)sender {
  if (doingSomeThing) return;

  doingSomeThing = YES;
  //doSomething
  doingSomeThing = NO;
}

但这种对于push一个页面那种会比较麻烦,需要下一个页面出现以后设置上一个页面的flag。

  • 方案二:delay几秒以后再还原flag
- (IBAction)buttonClicked:(UIbutton *)sender {
  if (doingSomeThing) return;

  doingSomeThing = YES;
  //doSomething

  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    doingSomeThing = NO;
  });
}

如果我们希望所有点击事件都可以有这样的特性,不用单独给每个click写这样的方法,就可以通过swizzle来tricky一下的hook系统方法~

  • 方案三:每次点击的时候delay执行任务,并先取消之前安排触发的任务
- (void)addaction{
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(doSomething) object:nil];
    [self performSelector:@selector(doSomething) withObject:nil afterDelay:5];
}

- (void)doSomething{
}

这样做的问题是,用户点击以后5秒才会真的执行操作,并且如果中间又点击了,这个时间会再往后拖5秒。

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

推荐阅读更多精彩内容

  • 1、WKWebView 白屏问题WKWebView 自诩拥有更快的加载速度,更低的内存占用,但实际上 WKWebV...
    无名感恩阅读 2,135评论 0 3
  • WKWebView 是苹果在 WWDC 2014 上推出的新一代 webView 组件,用以替代 UIKit 中笨...
    Aiana阅读 4,570评论 1 8
  • 两列布局(左边固定,右边自适应) html结构 1.浮动 inline-block布局 绝对定位 表格布局 弹性盒...
    consolelog阅读 630评论 0 0
  • 草綠松林茂,山高人影疏。都知美景自然殊,虛擬畫難如。� 霞斷千峰立,煙橫萬壑初。不來始信信終無,風暖雨清徐。
    圓月阅读 282评论 5 3
  • 有时候会觉得自己很烦,觉得现在自己所处的状态不是自己想要的,一整天昏昏沉沉,没有追求,不知道自己真正想要的是什么,...
    星星星星星欣阅读 153评论 0 0