diff --git a/README.md b/README.md
index 6a66a06..568a2ef 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,67 @@
# iOSDevelopmentTips
- iOS开发笔记
- 1. [《iOS9适配系列教程》](https://github.com/ChenYilong/iOS9AdaptationTips)
- 2. [《iOS10适配系列教程》]( https://github.com/ChenYilong/iOS10AdaptationTips )
- 2. [《iOS面试题集锦》](https://github.com/ChenYilong/iOSInterviewQuestions)
- 3. [《Parse源码学习》]( https://github.com/ChenYilong/ParseSourceCodeStudy )
- 4. [《HTTP状态码汇总》](https://github.com/ChenYilong/iOSBlog/blob/master/Tips/HTTP状态码汇总.md)
- 5. [《大话Socket》](https://github.com/ChenYilong/iOSBlog/blob/master/Tips/大话Socket.md)
- 6. [《IM 即时通讯技术在多应用场景下的技术实现,以及性能调优(iOS视角)》]( https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/IM%20即时通讯技术在多应用场景下的技术实现,以及性能调优(iOS视角).md )
+--------------------------------------------
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+--------------------------------------------
+
+
+
+
+
+ 1. [《iOS9适配系列教程》](https://github.com/ChenYilong/iOS9AdaptationTips)
+ 2. [《iOS10适配系列教程》]( https://github.com/ChenYilong/iOS10AdaptationTips )
+ 3. [《iOS11适配系列教程》](https://github.com/ChenYilong/iOS11AdaptationTips)
+ 4. [《iOS面试题集锦》](https://github.com/ChenYilong/iOSInterviewQuestions)
+ 5. [《Parse源码学习》]( https://github.com/ChenYilong/ParseSourceCodeStudy )
+ 6. [《HTTP状态码汇总》](https://github.com/ChenYilong/iOSBlog/issues/3)
+ 7. [《大话Socket》](https://github.com/ChenYilong/iOSBlog/issues/5)
+ 8. [《IM 即时通讯技术在多应用场景下的技术实现,以及性能调优(iOS视角)》]( https://github.com/ChenYilong/iOSBlog/issues/6)
+ 9. [《基于WebRTC的视频聊天技术在iOS端的实现》](https://github.com/ChenYilong/WebRTC)
+ 10. [《使用 Heap-Stack Dance 替代 Weak-Strong Dance,优雅避开循环引用》](https://github.com/ChenYilong/iOSBlog/issues/4)
+ 11. [《避免使用 GCD Global 队列创建 Runloop 常驻线程》](https://github.com/ChenYilong/iOSBlog/issues/9)
+ 12. [《iOS 常见耗电量检测方案调研》]( https://github.com/ChenYilong/iOSBlog/issues/10 )
+
----------
Posted by [微博@iOS程序犭袁](http://weibo.com/luohanchenyilong/)
原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | [Creative Commons BY-NC-ND 3.0](http://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)
+
+
diff --git "a/Tips/DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224/iOS\351\230\262DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224---Cookie\344\270\232\345\212\241\345\234\272\346\231\257.md" "b/Tips/DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224/iOS\351\230\262DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224---Cookie\344\270\232\345\212\241\345\234\272\346\231\257.md"
new file mode 100644
index 0000000..bd2c433
--- /dev/null
+++ "b/Tips/DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224/iOS\351\230\262DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224---Cookie\344\270\232\345\212\241\345\234\272\346\231\257.md"
@@ -0,0 +1,399 @@
+
+
+
+- [iOS 防 DNS 污染方案调研 --- Cookie 业务场景](#ios-%E9%98%B2-dns-%E6%B1%A1%E6%9F%93%E6%96%B9%E6%A1%88%E8%B0%83%E7%A0%94-----cookie-%E4%B8%9A%E5%8A%A1%E5%9C%BA%E6%99%AF)
+ - [概述](#%E6%A6%82%E8%BF%B0)
+ - [WKWebView 使用 NSURLProtocol 拦截请求无法获取 Cookie 信息](#wkwebview-%E4%BD%BF%E7%94%A8-nsurlprotocol-%E6%8B%A6%E6%88%AA%E8%AF%B7%E6%B1%82%E6%97%A0%E6%B3%95%E8%8E%B7%E5%8F%96-cookie-%E4%BF%A1%E6%81%AF)
+ - [利用 iOS11 API WKHTTPCookieStore 解决 WKWebView 首次请求不携带 Cookie 的问题](#%E5%88%A9%E7%94%A8-ios11-api-wkhttpcookiestore-%E8%A7%A3%E5%86%B3-wkwebview-%E9%A6%96%E6%AC%A1%E8%AF%B7%E6%B1%82%E4%B8%8D%E6%90%BA%E5%B8%A6-cookie-%E7%9A%84%E9%97%AE%E9%A2%98)
+ - [利用 iOS11 之前的 API 解决 WKWebView 首次请求不携带 Cookie 的问题](#%E5%88%A9%E7%94%A8-ios11-%E4%B9%8B%E5%89%8D%E7%9A%84-api-%E8%A7%A3%E5%86%B3-wkwebview-%E9%A6%96%E6%AC%A1%E8%AF%B7%E6%B1%82%E4%B8%8D%E6%90%BA%E5%B8%A6-cookie-%E7%9A%84%E9%97%AE%E9%A2%98)
+ - [Cookie包含动态 IP 导致登陆失效问题](#cookie%E5%8C%85%E5%90%AB%E5%8A%A8%E6%80%81-ip-%E5%AF%BC%E8%87%B4%E7%99%BB%E9%99%86%E5%A4%B1%E6%95%88%E9%97%AE%E9%A2%98)
+
+
+
+# iOS 防 DNS 污染方案调研 --- Cookie 业务场景
+
+## 概述
+
+本文将讨论下类似这样的问题:
+
+ - WKWebView 对于 Cookie 的管理一直是它的短板,那么 iOS11 是否有改进,如果有,如果利用这样的改进?
+ - 采用 IP 直连方案后,服务端返回的 Cookie 里的 Domain 字段也会使用 IP 。如果 IP 是动态的,就有可能导致一些问题:由于许多 H5 业务都依赖于 Cookie 作登录态校验,而 WKWebView 上请求不会自动携带 Cookie。
+
+
+## WKWebView 使用 NSURLProtocol 拦截请求无法获取 Cookie 信息
+
+iOS11推出了新的 API `WKHTTPCookieStore` 可以用来拦截 WKWebView 的 Cookie 信息
+
+用法示例如下:
+
+ ```Objective-C
+ WKHTTPCookieStore *cookieStroe = self.webView.configuration.websiteDataStore.httpCookieStore;
+ //get cookies
+ [cookieStroe getAllCookies:^(NSArray * _Nonnull cookies) {
+ NSLog(@"All cookies %@",cookies);
+ }];
+
+ //set cookie
+ NSMutableDictionary *dict = [NSMutableDictionary dictionary];
+ dict[NSHTTPCookieName] = @"userid";
+ dict[NSHTTPCookieValue] = @"123";
+ dict[NSHTTPCookieDomain] = @"xxxx.com";
+ dict[NSHTTPCookiePath] = @"/";
+
+ NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:dict];
+ [cookieStroe setCookie:cookie completionHandler:^{
+ NSLog(@"set cookie");
+ }];
+
+ //delete cookie
+ [cookieStroe deleteCookie:cookie completionHandler:^{
+ NSLog(@"delete cookie");
+ }];
+ ```
+
+
+### 利用 iOS11 API WKHTTPCookieStore 解决 WKWebView 首次请求不携带 Cookie 的问题
+
+
+问题说明:由于许多 H5 业务都依赖于 Cookie 作登录态校验,而 WKWebView 上请求不会自动携带 Cookie。比如,如果你在Native层面做了登陆操作,获取了Cookie信息,也使用 NSHTTPCookieStorage 存到了本地,但是使用 WKWebView 打开对应网页时,网页依然处于未登陆状态。如果是登陆也在 WebView 里做的,就不会有这个问题。
+
+iOS11 的 API 可以解决该问题,只要是存在 WKHTTPCookieStore 里的 cookie,WKWebView 每次请求都会携带,存在 NSHTTPCookieStorage 的cookie,并不会每次都携带。于是会发生首次 WKWebView 请求不携带 Cookie 的问题。
+
+解决方法:
+
+在执行 `-[WKWebView loadReques:]` 前将 `NSHTTPCookieStorage` 中的内容复制到 `WKHTTPCookieStore` 中。示例代码如下:
+
+ ```Objective-C
+ [self copyNSHTTPCookieStorageToWKHTTPCookieStoreWithCompletionHandler:^{
+ NSURL *url = [NSURL URLWithString:@"https://www.v2ex.com"];
+ NSURLRequest *request = [NSURLRequest requestWithURL:url];
+ [_webView loadRequest:request];
+ }];
+ ```
+
+ ```Objective-C
+- (void)copyNSHTTPCookieStorageToWKHTTPCookieStoreWithCompletionHandler:(nullable void (^)())theCompletionHandler; {
+ NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
+ WKHTTPCookieStore *cookieStroe = self.webView.configuration.websiteDataStore.httpCookieStore;
+ if (cookies.count == 0) {
+ !theCompletionHandler ?: theCompletionHandler();
+ return;
+ }
+ for (NSHTTPCookie *cookie in cookies) {
+ [cookieStroe setCookie:cookie completionHandler:^{
+ if ([[cookies lastObject] isEqual:cookie]) {
+ !theCompletionHandler ?: theCompletionHandler();
+ return;
+ }
+ }];
+ }
+}
+ ```
+
+这个是 iOS11 的API,针对iOS11之前的系统,需要另外处理。
+
+## 利用 iOS11 之前的 API 解决 WKWebView 首次请求不携带 Cookie 的问题
+
+通过让所有 WKWebView 共享同一个 WKProcessPool 实例,可以实现多个 WKWebView 之间共享 Cookie(session Cookie and persistent Cookie)数据。不过 WKWebView WKProcessPool 实例在 app 杀进程重启后会被重置,导致 WKProcessPool 中的 Cookie、session Cookie 数据丢失,目前也无法实现 WKProcessPool 实例本地化保存。可以采取 cookie 放入 Header 的方法来做。
+
+ ```Objective-C
+ WKWebView * webView = [WKWebView new];
+ NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx.com/login"]];
+ [request addValue:@"skey=skeyValue" forHTTPHeaderField:@"Cookie"];
+ [webView loadRequest:request];
+ ```
+
+其中对于 `skey=skeyValue` 这个cookie值的获取,也可以统一通过domain获取,获取的方法,可以参照下面的工具类:
+
+ ```Objective-C
+HTTPDNSCookieManager.h
+
+#ifndef HTTPDNSCookieManager_h
+#define HTTPDNSCookieManager_h
+
+// URL匹配Cookie规则
+typedef BOOL (^HTTPDNSCookieFilter)(NSHTTPCookie *, NSURL *);
+
+@interface HTTPDNSCookieManager : NSObject
+
++ (instancetype)sharedInstance;
+
+/**
+ 指定URL匹配Cookie策略
+
+ @param filter 匹配器
+ */
+- (void)setCookieFilter:(HTTPDNSCookieFilter)filter;
+
+/**
+ 处理HTTP Reponse携带的Cookie并存储
+
+ @param headerFields HTTP Header Fields
+ @param URL 根据匹配策略获取查找URL关联的Cookie
+ @return 返回添加到存储的Cookie
+ */
+- (NSArray *)handleHeaderFields:(NSDictionary *)headerFields forURL:(NSURL *)URL;
+
+/**
+ 匹配本地Cookie存储,获取对应URL的request cookie字符串
+
+ @param URL 根据匹配策略指定查找URL关联的Cookie
+ @return 返回对应URL的request Cookie字符串
+ */
+- (NSString *)getRequestCookieHeaderForURL:(NSURL *)URL;
+
+/**
+ 删除存储cookie
+
+ @param URL 根据匹配策略查找URL关联的cookie
+ @return 返回成功删除cookie数
+ */
+- (NSInteger)deleteCookieForURL:(NSURL *)URL;
+
+@end
+
+#endif /* HTTPDNSCookieManager_h */
+
+HTTPDNSCookieManager.m
+#import
+#import "HTTPDNSCookieManager.h"
+
+@implementation HTTPDNSCookieManager
+{
+ HTTPDNSCookieFilter cookieFilter;
+}
+
+- (instancetype)init {
+ if (self = [super init]) {
+ /**
+ 此处设置的Cookie和URL匹配策略比较简单,检查URL.host是否包含Cookie的domain字段
+ 通过调用setCookieFilter接口设定Cookie匹配策略,
+ 比如可以设定Cookie的domain字段和URL.host的后缀匹配 | URL是否符合Cookie的path设定
+ 细节匹配规则可参考RFC 2965 3.3节
+ */
+ cookieFilter = ^BOOL(NSHTTPCookie *cookie, NSURL *URL) {
+ if ([URL.host containsString:cookie.domain]) {
+ return YES;
+ }
+ return NO;
+ };
+ }
+ return self;
+}
+
++ (instancetype)sharedInstance {
+ static id singletonInstance = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ if (!singletonInstance) {
+ singletonInstance = [[super allocWithZone:NULL] init];
+ }
+ });
+ return singletonInstance;
+}
+
++ (id)allocWithZone:(struct _NSZone *)zone {
+ return [self sharedInstance];
+}
+
+- (id)copyWithZone:(struct _NSZone *)zone {
+ return self;
+}
+
+- (void)setCookieFilter:(HTTPDNSCookieFilter)filter {
+ if (filter != nil) {
+ cookieFilter = filter;
+ }
+}
+
+- (NSArray *)handleHeaderFields:(NSDictionary *)headerFields forURL:(NSURL *)URL {
+ NSArray *cookieArray = [NSHTTPCookie cookiesWithResponseHeaderFields:headerFields forURL:URL];
+ if (cookieArray != nil) {
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ for (NSHTTPCookie *cookie in cookieArray) {
+ if (cookieFilter(cookie, URL)) {
+ NSLog(@"Add a cookie: %@", cookie);
+ [cookieStorage setCookie:cookie];
+ }
+ }
+ }
+ return cookieArray;
+}
+
+- (NSString *)getRequestCookieHeaderForURL:(NSURL *)URL {
+ NSArray *cookieArray = [self searchAppropriateCookies:URL];
+ if (cookieArray != nil && cookieArray.count > 0) {
+ NSDictionary *cookieDic = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieArray];
+ if ([cookieDic objectForKey:@"Cookie"]) {
+ return cookieDic[@"Cookie"];
+ }
+ }
+ return nil;
+}
+
+- (NSArray *)searchAppropriateCookies:(NSURL *)URL {
+ NSMutableArray *cookieArray = [NSMutableArray array];
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ for (NSHTTPCookie *cookie in [cookieStorage cookies]) {
+ if (cookieFilter(cookie, URL)) {
+ NSLog(@"Search an appropriate cookie: %@", cookie);
+ [cookieArray addObject:cookie];
+ }
+ }
+ return cookieArray;
+}
+
+- (NSInteger)deleteCookieForURL:(NSURL *)URL {
+ int delCount = 0;
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ for (NSHTTPCookie *cookie in [cookieStorage cookies]) {
+ if (cookieFilter(cookie, URL)) {
+ NSLog(@"Delete a cookie: %@", cookie);
+ [cookieStorage deleteCookie:cookie];
+ delCount++;
+ }
+ }
+ return delCount;
+}
+
+@end
+ ```
+
+使用方法示例:
+
+发送请求
+
+ ```Objective-C
+ WKWebView * webView = [WKWebView new];
+ NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx.com/login"]];
+NSString *value = [[HTTPDNSCookieManager sharedInstance] getRequestCookieHeaderForURL:url];
+[request setValue:value forHTTPHeaderField:@"Cookie"];
+ [webView loadRequest:request];
+ ```
+
+接收处理请求:
+
+ ```Objective-C
+ NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
+ if (!error) {
+ NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
+ // 解析 HTTP Response Header,存储cookie
+ [[HTTPDNSCookieManager sharedInstance] handleHeaderFields:[httpResponse allHeaderFields] forURL:url];
+ }
+ }];
+ [task resume];
+ ```
+
+
+通过 `document.cookie` 设置 Cookie 解决后续页面(同域)Ajax、iframe 请求的 Cookie 问题;
+
+ ```Objective-C
+WKUserContentController* userContentController = [WKUserContentController new];
+ WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"document.cookie = 'skey=skeyValue';" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
+ [userContentController addUserScript:cookieScript];
+ ```
+
+## Cookie包含动态 IP 导致登陆失效问题
+
+
+关于COOKIE失效的问题,假如客户端登录 session 存在 COOKIE,此时这个域名配置了多个IP,使用域名访问会读对应域名的COOKIE,使用IP访问则去读对应IP的COOKIE,假如前后两次使用同一个域名配置的不同IP访问,会导致COOKIE的登录session失效,
+
+如果APP里面的webview页面需要用到系统COOKIE存的登录session,之前APP所有本地网络请求使用域名访问,是可以共用COOKIE的登录session的,但现在本地网络请求使用httpdns后改用IP访问,导致还使用域名访问的webview读不到系统COOKIE存的登录session了(系统COOKIE对应IP了)。IP直连后,服务端返回Cookie包含动态 IP 导致登陆失效。
+
+使用IP访问后,服务端返回的cookie也是IP。导致可能使用对应的域名访问,无法使用本地cookie,或者使用隶属于同一个域名的不同IP去访问,cookie也对不上,导致登陆失效,是吧。
+
+我这边的思路是这样的,
+ - 应该得干预cookie的存储,基于域名。
+ - 根源上,api域名返回单IP
+
+第二种思路将失去DNS调度特性,故不考虑。第一种思路更为可行。
+
+当每次服务端返回cookie后,在存储前都进行下改造,使用域名替换下IP。
+之后虽然每次网络请求都是使用IP访问,但是host我们都手动改为了域名,这样本地的cookie也是能对得上的。
+
+代码演示:
+
+在网络请求成功后,或者加载网页成功后,主动将本地的 domain 字段为 IP 的 Cookie 替换 IP 为 host 域名地址。
+
+ ```Objective-C
+- (void)updateWKHTTPCookieStoreDomainFromIP:(NSString *)IP toHost:(NSString *)host {
+ WKHTTPCookieStore *cookieStroe = self.webView.configuration.websiteDataStore.httpCookieStore;
+ [cookieStroe getAllCookies:^(NSArray * _Nonnull cookies) {
+ [[cookies copy] enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull cookie, NSUInteger idx, BOOL * _Nonnull stop) {
+ if ([cookie.domain isEqualToString:IP]) {
+ NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:cookie.properties];
+ dict[NSHTTPCookieDomain] = host;
+ NSHTTPCookie *newCookie = [NSHTTPCookie cookieWithProperties:[dict copy]];
+ [cookieStroe setCookie:newCookie completionHandler:^{
+ [self logCookies];
+ //FIXME: `-[WKHTTPCookieStore deleteCookie:]` 在 iOS11-beta3 中依然有bug,不会执行。(后续正式版修复后,再更新该注视。)
+ [cookieStroe deleteCookie:cookie
+ completionHandler:^{
+ [self logCookies];
+ }];
+ }];
+ }
+ }];
+ }];
+}
+ ```
+
+
+iOS11中也提供了对应的 API 供我们来处理替换 Cookie 的时机,那就是下面的API:
+
+ ```Objective-C
+@protocol WKHTTPCookieStoreObserver
+@optional
+- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore;
+@end
+ ```
+
+
+ ```Objective-C
+//WKHTTPCookieStore
+/*! @abstract Adds a WKHTTPCookieStoreObserver object with the cookie store.
+ @param observer The observer object to add.
+ @discussion The observer is not retained by the receiver. It is your responsibility
+ to unregister the observer before it becomes invalid.
+ */
+- (void)addObserver:(id)observer;
+
+/*! @abstract Removes a WKHTTPCookieStoreObserver object from the cookie store.
+ @param observer The observer to remove.
+ */
+- (void)removeObserver:(id)observer;
+ ```
+
+用法如下:
+
+ ```Objective-C
+@interface WebViewController ()
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [NSURLProtocol registerClass:[WebViewURLProtocol class]];
+ NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ [cookieStorage setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
+ WKHTTPCookieStore *cookieStroe = self.webView.configuration.websiteDataStore.httpCookieStore;
+ [cookieStroe addObserver:self];
+
+ [self.view addSubview:self.webView];
+ //... ...
+}
+
+#pragma mark -
+#pragma mark - WKHTTPCookieStoreObserver Delegate Method
+
+- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore {
+ [self updateWKHTTPCookieStoreDomainFromIP:CYLIP toHost:CYLHOST];
+}
+ ```
+
+`-updateWKHTTPCookieStoreDomainFromIP` 方法的实现,在上文已经给出。
+
+//TODO: 这个思路正在完善中... ...
+
+**相关的文章:**
+
+ - [《WKWebView 那些坑》]( https://zhuanlan.zhihu.com/p/24990222 )
+ - [《HTTPDNS域名解析场景下如何使用Cookie?》]( https://yq.aliyun.com/articles/64356 )
+
diff --git "a/Tips/DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224/iOS\351\230\262DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224---SNI\344\270\232\345\212\241\345\234\272\346\231\257.md" "b/Tips/DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224/iOS\351\230\262DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224---SNI\344\270\232\345\212\241\345\234\272\346\231\257.md"
new file mode 100644
index 0000000..df223b5
--- /dev/null
+++ "b/Tips/DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224/iOS\351\230\262DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224---SNI\344\270\232\345\212\241\345\234\272\346\231\257.md"
@@ -0,0 +1,381 @@
+
+
+
+- [iOS 防 DNS 污染方案调研--- SNI 业务场景](#ios-%E9%98%B2-dns-%E6%B1%A1%E6%9F%93%E6%96%B9%E6%A1%88%E8%B0%83%E7%A0%94----sni-%E4%B8%9A%E5%8A%A1%E5%9C%BA%E6%99%AF)
+ - [概述](#%E6%A6%82%E8%BF%B0)
+ - [基于 CFNetWork 有性能瓶颈](#%E5%9F%BA%E4%BA%8E-cfnetwork-%E6%9C%89%E6%80%A7%E8%83%BD%E7%93%B6%E9%A2%88)
+ - [调研性能瓶颈的原因](#%E8%B0%83%E7%A0%94%E6%80%A7%E8%83%BD%E7%93%B6%E9%A2%88%E7%9A%84%E5%8E%9F%E5%9B%A0)
+ - [调研性能瓶颈的方法](#%E8%B0%83%E7%A0%94%E6%80%A7%E8%83%BD%E7%93%B6%E9%A2%88%E7%9A%84%E6%96%B9%E6%B3%95)
+ - [能瓶颈原因](#%E8%83%BD%E7%93%B6%E9%A2%88%E5%8E%9F%E5%9B%A0)
+ - [Body 放入 Header 导致请求超时](#body-%E6%94%BE%E5%85%A5-header-%E5%AF%BC%E8%87%B4%E8%AF%B7%E6%B1%82%E8%B6%85%E6%97%B6)
+ - [换用其他提供了SNI字段配置接口的更底层网络库](#%E6%8D%A2%E7%94%A8%E5%85%B6%E4%BB%96%E6%8F%90%E4%BE%9B%E4%BA%86sni%E5%AD%97%E6%AE%B5%E9%85%8D%E7%BD%AE%E6%8E%A5%E5%8F%A3%E7%9A%84%E6%9B%B4%E5%BA%95%E5%B1%82%E7%BD%91%E7%BB%9C%E5%BA%93)
+ - [iOS CURL 库](#ios-curl-%E5%BA%93)
+ - [走过的弯路](#%E8%B5%B0%E8%BF%87%E7%9A%84%E5%BC%AF%E8%B7%AF)
+ - [误以为 iOS11 新 API 可以直接拦截 DNS 解析过程](#%E8%AF%AF%E4%BB%A5%E4%B8%BA-ios11-%E6%96%B0-api-%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E6%8B%A6%E6%88%AA-dns-%E8%A7%A3%E6%9E%90%E8%BF%87%E7%A8%8B)
+ - [参考链接:](#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5)
+
+
+
+# iOS 防 DNS 污染方案调研--- SNI 业务场景
+
+## 概述
+
+SNI(单IP多HTTPS证书)场景下,iOS上层网络库 `NSURLConnection/NSURLSession` 没有提供接口进行 `SNI 字段` 配置,因此需要 Socket 层级的底层网络库例如 `CFNetwork`,来实现 `IP 直连网络请求`适配方案。而基于 CFNetwork 的解决方案需要开发者考虑数据的收发、重定向、解码、缓存等问题(CFNetwork是非常底层的网络实现)。
+
+针对 SNI 场景的方案, Socket 层级的底层网络库,大致有两种:
+
+ - 基于 CFNetWork ,hook 证书校验步骤。
+ - 基于原生支持设置 SNI 字段的更底层的库,比如 libcurl。
+
+
+下面将目前面临的一些挑战,以及应对策略介绍一下:
+
+## 基于 CFNetWork 有性能瓶颈
+
+方案:
+
+ 1. 调研性能瓶颈的原因
+ 2. 换用其他提供了SNI字段配置接口的更底层网络库。
+
+### 调研性能瓶颈的原因
+
+在使用 CFNetWork 实现了基本的SNI解决方案后,虽然问题解决了,但是遇到了性能瓶颈,对比 `NSURLConnection/NSURLSession` ,打开流到结束流时间明显更长。介绍下对比性能时的调研方法:
+
+ /*one more thing*/
+
+
+
+#### 调研性能瓶颈的方法
+
+
+可以使用下面的方法,做一个简单的打点,将流开始和流结束记录下。
+
+记录的数据如下:
+
+key | from | to | vule
+-------|------|-------|------
+请求的序列号 | 开始时间戳 | 结束时间戳 | 耗时
+
+
+ ```Objective-C
+#import
+
+@interface CYLRequestTimeMonitor : NSObject
+
++ (NSString *)requestBeginTimeKeyWithID:(NSUInteger)ID;
++ (NSString *)requestEndTimeKeyWithID:(NSUInteger)ID;
++ (NSString *)requestSpentTimeKeyWithID:(NSUInteger)ID;
++ (NSString *)getKey:(NSString *)key ID:(NSUInteger)ID;
++ (NSUInteger)timeFromKey:(NSString *)key;
++ (NSUInteger)frontRequetNumber;
++ (NSUInteger)changeToNextRequetNumber;
++ (void)setCurrentTimeForKey:(NSString *)key taskID:(NSUInteger)taskID time:(NSTimeInterval *)time;
++ (void)setTime:(NSUInteger)time key:(NSString *)key taskID:(NSUInteger)taskID;
+
++ (void)setBeginTimeForTaskID:(NSUInteger)taskID;
++ (void)setEndTimeForTaskID:(NSUInteger)taskID;
++ (void)setSpentTimeForKey:(NSString *)key endTime:(NSUInteger)endTime taskID:(NSUInteger)taskID;
+
+@end
+ ```
+
+
+ ```Objective-C
+#import "CYLRequestTimeMonitor.h"
+
+@implementation CYLRequestTimeMonitor
+
+static NSString *const CYLRequestFrontNumber = @"CYLRequestFrontNumber";
+static NSString *const CYLRequestBeginTime = @"CYLRequestBeginTime";
+static NSString *const CYLRequestEndTime = @"CYLRequestEndTime";
+static NSString *const CYLRequestSpentTime = @"CYLRequestSpentTime";
+
++ (NSString *)requestBeginTimeKeyWithID:(NSUInteger)ID {
+ return [self getKey:CYLRequestBeginTime ID:ID];
+}
+
++ (NSString *)requestEndTimeKeyWithID:(NSUInteger)ID {
+ return [self getKey:CYLRequestEndTime ID:ID];
+}
+
++ (NSString *)requestSpentTimeKeyWithID:(NSUInteger)ID {
+ return [self getKey:CYLRequestSpentTime ID:ID];
+}
+
++ (NSString *)getKey:(NSString *)key ID:(NSUInteger)ID {
+ NSString *timeKeyWithID = [NSString stringWithFormat:@"%@-%@", @(ID), key];
+ return timeKeyWithID;
+}
+
++ (NSUInteger)timeFromKey:(NSString *)key {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSUInteger time = [defaults integerForKey:key];
+ return time ?: 0;
+}
+
++ (NSUInteger)frontRequetNumber {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSUInteger frontNumber = [defaults integerForKey:CYLRequestFrontNumber];
+ return frontNumber ?: 0;
+}
+
++ (NSUInteger)changeToNextRequetNumber {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSUInteger nextNumber = ([self frontRequetNumber]+ 1);
+ [defaults setInteger:nextNumber forKey:CYLRequestFrontNumber];
+ [defaults synchronize];
+ return nextNumber;
+}
+
++ (void)setCurrentTimeForKey:(NSString *)key taskID:(NSUInteger)taskID time:(NSTimeInterval *)time {
+ NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]*1000;
+ *time = currentTime;
+ [self setTime:currentTime key:key taskID:taskID];
+}
+
++ (void)setTime:(NSUInteger)time key:(NSString *)key taskID:(NSUInteger)taskID {
+ NSString *keyWithID = [self getKey:key ID:taskID];
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ [defaults setInteger:time forKey:keyWithID];
+ [defaults synchronize];
+}
+
++ (void)setBeginTimeForTaskID:(NSUInteger)taskID {
+ NSTimeInterval begin;
+ [self setCurrentTimeForKey:CYLRequestBeginTime taskID:taskID time:&begin];
+}
+
++ (void)setEndTimeForTaskID:(NSUInteger)taskID {
+ NSTimeInterval endTime = 0;
+ [self setCurrentTimeForKey:CYLRequestEndTime taskID:taskID time:&endTime];
+ [self setSpentTimeForKey:CYLRequestSpentTime endTime:endTime taskID:taskID];
+}
+
++ (void)setSpentTimeForKey:(NSString *)key endTime:(NSUInteger)endTime taskID:(NSUInteger)taskID {
+ NSString *beginTimeString = [self requestBeginTimeKeyWithID:taskID];
+ NSUInteger beginTime = [self timeFromKey:beginTimeString];
+ NSUInteger spentTime = endTime - beginTime;
+ [self setTime:spentTime key:CYLRequestSpentTime taskID:taskID];
+}
+
+@end
+
+ ```
+
+NSURLConnection 的打点位置如下:
+
+ ```Objective-C
+这里普通的做法就是继承NSURLProtocol 这个类写一个子类,然后在子类中实现NSURLConnectionDelegate 的那五个代理方法。
+
+- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
+// 这个方法里可以做计时的开始
+
+- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
+// 这里可以得到返回包的总大小
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
+// 这里将每次的data累加起来,可以做加载进度圆环之类的
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection
+// 这里作为结束的时间
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
+// 错误的收集
+ ```
+
+NSURLSession 类似。
+
+然后在自定义CFNetwork的下面两个方法中打点:流开始和流结束,命名大致如:`-startLoading`、`-didReceiveRedirection`。
+
+发送相同的网络请求,然后通过对比两个的时间来观察性能。
+
+### 能瓶颈原因
+
+#### Body 放入 Header 导致请求超时
+
+使用 NSURLProtocol 拦截 NSURLSession 请求丢失 body,故有以下几种解决方法:
+
+
+ 方案如下:
+
+ 1. 换用 NSURLConnection
+ 2. 将 body 放进 Header 中
+ 3. 使用 HTTPBodyStream 获取 body,并赋值到 body 中
+
+其中换用 NSURLConnection 这种方法,不用多少了,终究会被淘汰。不考虑。
+
+其中body放header的方法,2M以下没问题,超过2M会导致请求延迟,超过 10M 就直接 Request timeout。而且无法解决 Body 为二进制数据的问题,因为Header里都是文本数据,
+
+另一种方法是使用 HTTPBodyStream 获取 body,并赋值到 body 中,具体的代码如下,可以解决上面提到的问题:
+
+ ```Objective-C
+//
+// NSURLRequest+CYLNSURLProtocolExtension.h
+//
+//
+// Created by ElonChan on 28/07/2017.
+// Copyright © 2017 ChenYilong. All rights reserved.
+//
+
+#import
+
+@interface NSURLRequest (CYLNSURLProtocolExtension)
+
+- (NSMutableURLRequest *)cyl_getPostRequestIncludeBody;
+
+@end
+
+
+@interface NSMutableURLRequest (CYLNSURLProtocolExtension)
+
+- (void)cyl_handlePostRequestBody;
+
+@end
+
+
+ //
+// NSURLRequest+CYLNSURLProtocolExtension.h
+//
+//
+// Created by ElonChan on 28/07/2017.
+// Copyright © 2017 ChenYilong. All rights reserved.
+//
+
+#import "NSURLRequest+CYLNSURLProtocolExtension.h"
+
+@implementation NSURLRequest (CYLNSURLProtocolExtension)
+
+- (NSMutableURLRequest *)cyl_getPostRequestIncludeBody {
+ NSMutableURLRequest * req = [self mutableCopy];
+ if ([self.HTTPMethod isEqualToString:@"POST"]) {
+ if (!self.HTTPBody) {
+ uint8_t d[1024] = {0};
+ NSInputStream *stream = self.HTTPBodyStream;
+ NSMutableData *data = [[NSMutableData alloc] init];
+ [stream open];
+ while ([stream hasBytesAvailable]) {
+ NSInteger len = [stream read:d maxLength:1024];
+ if (len > 0 && stream.streamError == nil) {
+ [data appendBytes:(void *)d length:len];
+ }
+ }
+ req.HTTPBody = [data copy];
+ [stream close];
+ }
+ }
+ return req;
+}
+
+@end
+
+@implementation NSMutableURLRequest (CYLNSURLProtocolExtension)
+
+- (void)cyl_handlePostRequestBody {
+ if ([self.HTTPMethod isEqualToString:@"POST"]) {
+ if (!self.HTTPBody) {
+ uint8_t d[1024] = {0};
+ NSInputStream *stream = self.HTTPBodyStream;
+ NSMutableData *data = [[NSMutableData alloc] init];
+ [stream open];
+ while ([stream hasBytesAvailable]) {
+ NSInteger len = [stream read:d maxLength:1024];
+ if (len > 0 && stream.streamError == nil) {
+ [data appendBytes:(void *)d length:len];
+ }
+ }
+ self.HTTPBody = [data copy];
+ [stream close];
+ }
+ }
+}
+
+@end
+
+```
+
+使用方法:
+
+在用于拦截请求的 NSURLProtocol 的子类中实现方法 `-[NSURLProtocol startLoading]`,并处理 `request` 对象。
+
+
+ ```Objective-C
+
+/**
+ * 开始加载,在该方法中,加载一个请求
+ */
+- (void)startLoading {
+ NSMutableURLRequest *request = [self.request mutableCopy];
+ [request cyl_handlePostRequestBody];
+ // 表示该请求已经被处理,防止无限循环
+ [NSURLProtocol setProperty:@(YES) forKey:CYL_NSURLPROTOCOL_REQUESTED_FLAG_KEY inRequest:request];
+ curRequest = request;
+ [self startRequest];
+}
+ ```
+
+注意在拦截 `NSURLSession` 请求时,需要将用于拦截请求的 NSURLProtocol 的子类添加到 `NSURLSessionConfiguration` 中,用法如下:
+
+ ```Objective-C
+ NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
+ NSArray *protocolArray = @[ [CYLURLProtocol class] ];
+ configuration.protocolClasses = protocolArray;
+ NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
+```
+
+## 换用其他提供了SNI字段配置接口的更底层网络库
+
+如果使用第三方网络库:curl, 中有一个 `-resolve` 方法可以实现使用指定 ip 访问 https 网站,iOS 中集成 curl 库,参考 [curl文档](https://curl.haxx.se/libcurl/c/CURLOPT_RESOLVE.html) ;
+
+另外有一点也可以注意下,它也是支持 IPv6 环境的,只需要你在 build 时添加上 `--enable-ipv6` 即可。
+
+curl 支持指定 SNI 字段,设置 SNI 时我们需要构造的参数形如: `{HTTPS域名}:443:{IP地址}`
+
+假设你要访问. www.example.org ,若IP为 127.0.0.1 ,那么通过这个方式来调用来设置 SNI 即可:
+
+ > curl --resolve 'www.example.org:443:127.0.0.1'
+
+### iOS CURL 库
+
+使用[libcurl](https://curl.haxx.se/libcurl/c/) 来解决,`curl` 中有一个 `-resolve` 方法可以实现使用指定ip访问https网站。
+
+在iOS实现中,代码如下
+
+ ```Objective-C
+ //{HTTPS域名}:443:{IP地址}
+ NSString *curlHost = ...;
+ _hosts_list = curl_slist_append(_hosts_list, curlHost.UTF8String);
+ curl_easy_setopt(_curl, CURLOPT_RESOLVE, _hosts_list);
+ ```
+
+其中 `curlHost` 形如:
+
+ `{HTTPS域名}:443:{IP地址}`
+
+ `_hosts_list` 是结构体类型`hosts_list`,可以设置多个IP与Host之间的映射关系。`curl_easy_setopt`方法中传入`CURLOPT_RESOLVE` 将该映射设置到 HTTPS 请求中。
+
+这样就可以达到设置SNI的目的。
+
+我在这里写了一个 Demo:[CYLCURLNetworking](https://github.com/ChenYilong/CYLCURLNetworking),里面包含了编译好的支持 IPv6 的 libcurl 包,演示了下如何通过curl来进行类似NSURLSession。
+
+## 走过的弯路
+
+## 误以为 iOS11 新 API 可以直接拦截 DNS 解析过程
+
+参考:[NEDNSProxyProvider:DNS based on HTTP supported in iOS11](https://github.com/ChenYilong/iOS11AdaptationTips/issues/12)
+
+## 参考链接:
+
+ - [Apple - Communicating with HTTP Servers](https://developer.apple.com/library/content/documentation/Networking/Conceptual/CFNetwork/CFHTTPTasks/CFHTTPTasks.html?spm=5176.doc30143.2.3.5016q8)
+ - [Apple - HTTPS Server Trust Evaluation - Server Name Failures ](https://developer.apple.com/library/content/technotes/tn2232/_index.html?spm=5176.doc30143.2.4.5016q8#//apple_ref/doc/uid/DTS40012884-CH1-SECSERVERNAME)
+ - [Apple - HTTPS Server Trust Evaluation - Trusting One Specific Certificate ](https://developer.apple.com/library/content/technotes/tn2232/_index.html?spm=5176.doc30143.2.5.5016q8#//apple_ref/doc/uid/DTS40012884-CH1-SECCUSTOMCERT)
+ - [《HTTPDNS > 最佳实践 > HTTPS(含SNI)业务场景“IP直连”方案说明
+HTTPS(含SNI)业务场景“IP直连”方案说明》]( https://help.aliyun.com/document_detail/30143.html?spm=5176.doc30141.6.591.A8B1d3 )
+ - [《在 curl 中使用指定 ip 来进行请求 https》]( https://blog.mozcp.com/curl-request-https-specify-ip/ )
+ - [支持SNI与WebView的 alicloud-ios-demo](https://github.com/Dave1991/alicloud-ios-demo)
+
diff --git "a/Tips/DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224/iOS\351\230\262DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224---WebView\344\270\232\345\212\241\345\234\272\346\231\257.md" "b/Tips/DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224/iOS\351\230\262DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224---WebView\344\270\232\345\212\241\345\234\272\346\231\257.md"
new file mode 100644
index 0000000..ad48306
--- /dev/null
+++ "b/Tips/DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224/iOS\351\230\262DNS\346\261\241\346\237\223\346\226\271\346\241\210\350\260\203\347\240\224---WebView\344\270\232\345\212\241\345\234\272\346\231\257.md"
@@ -0,0 +1,143 @@
+
+
+
+- [iOS 防 DNS 污染方案调研--- WebView 业务场景](#ios-%E9%98%B2-dns-%E6%B1%A1%E6%9F%93%E6%96%B9%E6%A1%88%E8%B0%83%E7%A0%94----webview-%E4%B8%9A%E5%8A%A1%E5%9C%BA%E6%99%AF)
+ - [概述](#%E6%A6%82%E8%BF%B0)
+ - [面临的问题](#%E9%9D%A2%E4%B8%B4%E7%9A%84%E9%97%AE%E9%A2%98)
+ - [WKWebView 无法使用 NSURLProtocol 拦截请求](#wkwebview-%E6%97%A0%E6%B3%95%E4%BD%BF%E7%94%A8-nsurlprotocol-%E6%8B%A6%E6%88%AA%E8%AF%B7%E6%B1%82)
+ - [使用 NSURLProtocol 拦截 NSURLSession 请求丢失 body](#%E4%BD%BF%E7%94%A8-nsurlprotocol-%E6%8B%A6%E6%88%AA-nsurlsession-%E8%AF%B7%E6%B1%82%E4%B8%A2%E5%A4%B1-body)
+ - [302重定向问题](#302%E9%87%8D%E5%AE%9A%E5%90%91%E9%97%AE%E9%A2%98)
+ - [Cookie相关问题](#cookie%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98)
+ - [参考链接](#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5)
+ - [走过的弯路](#%E8%B5%B0%E8%BF%87%E7%9A%84%E5%BC%AF%E8%B7%AF)
+ - [误以为 iOS11 新 API 可以原生拦截 WKWebView 的 HTTP/HTTPS 网络请求](#%E8%AF%AF%E4%BB%A5%E4%B8%BA-ios11-%E6%96%B0-api-%E5%8F%AF%E4%BB%A5%E5%8E%9F%E7%94%9F%E6%8B%A6%E6%88%AA-wkwebview-%E7%9A%84-httphttps-%E7%BD%91%E7%BB%9C%E8%AF%B7%E6%B1%82)
+
+
+
+# iOS 防 DNS 污染方案调研--- WebView 业务场景
+
+## 概述
+
+为什么 WebView 需要特别适配SNI?
+
+
+因为形如 http://1.1.1.1/a/b.com 在 WebView 中是无法正常访问的,也是需要修改HOST,所以需要使用 NSURLProtocol 来 hook 网络请求,而且 HTTPS+SNI 场景是非常场景的。
+
+WebView的IP直连方案,基本的思路是接管网络请求,随之就会面临到一些重定向、cookie等问题。下面对这些问题做下记录、总结。
+
+## 面临的问题
+
+### WKWebView 无法使用 NSURLProtocol 拦截请求
+
+ 方案如下:
+
+
+ 1. 换用 UIWebView
+ 2. 使用私有API进行注册拦截
+
+换用 UIWebView 方案不做赘述,说明下使用私有API进行注册拦截的方法:
+
+ ```Objective-C
+//注册自己的protocol
+ [NSURLProtocol registerClass:[CustomProtocol class]];
+
+ //创建WKWebview
+ WKWebViewConfiguration * config = [[WKWebViewConfiguration alloc] init];
+ WKWebView * wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) configuration:config];
+ [wkWebView loadRequest:webViewReq];
+ [self.view addSubview:wkWebView];
+
+ //注册scheme
+ Class cls = NSClassFromString(@"WKBrowsingContextController");
+ SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
+ if ([cls respondsToSelector:sel]) {
+ // 通过http和https的请求,同理可通过其他的Scheme 但是要满足ULR Loading System
+ [cls performSelector:sel withObject:@"http"];
+ [cls performSelector:sel withObject:@"https"];
+ }
+
+ ```
+ 注意避免执行太晚,如果在 `- (void)viewDidLoad` 中注册,可能会因为注册太晚,引发问题。建议在`+load`方法中执行。
+
+然后同样会遇到 [《iOS SNI 场景下的防 DNS 污染方案调研》](https://github.com/ChenYilong/iOSBlog/issues/12) 里提到的各种 NSURLProtocol 相关的问题,可以参照里面的方法解决。
+
+### 使用 NSURLProtocol 拦截 NSURLSession 请求丢失 body
+
+ 方案如下:
+
+ 1. 换用 NSURLConnection
+ 2. 将 body 放进 Header 中
+ 3. 使用 HTTPBodyStream 获取 body,并赋值到 body 中
+
+ ```Objective-C
+//处理POST请求相关POST 用HTTPBodyStream来处理BODY体
+- (void)handlePostRequestBody {
+ if ([self.HTTPMethod isEqualToString:@"POST"]) {
+ if (!self.HTTPBody) {
+ uint8_t d[1024] = {0};
+ NSInputStream *stream = self.HTTPBodyStream;
+ NSMutableData *data = [[NSMutableData alloc] init];
+ [stream open];
+ while ([stream hasBytesAvailable]) {
+ NSInteger len = [stream read:d maxLength:1024];
+ if (len > 0 && stream.streamError == nil) {
+ [data appendBytes:(void *)d length:len];
+ }
+ }
+ self.HTTPBody = [data copy];
+ [stream close];
+ }
+ }
+}
+
+ ```
+
+使用 `-[WKWebView loadRequest]` 同样会遇到该问题,按照同样的方法修改。
+
+
+
+## 302重定向问题
+
+上面提到的 Cookie 方案无法解决302请求的 Cookie 问题,比如,第一个请求是 http://www.a.com ,我们通过在 request header 里带上 Cookie 解决该请求的 Cookie 问题,接着页面302跳转到 http://www.b.com ,这个时候 http://www.b.com 这个请求就可能因为没有携带 cookie 而无法访问。当然,由于每一次页面跳转前都会调用回调函数:
+
+ ```Objective-C
+ - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
+ ```
+
+可以在该回调函数里拦截302请求,copy request,在 request header 中带上 cookie 并重新 loadRequest。不过这种方法依然解决不了页面 iframe 跨域请求的 Cookie 问题,毕竟-[WKWebView loadRequest:]只适合加载 mainFrame 请求。
+
+
+## Cookie相关问题
+
+单独成篇: [《防 DNS 污染方案调研---iOS HTTPS(含SNI) 业务场景(四)-- Cookie 场景》]( https://github.com/ChenYilong/iOSBlog/issues/14 )
+
+
+## 参考链接
+
+
+**相关的库:**
+
+ - [GitHub:WebViewProxy](https://github.com/marcuswestin/WebViewProxy)
+ - [GitHub:NSURLProtocol-WebKitSupport](https://github.com/Yeatse/NSURLProtocol-WebKitSupport)
+ - [GitHub:happy-dns-objc](https://github.com/qiniu/happy-dns-objc)
+ - [Chrome For iOS ](https://chromium.googlesource.com/chromium/src.git/+/master/ios/)
+
+**相关的文章:**
+
+ - [《NSURLProtocol对WKWebView的处理》]( http://www.jianshu.com/p/8f5e1082f5e0 )
+ - [《可能是最全的iOS端HttpDns集成方案》]( http://www.jianshu.com/p/cd4c1bf1fd5f )
+ - [《WKWebView 那些坑》]( https://zhuanlan.zhihu.com/p/24990222 )
+
+
+**可以参考的Demo:**
+
+ - [支持SNI与WebView的 alicloud-ios-demo](https://github.com/Dave1991/alicloud-ios-demo)
+ - [HybirdWKWebVIew](https://github.com/LiuShuoyu/HybirdWKWebVIew/)
+ - [《WWDC 2017-WKWebView 新功能》]( https://zhuanlan.zhihu.com/p/27914128 )
+
+## 走过的弯路
+
+## 误以为 iOS11 新 API 可以原生拦截 WKWebView 的 HTTP/HTTPS 网络请求
+
+ 参考:[Deal With WKWebView DNS pollution problem in iOS11](https://github.com/ChenYilong/iOS11AdaptationTips/issues/16)
+
diff --git a/Tips/Heap-Stack Dance/Heap-Stack Dance.md b/Tips/Heap-Stack Dance/Heap-Stack Dance.md
new file mode 100644
index 0000000..97cf30d
--- /dev/null
+++ b/Tips/Heap-Stack Dance/Heap-Stack Dance.md
@@ -0,0 +1,260 @@
+ 【使用 Heap-Stack Dance 替代 Weak-Strong Dance,优雅避开循环引用】Weak-Strong Dance这一最佳实践的原理已经被讲烂了,开发者对该写法已经烂熟于心。有相当一部分开发者是不理解 Weak-Strong Dance 的原理,但却写得很溜,即使没必要加 `strongSelf` 的场景下也会添加上 `strongSelf`。没错,这样做,总是没错。
+
+有没有想过从API层面简化一下?
+
+介绍下我的做法:
+
+为 block 多加一个参数,也就是 self 所属类型的参数,那么在 block 内部,该参数就会和 `strongSelf` 的效果一致。同时你也可以不写 `weakSelf`,直接使用使用该参数(作用等同于直接使用 `strongSelf` )。这样就达到了:“多加一个参数,省掉两行代码”的效果。原理就是利用了“参数”的特性:参数是存放在栈中的(或寄存器中),系统负责回收,开发者无需关心。因为解决问题的思路是:将 block 会捕获变量到堆上的问题,化解为了:变量会被分配到栈(或寄存器中)上,所以我把种做法起名叫 Heap-Stack Dance 。
+
+具体用法示例如下:
+(详见仓库中的[Demo---文件夹叫做:weak-strong-drance-demo](https://github.com/ChenYilong/iOSBlog/tree/master/Tips/Heap-Stack%20Dance) )
+
+
+ ```Objective-C
+
+#import "Foo.h"
+
+typedef void (^Completion)(Foo *foo);
+
+@interface Foo ()
+
+@property (nonatomic, copy) Completion completion1;
+@property (nonatomic, copy) Completion completion2;
+
+@end
+
+@implementation Foo
+
+- (instancetype)init {
+ if (!(self = [super init])) {
+ return nil;
+ }
+ __weak typeof(self) weakSelf = self;
+ self.completion1 = ^(Foo *foo) {
+ NSLog(@"completion1");
+ };
+ self.completion2 = ^(Foo *foo) {
+ __strong typeof(self) strongSelf = weakSelf;
+ NSLog(@"completion2");
+ NSUInteger delaySeconds = 2;
+ dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC));
+ dispatch_after(when, dispatch_get_main_queue(), ^{
+ NSLog(@"两秒钟后");
+ foo.completion1(foo);//foo等价于strongSelf
+ });
+ };
+ self.completion2(self);
+ return self;
+}
+
+- (void)dealloc {
+ NSLog(@"dealloc");
+}
+
+@end
+
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ __autoreleasing Foo *foo = [Foo new];
+}
+
+@end
+ ```
+
+ 打印如下:
+
+ ```Objective-C
+ completion2
+ 两秒钟后
+ completion1
+ dealloc
+
+ ```
+
+ 举一个实际开发中的例子:
+
+ 如果我们为UIViewController添加了一个属性,叫做viewDidLoadBlock,让用户来进行一些UI设置。
+
+ 具体的做法如下:
+
+
+ ```Objective-C
+@property (nonatomic, copy) CYLViewDidLoadBlock viewDidLoadBlock;
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ //...
+ !self.viewDidLoadBlock ?: self.viewDidLoadBlock(self);
+}
+ ```
+
+ 那么可以想象block中必然是要使用到viewController本身,为了避免循环引用,之前我们不得不这样做:
+
+ 简化前:
+
+ ```Objective-C
+__weak typeof(controller) weakController = conversationController;
+[conversationController setViewDidLoadBlock:^{
+ [weakController.navigationItem setTitle:@"XXX"];
+}];
+ ```
+
+如果借助这种做法,简化后:
+
+ ```Objective-C
+ [conversationViewController setViewDidLoadBlock:^(LCCKBaseViewController *viewController) {
+ viewController.navigationItem.title = @"XXX";
+ }];
+ ```
+
+ 这种可能优势不太明显,毕竟编译器都能看出来,会报警告。但如果遇到了那种很难看出会造成循环引用的情景下,优势就显现出来了。
+ 尤其是在公开的 API 中,无法获知 `block` 是否被 `self` 持有的,如果在 `block` 中加增一个 `self` 类型的参数,因为 `block` 内部已经提供了 `weakSelf` 或者是 `strongSelf` 的替代者,那么调用者就可以在不使用 Weak-Strong Dance 的情况下避免循环引用。
+
+ 下面这个语句,编译器不会报警告,你能看出来有循环应用吗?
+
+ 比如我们为 `UIViewController` 添加了一个方法,这个方法主要作用就是配置下 `navigationBar` 右上角的 `item` 样式以及点击事件:
+
+ ```Objective-C
+ [aConversationController configureBarButtonItemStyle:LCCKBarButtonItemStyleGroupProfile
+ action:^(__kindof LCCKBaseViewController *viewController, UIBarButtonItem *sender, UIEvent *event) { [aConversationController.navigationController pushViewController:[UIViewController new] animated:YES];
+ }];
+ ```
+
+实际上你必须点击进去看一下该 API 的实现,你才能发现原来 `aConversationController` 持有了 `action` 这个 `block`,而在这种用法中 `block` 又持有了 `aConversationController` ,所以这种情况是有循环引用的。
+
+可以看下上述方法的具体的实现:
+
+ ```Objective-C
+- (void)configureBarButtonItemStyle:(LCCKBarButtonItemStyle)style action:(LCCKBarButtonItemActionBlock)action {
+ NSString *icon;
+ switch (style) {
+ case LCCKBarButtonItemStyleSetting: {
+ icon = @"barbuttonicon_set";
+ break;
+ }
+ case LCCKBarButtonItemStyleMore: {
+ icon = @"barbuttonicon_more";
+ break;
+ }
+ case LCCKBarButtonItemStyleAdd: {
+ icon = @"barbuttonicon_add";
+ break;
+ }
+ case LCCKBarButtonItemStyleAddFriends:
+ icon = @"barbuttonicon_addfriends";
+ break;
+ case LCCKBarButtonItemStyleSingleProfile:
+ icon = @"barbuttonicon_InfoSingle";
+ break;
+ case LCCKBarButtonItemStyleGroupProfile:
+ icon = @"barbuttonicon_InfoMulti";
+ break;
+ case LCCKBarButtonItemStyleShare:
+ icon = @"barbuttonicon_Operate";
+ break;
+ }
+ self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage lcck_imageNamed:icon bundleName:@"BarButtonIcon" bundleForClass:[self class]] style:UIBarButtonItemStylePlain target:self action:@selector(clickedBarButtonItemAction:event:)];
+ self.barButtonItemAction = action;
+}
+
+- (void)clickedBarButtonItemAction:(UIBarButtonItem *)sender event:(UIEvent *)event {
+ if (self.barButtonItemAction) {
+ self.barButtonItemAction(self, sender, event);
+ }
+}
+ ```
+
+
+> 必须让调用者理解了内部实现,才能用得好的API,不是一个好的API设计。
+
+能不能在API层面就避免?增加一个self类型的参数就好了:
+
+ ```Objective-C
+ [aConversationController configureBarButtonItemStyle:LCCKBarButtonItemStyleGroupProfile
+ action:^(__kindof LCCKBaseViewController *viewController, UIBarButtonItem *sender, UIEvent *event) {
+ [viewController.navigationController pushViewController:[UIViewController new] animated:YES];
+ }];
+
+ ```
+
+ 各位如果觉得好用,可以到你的项目中使用 `Heap-Stack Dance` 替代 `Weak-Strong Dance`,重构一些代码。
+
+
+
+这里还有另外一种方法来证明 self 做参数传进 block 不会被 Block 捕获:
+
+用 clang 对 Foo.m 文件转成c/c++代码:
+
+ > clang -rewrite-objc Foo.m -Wno-deprecated-declarations -fobjc-arc
+
+
+比如如下代码:
+
+ ```Objective-C
+ int tmpTarget;
+ self.completion1 = ^(Foo *foo) {
+ tmpTarget;
+ NSLog(@"completion1");
+ };
+ self.completion1(self);
+ ```
+
+可以看到 Block 只会对传入的 `tmpTarget` 引用,`self` 不会捕获:
+
+
+ ```C
+struct __Foo__init_block_impl_0 {
+ struct __block_impl impl;
+ struct __Foo__init_block_desc_0* Desc;
+ int tmpTarget;
+ __Foo__init_block_impl_0(void *fp, struct __Foo__init_block_desc_0 *desc, int _tmpTarget, int flags=0) : tmpTarget(_tmpTarget) {
+ impl.isa = &_NSConcreteStackBlock;
+ impl.Flags = flags;
+ impl.FuncPtr = fp;
+ Desc = desc;
+ }
+};
+ ```
+
+
+而如果是如下代码 self 就会被捕获:
+
+
+ ```Objective-C
+ int tmpTarget;
+ self.completion1 = ^(Foo *foo) {
+ tmpTarget;
+ _b;
+ NSLog(@"completion1");
+ };
+ self.completion1(self);
+ ```
+
+
+ ```Objective-C
+struct __Foo__init_block_impl_0 {
+ struct __block_impl impl;
+ struct __Foo__init_block_desc_0* Desc;
+ int tmpTarget;
+ Foo *__strong self;
+ __Foo__init_block_impl_0(void *fp, struct __Foo__init_block_desc_0 *desc, int _tmpTarget, Foo *__strong _self, int flags=0) : tmpTarget(_tmpTarget), self(_self) {
+ impl.isa = &_NSConcreteStackBlock;
+ impl.Flags = flags;
+ impl.FuncPtr = fp;
+ Desc = desc;
+ }
+};
+ ```
+
+----------
+
+QQ交流群:515295083
+
+Posted by [微博@iOS程序犭袁](http://weibo.com/luohanchenyilong/)
+原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | [Creative Commons BY-NC-ND 3.0](http://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)
+
+
+
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo.xcodeproj/project.pbxproj b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..1c04669
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo.xcodeproj/project.pbxproj
@@ -0,0 +1,315 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 9AB072FB1E099BDF00A7B4F8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AB072FA1E099BDF00A7B4F8 /* main.m */; };
+ 9AB072FE1E099BDF00A7B4F8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AB072FD1E099BDF00A7B4F8 /* AppDelegate.m */; };
+ 9AB073011E099BDF00A7B4F8 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AB073001E099BDF00A7B4F8 /* ViewController.m */; };
+ 9AB073041E099BDF00A7B4F8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9AB073021E099BDF00A7B4F8 /* Main.storyboard */; };
+ 9AB073061E099BDF00A7B4F8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9AB073051E099BDF00A7B4F8 /* Assets.xcassets */; };
+ 9AB073091E099BDF00A7B4F8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9AB073071E099BDF00A7B4F8 /* LaunchScreen.storyboard */; };
+ 9AB073121E099C4F00A7B4F8 /* Foo.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AB073111E099C4F00A7B4F8 /* Foo.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 9AB072F61E099BDF00A7B4F8 /* weak-strong-drance-demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "weak-strong-drance-demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9AB072FA1E099BDF00A7B4F8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ 9AB072FC1E099BDF00A7B4F8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
+ 9AB072FD1E099BDF00A7B4F8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ 9AB072FF1E099BDF00A7B4F8 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; };
+ 9AB073001E099BDF00A7B4F8 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; };
+ 9AB073031E099BDF00A7B4F8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 9AB073051E099BDF00A7B4F8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 9AB073081E099BDF00A7B4F8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 9AB0730A1E099BDF00A7B4F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9AB073101E099C4F00A7B4F8 /* Foo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Foo.h; sourceTree = ""; };
+ 9AB073111E099C4F00A7B4F8 /* Foo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Foo.m; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 9AB072F31E099BDF00A7B4F8 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 9AB072ED1E099BDF00A7B4F8 = {
+ isa = PBXGroup;
+ children = (
+ 9AB072F81E099BDF00A7B4F8 /* weak-strong-drance-demo */,
+ 9AB072F71E099BDF00A7B4F8 /* Products */,
+ );
+ sourceTree = "";
+ };
+ 9AB072F71E099BDF00A7B4F8 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 9AB072F61E099BDF00A7B4F8 /* weak-strong-drance-demo.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 9AB072F81E099BDF00A7B4F8 /* weak-strong-drance-demo */ = {
+ isa = PBXGroup;
+ children = (
+ 9AB072FC1E099BDF00A7B4F8 /* AppDelegate.h */,
+ 9AB072FD1E099BDF00A7B4F8 /* AppDelegate.m */,
+ 9AB072FF1E099BDF00A7B4F8 /* ViewController.h */,
+ 9AB073001E099BDF00A7B4F8 /* ViewController.m */,
+ 9AB073101E099C4F00A7B4F8 /* Foo.h */,
+ 9AB073111E099C4F00A7B4F8 /* Foo.m */,
+ 9AB073021E099BDF00A7B4F8 /* Main.storyboard */,
+ 9AB073051E099BDF00A7B4F8 /* Assets.xcassets */,
+ 9AB073071E099BDF00A7B4F8 /* LaunchScreen.storyboard */,
+ 9AB0730A1E099BDF00A7B4F8 /* Info.plist */,
+ 9AB072F91E099BDF00A7B4F8 /* Supporting Files */,
+ );
+ path = "weak-strong-drance-demo";
+ sourceTree = "";
+ };
+ 9AB072F91E099BDF00A7B4F8 /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 9AB072FA1E099BDF00A7B4F8 /* main.m */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 9AB072F51E099BDF00A7B4F8 /* weak-strong-drance-demo */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9AB0730D1E099BE000A7B4F8 /* Build configuration list for PBXNativeTarget "weak-strong-drance-demo" */;
+ buildPhases = (
+ 9AB072F21E099BDF00A7B4F8 /* Sources */,
+ 9AB072F31E099BDF00A7B4F8 /* Frameworks */,
+ 9AB072F41E099BDF00A7B4F8 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "weak-strong-drance-demo";
+ productName = "weak-strong-drance-demo";
+ productReference = 9AB072F61E099BDF00A7B4F8 /* weak-strong-drance-demo.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 9AB072EE1E099BDF00A7B4F8 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0730;
+ ORGANIZATIONNAME = ElonChan;
+ TargetAttributes = {
+ 9AB072F51E099BDF00A7B4F8 = {
+ CreatedOnToolsVersion = 7.3;
+ };
+ };
+ };
+ buildConfigurationList = 9AB072F11E099BDF00A7B4F8 /* Build configuration list for PBXProject "weak-strong-drance-demo" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 9AB072ED1E099BDF00A7B4F8;
+ productRefGroup = 9AB072F71E099BDF00A7B4F8 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 9AB072F51E099BDF00A7B4F8 /* weak-strong-drance-demo */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 9AB072F41E099BDF00A7B4F8 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9AB073091E099BDF00A7B4F8 /* LaunchScreen.storyboard in Resources */,
+ 9AB073061E099BDF00A7B4F8 /* Assets.xcassets in Resources */,
+ 9AB073041E099BDF00A7B4F8 /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 9AB072F21E099BDF00A7B4F8 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9AB073121E099C4F00A7B4F8 /* Foo.m in Sources */,
+ 9AB073011E099BDF00A7B4F8 /* ViewController.m in Sources */,
+ 9AB072FE1E099BDF00A7B4F8 /* AppDelegate.m in Sources */,
+ 9AB072FB1E099BDF00A7B4F8 /* main.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 9AB073021E099BDF00A7B4F8 /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 9AB073031E099BDF00A7B4F8 /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 9AB073071E099BDF00A7B4F8 /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 9AB073081E099BDF00A7B4F8 /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 9AB0730B1E099BDF00A7B4F8 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.3;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 9AB0730C1E099BDF00A7B4F8 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.3;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 9AB0730E1E099BE000A7B4F8 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ INFOPLIST_FILE = "weak-strong-drance-demo/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = "ElonChan.weak-strong-drance-demo";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ 9AB0730F1E099BE000A7B4F8 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ INFOPLIST_FILE = "weak-strong-drance-demo/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = "ElonChan.weak-strong-drance-demo";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 9AB072F11E099BDF00A7B4F8 /* Build configuration list for PBXProject "weak-strong-drance-demo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9AB0730B1E099BDF00A7B4F8 /* Debug */,
+ 9AB0730C1E099BDF00A7B4F8 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9AB0730D1E099BE000A7B4F8 /* Build configuration list for PBXNativeTarget "weak-strong-drance-demo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9AB0730E1E099BE000A7B4F8 /* Debug */,
+ 9AB0730F1E099BE000A7B4F8 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 9AB072EE1E099BDF00A7B4F8 /* Project object */;
+}
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..661aad8
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/AppDelegate.h b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/AppDelegate.h
new file mode 100644
index 0000000..6a0ffb3
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/AppDelegate.h
@@ -0,0 +1,17 @@
+//
+// AppDelegate.h
+// weak-strong-drance-demo
+//
+// Created by 陈宜龙 on 12/21/16.
+// Copyright © 2016 ElonChan. All rights reserved.
+//
+
+#import
+
+@interface AppDelegate : UIResponder
+
+@property (strong, nonatomic) UIWindow *window;
+
+
+@end
+
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/AppDelegate.m b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/AppDelegate.m
new file mode 100644
index 0000000..59ff2d5
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/AppDelegate.m
@@ -0,0 +1,45 @@
+//
+// AppDelegate.m
+// weak-strong-drance-demo
+//
+// Created by 陈宜龙 on 12/21/16.
+// Copyright © 2016 ElonChan. All rights reserved.
+//
+
+#import "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Override point for customization after application launch.
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+}
+
+@end
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..eeea76c
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,73 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "83.5x83.5",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Base.lproj/LaunchScreen.storyboard b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..2e721e1
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Base.lproj/Main.storyboard b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f56d2f3
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Base.lproj/Main.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Foo.h b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Foo.h
new file mode 100644
index 0000000..6bac2f2
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Foo.h
@@ -0,0 +1,13 @@
+//
+// Foo.h
+// weak-strong-drance-demo
+//
+// Created by 陈宜龙 on 12/21/16.
+// Copyright © 2016 ElonChan. All rights reserved.
+//
+
+#import
+
+@interface Foo : NSObject
+
+@end
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Foo.m b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Foo.m
new file mode 100644
index 0000000..59cd4c6
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Foo.m
@@ -0,0 +1,48 @@
+//
+// Foo.m
+// weak-strong-drance-demo
+//
+// Created by 陈宜龙 on 12/21/16.
+// Copyright © 2016 ElonChan. All rights reserved.
+//
+
+#import "Foo.h"
+
+typedef void (^Completion)(Foo *foo);
+
+@interface Foo ()
+
+@property (nonatomic, copy) Completion completion1;
+@property (nonatomic, copy) Completion completion2;
+
+@end
+
+@implementation Foo
+
+- (instancetype)init {
+ if (!(self = [super init])) {
+ return nil;
+ }
+ __weak typeof(self) weakSelf = self;
+ self.completion1 = ^(Foo *foo) {
+ NSLog(@"completion1");
+ };
+ self.completion2 = ^(Foo *foo) {
+ __strong typeof(self) strongSelf = weakSelf;
+ NSLog(@"completion2");
+ NSUInteger delaySeconds = 2;
+ dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC));
+ dispatch_after(when, dispatch_get_main_queue(), ^{
+ NSLog(@"两秒钟后");
+ foo.completion1(foo);//foo等价于strongSelf
+ });
+ };
+ self.completion2(self);
+ return self;
+}
+
+- (void)dealloc {
+ NSLog(@"dealloc");
+}
+
+@end
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Info.plist b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Info.plist
new file mode 100644
index 0000000..40c6215
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/Info.plist
@@ -0,0 +1,47 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/ViewController.h b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/ViewController.h
new file mode 100644
index 0000000..8ee9e62
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/ViewController.h
@@ -0,0 +1,15 @@
+//
+// ViewController.h
+// weak-strong-drance-demo
+//
+// Created by 陈宜龙 on 12/21/16.
+// Copyright © 2016 ElonChan. All rights reserved.
+//
+
+#import
+#import "Foo.h"
+
+@interface ViewController : UIViewController
+
+@end
+
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/ViewController.m b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/ViewController.m
new file mode 100644
index 0000000..e4d384c
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/ViewController.m
@@ -0,0 +1,28 @@
+//
+// ViewController.m
+// weak-strong-drance-demo
+//
+// Created by 陈宜龙 on 12/21/16.
+// Copyright © 2016 ElonChan. All rights reserved.
+//
+
+#import "ViewController.h"
+
+@interface ViewController ()
+
+@end
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ __autoreleasing Foo *foo = [Foo new];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/main.m b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/main.m
new file mode 100644
index 0000000..68e9c0b
--- /dev/null
+++ b/Tips/Heap-Stack Dance/weak-strong-drance-demo/weak-strong-drance-demo/main.m
@@ -0,0 +1,16 @@
+//
+// main.m
+// weak-strong-drance-demo
+//
+// Created by 陈宜龙 on 12/21/16.
+// Copyright © 2016 ElonChan. All rights reserved.
+//
+
+#import
+#import "AppDelegate.h"
+
+int main(int argc, char * argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git "a/Tips/iOS\345\270\270\350\247\201\350\200\227\347\224\265\351\207\217\346\243\200\346\265\213\346\226\271\346\241\210\350\260\203\347\240\224/iOS\345\270\270\350\247\201\350\200\227\347\224\265\351\207\217\346\243\200\346\265\213\346\226\271\346\241\210\350\260\203\347\240\224.md" "b/Tips/iOS\345\270\270\350\247\201\350\200\227\347\224\265\351\207\217\346\243\200\346\265\213\346\226\271\346\241\210\350\260\203\347\240\224/iOS\345\270\270\350\247\201\350\200\227\347\224\265\351\207\217\346\243\200\346\265\213\346\226\271\346\241\210\350\260\203\347\240\224.md"
new file mode 100644
index 0000000..7d04a18
--- /dev/null
+++ "b/Tips/iOS\345\270\270\350\247\201\350\200\227\347\224\265\351\207\217\346\243\200\346\265\213\346\226\271\346\241\210\350\260\203\347\240\224/iOS\345\270\270\350\247\201\350\200\227\347\224\265\351\207\217\346\243\200\346\265\213\346\226\271\346\241\210\350\260\203\347\240\224.md"
@@ -0,0 +1,141 @@
+# iOS 常见耗电量检测方案调研
+
+
+本文对应 Demo 以及 Markdown 文件在 [ GitHub 仓库中]( https://github.com/ChenYilong/iOSBlog/blob/master/Tips/iOS常见耗电量检测方案调研/iOS常见耗电量检测方案调研.md ),文中的错误可以提 PR 到这个文件,我会及时更改。
+
+
+
+
+- [前言](#%E5%89%8D%E8%A8%80)
+- [系统接口](#%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3)
+- [测试平台](#%E6%B5%8B%E8%AF%95%E5%B9%B3%E5%8F%B0)
+- [常用的电量测试方法:](#%E5%B8%B8%E7%94%A8%E7%9A%84%E7%94%B5%E9%87%8F%E6%B5%8B%E8%AF%95%E6%96%B9%E6%B3%95)
+- [软件工具检测](#%E8%BD%AF%E4%BB%B6%E5%B7%A5%E5%85%B7%E6%A3%80%E6%B5%8B)
+- [iOS电量测试方法](#ios%E7%94%B5%E9%87%8F%E6%B5%8B%E8%AF%95%E6%96%B9%E6%B3%95)
+ - [1.iOS 设置选项 ->开发者选项->logging ->start recording](#1ios-%E8%AE%BE%E7%BD%AE%E9%80%89%E9%A1%B9--%E5%BC%80%E5%8F%91%E8%80%85%E9%80%89%E9%A1%B9logging-start-recording)
+ - [2.进行需要测试电量的场景操作后进入开发者选项点击stop recording](#2%E8%BF%9B%E8%A1%8C%E9%9C%80%E8%A6%81%E6%B5%8B%E8%AF%95%E7%94%B5%E9%87%8F%E7%9A%84%E5%9C%BA%E6%99%AF%E6%93%8D%E4%BD%9C%E5%90%8E%E8%BF%9B%E5%85%A5%E5%BC%80%E5%8F%91%E8%80%85%E9%80%89%E9%A1%B9%E7%82%B9%E5%87%BBstop-recording)
+ - [3.将iOS设备和Mac连接](#3%E5%B0%86ios%E8%AE%BE%E5%A4%87%E5%92%8Cmac%E8%BF%9E%E6%8E%A5)
+ - [4.打开Instrument,选择Energy Diagnostics](#4%E6%89%93%E5%BC%80instrument%E9%80%89%E6%8B%A9energy-diagnostics)
+ - [5.选择 File > Import Logged Data from Device](#5%E9%80%89%E6%8B%A9-file--import-logged-data-from-device)
+ - [6.保存的数据以时间轴输出到Instrument面板](#6%E4%BF%9D%E5%AD%98%E7%9A%84%E6%95%B0%E6%8D%AE%E4%BB%A5%E6%97%B6%E9%97%B4%E8%BD%B4%E8%BE%93%E5%87%BA%E5%88%B0instrument%E9%9D%A2%E6%9D%BF)
+ - [其他](#%E5%85%B6%E4%BB%96)
+- [硬件检测](#%E7%A1%AC%E4%BB%B6%E6%A3%80%E6%B5%8B)
+
+
+
+
+## 前言
+
+如果我们想看下我们的 APP 或 SDK 是否耗电,需要给一些数据来展示,所以就对常见的电量测试方案做了一下调研。
+
+影响 iOS 电量的因素,几个典型的耗电场景如下:
+
+ 1. 定位,尤其是调用GPS定位
+ 2. 网络传输,尤其是非Wifi环境
+ 3. cpu频率
+ 4. 内存调度频度
+ 5. 后台运行
+
+
+## 系统接口
+
+iOS 10 系统内置的 Setting 里可以查看各个 App 的电池消耗。
+
+
+
+系统接口,能获取到整体的电池利用率,以及充电状态。代码演示如下:
+
+ ```Objective-C
+ //#import
+ UIDevice *device = [UIDevice currentDevice];
+ device.batteryMonitoringEnabled = YES;
+ //UIDevice返回的batteryLevel的范围在0到1之间。
+ NSUInteger batteryLevel = device.batteryLevel * 100;
+ //获取充电状态
+ UIDeviceBatteryState state = device.batteryState;
+ if (state == UIDeviceBatteryStateCharging || state == UIDeviceBatteryStateFull) {
+ //正在充电和电池已满
+ }
+ ```
+
+这些均不符合我们的检测需求,不能检测固定某一时间段内的电池精准消耗。
+
+
+## 测试平台
+
+ **阿里云移动测试[MQC](http://mqc.yunos.com)**
+
+[MQC](http://mqc.yunos.com) 调研,结论:没有iOS性能测试,无法提供耗电量指标。
+
+解释 | 截图
+-------------|-------------
+安卓有性能测试项目|  |
+安卓的性能测试项目 | |
+iOS没有性能测试,无法提供耗电量指标| 
+
+
+百度移动云测试中心 MTC 同样没有 iOS 的性能测试。
+
+其他测试平台类似。
+
+## 常用的电量测试方法:
+
+ 1. 硬件测试
+ 2. 软件工具检测
+
+
+
+## 软件工具检测
+
+下面介绍通过软件 Instrument 来进行耗电检测。
+
+
+
+## iOS电量测试方法
+
+#### 1.iOS 设置选项 ->开发者选项->logging ->start recording
+
+
+
+#### 2.进行需要测试电量的场景操作后进入开发者选项点击stop recording
+#### 3.将iOS设备和Mac连接
+#### 4.打开Instrument,选择Energy Diagnostics
+#### 5.选择 File > Import Logged Data from Device
+
+
+
+
+#### 6.保存的数据以时间轴输出到Instrument面板
+
+
+#### 其他
+
+ - 测试过程中要断开 iOS设备和电脑、电源的连接
+ - 电量使用level为0-20,1/20:表示运行该app,电池生命会有20个小时;20/20:表示运行该app,电池电量仅有1小时的生命
+ - 数据不能导出计算,只能手动计算平均值
+
+
+## 硬件检测
+
+ 通过硬件 [PowerMonitor]( https://www.msoon.com/LabEquipment/PowerMonitor/ ) 可以精准地获得应用的电量消耗。
+
+ 步骤如下:
+
+ 1. 拆开iOS设备的外壳,找到电池后面的电源针脚。
+ 2. 连接电源监控器的设备针脚
+ 3. 运行应用
+ 4. 测量电量消耗
+
+ 下图展示了与iPhone的电池针脚连接的电源监控器工具。
+
+ 
+
+ 可以参考:[**Using Monsoon Power Monitor with iPhone 5s**]( https://www.bottleofcode.com/2015/07/12/using-monsoon-power-monitor-with-iphone-5s/)。
+
+ - 可以精准地获得应用的电量消耗。
+ - 设备价格 $771.00 USD
+ - 需要拆解手机
+
+
+这样看来,只有 Instrument 的方案更适合,大家有什么方案的话,也可以贴在下面。
+
diff --git "a/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/IM \345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257\345\234\250\345\244\232\345\272\224\347\224\250\345\234\272\346\231\257\344\270\213\347\232\204\346\212\200\346\234\257\345\256\236\347\216\260\357\274\214\344\273\245\345\217\212\346\200\247\350\203\275\350\260\203\344\274\230\357\274\210iOS\350\247\206\350\247\222\357\274\211.md" "b/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/IM \345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257\345\234\250\345\244\232\345\272\224\347\224\250\345\234\272\346\231\257\344\270\213\347\232\204\346\212\200\346\234\257\345\256\236\347\216\260\357\274\214\344\273\245\345\217\212\346\200\247\350\203\275\350\260\203\344\274\230\357\274\210iOS\350\247\206\350\247\222\357\274\211.md"
index 5cb3f5f..02a270f 100644
--- "a/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/IM \345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257\345\234\250\345\244\232\345\272\224\347\224\250\345\234\272\346\231\257\344\270\213\347\232\204\346\212\200\346\234\257\345\256\236\347\216\260\357\274\214\344\273\245\345\217\212\346\200\247\350\203\275\350\260\203\344\274\230\357\274\210iOS\350\247\206\350\247\222\357\274\211.md"
+++ "b/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/IM \345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257\345\234\250\345\244\232\345\272\224\347\224\250\345\234\272\346\231\257\344\270\213\347\232\204\346\212\200\346\234\257\345\256\236\347\216\260\357\274\214\344\273\245\345\217\212\346\200\247\350\203\275\350\260\203\344\274\230\357\274\210iOS\350\247\206\350\247\222\357\274\211.md"
@@ -1,6 +1,9 @@
# IM 即时通讯技术在多应用场景下的技术实现,以及性能调优(iOS视角)
-演讲视频(上下两部,时长将近2个半小时)以及 PPT 下载:链接: http://pan.baidu.com/s/1i5oH6LZ 密码: 4ayq
+- 文章备份地址: [《IM 即时通讯技术在多应用场景下的技术实现,以及性能调优( iOS 视角)(附 PPT 与 2 个半小时视频)》]( https://www.jianshu.com/p/8cd908148f9e ) (防止图床挂了)
+- 演讲视频(上下两部,时长将近2个半小时)以及 PPT 下载:链接: https://pan.baidu.com/s/1FfhxcRImvwL7w38ZXnnzaw 密码: hb1y
+- 油管在线观看 [《IM 即时通讯技术在多应用场景下的技术实现,以及性能调优( iOS 视角)(附 PPT 与 2 个半小时视频)》]( https://youtu.be/yIOlzzA_dRQ "")
+
2016年9月份[我](https://github.com/ChenYilong)参加了 MDCC2016(中国移动开发者大会),
@@ -279,7 +282,7 @@ Message 在发送后,在服务端维护一个表,一段时间内,比如15
你可能见过这种推送消息:
-
+
这中间发生了什么?
@@ -505,4 +508,4 @@ IM 系列文章分为下面这几篇:
Posted by [微博@iOS程序犭袁](http://weibo.com/luohanchenyilong/)
原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | [Creative Commons BY-NC-ND 3.0](http://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)
-
\ No newline at end of file
+
diff --git "a/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/\346\234\211\344\270\200\347\247\215 Block \345\217\253 Callback\357\274\214\346\234\211\344\270\200\347\247\215 Callback \345\201\232 CompletionHandler.md" "b/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/\346\234\211\344\270\200\347\247\215 Block \345\217\253 Callback\357\274\214\346\234\211\344\270\200\347\247\215 Callback \345\201\232 CompletionHandler.md"
index f84f80b..a41c5a8 100644
--- "a/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/\346\234\211\344\270\200\347\247\215 Block \345\217\253 Callback\357\274\214\346\234\211\344\270\200\347\247\215 Callback \345\201\232 CompletionHandler.md"
+++ "b/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/\346\234\211\344\270\200\347\247\215 Block \345\217\253 Callback\357\274\214\346\234\211\344\270\200\347\247\215 Callback \345\201\232 CompletionHandler.md"
@@ -1,4 +1,4 @@
-# 有一种 Block 叫 Callback,有一种 Callback 做 CompletionHandler
+# 有一种 Block 叫 Callback,有一种 Callback 叫 CompletionHandler
## IM系列文章
@@ -6,7 +6,7 @@ IM系列文章分为下面这几篇:
- [《IM 即时通讯技术在多应用场景下的技术实现,以及性能调优(iOS视角)》](https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/IM%20即时通讯技术在多应用场景下的技术实现,以及性能调优(iOS视角).md)
- [《技术实现细节》]( https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/技术实现细节.md )
- - [《有一种 Block 叫 Callback,有一种 Callback 做 CompletionHandler》]( https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/有一种%20Block%20叫%20Callback,有一种%20Callback%20做%20CompletionHandler.md ) (本文)
+ - [《有一种 Block 叫 Callback,有一种 Callback 叫 CompletionHandler》]( https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/有一种%20Block%20叫%20Callback,有一种%20Callback%20做%20CompletionHandler.md ) (本文)
- [《防 DNS 污染方案》]( https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/防%20DNS%20污染方案.md )
@@ -23,11 +23,26 @@ IM系列文章分为下面这几篇:
- Lib 通知开发者,**Lib**操作已经完成。一般命名为 Callback
- 开发者通知 Lib,**开发者**的操作已经完成。一般可以命名为 CompletionHandler。
-我们常常见到 `CompletionHandler` 被用到了第一种场景,而我觉得第一种场景叫Callback更合适
+这两处的区别: 前者是 “Block 的执行”,后者是 “Block 的填充”。
- > 不是所有 Block 都可以叫做 CompletionHandler
+ `Callback vs CompletionHandler` 命名与功能的差别,Apple 也没有明确的编码规范指出过,只不过如果按照“执行与填充”的功能划分的话,`callback` 与 `completionHandler` 的命名可以区分开来对待。同时也方便调用者理解 block 的功能。但总体来说,Apple 官方的命名中,“Block 填充“这个功能一般都会命名为 “completionHandler”,“Block 执行”这个功能大多命名为了“callback” ,也有少部分命名为了 “completionHandler”。
-一般情况下,CompletionHandler 的设计往往考虑到多线程操作,于是,你就完全可以异步操作,然后在线程结束时执行该 CompletionHandler。
+比如:
+
+NSURLSession 中,下面的函数将 “callback” 命名为了 “completionHandler”:
+
+
+ ```Objective-C
+- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
+
+ ```
+
+
+我们常常见到 `CompletionHandler` 被用到了第一种场景,而第一种场景“Block 执行”命名为 Callback 则更合适。
+
+ > 不是所有 Block 都适合叫做 CompletionHandler
+
+一般情况下,CompletionHandler 的设计往往考虑到多线程操作,于是,你就完全可以异步操作,然后在线程结束时执行该 CompletionHandler,下文的例子中会讲述下 `CompletionHandler` 方式在多线程场景下的一些优势。
## CompletionHandler + Delegate 组合
@@ -53,6 +68,20 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler;
```
+
+
+还有另外一个也非常普遍的例子(Delegate 方式使用URLSession 时候必不可少的 4个代理函数之一 )
+
+
+ ```Objective-C
+- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
+ didReceiveResponse:(NSURLResponse *)response
+ completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
+ ```
+
+
+在代理方法实现代码里面,若是不执行 completionHandler(NSURLSessionResponseAllow) 话,http请求就终止了。
+
## CompletionHandler + Block 组合
函数中将函数作为参数或者返回值,就叫做高阶函数。
@@ -63,7 +92,7 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
如果有这样一个需求:
-当你的应用想要集成一个 IM 服务时,可能这时候,你的 APP 已经上架了,已经有自己的注册、登录等流程了。用 ChatKit 进行聊天很简单,只需要给 ChatKit 一个 id 就够了,就像 Demo 里做的那样。聊天是正常了,但是双方只能看到一个id,这样体验很不好。但是如何展示头像、昵称呢?于是就设计了这样一个接口,`-setFetchProfilesBlock:` 。
+以我之前的一个 IM 项目 ChatKit-OC (开源的,下面简称 ChatKit)为例,当你的应用想要集成一个 IM 服务时,可能这时候,你的 APP 已经上架了,已经有自己的注册、登录等流程了。用 ChatKit 进行聊天很简单,只需要给 ChatKit 一个 id 就够了。聊天是正常了,但是双方只能看到一个id,这样体验很不好。但是如何展示头像、昵称呢?于是就设计了这样一个接口,`-setFetchProfilesBlock:` 。
这是上层(APP)提供用户信息的 Block,由于 ChatKit 并不关心业务逻辑信息,比如用户昵称,用户头像等。用户可以通过 ChatKit 单例向 ChatKit 注入一个用户信息内容提供 Block,通过这个用户信息提供 Block,ChatKit 才能够正确的进行业务逻辑数据的绘制。
@@ -153,6 +182,8 @@ typedef void(^LCCKFetchProfilesBlock)(NSArray *userIds, LCCKFetchPro
```
+对于以上 Fetch 方法的这种应用场景,其实用方法的返回值也可以实现,但是与 CompletionHandler 相比,无法自由切换线程是个弊端。
+
## IM系列文章
@@ -160,7 +191,7 @@ IM系列文章分为下面这几篇:
- [《IM 即时通讯技术在多应用场景下的技术实现,以及性能调优(iOS视角)》](https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/IM%20即时通讯技术在多应用场景下的技术实现,以及性能调优(iOS视角).md)
- [《技术实现细节》]( https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/技术实现细节.md )
- - [《有一种 Block 叫 Callback,有一种 Callback 做 CompletionHandler》]( https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/有一种%20Block%20叫%20Callback,有一种%20Callback%20做%20CompletionHandler.md ) (本文)
+ - [《有一种 Block 叫 Callback,有一种 Callback 叫 CompletionHandler》]( https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/有一种%20Block%20叫%20Callback,有一种%20Callback%20做%20CompletionHandler.md ) (本文)
- [《防 DNS 污染方案》]( https://github.com/ChenYilong/iOSBlog/blob/master/Tips/基于Websocket的IM即时通讯技术/防%20DNS%20污染方案.md )
@@ -170,4 +201,5 @@ IM系列文章分为下面这几篇:
Posted by [微博@iOS程序犭袁](http://weibo.com/luohanchenyilong/)
原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | [Creative Commons BY-NC-ND 3.0](http://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)
-
\ No newline at end of file
+
+
diff --git "a/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/\351\230\262 DNS \346\261\241\346\237\223\346\226\271\346\241\210.md" "b/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/\351\230\262 DNS \346\261\241\346\237\223\346\226\271\346\241\210.md"
index 227367c..4039aca 100644
--- "a/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/\351\230\262 DNS \346\261\241\346\237\223\346\226\271\346\241\210.md"
+++ "b/Tips/\345\237\272\344\272\216Websocket\347\232\204IM\345\215\263\346\227\266\351\200\232\350\256\257\346\212\200\346\234\257/\351\230\262 DNS \346\261\241\346\237\223\346\226\271\346\241\210.md"
@@ -40,7 +40,7 @@ DNS 劫持、污染一般是针对递归 DNS 服务器的 DNS 劫持攻击,
某些地区的中国移动还有个简单粗爆的域名检查系统,包含 av 字样的域名一率返回错误的 IP,
LeanCloud 之前叫做 AVOSCLoud,域名是:https://cn.avoscloud.com,嗯,我们很受伤。
-后来我们改名了,域名也切换到了 api.leancloud.cn,我们用户的 DNS 问题已经大大的减少了。
+后来我们改名了,域名也切换到了 api.leancloud.cn ,我们用户的 DNS 问题已经大大的减少了。
鬼知道我们经历了什么。
@@ -62,7 +62,7 @@ LeanCloud 之前叫做 AVOSCLoud,域名是:https://cn.avoscloud.com,嗯,
移动环境下,向中国移动打 10086 电话投诉,告之受影响的域名及 DNS 服务器的 IP,才能解决问题。
如果是在无线网络情况下, DNS 异常,则请通过路由器的 DHCP 设置,将默认的 DNS 修改为正常的 DNS(推荐 114.114.114.114),并重启路由器即可。
-投诉到中国移动后 48 小时问题仍未解决的话,依据中国相关法律法规规定,可以向工信部申诉,网址是 http://www.chinatcc.gov.cn:8080/cms/shensus/,这里最好是以邮件的方式申诉,将具体细节和截图写在邮件里发送给 accept@chinatcc.gov.cn,工信部的相关同学最早会在第 2 天回电话并催促中国移动。
+投诉到中国移动后 48 小时问题仍未解决的话,依据中国相关法律法规规定,可以向工信部申诉,网址是 http://www.chinatcc.gov.cn:8080/cms/shensus/ ,这里最好是以邮件的方式申诉,将具体细节和截图写在邮件里发送给 accept@chinatcc.gov.cn,工信部的相关同学最早会在第 2 天回电话并催促中国移动。
申诉邮件的内容需要包括两个部分:
一是申诉者的姓名、身份证号码、通信地址、邮编、联系电话、申诉涉及到的电话号码、电子邮件、申诉日期
@@ -82,13 +82,13 @@ IP或域名在到达服务器前,经历了两个步骤往往会被我们所忽

-如果你拿一个IPv4的IP或域名进行请求,有两个机制可以保证最终到达 Server 的是一个IPv6地址。
+如果你拿一个 IPv4 的 IP 或域名进行请求,在 IPv6-Only 环境下,有两个机制可以保证最终能够到达 Server 地址。
-第一个机制是绿色部分,指的是 iOS系统级别的 IPv4 兼容方案,只要你使用了 `NSURLSession` 或 `CFNetwork`, 那么iOS系统会将帮你把它转为IPv6地址。
+第一个机制是绿色部分,指的是 iOS系统级别的 IPv4 兼容方案,只要你使用了 `NSURLSession` 或 `CFNetwork`, 那么 iOS 系统会将帮你把它转为 IPv6 地址。
> NSURLSession and CFNetwork automatically synthesize IPv6 addresses from IPv4 literals locally on devices operating on DNS64/NAT64 networks.(如果当前网络是 IPv6 网络,那么会在iOS系统层面转换成 IPv6.)
- 第二个机制是 DNS 服务的兼容方案,可以是运营商提供的服务,也可以是第三方 DNS 解析机构比如 DNSPod。如果 DNS 解析出来的域名是 IPv4 地址,也会转为 IPv6 地址。
+ 第二个机制是 DNS 服务的兼容方案,可以是运营商提供的服务,也可以是第三方 DNS 解析机构比如 DNSPod。如果 DNS 解析出来的域名是 IPv4 地址,也会转为 IPv6 兼容的地址。DNS64/NAT64 起到的作用就是将网关的出口地址进行转换,映射到 IPv4 地址上,保证路由能寻址到 IPv4 的地址。
综上所述,IPv6 政策的应对方案可以有下面几种:
@@ -96,6 +96,8 @@ IP或域名在到达服务器前,经历了两个步骤往往会被我们所忽
2. 升级服务器,让服务端支持 IPv6。在 APP 中替换 IPv4 的地址。
3. 如果你的 APP 需要使用了更底层的 API 连接到仅支持 IPv4 的服务器,且不使用 DNS 域名解析,请在APP端使用 `getaddrinfo` 处理 IPv4 地址串( `getaddrinfo` 可通过传入一个IPv4或IPv6地址,得到一个 sockaddr 结构链表)。如果当前的网络接口不支持 IPv4,仅支持 IPv6,NAT64和DNS64,这样做可以得到一个合成的IPv6地址。
+就目前国内的情况来看,据大部分的服务端器是不支持IPv6的,最后一种方法更加适用。这样一来,服务端完全不用做更改,在服务端看来,客户端是能够正常连接到 IPv4 的地址的。
+
参考:[《iOS支持IPv6 DNS64/NAT64网络》]( http://www.pchou.info/ios/2016/06/05/ios-supporting-ipv6.html )
#### 在 HTTPS 业务场景下的防 DNS 污染方案
@@ -187,6 +189,7 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
1. [《如何使用ip直接访问https网站?》]( https://segmentfault.com/a/1190000004359232?utm_source=Weibo )
2. [《HTTPS业务场景解决方案》]( https://help.aliyun.com/document_detail/30143.html )
+ 3. [Supporting IPv6 DNS64/NAT64 Networks](https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html#//apple_ref/doc/uid/TP40010220-CH213-SW1)
## IM系列文章
@@ -205,4 +208,5 @@ IM系列文章分为下面这几篇:
Posted by [微博@iOS程序犭袁](http://weibo.com/luohanchenyilong/)
原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | [Creative Commons BY-NC-ND 3.0](http://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)
-
\ No newline at end of file
+
+
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo.xcodeproj/project.pbxproj" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo.xcodeproj/project.pbxproj"
new file mode 100644
index 0000000..ac4035c
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo.xcodeproj/project.pbxproj"
@@ -0,0 +1,536 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 9A0A87A11EE7996E0099514C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0A87A01EE7996E0099514C /* main.m */; };
+ 9A0A87A41EE7996E0099514C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0A87A31EE7996E0099514C /* AppDelegate.m */; };
+ 9A0A87A71EE7996E0099514C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0A87A61EE7996E0099514C /* ViewController.m */; };
+ 9A0A87AA1EE7996E0099514C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A0A87A81EE7996E0099514C /* Main.storyboard */; };
+ 9A0A87AC1EE7996E0099514C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A0A87AB1EE7996E0099514C /* Assets.xcassets */; };
+ 9A0A87BA1EE7996F0099514C /* CYLGCDRunloopDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0A87B91EE7996F0099514C /* CYLGCDRunloopDemoTests.m */; };
+ 9A0A87C51EE7996F0099514C /* CYLGCDRunloopDemoUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0A87C41EE7996F0099514C /* CYLGCDRunloopDemoUITests.m */; };
+ 9A0A87D41EE7EC7C0099514C /* Foo.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A0A87D31EE7EC7C0099514C /* Foo.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 9A0A87B61EE7996F0099514C /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A0A87941EE7996E0099514C /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A0A879B1EE7996E0099514C;
+ remoteInfo = CYLGCDRunloopDemo;
+ };
+ 9A0A87C11EE7996F0099514C /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A0A87941EE7996E0099514C /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A0A879B1EE7996E0099514C;
+ remoteInfo = CYLGCDRunloopDemo;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 9A0A879C1EE7996E0099514C /* CYLGCDRunloopDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CYLGCDRunloopDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9A0A87A01EE7996E0099514C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ 9A0A87A21EE7996E0099514C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
+ 9A0A87A31EE7996E0099514C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ 9A0A87A51EE7996E0099514C /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; };
+ 9A0A87A61EE7996E0099514C /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; };
+ 9A0A87A91EE7996E0099514C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 9A0A87AB1EE7996E0099514C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 9A0A87B01EE7996E0099514C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9A0A87B51EE7996F0099514C /* CYLGCDRunloopDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CYLGCDRunloopDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9A0A87B91EE7996F0099514C /* CYLGCDRunloopDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CYLGCDRunloopDemoTests.m; sourceTree = ""; };
+ 9A0A87BB1EE7996F0099514C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9A0A87C01EE7996F0099514C /* CYLGCDRunloopDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CYLGCDRunloopDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9A0A87C41EE7996F0099514C /* CYLGCDRunloopDemoUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CYLGCDRunloopDemoUITests.m; sourceTree = ""; };
+ 9A0A87C61EE7996F0099514C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9A0A87D21EE7EC7C0099514C /* Foo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Foo.h; sourceTree = ""; };
+ 9A0A87D31EE7EC7C0099514C /* Foo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Foo.m; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 9A0A87991EE7996E0099514C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A0A87B21EE7996F0099514C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A0A87BD1EE7996F0099514C /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 9A0A87931EE7996E0099514C = {
+ isa = PBXGroup;
+ children = (
+ 9A0A879E1EE7996E0099514C /* CYLGCDRunloopDemo */,
+ 9A0A87B81EE7996F0099514C /* CYLGCDRunloopDemoTests */,
+ 9A0A87C31EE7996F0099514C /* CYLGCDRunloopDemoUITests */,
+ 9A0A879D1EE7996E0099514C /* Products */,
+ );
+ sourceTree = "";
+ };
+ 9A0A879D1EE7996E0099514C /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 9A0A879C1EE7996E0099514C /* CYLGCDRunloopDemo.app */,
+ 9A0A87B51EE7996F0099514C /* CYLGCDRunloopDemoTests.xctest */,
+ 9A0A87C01EE7996F0099514C /* CYLGCDRunloopDemoUITests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 9A0A879E1EE7996E0099514C /* CYLGCDRunloopDemo */ = {
+ isa = PBXGroup;
+ children = (
+ 9A0A87A21EE7996E0099514C /* AppDelegate.h */,
+ 9A0A87A31EE7996E0099514C /* AppDelegate.m */,
+ 9A0A87A51EE7996E0099514C /* ViewController.h */,
+ 9A0A87A61EE7996E0099514C /* ViewController.m */,
+ 9A0A87D21EE7EC7C0099514C /* Foo.h */,
+ 9A0A87D31EE7EC7C0099514C /* Foo.m */,
+ 9A0A87A81EE7996E0099514C /* Main.storyboard */,
+ 9A0A87AB1EE7996E0099514C /* Assets.xcassets */,
+ 9A0A87B01EE7996E0099514C /* Info.plist */,
+ 9A0A879F1EE7996E0099514C /* Supporting Files */,
+ );
+ path = CYLGCDRunloopDemo;
+ sourceTree = "";
+ };
+ 9A0A879F1EE7996E0099514C /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 9A0A87A01EE7996E0099514C /* main.m */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
+ 9A0A87B81EE7996F0099514C /* CYLGCDRunloopDemoTests */ = {
+ isa = PBXGroup;
+ children = (
+ 9A0A87B91EE7996F0099514C /* CYLGCDRunloopDemoTests.m */,
+ 9A0A87BB1EE7996F0099514C /* Info.plist */,
+ );
+ path = CYLGCDRunloopDemoTests;
+ sourceTree = "";
+ };
+ 9A0A87C31EE7996F0099514C /* CYLGCDRunloopDemoUITests */ = {
+ isa = PBXGroup;
+ children = (
+ 9A0A87C41EE7996F0099514C /* CYLGCDRunloopDemoUITests.m */,
+ 9A0A87C61EE7996F0099514C /* Info.plist */,
+ );
+ path = CYLGCDRunloopDemoUITests;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 9A0A879B1EE7996E0099514C /* CYLGCDRunloopDemo */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9A0A87C91EE7996F0099514C /* Build configuration list for PBXNativeTarget "CYLGCDRunloopDemo" */;
+ buildPhases = (
+ 9A0A87981EE7996E0099514C /* Sources */,
+ 9A0A87991EE7996E0099514C /* Frameworks */,
+ 9A0A879A1EE7996E0099514C /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = CYLGCDRunloopDemo;
+ productName = CYLGCDRunloopDemo;
+ productReference = 9A0A879C1EE7996E0099514C /* CYLGCDRunloopDemo.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 9A0A87B41EE7996F0099514C /* CYLGCDRunloopDemoTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9A0A87CC1EE7996F0099514C /* Build configuration list for PBXNativeTarget "CYLGCDRunloopDemoTests" */;
+ buildPhases = (
+ 9A0A87B11EE7996F0099514C /* Sources */,
+ 9A0A87B21EE7996F0099514C /* Frameworks */,
+ 9A0A87B31EE7996F0099514C /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 9A0A87B71EE7996F0099514C /* PBXTargetDependency */,
+ );
+ name = CYLGCDRunloopDemoTests;
+ productName = CYLGCDRunloopDemoTests;
+ productReference = 9A0A87B51EE7996F0099514C /* CYLGCDRunloopDemoTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 9A0A87BF1EE7996F0099514C /* CYLGCDRunloopDemoUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9A0A87CF1EE7996F0099514C /* Build configuration list for PBXNativeTarget "CYLGCDRunloopDemoUITests" */;
+ buildPhases = (
+ 9A0A87BC1EE7996F0099514C /* Sources */,
+ 9A0A87BD1EE7996F0099514C /* Frameworks */,
+ 9A0A87BE1EE7996F0099514C /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 9A0A87C21EE7996F0099514C /* PBXTargetDependency */,
+ );
+ name = CYLGCDRunloopDemoUITests;
+ productName = CYLGCDRunloopDemoUITests;
+ productReference = 9A0A87C01EE7996F0099514C /* CYLGCDRunloopDemoUITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 9A0A87941EE7996E0099514C /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0830;
+ ORGANIZATIONNAME = "Elon Chan";
+ TargetAttributes = {
+ 9A0A879B1EE7996E0099514C = {
+ CreatedOnToolsVersion = 8.3.2;
+ DevelopmentTeam = QBMN2BBW3K;
+ ProvisioningStyle = Automatic;
+ };
+ 9A0A87B41EE7996F0099514C = {
+ CreatedOnToolsVersion = 8.3.2;
+ ProvisioningStyle = Automatic;
+ TestTargetID = 9A0A879B1EE7996E0099514C;
+ };
+ 9A0A87BF1EE7996F0099514C = {
+ CreatedOnToolsVersion = 8.3.2;
+ ProvisioningStyle = Automatic;
+ TestTargetID = 9A0A879B1EE7996E0099514C;
+ };
+ };
+ };
+ buildConfigurationList = 9A0A87971EE7996E0099514C /* Build configuration list for PBXProject "CYLGCDRunloopDemo" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 9A0A87931EE7996E0099514C;
+ productRefGroup = 9A0A879D1EE7996E0099514C /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 9A0A879B1EE7996E0099514C /* CYLGCDRunloopDemo */,
+ 9A0A87B41EE7996F0099514C /* CYLGCDRunloopDemoTests */,
+ 9A0A87BF1EE7996F0099514C /* CYLGCDRunloopDemoUITests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 9A0A879A1EE7996E0099514C /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A0A87AC1EE7996E0099514C /* Assets.xcassets in Resources */,
+ 9A0A87AA1EE7996E0099514C /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A0A87B31EE7996F0099514C /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A0A87BE1EE7996F0099514C /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 9A0A87981EE7996E0099514C /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A0A87A71EE7996E0099514C /* ViewController.m in Sources */,
+ 9A0A87A41EE7996E0099514C /* AppDelegate.m in Sources */,
+ 9A0A87D41EE7EC7C0099514C /* Foo.m in Sources */,
+ 9A0A87A11EE7996E0099514C /* main.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A0A87B11EE7996F0099514C /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A0A87BA1EE7996F0099514C /* CYLGCDRunloopDemoTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A0A87BC1EE7996F0099514C /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A0A87C51EE7996F0099514C /* CYLGCDRunloopDemoUITests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 9A0A87B71EE7996F0099514C /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A0A879B1EE7996E0099514C /* CYLGCDRunloopDemo */;
+ targetProxy = 9A0A87B61EE7996F0099514C /* PBXContainerItemProxy */;
+ };
+ 9A0A87C21EE7996F0099514C /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A0A879B1EE7996E0099514C /* CYLGCDRunloopDemo */;
+ targetProxy = 9A0A87C11EE7996F0099514C /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 9A0A87A81EE7996E0099514C /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 9A0A87A91EE7996E0099514C /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 9A0A87C71EE7996F0099514C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 10.3;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 9A0A87C81EE7996F0099514C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 10.3;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 9A0A87CA1EE7996F0099514C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ DEVELOPMENT_TEAM = QBMN2BBW3K;
+ INFOPLIST_FILE = CYLGCDRunloopDemo/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = "Elon-Chan.CYLGCDRunloopDemo";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ 9A0A87CB1EE7996F0099514C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ DEVELOPMENT_TEAM = QBMN2BBW3K;
+ INFOPLIST_FILE = CYLGCDRunloopDemo/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = "Elon-Chan.CYLGCDRunloopDemo";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+ 9A0A87CD1EE7996F0099514C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ INFOPLIST_FILE = CYLGCDRunloopDemoTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = "Elon-Chan.CYLGCDRunloopDemoTests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CYLGCDRunloopDemo.app/CYLGCDRunloopDemo";
+ };
+ name = Debug;
+ };
+ 9A0A87CE1EE7996F0099514C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ INFOPLIST_FILE = CYLGCDRunloopDemoTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = "Elon-Chan.CYLGCDRunloopDemoTests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CYLGCDRunloopDemo.app/CYLGCDRunloopDemo";
+ };
+ name = Release;
+ };
+ 9A0A87D01EE7996F0099514C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ INFOPLIST_FILE = CYLGCDRunloopDemoUITests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = "Elon-Chan.CYLGCDRunloopDemoUITests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_TARGET_NAME = CYLGCDRunloopDemo;
+ };
+ name = Debug;
+ };
+ 9A0A87D11EE7996F0099514C /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ INFOPLIST_FILE = CYLGCDRunloopDemoUITests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = "Elon-Chan.CYLGCDRunloopDemoUITests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_TARGET_NAME = CYLGCDRunloopDemo;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 9A0A87971EE7996E0099514C /* Build configuration list for PBXProject "CYLGCDRunloopDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9A0A87C71EE7996F0099514C /* Debug */,
+ 9A0A87C81EE7996F0099514C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9A0A87C91EE7996F0099514C /* Build configuration list for PBXNativeTarget "CYLGCDRunloopDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9A0A87CA1EE7996F0099514C /* Debug */,
+ 9A0A87CB1EE7996F0099514C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9A0A87CC1EE7996F0099514C /* Build configuration list for PBXNativeTarget "CYLGCDRunloopDemoTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9A0A87CD1EE7996F0099514C /* Debug */,
+ 9A0A87CE1EE7996F0099514C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9A0A87CF1EE7996F0099514C /* Build configuration list for PBXNativeTarget "CYLGCDRunloopDemoUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9A0A87D01EE7996F0099514C /* Debug */,
+ 9A0A87D11EE7996F0099514C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 9A0A87941EE7996E0099514C /* Project object */;
+}
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata"
new file mode 100644
index 0000000..8bb635d
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata"
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/AppDelegate.h" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/AppDelegate.h"
new file mode 100644
index 0000000..a6d42a6
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/AppDelegate.h"
@@ -0,0 +1,17 @@
+//
+// AppDelegate.h
+// CYLGCDRunloopDemo
+//
+// Created by chenyilong on 2017/6/7.
+// Copyright © 2017年 Elon Chan. All rights reserved.
+//
+
+#import
+
+@interface AppDelegate : UIResponder
+
+@property (strong, nonatomic) UIWindow *window;
+
+
+@end
+
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/AppDelegate.m" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/AppDelegate.m"
new file mode 100644
index 0000000..bebc946
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/AppDelegate.m"
@@ -0,0 +1,84 @@
+//
+// AppDelegate.m
+// CYLGCDRunloopDemo
+//
+// Created by chenyilong on 2017/6/7.
+// Copyright © 2017年 Elon Chan. All rights reserved.
+//
+
+#import "AppDelegate.h"
+#import "Foo.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Override point for customization after application launch.
+
+// NSOperationQueue *asyncOperationQueue = [[NSOperationQueue alloc] init];
+// [asyncOperationQueue setMaxConcurrentOperationCount:300];
+// for (int i = 0; i < 300 ; i++) {
+// NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+// NSString *currentThreadName = [NSString stringWithFormat:@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @""];
+// [[NSThread currentThread] setName:@"didFinishLaunchingWithOptions"];
+//// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
+//// NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"");
+//// });
+////NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"");
+//
+//
+//
+//
+// NSThread *networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(ayncThread:) object:@(i)];
+// [networkRequestThread start];
+// NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @(i));
+//
+//
+//// [[Foo new] test];
+//// [[Foo new] begin];
+// }];
+//
+//
+//
+// [asyncOperationQueue addOperation:operation];
+// }
+
+ return YES;
+}
+
+//- (void)ayncThread:(id)i {
+// NSLog(@"💚类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), i);
+//
+//}
+- (void)applicationWillResignActive:(UIApplication *)application {
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
+}
+
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+}
+
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
+}
+
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+}
+
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+}
+
+
+@end
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Contents.json" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Contents.json"
new file mode 100644
index 0000000..bfdf653
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Contents.json"
@@ -0,0 +1,148 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-Small.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-Small@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-Small@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "57x57",
+ "idiom" : "iphone",
+ "filename" : "Icon.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "57x57",
+ "idiom" : "iphone",
+ "filename" : "Icon@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-Small.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-Small@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-40.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "50x50",
+ "idiom" : "ipad",
+ "filename" : "Icon-Small-50.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "50x50",
+ "idiom" : "ipad",
+ "filename" : "Icon-Small-50@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "72x72",
+ "idiom" : "ipad",
+ "filename" : "Icon-72.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "72x72",
+ "idiom" : "ipad",
+ "filename" : "Icon-72@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-76.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-83.5@2x.png",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-40.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-40.png"
new file mode 100644
index 0000000..8f71be1
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-40.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png"
new file mode 100644
index 0000000..8a46471
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png"
new file mode 100644
index 0000000..9f062da
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png"
new file mode 100644
index 0000000..9f062da
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png"
new file mode 100644
index 0000000..bfc2bba
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-72.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-72.png"
new file mode 100644
index 0000000..790c023
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-72.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png"
new file mode 100644
index 0000000..c66d368
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-76.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-76.png"
new file mode 100644
index 0000000..38177b9
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-76.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png"
new file mode 100644
index 0000000..51e9214
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png"
new file mode 100644
index 0000000..c7bf305
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png"
new file mode 100644
index 0000000..55f2bde
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png"
new file mode 100644
index 0000000..cd092e2
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small.png"
new file mode 100644
index 0000000..bdcf2ec
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png"
new file mode 100644
index 0000000..1589d4b
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png"
new file mode 100644
index 0000000..90cf7cc
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon.png"
new file mode 100644
index 0000000..f493505
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon@2x.png"
new file mode 100644
index 0000000..3702bf9
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/AppIcon.appiconset/Icon@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/Contents.json" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/Contents.json"
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/Contents.json"
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Contents.json" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Contents.json"
new file mode 100644
index 0000000..2a92010
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Contents.json"
@@ -0,0 +1,162 @@
+{
+ "images" : [
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "filename" : "Default.png",
+ "orientation" : "portrait",
+ "scale" : "1x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "filename" : "Default@2x.png",
+ "minimum-system-version" : "7.0",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "retina4",
+ "filename" : "Default-568h@2x.png",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "retina4",
+ "filename" : "Default-568h@2x.png",
+ "minimum-system-version" : "7.0",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "filename" : "Default@2x.png",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "to-status-bar",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad.png",
+ "orientation" : "portrait",
+ "scale" : "1x"
+ },
+ {
+ "extent" : "to-status-bar",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad@2x.png",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "to-status-bar",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad~landscape.png",
+ "orientation" : "landscape",
+ "scale" : "1x"
+ },
+ {
+ "extent" : "to-status-bar",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad~landscape@2x.png",
+ "orientation" : "landscape",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad~nostatusbar.png",
+ "minimum-system-version" : "7.0",
+ "orientation" : "portrait",
+ "scale" : "1x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad~nostatusbar.png",
+ "orientation" : "portrait",
+ "scale" : "1x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad~nostatusbar@2x.png",
+ "minimum-system-version" : "7.0",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad~nostatusbar@2x.png",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad~landscape~nostatusbar.png",
+ "minimum-system-version" : "7.0",
+ "orientation" : "landscape",
+ "scale" : "1x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad~landscape~nostatusbar.png",
+ "orientation" : "landscape",
+ "scale" : "1x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad~landscape~nostatusbar@2x.png",
+ "minimum-system-version" : "7.0",
+ "orientation" : "landscape",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "ipad",
+ "filename" : "Default~ipad~landscape~nostatusbar@2x.png",
+ "orientation" : "landscape",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "736h",
+ "filename" : "iPhone6-Plus-portrait@3x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "portrait",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "736h",
+ "filename" : "iPhone6-Plus-landscape@3x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "landscape",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "667h",
+ "filename" : "iPhone6-portrait@2x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png"
new file mode 100644
index 0000000..e174f33
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default.png"
new file mode 100644
index 0000000..e1ffaab
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default@2x.png"
new file mode 100644
index 0000000..f50c007
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad.png"
new file mode 100644
index 0000000..b09bce1
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad@2x.png"
new file mode 100644
index 0000000..8879fe0
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape.png"
new file mode 100644
index 0000000..dfc0616
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape@2x.png"
new file mode 100644
index 0000000..bc1c444
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape~nostatusbar.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape~nostatusbar.png"
new file mode 100644
index 0000000..4509864
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape~nostatusbar.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape~nostatusbar@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape~nostatusbar@2x.png"
new file mode 100644
index 0000000..612db73
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~landscape~nostatusbar@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~nostatusbar.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~nostatusbar.png"
new file mode 100644
index 0000000..38e3b6f
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~nostatusbar.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~nostatusbar@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~nostatusbar@2x.png"
new file mode 100644
index 0000000..7edf4d9
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/Default~ipad~nostatusbar@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/iPhone6-Plus-landscape@3x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/iPhone6-Plus-landscape@3x.png"
new file mode 100644
index 0000000..f09368e
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/iPhone6-Plus-landscape@3x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/iPhone6-Plus-portrait@3x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/iPhone6-Plus-portrait@3x.png"
new file mode 100644
index 0000000..20713d4
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/iPhone6-Plus-portrait@3x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/iPhone6-portrait@2x.png" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/iPhone6-portrait@2x.png"
new file mode 100644
index 0000000..37424d6
Binary files /dev/null and "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Assets.xcassets/LaunchImage.launchimage/iPhone6-portrait@2x.png" differ
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Base.lproj/LaunchScreen.storyboard" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Base.lproj/LaunchScreen.storyboard"
new file mode 100644
index 0000000..fdf3f97
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Base.lproj/LaunchScreen.storyboard"
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Base.lproj/Main.storyboard" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Base.lproj/Main.storyboard"
new file mode 100644
index 0000000..89b68dd
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Base.lproj/Main.storyboard"
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Foo.h" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Foo.h"
new file mode 100644
index 0000000..efd9f70
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Foo.h"
@@ -0,0 +1,13 @@
+//
+// Foo.h
+// CYLGCDRunloopDemo
+//
+// Created by chenyilong on 2017/6/7.
+// Copyright © 2017年 Elon Chan. All rights reserved.
+//
+
+#import
+
+@interface Foo : NSObject
+- (id)test;
+@end
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Foo.m" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Foo.m"
new file mode 100644
index 0000000..ecebc2d
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Foo.m"
@@ -0,0 +1,70 @@
+
+//
+// Foo.m
+// CYLGCDRunloopDemo
+//
+// Created by chenyilong on 2017/6/7.
+// Copyright © 2017年 Elon Chan. All rights reserved.
+//
+
+#import "Foo.h"
+
+@interface Foo() {
+ NSRunLoop *_runloop;
+ NSTimer *_timeoutTimer;
+ NSTimeInterval _timeoutInterval;
+ dispatch_semaphore_t _sem;
+}
+@end
+
+@implementation Foo
+
+- (instancetype)init {
+ if (!(self = [super init])) {
+ return nil;
+ }
+ _timeoutInterval = 1 ;
+ _sem = dispatch_semaphore_create(0);
+ // Do any additional setup after loading the view, typically from a nib.
+ return self;
+}
+
+- (id)test {
+ // 第一种方式:
+ // NSThread *networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint0:) object:nil];
+ // [networkRequestThread start];
+ //第二种方式:
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
+ [self networkRequestThreadEntryPoint0:nil];
+ });
+ dispatch_semaphore_wait(_sem, DISPATCH_TIME_FOREVER);
+ NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"");
+ return @(YES);
+}
+
+- (void)networkRequestThreadEntryPoint0:(id)__unused object {
+ @autoreleasepool {
+ NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), [NSThread currentThread]);
+ [[NSThread currentThread] setName:@"CYLTest"];
+ _runloop = [NSRunLoop currentRunLoop];
+ [_runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
+
+ NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"");
+ _timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(stopLoop) userInfo:nil repeats:NO];
+ [_runloop addTimer:_timeoutTimer forMode:NSRunLoopCommonModes];
+ [_runloop run];//在实际开发中最好使用这种方式来确保能runloop退出,做双重的保障[runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:(timeoutInterval+5)]];
+ NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), [NSThread currentThread]);
+ }
+}
+
+- (void)stopLoop {
+ NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"stop loop");
+ CFRunLoopStop([_runloop getCFRunLoop]);
+ dispatch_semaphore_signal(_sem);
+}
+
+- (void)dealloc {
+ NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"");
+}
+
+@end
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Info.plist" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Info.plist"
new file mode 100644
index 0000000..fe409cb
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/Info.plist"
@@ -0,0 +1,43 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UIMainStoryboardFile
+ Main
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/ViewController.h" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/ViewController.h"
new file mode 100644
index 0000000..f44e180
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/ViewController.h"
@@ -0,0 +1,15 @@
+//
+// ViewController.h
+// CYLGCDRunloopDemo
+//
+// Created by chenyilong on 2017/6/7.
+// Copyright © 2017年 Elon Chan. All rights reserved.
+//
+
+#import
+
+@interface ViewController : UIViewController
+
+
+@end
+
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/ViewController.m" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/ViewController.m"
new file mode 100644
index 0000000..a459b5a
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/ViewController.m"
@@ -0,0 +1,33 @@
+//
+// ViewController.m
+// CYLGCDRunloopDemo
+//
+// Created by chenyilong on 2017/6/7.
+// Copyright © 2017年 Elon Chan. All rights reserved.
+//
+
+#import "ViewController.h"
+#import "Foo.h"
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // NSOperationQueue *asyncOperationQueue = [[NSOperationQueue alloc] init];
+ // [asyncOperationQueue setMaxConcurrentOperationCount:300];
+ // for (int i = 0; i < 300 ; i++) {
+ // NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ // NSString *currentThreadName = [NSString stringWithFormat:@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @""];
+ // [[NSThread currentThread] setName:@"didFinishLaunchingWithOptions"];
+ // [[Foo new] test];
+ // }];
+ // [asyncOperationQueue addOperation:operation];
+ // }
+ for (int i = 0; i < 300 ; i++) {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
+ [[Foo new] test];
+ NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"");
+ });
+ }
+}
+@end
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/main.m" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/main.m"
new file mode 100644
index 0000000..1a265f3
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemo/main.m"
@@ -0,0 +1,16 @@
+//
+// main.m
+// CYLGCDRunloopDemo
+//
+// Created by chenyilong on 2017/6/7.
+// Copyright © 2017年 Elon Chan. All rights reserved.
+//
+
+#import
+#import "AppDelegate.h"
+
+int main(int argc, char * argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoTests/CYLGCDRunloopDemoTests.m" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoTests/CYLGCDRunloopDemoTests.m"
new file mode 100644
index 0000000..f73df81
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoTests/CYLGCDRunloopDemoTests.m"
@@ -0,0 +1,39 @@
+//
+// CYLGCDRunloopDemoTests.m
+// CYLGCDRunloopDemoTests
+//
+// Created by chenyilong on 2017/6/7.
+// Copyright © 2017年 Elon Chan. All rights reserved.
+//
+
+#import
+
+@interface CYLGCDRunloopDemoTests : XCTestCase
+
+@end
+
+@implementation CYLGCDRunloopDemoTests
+
+- (void)setUp {
+ [super setUp];
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+}
+
+- (void)tearDown {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ [super tearDown];
+}
+
+- (void)testExample {
+ // This is an example of a functional test case.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+}
+
+- (void)testPerformanceExample {
+ // This is an example of a performance test case.
+ [self measureBlock:^{
+ // Put the code you want to measure the time of here.
+ }];
+}
+
+@end
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoTests/Info.plist" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoTests/Info.plist"
new file mode 100644
index 0000000..6c6c23c
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoTests/Info.plist"
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoUITests/CYLGCDRunloopDemoUITests.m" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoUITests/CYLGCDRunloopDemoUITests.m"
new file mode 100644
index 0000000..fa179e8
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoUITests/CYLGCDRunloopDemoUITests.m"
@@ -0,0 +1,40 @@
+//
+// CYLGCDRunloopDemoUITests.m
+// CYLGCDRunloopDemoUITests
+//
+// Created by chenyilong on 2017/6/7.
+// Copyright © 2017年 Elon Chan. All rights reserved.
+//
+
+#import
+
+@interface CYLGCDRunloopDemoUITests : XCTestCase
+
+@end
+
+@implementation CYLGCDRunloopDemoUITests
+
+- (void)setUp {
+ [super setUp];
+
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ // In UI tests it is usually best to stop immediately when a failure occurs.
+ self.continueAfterFailure = NO;
+ // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
+ [[[XCUIApplication alloc] init] launch];
+
+ // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
+}
+
+- (void)tearDown {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ [super tearDown];
+}
+
+- (void)testExample {
+ // Use recording to get started writing UI tests.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+}
+
+@end
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoUITests/Info.plist" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoUITests/Info.plist"
new file mode 100644
index 0000000..6c6c23c
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/CYLGCDRunloopDemoUITests/Info.plist"
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/Podfile" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/Podfile"
new file mode 100644
index 0000000..92b02c6
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/CYLGCDRunloopDemo/Podfile"
@@ -0,0 +1,27 @@
+# Uncomment the next line to define a global platform for your project
+# platform :ios, '9.0'
+
+# 指定 Master 仓库和阿里云仓库
+source 'https://github.com/CocoaPods/Specs.git'
+source 'https://github.com/aliyun/aliyun-specs.git'
+
+
+target 'CYLGCDRunloopDemo' do
+ # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
+ # use_frameworks!
+
+ pod 'AlicloudFeedback', '~> 3.0.1'
+
+ # Pods for CYLGCDRunloopDemo
+
+ target 'CYLGCDRunloopDemoTests' do
+ inherit! :search_paths
+ # Pods for testing
+ end
+
+ target 'CYLGCDRunloopDemoUITests' do
+ inherit! :search_paths
+ # Pods for testing
+ end
+
+end
diff --git "a/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213.md" "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213.md"
new file mode 100644
index 0000000..624fa15
--- /dev/null
+++ "b/Tips/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213/\351\201\277\345\205\215\344\275\277\347\224\250GCD-Global\351\230\237\345\210\227\345\210\233\345\273\272Runloop\345\270\270\351\251\273\347\272\277\347\250\213.md"
@@ -0,0 +1,336 @@
+# 避免使用 GCD Global队列创建Runloop常驻线程
+
+本文对应 Demo 以及 Markdown 文件在[仓库中](https://github.com/ChenYilong/iOSBlog/tree/master/Tips/避免使用GCD-Global队列创建Runloop常驻线程),文中的错误可以提 PR 到这个文件,我会及时更改。
+
+## 目录
+
+
+
+
+- [避免使用 GCD Global队列创建Runloop常驻线程](#%E9%81%BF%E5%85%8D%E4%BD%BF%E7%94%A8-gcd-global%E9%98%9F%E5%88%97%E5%88%9B%E5%BB%BArunloop%E5%B8%B8%E9%A9%BB%E7%BA%BF%E7%A8%8B)
+ - [GCD Global队列创建线程进行耗时操作的风险](#gcd-global%E9%98%9F%E5%88%97%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B%E8%BF%9B%E8%A1%8C%E8%80%97%E6%97%B6%E6%93%8D%E4%BD%9C%E7%9A%84%E9%A3%8E%E9%99%A9)
+ - [避免使用 GCD Global 队列创建 Runloop 常驻线程](#%E9%81%BF%E5%85%8D%E4%BD%BF%E7%94%A8-gcd-global-%E9%98%9F%E5%88%97%E5%88%9B%E5%BB%BA-runloop-%E5%B8%B8%E9%A9%BB%E7%BA%BF%E7%A8%8B)
+ - [单一 Runloop 常驻线程](#%E5%8D%95%E4%B8%80-runloop-%E5%B8%B8%E9%A9%BB%E7%BA%BF%E7%A8%8B)
+ - [多个 Runloop 常驻线程](#%E5%A4%9A%E4%B8%AA-runloop-%E5%B8%B8%E9%A9%BB%E7%BA%BF%E7%A8%8B)
+
+
+
+
+## GCD Global队列创建线程进行耗时操作的风险
+
+先思考下如下几个问题:
+
+ - 新建线程的方式有哪些?各自的优缺点是什么?
+ - dispatch_async 函数分发到全局队列一定会新建线程执行任务么?
+ - 如果全局队列对应的线程池如果满了,后续的派发的任务会怎么处置?有什么风险?
+
+答案大致是这样的:dispatch_async 函数分发到全局队列不一定会新建线程执行任务,全局队列底层有一个的线程池,如果线程池满了,那么后续的任务会被 block 住,等待前面的任务执行完成,才会继续执行。如果线程池中的线程长时间不结束,后续堆积的任务会越来越多,此时就会存在 APP crash的风险。
+
+ 比如:
+
+
+```objective-c
+- (void)dispatchTest1 {
+ for (NSInteger i = 0; i< 10000 ; i++) {
+dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ [self dispatchTask:i];
+ });
+ }
+}
+
+- (void)dispatchTask:(NSInteger)index {
+ //模拟耗时操作,比如DB,网络,文件读写等等
+ sleep(30);
+ NSLog(@"----:%ld",index);
+}
+```
+
+以上逻辑用真机测试会有卡死的几率,并非每次都会发生,但多尝试几次就会复现,伴随前后台切换,crash几率增大。
+
+
+
+下面做一下分析:
+
+参看 GCD 源码我们可以看到全局队列的相关源码如下:
+
+ ``` c
+ DISPATCH_NOINLINE
+static void
+_dispatch_queue_wakeup_global_slow(dispatch_queue_t dq, unsigned int n)
+{
+ dispatch_root_queue_context_t qc = dq->do_ctxt;
+ uint32_t i = n;
+ int r;
+
+ _dispatch_debug_root_queue(dq, __func__);
+ dispatch_once_f(&_dispatch_root_queues_pred, NULL,
+ _dispatch_root_queues_init);
+
+#if HAVE_PTHREAD_WORKQUEUES
+#if DISPATCH_USE_PTHREAD_POOL
+ if (qc->dgq_kworkqueue != (void*)(~0ul))
+#endif
+ {
+ _dispatch_root_queue_debug("requesting new worker thread for global "
+ "queue: %p", dq);
+#if DISPATCH_USE_LEGACY_WORKQUEUE_FALLBACK
+ if (qc->dgq_kworkqueue) {
+ pthread_workitem_handle_t wh;
+ unsigned int gen_cnt;
+ do {
+ r = pthread_workqueue_additem_np(qc->dgq_kworkqueue,
+ _dispatch_worker_thread4, dq, &wh, &gen_cnt);
+ (void)dispatch_assume_zero(r);
+ } while (--i);
+ return;
+ }
+#endif // DISPATCH_USE_LEGACY_WORKQUEUE_FALLBACK
+#if HAVE_PTHREAD_WORKQUEUE_SETDISPATCH_NP
+ if (!dq->dq_priority) {
+ r = pthread_workqueue_addthreads_np(qc->dgq_wq_priority,
+ qc->dgq_wq_options, (int)i);
+ (void)dispatch_assume_zero(r);
+ return;
+ }
+#endif
+#if HAVE_PTHREAD_WORKQUEUE_QOS
+ r = _pthread_workqueue_addthreads((int)i, dq->dq_priority);
+ (void)dispatch_assume_zero(r);
+#endif
+ return;
+ }
+#endif // HAVE_PTHREAD_WORKQUEUES
+#if DISPATCH_USE_PTHREAD_POOL
+ dispatch_pthread_root_queue_context_t pqc = qc->dgq_ctxt;
+ if (fastpath(pqc->dpq_thread_mediator.do_vtable)) {
+ while (dispatch_semaphore_signal(&pqc->dpq_thread_mediator)) {
+ if (!--i) {
+ return;
+ }
+ }
+ }
+ uint32_t j, t_count;
+ // seq_cst with atomic store to tail
+ t_count = dispatch_atomic_load2o(qc, dgq_thread_pool_size, seq_cst);
+ do {
+ if (!t_count) {
+ _dispatch_root_queue_debug("pthread pool is full for root queue: "
+ "%p", dq);
+ return;
+ }
+ j = i > t_count ? t_count : i;
+ } while (!dispatch_atomic_cmpxchgvw2o(qc, dgq_thread_pool_size, t_count,
+ t_count - j, &t_count, acquire));
+
+ pthread_attr_t *attr = &pqc->dpq_thread_attr;
+ pthread_t tid, *pthr = &tid;
+#if DISPATCH_ENABLE_PTHREAD_ROOT_QUEUES
+ if (slowpath(dq == &_dispatch_mgr_root_queue)) {
+ pthr = _dispatch_mgr_root_queue_init();
+ }
+#endif
+ do {
+ _dispatch_retain(dq);
+ while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) {
+ if (r != EAGAIN) {
+ (void)dispatch_assume_zero(r);
+ }
+ _dispatch_temporary_resource_shortage();
+ }
+ } while (--j);
+#endif // DISPATCH_USE_PTHREAD_POOL
+}
+
+ ```
+
+
+对于执行的任务来说,所执行的线程具体是哪个线程,则是通过 GCD 的线程池(Thread Pool)来进行调度,正如[Concurrent Programming: APIs and Challenges](https://www.objc.io/issues/2-concurrency/concurrency-apis-and-pitfalls/)文章里给的示意图所示:
+
+
+
+
+上面贴的源码,我们关注如下的部分:
+
+其中有一个用来记录线程池大小的字段 `dgq_thread_pool_size`。这个字段标记着GCD线程池的大小。摘录上面源码的一部分:
+
+ ```c
+ uint32_t j, t_count;
+ // seq_cst with atomic store to tail
+ t_count = dispatch_atomic_load2o(qc, dgq_thread_pool_size, seq_cst);
+ do {
+ if (!t_count) {
+ _dispatch_root_queue_debug("pthread pool is full for root queue: "
+ "%p", dq);
+ return;
+ }
+ j = i > t_count ? t_count : i;
+ } while (!dispatch_atomic_cmpxchgvw2o(qc, dgq_thread_pool_size, t_count,
+ t_count - j, &t_count, acquire));
+
+ ```
+
+
+从源码中我们可以对应到[官方文档 :Getting the Global Concurrent Dispatch Queues](https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW5)里的说法:
+
+> A concurrent dispatch queue is useful when you have multiple tasks that can run in parallel. A concurrent queue is still a queue in that it dequeues tasks in a first-in, first-out order; however, a concurrent queue may dequeue additional tasks before any previous tasks finish. The actual number of tasks executed by a concurrent queue at any given moment is variable and can change dynamically as conditions in your application change. Many factors affect the number of tasks executed by the concurrent queues, including the number of available cores, the amount of work being done by other processes, and the number and priority of tasks in other serial dispatch queues.
+
+也就是说:
+
+全局队列的底层是一个线程池,向全局队列中提交的 block,都会被放到这个线程池中执行,如果线程池已满,后续再提交 block 就不会再重新创建线程。这就是为什么 Demo 会造成卡顿甚至冻屏的原因。
+
+
+
+## 避免使用 GCD Global 队列创建 Runloop 常驻线程
+
+在做网路请求时我们常常创建一个 Runloop 常驻线程用来接收、响应后续的服务端回执,比如NSURLConnection、AFNetworking等等,我们可以称这种线程为 Runloop 常驻线程。
+
+正如上文所述,用 GCD Global 队列创建线程进行耗时操作是存在风险的。那么我们可以试想下,如果这个耗时操作变成了 runloop 常驻线程,会是什么结果?下面做一下分析:
+
+先介绍下 Runloop 常驻线程的原理,在开发中一般有两种用法:
+
+ - 单一 Runloop 常驻线程:在 APP 的生命周期中开启了唯一的常驻线程来进行网络请求,常用于网络库,或者有维持长连接需求的库,比如: AFNetworking 、 [SocketRocket](https://github.com/facebook/SocketRocket)。
+ - 多个 Runloop 常驻线程:每进行一次网络请求就开启一条 Runloop 常驻线程,这条线程的生命周期的起点是网络请求开始,终点是网络请求结束,或者网络请求超时。
+
+
+### 单一 Runloop 常驻线程
+先说第一种用法:
+
+以 AFNetworking 为例,[AFURLConnectionOperation](https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking%2FAFURLConnectionOperation.m) 这个类是基于 NSURLConnection 构建的,其希望能在后台线程接收 Delegate 回调。为此 AFNetworking 单独创建了一个线程,并在这个线程中启动了一个 RunLoop:
+
+```objective-c
++ (void)networkRequestThreadEntryPoint:(id)__unused object {
+ @autoreleasepool {
+ [[NSThread currentThread] setName:@"AFNetworking"];
+ NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
+ [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
+ [runLoop run];
+ }
+}
+
++ (NSThread *)networkRequestThread {
+ static NSThread *_networkRequestThread = nil;
+ static dispatch_once_t oncePredicate;
+ dispatch_once(&oncePredicate, ^{
+ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
+ [_networkRequestThread start];
+ });
+ return _networkRequestThread;
+}
+```
+
+### 多个 Runloop 常驻线程
+
+第二种用法,我写了一个小 Demo 来模拟这种场景,
+
+我们模拟了一个场景:假设所有的网络请求全部超时,或者服务端根本不响应,然后网络库超时检测机制的做法:
+
+```objective-c
+#import "Foo.h"
+
+@interface Foo() {
+ NSRunLoop *_runloop;
+ NSTimer *_timeoutTimer;
+ NSTimeInterval _timeoutInterval;
+ dispatch_semaphore_t _sem;
+}
+@end
+
+@implementation Foo
+
+- (instancetype)init {
+ if (!(self = [super init])) {
+ return nil;
+ }
+ _timeoutInterval = 1 ;
+ _sem = dispatch_semaphore_create(0);
+ // Do any additional setup after loading the view, typically from a nib.
+ return self;
+}
+
+- (id)test {
+ // 第一种方式:
+ // NSThread *networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint0:) object:nil];
+ // [networkRequestThread start];
+ //第二种方式:
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
+ [self networkRequestThreadEntryPoint0:nil];
+ });
+ dispatch_semaphore_wait(_sem, DISPATCH_TIME_FOREVER);
+ return @(YES);
+}
+
+- (void)networkRequestThreadEntryPoint0:(id)__unused object {
+ @autoreleasepool {
+ [[NSThread currentThread] setName:@"CYLTest"];
+ _runloop = [NSRunLoop currentRunLoop];
+ [_runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
+ _timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(stopLoop) userInfo:nil repeats:NO];
+ [_runloop addTimer:_timeoutTimer forMode:NSRunLoopCommonModes];
+ [_runloop run];//在实际开发中最好使用这种方式来确保能runloop退出,做双重的保障[runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:(timeoutInterval+5)]];
+ }
+}
+
+- (void)stopLoop {
+ CFRunLoopStop([_runloop getCFRunLoop]);
+ dispatch_semaphore_signal(_sem);
+}
+
+@end
+```
+
+
+如果
+
+```objective-c
+ for (int i = 0; i < 300 ; i++) {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
+ [[Foo new] test];
+ NSLog(@"🔴类名与方法名:%@(在第%@行),描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"");
+ });
+ }
+```
+
+以上逻辑用真机测试会有卡死的几率,并非每次都会发生,但多尝试几次就会复现,伴随前后台切换,crash几率增大。
+
+其中我们采用了 GCD 全局队列的方式来创建常驻线程,因为在创建时可能已经出现了全局队列的线程池满了的情况,所以 GCD 派发的任务,无法执行,而且我们把超时检测的逻辑放进了这个任务中,所以导致的情况就是,有很多任务的超时检测功能失效了。此时就只能依赖于服务端响应来结束该任务(服务端响应能结束该任务的逻辑在 Demo 中未给出),但是如果再加之服务端不响应,那么任务就永远不会结束。后续的网络请求也会就此 block 住,造成 crash。
+
+如果我们把 GCD 全局队列换成 NSThread 的方式,那么就可以保证每次都会创建新的线程。
+
+
+注意:文章中只演示的是超时 cancel runloop 的操作,实际项目中一定有其他主动 cancel runloop 的操作,就比如网络请求成功或失败后需要进行cancel操作。代码中没有展示网络请求成功或失败后的 cancel 操作。
+
+
+Demo 的这种模拟可能比较极端,但是如果你维护的是一个像 AFNetworking 这样的一个网络库,你会放心把创建常驻线程这样的操作交给 GCD 全局队列吗?因为整个 APP 是在共享一个全局队列的线程池,那么如果 APP 把线程池沾满了,甚至线程池长时间占满且不结束,那么 AFNetworking 就自然不能再执行任务了,所以我们看到,即使是只会创建一条常驻线程, AFNetworking 依然采用了 NSThread 的方式而非 GCD 全局队列这种方式。
+
+注释:以下方法存在于老版本[AFN 2.x](https://github.com/AFNetworking/AFNetworking/tree/2.x) 中。
+
+
+```objective-c
++ (void)networkRequestThreadEntryPoint:(id)__unused object {
+ @autoreleasepool {
+ [[NSThread currentThread] setName:@"AFNetworking"];
+ NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
+ [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
+ [runLoop run];
+ }
+}
+
++ (NSThread *)networkRequestThread {
+ static NSThread *_networkRequestThread = nil;
+ static dispatch_once_t oncePredicate;
+ dispatch_once(&oncePredicate, ^{
+ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
+ [_networkRequestThread start];
+ });
+ return _networkRequestThread;
+}
+```
+
+正如你所看到的,没有任何一个库会用 GCD 全局队列来创建常驻线程,而你也应该
+
+ > 避免使用 GCD Global 队列来创建 Runloop 常驻线程。
+
+
+
+
+