diff --git a/_config.yml b/_config.yml
index 409f2d263dc..3818a53d5e3 100644
--- a/_config.yml
+++ b/_config.yml
@@ -1,28 +1,28 @@
# Site settings
-title: BY Blog
-SEOTitle: 柏荧的博客 | BY Blog
+title: Hax的学习笔记
+SEOTitle: Hax的学习笔记 | Haxwwwww Blog
header-img: img/post-bg-desk.jpg
-email: qiubaiying@gmail.com
+#email: qiubaiying@gmail.com
description: "Every failure is leading towards success."
-keyword: "BY, BY Blog, 柏荧的博客, qiubaiying, 邱柏荧, iOS, Apple, iPhone"
-url: "http://qiubaiying.github.io" # your host, for absolute URL
+keyword: "Haxwwwww, Haxwwwww Blog, 前端, JavaScript"
+url: "http://Haxwwwww.github.io" # your host, for absolute URL
baseurl: "" # for example, '/blog' if your blog hosted on 'host/blog'
-github_repo: "https://github.com/qiubaiying/qiubaiying.github.io.git" # you code repository
+#github_repo: "https://github.com/qiubaiying/qiubaiying.github.io.git" # you code repository
# Sidebar settings
sidebar: true # whether or not using Sidebar.
sidebar-about-description: "Goals determine what you going to be!"
-sidebar-avatar: /img/about-BY-gentle.jpg # use absolute URL, seeing it's used in both `/` and `/about/`
+sidebar-avatar: /img/Jeff.jpg # use absolute URL, seeing it's used in both `/` and `/about/`
# SNS settings
-RSS: false
+#RSS: false
# weibo_username: qiubaiying
-zhihu_username: qiubaiying
-github_username: qiubaiying
-facebook_username: baiying.qiu.7
-jianshu_username: e71990ada2fd
+# zhihu_username: qiubaiying
+# github_username: qiubaiying
+# facebook_username: baiying.qiu.7
+# jianshu_username: e71990ada2fd
#twitter_username: qiubaiying
@@ -60,33 +60,33 @@ kramdown:
# disqus_username: qiubaiying
# Gitalk
-gitalk:
- enable: true #是否开启Gitalk评论
- clientID: f2c84e7629bb1446c1a4 #生成的clientID
- clientSecret: ca6d6139d1e1b8c43f8b2e19492ddcac8b322d0d #生成的clientSecret
- repo: qiubaiying.github.io #仓库名称
- owner: qiubaiying #github用户名
- admin: qiubaiying
- distractionFreeMode: true #是否启用类似FB的阴影遮罩
+#gitalk:
+ #enable: true #是否开启Gitalk评论
+ #clientID: f2c84e7629bb1446c1a4 #生成的clientID
+ #clientSecret: ca6d6139d1e1b8c43f8b2e19492ddcac8b322d0d #生成的clientSecret
+ #repo: qiubaiying.github.io #仓库名称
+ #owner: qiubaiying #github用户名
+ #admin: qiubaiying
+ #distractionFreeMode: true #是否启用类似FB的阴影遮罩
# 统计
# Analytics settings
# Baidu Analytics
-ba_track_id: b50bf2b12b5338a1845e33832976fd68
+#ba_track_id: b50bf2b12b5338a1845e33832976fd68
# Google Analytics
-ga_track_id: 'UA-90855596-1' # Format: UA-xxxxxx-xx
-ga_domain: qiubaiying.top # 默认的是 auto, 这里我是自定义了的域名,你如果没有自己的域名,需要改成auto
+#ga_track_id: 'UA-90855596-1' # Format: UA-xxxxxx-xx
+#ga_domain: qiubaiying.top # 默认的是 auto, 这里我是自定义了的域名,你如果没有自己的域名,需要改成auto
# Featured Tags
-featured-tags: true # 是否使用首页标签
-featured-condition-size: 1 # 相同标签数量大于这个数,才会出现在首页
+# featured-tags: true # 是否使用首页标签
+# featured-condition-size: 1 # 相同标签数量大于这个数,才会出现在首页
@@ -97,18 +97,18 @@ service-worker: true
# Friends
-friends: [
- {
- title: "WY",
- href: "http://zhengwuyang.com"
- },{
- title: "简书·BY",
- href: "http://www.jianshu.com/u/e71990ada2fd"
- },{
- title: "Apple",
- href: "https://apple.com"
- },{
- title: "Apple Developer",
- href: "https://developer.apple.com/"
- }
-]
+# friends: [
+# {
+# title: "WY",
+# href: "http://zhengwuyang.com"
+# },{
+# title: "简书·BY",
+# href: "http://www.jianshu.com/u/e71990ada2fd"
+# },{
+# title: "Apple",
+# href: "https://apple.com"
+# },{
+# title: "Apple Developer",
+# href: "https://developer.apple.com/"
+# }
+# ]
diff --git a/_includes/head.html b/_includes/head.html
index fbf7a47cf08..bd0710ac452 100644
--- a/_includes/head.html
+++ b/_includes/head.html
@@ -8,7 +8,7 @@
{% if page.title %}{{ page.title }} - {{ site.SEOTitle }}{% else %}{{ site.SEOTitle }}{% endif %}
-
+
diff --git a/_layouts/post.html b/_layouts/post.html
index ae5ac8b25d3..29fe10a248b 100644
--- a/_layouts/post.html
+++ b/_layouts/post.html
@@ -225,4 +225,4 @@ FRIENDS
}
}
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git "a/_posts/2016-01-06-ReactiveCocoa-\350\277\233\351\230\266.md" "b/_posts/2016-01-06-ReactiveCocoa-\350\277\233\351\230\266.md"
deleted file mode 100644
index 13c16abd945..00000000000
--- "a/_posts/2016-01-06-ReactiveCocoa-\350\277\233\351\230\266.md"
+++ /dev/null
@@ -1,1518 +0,0 @@
----
-layout: post
-title: ReactiveCocoa 进阶
-subtitle: 函数式编程框架 ReactiveCocoa 进阶
-date: 2017-01-06
-author: BY
-header-img: img/post-bg-ios9-web.jpg
-catalog: true
-tags:
- - iOS
- - ReactiveCocoa
- - 函数式编程
- - 开源框架
----
-# 前言
-
->在[上篇文章](http://qiubaiying.github.io/2016/12/26/ReactiveCocoa-基础/)中介绍了**ReactiveCocoa**的基础知识,接下来我们来深入介绍**ReactiveCocoa**及其在**MVVM**中的用法。
-
-
-![ReactiveCocoa进阶思维导图](https://ww3.sinaimg.cn/large/006y8lVagw1fbgye3re5xj30je0iomz8.jpg)
-# 常见操作方法介绍
-
-
-#### 操作须知
-
-所有的信号(RACSignal)都可以进行操作处理,因为所有操作方法都定义在RACStream.h中,因此只要继承RACStream就有了操作处理方法。
-#### 操作思想
-
-运用的是Hook(钩子)思想,Hook是一种用于改变API(应用程序编程接口:方法)执行结果的技术.
-
-Hook用处:截获API调用的技术。
-
-有关Hook的知识可以看我的这篇博客[《Objective-C Runtime 的一些基本使用》](http://www.jianshu.com/p/ff114e69cc0a)中的 *更换代码的实现方法* 一节,
-
-Hook原理:在每次调用一个API返回结果之前,先执行你自己的方法,改变结果的输出。
-
-#### 操作方法
-
-#### **bind**(绑定)- ReactiveCocoa核心方法
-
-**ReactiveCocoa** 操作的核心方法是 **bind**(绑定),而且也是RAC中核心开发方式。之前的开发方式是赋值,而用RAC开发,应该把重心放在绑定,也就是可以在创建一个对象的时候,就绑定好以后想要做的事情,而不是等赋值之后在去做事情。
-
-列如,把数据展示到控件上,之前都是重写控件的 `setModel` 方法,用RAC就可以在一开始创建控件的时候,就绑定好数据。
-
-- **作用**
-
- RAC底层都是调用**bind**, 在开发中很少直接使用 **bind** 方法,**bind**属于RAC中的底层方法,我们只需要调用封装好的方法,**bind**用作了解即可.
-
-- **bind方法使用步骤**
- 1. 传入一个返回值 `RACStreamBindBlock` 的 block。
- 2. 描述一个 `RACStreamBindBlock` 类型的 `bindBlock`作为block的返回值。
- 3. 描述一个返回结果的信号,作为 `bindBlock` 的返回值。
-
- 注意:在bindBlock中做信号结果的处理。
-- **bind方法参数**
-
- **RACStreamBindBlock**:
-`typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop);`
-
- `参数一(value)`:表示接收到信号的原始值,还没做处理
-
- `参数二(*stop)`:用来控制绑定Block,如果*stop = yes,那么就会结束绑定。
-
- `返回值`:信号,做好处理,在通过这个信号返回出去,一般使用 `RACReturnSignal`,需要手动导入头文件`RACReturnSignal.h`
-
-- **使用**
-
- 假设想监听文本框的内容,并且在每次输出结果的时候,都在文本框的内容拼接一段文字“输出:”
-
- - 使用封装好的方法:在返回结果后,拼接。
-
- ```
- [_textField.rac_textSignal subscribeNext:^(id x) {
-
- // 在返回结果后,拼接 输出:
- NSLog(@"输出:%@",x);
-
- }];
- ```
-
-
- - 方式二:,使用RAC中 `bind` 方法做处理,在返回结果前,拼接。
-
- 这里需要手动导入`#import `,才能使用`RACReturnSignal`
-
- ```
- [[_textField.rac_textSignal bind:^RACStreamBindBlock{
- // 什么时候调用:
- // block作用:表示绑定了一个信号.
-
- return ^RACStream *(id value, BOOL *stop){
-
- // 什么时候调用block:当信号有新的值发出,就会来到这个block。
-
- // block作用:做返回值的处理
-
- // 做好处理,在返回结果前,拼接 输出:
- return [RACReturnSignal return:[NSString stringWithFormat:@"输出:%@",value]];
- };
-
- }] subscribeNext:^(id x) {
-
- NSLog(@"%@",x);
-
- }];
-
- ```
-
-- **底层实现**
- 1. 源信号调用bind,会重新创建一个绑定信号。
- 2. 当绑定信号被订阅,就会调用绑定信号中的 `didSubscribe` ,生成一个 `bindingBlock` 。
- 3. 当源信号有内容发出,就会把内容传递到 `bindingBlock` 处理,调用`bindingBlock(value,stop)`
- 4. 调用`bindingBlock(value,stop)`,会返回一个内容处理完成的信号`RACReturnSignal`。
- 5. 订阅`RACReturnSignal`,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
-
- 注意:不同订阅者,保存不同的nextBlock,看源码的时候,一定要看清楚订阅者是哪个。
-
-#### 映射
-
-映射主要用这两个方法实现:**flattenMap**,**Map**,用于把源信号内容映射成新的内容。
-
-###### flattenMap
-
-- **作用**
-
- 把源信号的内容映射成一个新的信号,信号可以是任意类型
-
-- **使用步骤**
-
- 1. 传入一个block,block类型是返回值`RACStream`,参数value
- 2. 参数value就是源信号的内容,拿到源信号的内容做处理
- 3. 包装成`RACReturnSignal`信号,返回出去。
-
-
-
-- **使用**
-
- 监听文本框的内容改变,把结构重新映射成一个新值.
-
- ```
- [[_textField.rac_textSignal flattenMap:^RACStream *(id value) {
-
- // block调用时机:信号源发出的时候
-
- // block作用:改变信号的内容
-
- // 返回RACReturnSignal
- return [RACReturnSignal return:[NSString stringWithFormat:@"信号内容:%@", value]];
-
- }] subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
- ```
-- **底层实现**
-
- 0. **flattenMap**内部调用 `bind` 方法实现的,**flattenMap**中block的返回值,会作为bind中bindBlock的返回值。
- 1. 当订阅绑定信号,就会生成 `bindBlock`。
- 2. 当源信号发送内容,就会调用` bindBlock(value, *stop)`
- 3. 调用`bindBlock`,内部就会调用 **flattenMap** 的 bloc k,**flattenMap** 的block作用:就是把处理好的数据包装成信号。
- 4. 返回的信号最终会作为 `bindBlock` 中的返回信号,当做 `bindBlock` 的返回信号。
- 5. 订阅 `bindBlock` 的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
-
-###### Map
-
-- **作用**
-
- 把源信号的值映射成一个新的值
-
-
-- **使用步骤**
- 1. 传入一个block,类型是返回对象,参数是 `value`
- 2. `value`就是源信号的内容,直接拿到源信号的内容做处理
- 3. 把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值。
-
-- **使用**
-
- 监听文本框的内容改变,把结构重新映射成一个新值.
-
- ```
- [[_textField.rac_textSignal map:^id(id value) {
-
- // 拼接完后,返回对象
- return [NSString stringWithFormat:@"信号内容: %@", value];
-
- }] subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
- ```
-- **底层实现**:
- 0. Map底层其实是调用 `flatternMa`p,`Map` 中block中的返回的值会作为 `flatternMap` 中block中的值
- 1. 当订阅绑定信号,就会生成 `bindBlock`
- 3. 当源信号发送内容,就会调用 `bindBlock(value, *stop)`
- 4. 调用 `bindBlock` ,内部就会调用 `flattenMap的block`
- 5. `flattenMap的block` 内部会调用 `Map` 中的block,把 `Map` 中的block返回的内容包装成返回的信号
- 5. 返回的信号最终会作为 `bindBlock` 中的返回信号,当做 `bindBlock` 的返回信号
- 6. 订阅 `bindBlock` 的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
-
-###### FlatternMap 和 Map 的区别
-- **FlatternMap** 中的Block **返回信号**。
-2. **Map** 中的Block **返回对象**。
-3. 开发中,如果信号发出的值 **不是信号** ,映射一般使用 `Map`
-4. 如果信号发出的值 **是信号**,映射一般使用 `FlatternMap`。
-
-
-
-- `signalOfsignals`用 **FlatternMap**
-
- ```
- // 创建信号中的信号
- RACSubject *signalOfsignals = [RACSubject subject];
- RACSubject *signal = [RACSubject subject];
-
- [[signalOfsignals flattenMap:^RACStream *(id value) {
-
- // 当signalOfsignals的signals发出信号才会调用
-
- return value;
-
- }] subscribeNext:^(id x) {
-
- // 只有signalOfsignals的signal发出信号才会调用,因为内部订阅了bindBlock中返回的信号,也就是flattenMap返回的信号。
- // 也就是flattenMap返回的信号发出内容,才会调用。
-
- NSLog(@"signalOfsignals:%@",x);
- }];
-
- // 信号的信号发送信号
- [signalOfsignals sendNext:signal];
-
- // 信号发送内容
- [signal sendNext:@"hi"];
-
- ```
-
-#### 组合
-
-组合就是将多个信号按照某种规则进行拼接,合成新的信号。
-
-###### concat
-
-- **作用**
-
- 按**顺序拼接**信号,当多个信号发出的时候,有顺序的接收信号。
-- **底层实现**
- 1. 当拼接信号被订阅,就会调用拼接信号的didSubscribe
- 2. didSubscribe中,会先订阅第一个源信号(signalA)
- 3. 会执行第一个源信号(signalA)的didSubscribe
- 4. 第一个源信号(signalA)didSubscribe中发送值,就会调用第一个源信号(signalA)订阅者的nextBlock,通过拼接信号的订阅者把值发送出来.
- 5. 第一个源信号(signalA)didSubscribe中发送完成,就会调用第一个源信号(signalA)订阅者的completedBlock,订阅第二个源信号(signalB)这时候才激活(signalB)。
- 6. 订阅第二个源信号(signalB),执行第二个源信号(signalB)的didSubscribe
- 7. 第二个源信号(signalA)didSubscribe中发送值,就会通过拼接信号的订阅者把值发送出来.
-- **使用步骤**
-
- 1. 使用`concat:`拼接信号
- 2. 订阅拼接信号,内部会自动按拼接顺序订阅信号
-- **使用**
-
- 拼接信号 `signalA`、 `signalB`、 `signalC`
-
- ```
- RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"Hello"];
-
- [subscriber sendCompleted];
-
- return nil;
- }];
-
- RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"World"];
-
- [subscriber sendCompleted];
-
- return nil;
- }];
-
- RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"!"];
-
- [subscriber sendCompleted];
-
- return nil;
- }];
-
- // 拼接 A B, 把signalA拼接到signalB后,signalA发送完成,signalB才会被激活。
- RACSignal *concatSignalAB = [signalA concat:signalB];
-
- // A B + C
- RACSignal *concatSignalABC = [concatSignalAB concat:signalC];
-
-
- // 订阅拼接的信号, 内部会按顺序订阅 A->B->C
- // 注意:第一个信号必须发送完成,第二个信号才会被激活...
- [concatSignalABC subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
- ```
-
-###### then
-- **作用**
-
- 用于连接两个信号,当第一个信号完成,才会连接then返回的信号。
-- **底层实现**
-
- 1. 先过滤掉之前的信号发出的值
- 2. 使用concat连接then返回的信号
-
-- **使用**
-
- ```
- [[[RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@1];
-
- [subscriber sendCompleted];
-
- return nil;
-
- }] then:^RACSignal *{
-
- return [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@2];
-
- return nil;
- }];
-
- }] subscribeNext:^(id x) {
-
- // 只能接收到第二个信号的值,也就是then返回信号的值
- NSLog(@"%@", x);
-
- }];
-
- ///
- 输出:2
- ```
-- **注意**
-
- 注意使用`then`,之前信号的值会被忽略掉.
-
-###### merge
-- **作用**
-
- 合并信号,任何一个信号发送数据,都能监听到.
-- **底层实现**
-
- 1. 合并信号被订阅的时候,就会遍历所有信号,并且发出这些信号。
- 2. 每发出一个信号,这个信号就会被订阅
- 3. 也就是合并信号一被订阅,就会订阅里面所有的信号。
- 4. 只要有一个信号被发出就会被监听。
-- **使用**
-
- ```
- RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"A"];
-
- return nil;
- }];
-
- RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"B"];
-
- return nil;
- }];
-
- // 合并信号, 任何一个信号发送数据,都能监听到
- RACSignal *mergeSianl = [signalA merge:signalB];
-
- [mergeSianl subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
-
- // 输出
- 2017-01-03 13:29:08.013 ReactiveCocoa进阶[3627:718315] A
- 2017-01-03 13:29:08.014 ReactiveCocoa进阶[3627:718315] B
-
-
- ```
-
-###### zip
-
-- **作用**
-
- 把两个信号压缩成一个信号,只有当两个信号 **同时** 发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件。
-- **底层实现**
-
- 1. 定义压缩信号,内部就会自动订阅signalA,signalB
- 2. 每当signalA或者signalB发出信号,就会判断signalA,signalB有没有发出个信号,有就会把每个信号 第一次 发出的值包装成元组发出
-
-- **使用**
-
- ```
- RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"A1"];
- [subscriber sendNext:@"A2"];
-
- return nil;
- }];
-
- RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"B1"];
- [subscriber sendNext:@"B2"];
- [subscriber sendNext:@"B3"];
-
- return nil;
- }];
-
- RACSignal *zipSignal = [signalA zipWith:signalB];
-
- [zipSignal subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
-
- // 输出
- 2017-01-03 13:48:09.234 ReactiveCocoa进阶[3997:789720] zipWith: (
- A1,
- B1
- )
- 2017-01-03 13:48:09.234 ReactiveCocoa进阶[3997:789720] zipWith: (
- A2,
- B2
- )
- ```
-
-
-###### combineLatest
-- **作用**
-
- 将多个信号合并起来,并且拿到各个信号最后一个值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
-
-- **底层实现**
-
- 1. 当组合信号被订阅,内部会自动订阅signalA,signalB,必须两个信号都发出内容,才会被触发。
- 2. 并且把两个信号的 最后一次 发送的值组合成元组发出。
-
-- **使用**
-
- ```
- RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"A1"];
- [subscriber sendNext:@"A2"];
-
- return nil;
- }];
-
- RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"B1"];
- [subscriber sendNext:@"B2"];
- [subscriber sendNext:@"B3"];
-
- return nil;
- }];
-
- RACSignal *combineSianal = [signalA combineLatestWith:signalB];
-
- [combineSianal subscribeNext:^(id x) {
-
- NSLog(@"combineLatest:%@", x);
- }];
-
- // 输出
- 2017-01-03 13:48:09.235 ReactiveCocoa进阶[3997:789720] combineLatest: (
- A2,
- B1
- )
- 2017-01-03 13:48:09.235 ReactiveCocoa进阶[3997:789720] combineLatest: (
- A2,
- B2
- )
- 2017-01-03 13:48:09.236 ReactiveCocoa进阶[3997:789720] combineLatest: (
- A2,
- B3
- )
- ```
-
-- **注意**
-
- **combineLatest**与**zip**用法相似,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
-
- 区别看下图:
-
- ![](https://ww2.sinaimg.cn/large/006y8lVagw1fbdf6cyez6j30id0kkabf.jpg)
-
-
-###### reduce
-
-- **作用**
-
- 把信号发出元组的值聚合成一个值
-- **底层实现**
-
- 1. 订阅聚合信号,
- 2. 每次有内容发出,就会执行reduceblcok,把信号内容转换成reduceblcok返回的值。
-
-- **使用**
-
- 常见的用法,(先组合在聚合)`combineLatest:(id)signals reduce:(id (^)())reduceBlock`
-
- reduce中的block简介:
-
- reduceblcok中的参数,有多少信号组合,reduceblcok就有多少参数,每个参数就是之前信号发出的内容
- reduceblcok的返回值:聚合信号之后的内容。
-
-
-
- ```
- RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"A1"];
- [subscriber sendNext:@"A2"];
-
- return nil;
- }];
-
- RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"B1"];
- [subscriber sendNext:@"B2"];
- [subscriber sendNext:@"B3"];
-
- return nil;
- }];
-
-
- RACSignal *reduceSignal = [RACSignal combineLatest:@[signalA, signalB] reduce:^id(NSString *str1, NSString *str2){
-
- return [NSString stringWithFormat:@"%@ %@", str1, str2];
- }];
-
- [reduceSignal subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
-
- // 输出
- 2017-01-03 15:42:41.803 ReactiveCocoa进阶[4248:1264674] A2 B1
- 2017-01-03 15:42:41.803 ReactiveCocoa进阶[4248:1264674] A2 B2
- 2017-01-03 15:42:41.803 ReactiveCocoa进阶[4248:1264674] A2 B3
-
- ```
-
-#### 过滤
-
-过滤就是过滤信号中的 特定值 ,或者过滤指定 发送次数 的信号。
-
-###### filter
-
-- **作用**
-
- 过滤信号,使用它可以获取满足条件的信号.
-
- block的返回值是Bool值,返回`NO`则过滤该信号
-
-- **使用**
-
- ```
- // 过滤:
- // 每次信号发出,会先执行过滤条件判断.
- [[_textField.rac_textSignal filter:^BOOL(NSString *value) {
-
- NSLog(@"原信号: %@", value);
-
- // 过滤 长度 <= 3 的信号
- return value.length > 3;
-
- }] subscribeNext:^(id x) {
-
- NSLog(@"长度大于3的信号:%@", x);
- }];
-
- // 在_textField中输出12345
- // 输出
- 2017-01-03 16:36:54.938 ReactiveCocoa进阶[4714:1552910] 原信号: 1
- 2017-01-03 16:36:55.383 ReactiveCocoa进阶[4714:1552910] 原信号: 12
- 2017-01-03 16:36:55.706 ReactiveCocoa进阶[4714:1552910] 原信号: 123
- 2017-01-03 16:36:56.842 ReactiveCocoa进阶[4714:1552910] 原信号: 1234
- 2017-01-03 16:36:56.842 ReactiveCocoa进阶[4714:1552910] 长度大于3的信号:1234
- 2017-01-03 16:36:58.350 ReactiveCocoa进阶[4714:1552910] 原信号: 12345
- 2017-01-03 16:36:58.351 ReactiveCocoa进阶[4714:1552910] 长度大于3的信号:12345
- ```
-
-###### ignore
-
-- **作用**
-
- 忽略某些信号.
-
-- **使用**
-
-- **作用**
-
- 忽略某些值的信号.
-
- 底层调用了 `filter` 与 过滤值进行比较,若相等返回则 `NO`
-
-- **使用**
-
- ```
- // 内部调用filter过滤,忽略掉字符为 @“1”的值
-[[_textField.rac_textSignal ignore:@"1"] subscribeNext:^(id x) {
-
- NSLog(@"%@",x);
-}];
-
-
- ```
-
-###### distinctUntilChanged
-
-- **作用**
-
- 当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
-
-- **使用**
-
- ```
- [[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
-
- NSLog(@"%@",x);
- }];
- ```
-
-###### skip
-
-- **作用**
-
- 跳过 **第N次** 的发送的信号.
-
-- **使用**
-
- ```
-// 表示输入第一次,不会被监听到,跳过第一次发出的信号
-[[_textField.rac_textSignal skip:1] subscribeNext:^(id x) {
-
- NSLog(@"%@",x);
-}];
- ```
-
-
-
-##### take
-- **作用**
-
- 取 **前N次** 的发送的信号.
-- **使用**
-
- ```
- RACSubject *subject = [RACSubject subject] ;
-
- // 取 前两次 发送的信号
- [[subject take:2] subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
-
- [subject sendNext:@1];
- [subject sendNext:@2];
- [subject sendNext:@3];
-
- // 输出
- 2017-01-03 17:35:54.566 ReactiveCocoa进阶[4969:1677908] 1
- 2017-01-03 17:35:54.567 ReactiveCocoa进阶[4969:1677908] 2
- ```
-
-###### takeLast
-
-- **作用**
-
- 取 **最后N次** 的发送的信号
-
- 前提条件,订阅者必须调用完成 `sendCompleted`,因为只有完成,就知道总共有多少信号.
-
-- **使用**
-
- ```
- RACSubject *subject = [RACSubject subject] ;
-
- // 取 后两次 发送的信号
- [[subject takeLast:2] subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
-
- [subject sendNext:@1];
- [subject sendNext:@2];
- [subject sendNext:@3];
-
- // 必须 跳用完成
- [subject sendCompleted];
- ```
-
-###### takeUntil
-
-- **作用**
-
- 获取信号直到某个信号执行完成
-- **使用**
-
- ```
- // 监听文本框的改变直到当前对象被销毁
-[_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
- ```
-
-###### switchToLatest
-- **作用**
-
- 用于signalOfSignals(信号的信号),有时候信号也会发出信号,会在signalOfSignals中,获取signalOfSignals发送的最新信号。
-
-- **注意**
-
- switchToLatest:只能用于信号中的信号
-
-- **使用**
-
- ```
- RACSubject *signalOfSignals = [RACSubject subject];
- RACSubject *signal = [RACSubject subject];
-
- // 获取信号中信号最近发出信号,订阅最近发出的信号。
- [signalOfSignals.switchToLatest subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
-
- [signalOfSignals sendNext:signal];
- [signal sendNext:@1];
- ```
-
-#### 秩序
-
-秩序包括 `doNext` 和 `doCompleted` 这两个方法,主要是在 执行`sendNext` 或者 `sendCompleted`之前,先执行这些方法中Block。
-
-###### doNext
-
-执行`sendNext`之前,会先执行这个`doNext`的 Block
-
-###### doCompleted
-
-执行`sendCompleted`之前,会先执行这`doCompleted`的`Block`
-
-```
-[[[[RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"hi"];
-
- [subscriber sendCompleted];
-
- return nil;
-
-}] doNext:^(id x) {
-
- // 执行 [subscriber sendNext:@"hi"] 之前会调用这个 Block
- NSLog(@"doNext");
-
-}] doCompleted:^{
-
- // 执行 [subscriber sendCompleted] 之前会调用这 Block
- NSLog(@"doCompleted");
-}] subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
-}];
-
-
-```
-
-#### 线程
-
-**ReactiveCocoa** 中的线程操作 包括 `deliverOn` 和 `subscribeOn`这两种,将 *传递的内容* 或 创建信号时 *block中的代码* 切换到指定的线程中执行。
-
-###### deliverOn
-
-- **作用**
-
- 内容传递切换到制定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用。
-- **使用**
-
- ```
- // 在子线程中执行
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-
- [[[RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- NSLog(@"%@", [NSThread currentThread]);
-
- [subscriber sendNext:@123];
-
- [subscriber sendCompleted];
-
- return nil;
- }]
- deliverOn:[RACScheduler mainThreadScheduler]]
-
- subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
-
- NSLog(@"%@", [NSThread currentThread]);
- }];
- });
-
- // 输出
-2017-01-04 10:35:55.415 ReactiveCocoa进阶[1183:224535] {number = 3, name = (null)}
-2017-01-04 10:35:55.415 ReactiveCocoa进阶[1183:224482] 123
-2017-01-04 10:35:55.415 ReactiveCocoa进阶[1183:224482] {number = 1, name = main}
- ```
-
- 可以看到`副作用`在 *子线程* 中执行,而 `传递的内容` 在 *主线程* 中接收
-
-
-###### subscribeOn
-- **作用**
-
- **subscribeOn**则是将 `内容传递` 和 `副作用` 都会切换到指定线程中
-- **使用**
-
- ```
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-
- [[[RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- NSLog(@"%@", [NSThread currentThread]);
-
- [subscriber sendNext:@123];
-
- [subscriber sendCompleted];
-
- return nil;
- }]
- subscribeOn:[RACScheduler mainThreadScheduler]] //传递的内容到主线程中
- subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
-
- NSLog(@"%@", [NSThread currentThread]);
- }];
- });
- //
-2017-01-04 10:44:47.558 ReactiveCocoa进阶[1243:275126] {number = 1, name = main}
-2017-01-04 10:44:47.558 ReactiveCocoa进阶[1243:275126] 123
-2017-01-04 10:44:47.558 ReactiveCocoa进阶[1243:275126] {number = 1, name = main}
- ```
-
- `内容传递` 和 `副作用` 都切换到了 *主线程* 执行
-
-#### 时间
-
-时间操作就会设置信号超时,定时和延时。
-
-###### interval 定时
-- **作用**
-
- 定时:每隔一段时间发出信号
-
- ```
- // 每隔1秒发送信号,指定当前线程执行
- [[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
-
- NSLog(@"定时:%@", x);
- }];
-
- // 输出
- 2017-01-04 13:48:55.196 ReactiveCocoa进阶[1980:492724] 定时:2017-01-04 05:48:55 +0000
- 2017-01-04 13:48:56.195 ReactiveCocoa进阶[1980:492724] 定时:2017-01-04 05:48:56 +0000
- 2017-01-04 13:48:57.196 ReactiveCocoa进阶[1980:492724] 定时:2017-01-04 05:48:57 +0000
- ```
-
-
-###### timeout 超时
-
-- **作用**
-
- 超时,可以让一个信号在一定的时间后,自动报错。
-
- ```
- RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- // 不发送信号,模拟超时状态
- // [subscriber sendNext:@"hello"];
- //[subscriber sendCompleted];
-
- return nil;
- }] timeout:1 onScheduler:[RACScheduler currentScheduler]];// 设置1秒超时
-
- [signal subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- } error:^(NSError *error) {
-
- NSLog(@"%@", error);
- }];
-
- // 执行代码 1秒后 输出:
- 2017-01-04 13:48:55.195 ReactiveCocoa进阶[1980:492724] Error Domain=RACSignalErrorDomain Code=1 "(null)"
- ```
-
-###### delay 延时
-- **作用**
-
- 延时,延迟一段时间后发送信号
-
- ```
- RACSignal *signal2 = [[[RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@"延迟输出"];
-
- return nil;
- }] delay:2] subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
-
- // 执行代码 2秒后 输出
- 2017-01-04 13:55:23.751 ReactiveCocoa进阶[2030:525038] 延迟输出
- ```
-
-
-#### 重复
-
-###### retry
-
-- **作用**
-
- 重试:只要 发送错误 `sendError:`,就会 重新执行 创建信号的Block 直到成功
-
- ```
- __block int i = 0;
-
- [[[RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- if (i == 5) {
-
- [subscriber sendNext:@"Hello"];
-
- } else {
-
- // 发送错误
- NSLog(@"收到错误:%d", i);
- [subscriber sendError:nil];
- }
-
- i++;
-
- return nil;
-
- }] retry] subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
-
- } error:^(NSError *error) {
-
- NSLog(@"%@", error);
-
- }];
-
- // 输出
-2017-01-04 14:36:51.594 ReactiveCocoa进阶[2443:667226] 收到错误信息:0
-2017-01-04 14:36:51.595 ReactiveCocoa进阶[2443:667226] 收到错误信息:1
-2017-01-04 14:36:51.595 ReactiveCocoa进阶[2443:667226] 收到错误信息:2
-2017-01-04 14:36:51.596 ReactiveCocoa进阶[2443:667226] 收到错误信息:3
-2017-01-04 14:36:51.596 ReactiveCocoa进阶[2443:667226] 收到错误信息:4
-2017-01-04 14:36:51.596 ReactiveCocoa进阶[2443:667226] Hello
-
- ```
-
-###### replay
-
-- **作用**
-
- 重放:当一个信号被多次订阅,反复播放内容
-
- ```
- RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- [subscriber sendNext:@1];
- [subscriber sendNext:@2];
-
- return nil;
- }] replay];
-
- [signal subscribeNext:^(id x) {
- NSLog(@"%@", x);
- }];
-
- [signal subscribeNext:^(id x) {
- NSLog(@"%@", x);
- }];
-
- // 输出
-2017-01-04 14:51:01.934 ReactiveCocoa进阶[2544:706740] 1
-2017-01-04 14:51:01.934 ReactiveCocoa进阶[2544:706740] 2
-2017-01-04 14:51:01.934 ReactiveCocoa进阶[2544:706740] 1
-2017-01-04 14:51:01.935 ReactiveCocoa进阶[2544:706740] 2
- ```
-
-
-###### throttle
-
-- **作用**
-
- 节流:当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。
-
- ```
- RACSubject *subject = [RACSubject subject];
-
- // 节流1秒,1秒后接收最后一个发送的信号
- [[subject throttle:1] subscribeNext:^(id x) {
-
- NSLog(@"%@", x);
- }];
-
- [subject sendNext:@1];
- [subject sendNext:@2];
- [subject sendNext:@3];
-
- // 输出
- 2017-01-04 15:02:37.543 ReactiveCocoa进阶[2731:758193] 3
- ```
-
-# MVVM架构思想
----
-程序为什么要有架构?便于程序开发与维护.
-
-#### 常见的架构
-- **MVC**
-
- M:模型 V:视图 C:控制器
-
-- **MVVM**
-
- M:模型 V:视图+控制器 VM:视图模型
-
-- **MVCS**
-
- M:模型 V:视图 C:控制器 C:服务类
-
-- [**VIPER**](http://www.cocoachina.com/ios/20140703/9016.html)
-
- V:视图 I:交互器 P:展示器 E:实体 R:路由
-
-#### MVVM介绍
-
-- 模型(M):保存视图数据。
-
-- 视图+控制器(V):展示内容 + 如何展示
-
-- 视图模型(VM):处理展示的业务逻辑,包括按钮的点击,数据的请求和解析等等。
-
-# 实战一:登录界面
-
-#### 需求
-1. 监听两个文本框的内容
-2. 有内容登录按键才允许按钮点击
-3. 返回登录结果
-
-#### 分析
-1. 界面的所有业务逻辑都交给控制器做处理
-2. 在MVVM架构中把控制器的业务全部搬去VM模型,也就是每个控制器对应一个VM模型.
-
-#### 步骤
-1. 创建LoginViewModel类,处理登录界面业务逻辑.
-2. 这个类里面应该保存着账号的信息,创建一个账号Account模型
-3. LoginViewModel应该保存着账号信息Account模型。
-4. 需要时刻监听Account模型中的账号和密码的改变,怎么监听?
-5. 在非RAC开发中,都是习惯赋值,在RAC开发中,需要改变开发思维,由赋值转变为绑定,可以在一开始初始化的时候,就给Account模型中的属性绑定,并不需要重写set方法。
-6. 每次Account模型的值改变,就需要判断按钮能否点击,在VM模型中做处理,给外界提供一个能否点击按钮的信号.
-7. 这个登录信号需要判断Account中账号和密码是否有值,用KVO监听这两个值的改变,把他们聚合成登录信号.
-8. 监听按钮的点击,由VM处理,应该给VM声明一个RACCommand,专门处理登录业务逻辑.
-9. 执行命令,把数据包装成信号传递出去
-10. 监听命令中信号的数据传递
-11. 监听命令的执行时刻
-
-
-
-#### 运行效果
-
-![登录界面](https://ww3.sinaimg.cn/large/006y8lVagw1fbgvoh8yu6j30bj0l43yz.jpg)
-
-#### 代码
-
-`MyViewController.m`
-
-```
-#import "MyViewController.h"
-#import "LoginViewModel.h"
-
-@interface MyViewController ()
-
-@property (nonatomic, strong) LoginViewModel *loginViewModel;
-
-@property (weak, nonatomic) IBOutlet UITextField *accountField;
-
-@property (weak, nonatomic) IBOutlet UITextField *pwdField;
-
-@property (weak, nonatomic) IBOutlet UIButton *loginBtn;
-
-@end
-
-@implementation MyViewController
-
-- (void)viewDidLoad {
- [super viewDidLoad];
-
- [self bindModel];
-
-}
-
-- (void)didReceiveMemoryWarning {
- [super didReceiveMemoryWarning];
- // Dispose of any resources that can be recreated.
-}
-
-
-
-// 视图模型绑定
-- (void)bindModel {
-
- // 给模型的属性绑定信号
- //
- RAC(self.loginViewModel.account, account) = _accountField.rac_textSignal;
- RAC(self.loginViewModel.account, pwd) = _pwdField.rac_textSignal;
-
- RAC(self.loginBtn, enabled) = self.loginViewModel.enableLoginSignal;
-
- // 监听登录点击
- [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
-
- [self.loginViewModel.LoginCommand execute:nil];
- }];
-
-}
-- (IBAction)btnTap:(id)sender {
-
-
-}
-
-#pragma mark - lazyLoad
-
-- (LoginViewModel *)loginViewModel {
-
- if (nil == _loginViewModel) {
- _loginViewModel = [[LoginViewModel alloc] init];
- }
-
- return _loginViewModel;
-}
-```
-
-`LoginViewModel.h`
-
-```
-#import
-
-@interface Account : NSObject
-
-@property (nonatomic, strong) NSString *account;
-@property (nonatomic, strong) NSString *pwd;
-
-@end
-
-
-@interface LoginViewModel : UIViewController
-
-@property (nonatomic, strong) Account *account;
-
-// 是否允许登录的信号
-@property (nonatomic, strong, readonly) RACSignal *enableLoginSignal;
-
-@property (nonatomic, strong, readonly) RACCommand *LoginCommand;
-
-@end
-
-```
-
-`LoginViewModel.m`
-
-```
-#import "LoginViewModel.h"
-
-@implementation Account
-
-@end
-
-
-@interface LoginViewModel ()
-
-@end
-
-@implementation LoginViewModel
-
-- (instancetype)init {
-
- if (self = [super init]) {
- [self initialBind];
- }
- return self;
-}
-
-- (void)initialBind {
-
- // 监听账号属性改变, 把他们合成一个信号
- _enableLoginSignal = [RACSubject combineLatest:@[RACObserve(self.account, account), RACObserve(self.account, pwd)] reduce:^id(NSString *accout, NSString *pwd){
-
- return @(accout.length && pwd.length);
- }];
-
- // 处理业务逻辑
- _LoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
-
- NSLog(@"点击了登录");
- return [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- // 模仿网络延迟
-
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
-
- // 返回登录成功 发送成功信号
- [subscriber sendNext:@"登录成功"];
- });
-
- return nil;
- }];
- }];
-
-
- // 监听登录产生的数据
- [_LoginCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
-
- if ([x isEqualToString:@"登录成功"]) {
- NSLog(@"登录成功");
- }
-
- }];
-
- [[_LoginCommand.executing skip:1] subscribeNext:^(id x) {
-
- if ([x isEqualToNumber:@(YES)]) {
-
- NSLog(@"正在登陆...");
- } else {
-
- // 登录成功
- NSLog(@"登陆成功");
-
- }
-
- }];
-}
-
-#pragma mark - lazyLoad
-
-- (Account *)account
-{
- if (_account == nil) {
- _account = [[Account alloc] init];
- }
- return _account;
-}
-
-- (void)viewDidLoad {
- [super viewDidLoad];
-
-}
-
-@end
-
-```
-
-# 实战二:网络请求数据
-
-#### 需求
-1. 请求一段网络数据,将请求到的数据在`tableView`上展示
-2. 该数据为豆瓣图书的搜索返回结果,URL:url:https://api.douban.com/v2/book/search?q=悟空传
-
-#### 分析
-1. 界面的所有业务逻辑都交给**控制器**做处理
-2. 网络请求交给**MV**模型处理
-
-#### 步骤
-
-1. 控制器提供一个视图模型(requesViewModel),处理界面的业务逻辑
-2. VM提供一个命令,处理请求业务逻辑
-3. 在创建命令的block中,会把请求包装成一个信号,等请求成功的时候,就会把数据传递出去。
-4. 请求数据成功,应该把字典转换成模型,保存到视图模型中,控制器想用就直接从视图模型中获取。
-
-#### 其他
-
-网络请求与图片缓存用到了[AFNetworking](https://github.com/AFNetworking/AFNetworking) 和 [SDWebImage](https://github.com/rs/SDWebImage),自行在Pods中导入。
-
-```
-platform :ios, '8.0'
-
-target 'ReactiveCocoa进阶' do
-
-use_frameworks!
-pod 'ReactiveCocoa', '~> 2.5'
-pod 'AFNetworking'
-pod 'SDWebImage'
-end
-```
-
-#### 运行效果
-
-![](https://ww3.sinaimg.cn/large/006y8lVagw1fbgw1xnz74j30bj0l4408.jpg)
-
-
-#### 代码
-
-`SearchViewController.m`
-
-```
-#import "SearchViewController.h"
-#import "RequestViewModel.h"
-
-@interface SearchViewController ()
-
-@property (nonatomic, strong) UITableView *tableView;
-
-@property (nonatomic, strong) RequestViewModel *requesViewModel;
-
-@end
-
-@implementation SearchViewController
-
-- (RequestViewModel *)requesViewModel
-{
- if (_requesViewModel == nil) {
- _requesViewModel = [[RequestViewModel alloc] init];
- }
- return _requesViewModel;
-}
-
-- (void)viewDidLoad {
- [super viewDidLoad];
-
-
- self.tableView = [[UITableView alloc] initWithFrame:self.view.frame];
-
- self.tableView.dataSource = self;
-
- [self.view addSubview:self.tableView];
-
- //
- RACSignal *requesSiganl = [self.requesViewModel.reuqesCommand execute:nil];
-
- [requesSiganl subscribeNext:^(NSArray *x) {
-
- self.requesViewModel.models = x;
-
- [self.tableView reloadData];
- }];
-}
-
-- (void)didReceiveMemoryWarning {
- [super didReceiveMemoryWarning];
- // Dispose of any resources that can be recreated.
-}
-
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
-{
- return self.requesViewModel.models.count;
-}
-
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
-{
- static NSString *ID = @"cell";
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
- if (cell == nil) {
-
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
- }
-
- Book *book = self.requesViewModel.models[indexPath.row];
- cell.detailTextLabel.text = book.subtitle;
- cell.textLabel.text = book.title;
-
- [cell.imageView sd_setImageWithURL:[NSURL URLWithString:book.image] placeholderImage:[UIImage imageNamed:@"cellImage"]];
-
-
- return cell;
-}
-@end
-```
-
-`RequestViewModel.h`
-
-```
-#import
-
-@interface Book : NSObject
-
-@property (nonatomic, copy) NSString *subtitle;
-@property (nonatomic, copy) NSString *title;
-@property (nonatomic, copy) NSString *image;
-
-@end
-
-@interface RequestViewModel : NSObject
-
-// 请求命令
-@property (nonatomic, strong, readonly) RACCommand *reuqesCommand;
-
-//模型数组
-@property (nonatomic, strong) NSArray *models;
-
-
-@end
-```
-
-`RequestViewModel.m`
-
-```
-#import "RequestViewModel.h"
-
-@implementation Book
-
-- (instancetype)initWithValue:(NSDictionary *)value {
-
- if (self = [super init]) {
-
- self.title = value[@"title"];
- self.subtitle = value[@"subtitle"];
- self.image = value[@"image"];
- }
- return self;
-}
-
-+ (Book *)bookWithDict:(NSDictionary *)value {
-
- return [[self alloc] initWithValue:value];
-}
-
-
-
-@end
-
-@implementation RequestViewModel
-
-- (instancetype)init
-{
- if (self = [super init]) {
-
- [self initialBind];
- }
- return self;
-}
-
-
-- (void)initialBind
-{
- _reuqesCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
-
- RACSignal *requestSiganl = [RACSignal createSignal:^RACDisposable *(id subscriber) {
-
- NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
- parameters[@"q"] = @"悟空传";
-
- //
- [[AFHTTPSessionManager manager] GET:@"https://api.douban.com/v2/book/search" parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
-
- NSLog(@"downloadProgress: %@", downloadProgress);
- } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
-
- // 数据请求成功就讲数据发送出去
- NSLog(@"responseObject:%@", responseObject);
-
- [subscriber sendNext:responseObject];
-
- [subscriber sendCompleted];
-
- } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
-
- NSLog(@"error: %@", error);
- }];
-
-
- return nil;
- }];
-
- // 在返回数据信号时,把数据中的字典映射成模型信号,传递出去
- return [requestSiganl map:^id(NSDictionary *value) {
-
- NSMutableArray *dictArr = value[@"books"];
-
- NSArray *modelArr = [[dictArr.rac_sequence map:^id(id value) {
-
- return [Book bookWithDict:value];
-
- }] array];
-
- return modelArr;
-
- }];
-
- }];
-}
-
-
-@end
-
-```
-
->最后附上GitHub:
diff --git "a/_posts/2016-10-10-iOS-\346\211\213\345\212\277\344\270\216\345\217\230\345\275\242.md" "b/_posts/2016-10-10-iOS-\346\211\213\345\212\277\344\270\216\345\217\230\345\275\242.md"
deleted file mode 100644
index 46df1778ae1..00000000000
--- "a/_posts/2016-10-10-iOS-\346\211\213\345\212\277\344\270\216\345\217\230\345\275\242.md"
+++ /dev/null
@@ -1,467 +0,0 @@
----
-layout: post
-title: iOS手势与变形
-subtitle: 手势与变形基础知识笔记
-date: 2016-10-10
-author: BY
-header-img: img/post-bg-ios9-web.jpg
-catalog: true
-tags:
- - iOS
- - iOS开发基础
----
-
->手势在用户交互中有着举足轻重的作用,这篇文字简单的介绍了iOS中的手势,并通过手势对控件进行变形处理。
-
-# 手势
-
-iOS手势分为下面这几种:
-
-- UITapGestureRecognizer(点按)
-- UIPanGestureRecognizer(拖动)
-- UIScreenEdgePanGestureRecognizer (边缘拖动)
-- UIPinchGestureRecognizer(捏合)
-- UIRotationGestureRecognizer(旋转)
-- UILongPressGestureRecognizer(长按)
-- UISwipeGestureRecognizer(轻扫)
-
-
-
-这些手势大都继承于**UIGestureRecognizer**类,(`UIScreenEdgePanGestureRecognizer`继承于`UIPanGestureRecognizer`类),
-
-需要说明的是这些手势只有一个是**离散型手势**,那就是`UITapGestureRecognizer`,一旦识别就无法取消,而且只会调用一次手势操作事件。
-
-换句话说其他手势是**连续型手势**,而连续型手势的特点就是:会多次调用手势操作事件,而且在连续手势识别后可以取消手势。
-
-从下图可以看出两者调用操作事件的次数是不同的:
-
-![](http://ww3.sinaimg.cn/large/006tNc79gw1fb0neee6mlj30dw0aldgf.jpg)
-
-这些手势类有着以下共同的方法:
-
-
-创建方法:
-
-```
-- (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action;
-```
-
-移除方法:
-
-```
-- (void)removeTarget:(nullable id)target action:(nullable SEL)action;
-```
-
-添加事件:
-
-```
-- (void)addTarget:(id)target action:(SEL)action;
-```
-
-还有下面这些属性等:
-
-```
-@property(nonatomic,readonly) UIGestureRecognizerState state;// 手势状态
-
-typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
- UIGestureRecognizerStatePossible, // 尚未识别是何种手势操作(但可能已经触发了触摸事件),默认状态
- UIGestureRecognizerStateBegan, // 手势已经开始,此时已经被识别,但是这个过程中可能发生变化,手势操作尚未完成
- UIGestureRecognizerStateChanged, // 手势状态发生转变
- UIGestureRecognizerStateEnded, // 手势识别操作完成(此时已经松开手指)
- UIGestureRecognizerStateCancelled, // 手势被取消,恢复到默认状态
- UIGestureRecognizerStateFailed, // 手势识别失败,恢复到默认状态
- UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // 手势识别完成,同UIGestureRecognizerStateEnded
- };
-
-@property(nullable,nonatomic,weak) id delegate; // 代理
-
-@property(nonatomic, getter=isEnabled) BOOL enabled;
-```
-
-当然我们也可以自定义手势来实现特殊的需求,关于自定义手势可以看[这篇博客](http://blog.csdn.net/mmoaay/article/details/47355709).
-
-接下来我们来看看这些常用手势的用法.
-
-#### UITapGestureRecognizer(点按)
-
-Tap手势有两个属性,
-
-- numberOfTapsRequired
-- numberOfTouchesRequired:
-
-`numberOfTapsRequired`为触发事件需要点击的次数,默认是1;
-
-`numberOfTouchesRequired`为触发事件需要的几个手指点按,默认是1;
-
-若都设置为2,就需要`两个`手指同时点按`2次`才会触发事件。
-
-Tap手势也是我们最常用的手势之一, 比如点击ImageView跳转到其他界面,或者双击图片放大缩小等。
-
-创建:
-
-```
- UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
- tap.numberOfTapsRequired = 2;
- tap.numberOfTouchesRequired = 1;
- [self.imageView addGestureRecognizer:tap];
-```
-
-#### UIPanGestureRecognizer(拖动)
-
-Pan手势的属性和方法:
-
-- @property (nonatomic) NSUInteger minimumNumberOfTouches __TVOS_PROHIBITED;
-- @property (nonatomic) NSUInteger minimumNumberOfTouches __TVOS_PROHIBITED;
-- \-(CGPoint)translationInView:(nullable UIView *)view;
-- \-(void)setTranslation:(CGPoint)translation inView:(nullable UIView *)view;
-- \-(CGPoint)velocityInView:(nullable UIView *)view;
-
-`translationInView:`方法获取`View`的偏移量;
-
-`setTranslation:`方法设置手势的偏移量;
-
-`velocityInView:`方法获取速度;
-
-所以手势的创建方法都类似,这里就不在一一列举了。
-
-
-#### UIScreenEdgePanGestureRecognizer (边缘拖动)
-
-ScreenEdgePan继承于`UIPanGestureRecognizer`,在屏幕边缘滑动才会触发
-
-- @property (readwrite, nonatomic, assign) UIRectEdge edges;
-
-`edges`为指定边缘拖动触发的边,是一个枚举:
-
-```
-typedef NS_OPTIONS(NSUInteger, UIRectEdge) {
- UIRectEdgeNone = 0,
- UIRectEdgeTop = 1 << 0,
- UIRectEdgeLeft = 1 << 1,
- UIRectEdgeBottom = 1 << 2,
- UIRectEdgeRight = 1 << 3,
- UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight
-} NS_ENUM_AVAILABLE_IOS(7_0);
-```
-其他方法和Tap手势一致,主要用于像左右抽屉视图的变换等处理。
-
-
-#### UIPinchGestureRecognizer(捏合)
-Pinch手势有两个属性:
-
-- @property (nonatomic) CGFloat scale;
-- @property (nonatomic,readonly) CGFloat velocity;
-
-`scale`:捏合比例
-
-`velocity`:捏合速度 = `scale/second`
-
-#### UIRotationGestureRecognizer(旋转)
-Rotation手势和Pinch手势类似,同样有两个手势:
-
-- @property (nonatomic) CGFloat rotation;
-- @property (nonatomic,readonly) CGFloat velocity;
-
-`rotation`:旋转弧度,注意,这里的单位是**弧度**
-
-`velocity`:旋转速度
-
-#### UILongPressGestureRecognizer(长按)
-
-LongPress的属性:
-
-- @property (nonatomic) NSUInteger numberOfTapsRequired; // Default is 0.
-- @property (nonatomic) NSUInteger numberOfTouchesRequired __TVOS_PROHIBITED; // Default is 1.
-- @property (nonatomic) CFTimeInterval minimumPressDuration;
-- @property (nonatomic) CGFloat allowableMovement;
-
-`numberOfTapsRequired`和`numberOfTouchesRequired`和Tap手势类似,都是指定触发需要的点击次数和手指数量,但是LongPress手势的`numberOfTapsRequired`是指定长按前需要点击的次数。
-
-`minimumPressDuration`:触发时间
-
-`allowableMovement`:允许长按时间触发前允许手指滑动的范围。若是你在长按时手指移动,该长按手势将会失败,`allowableMovement`设置你能容忍的滑动范围,默认是10.
-
-# 变形
----
-iOS的变形指的是图片的旋转、平移和缩放。这些变形可以和上面介绍的手势结合,完成许多变形操作。
-
-说变形前我们来看看**CGAffineTransform**,**CGAffineTransform**为一个结构体:
-
-```
-struct CGAffineTransform {
- CGFloat a, b, c, d;
- CGFloat tx, ty;
-};
-```
-我们输出一个控件的`transform`看看
-
-```
-NSLog(@"%@", NSStringFromCGAffineTransform(self.label.transform));
-```
-
-输出
-
-```
-2016-12-22 17:01:19.211 手势[6489:1481987] [1, 0, 0, 1, 0, 0]
-```
-
-我们可以看到输出了一个长度为6的数组:`[1, 0, 0, 1, 0, 0]`,并且我们可以猜测对应结构体中的`[a, b, c, d, tx, ty]`,并且默认的`transform`值就是`[1, 0, 0, 1, 0, 0]`。
-
-想进一步了解可以看这篇[《iOS CGAffineTransform详解》](http://blog.csdn.net/ashiyanshi/article/details/48160429)
-
-对iOS控件进行变形实际就是对控件`transform`属性进行操作。
-
-但是我们使用中,使用已经封装好的的API对控件进行变形处理。分别是:
-
-
-- CGAffineTransformScale()
-- CGAffineTransformTranslate()
-- CGAffineTransformRotate()
-
-和:
-
-- CGAffineTransformMakeScale()
-- CGAffineTransformMakeTranslate()
-- CGAffineTransformMakeRotate()
-
-这些API都是对设置`CGAffineTransform`的一个封装,针对`[a, b, c, d, tx, ty]`中不同的位置进行操作。
-
-
-下面我们在ViewController创建一个`UILabel`控件。然后对它进行变形操作。
-
-![](http://ww4.sinaimg.cn/large/006tNc79jw1fazplz4mvmj306x0cbt8m.jpg)
-
-#### 缩放
-
-首先来看一个缩放操作
-
-```
-// 缩放到90%(相对)
-self.label.transform = CGAffineTransformScale(self.label.transform, 0.9, 0.9);
-
-NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform));
-
-```
-
-输出:
-
-```
-2016-12-22 17:26:25.074 手势[6526:1564064] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0]
-2016-12-22 17:26:26.096 手势[6526:1564064] [0.81000000000000005, 0, 0, 0.81000000000000005, 0, 0]
-2016-12-22 17:26:26.963 手势[6526:1564064] [0.72900000000000009, 0, 0, 0.72900000000000009, 0, 0]
-2016-12-22 17:26:28.830 手势[6526:1564064] [0.65610000000000013, 0, 0, 0.65610000000000013, 0, 0]
-```
-
-我们再看看另一个缩放:
-
-```
-self.label.transform = CGAffineTransformMakeScale(0.9, 0.9);
-
-NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform));
-```
-
-输出
-
-```
-2016-12-22 17:32:32.972 手势[6581:1600236] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0]
-2016-12-22 17:32:34.164 手势[6581:1600236] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0]
-2016-12-22 17:32:35.246 手势[6581:1600236] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0]
-```
-
-对比可以发现`CGAffineTransformScale()`与`CGAffineTransformMakeScale()`的区别在于,`CGAffineTransformScale()`实在原理的基础上在进行缩放操作,而`CGAffineTransformMakeScale()`直接将缩放值设定为0.9不变了。
-
-缩放操作变动的是构体中`[a, b, c, d, tx, ty]`的`a`和`d`,值和变形系数`Scale`是相对应的,大于1是放大,小于1是缩小。。
-
-`a`是横向缩放, `d`是纵向缩放。
-
-#### 平移
-
-先来看一个平移操作:
-
-```
-self.label.transform = CGAffineTransformTranslate(self.label.transform, 10, 10);
- NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform));
-```
-输出
-
-```
-2016-12-22 17:40:38.568 手势[6608:1631232] [1, 0, 0, 1, 10, 10]
-2016-12-22 17:40:40.833 手势[6608:1631232] [1, 0, 0, 1, 20, 20]
-2016-12-22 17:40:41.834 手势[6608:1631232] [1, 0, 0, 1, 30, 30]
-2016-12-22 17:40:42.532 手势[6608:1631232] [1, 0, 0, 1, 40, 40]
-2016-12-22 17:40:43.162 手势[6608:1631232] [1, 0, 0, 1, 50, 50]
-```
-
-我们可以看到label往右下角移动
-
-![](http://ww1.sinaimg.cn/large/006tNc79jw1fazply7kkpj306v0ca0sp.jpg)
-
-对应xy的正向坐标为右下角。
-
-#### 旋转
-
-```
-self.label.transform = CGAffineTransformRotate(self.label.transform, M_PI_2);
-NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform));
-```
-输出:
-
-```
-2016-12-22 17:59:43.680 手势[6667:1717130] [6.123233995736766e-17, 1, -1, 6.123233995736766e-17, 0, 0]
-```
-
-![](http://ww2.sinaimg.cn/large/006tNc79gw1fazq3j2ud5j306z0cfdft.jpg)
-
-可以看到`label`顺时针旋转了`π/2`弧度(90°)。
-
-# 手势结合变形
----
-手势结合变形就是通过手势对控件变形处理。
-
-上代码:
-
-```
-#import "ViewController.h"
-
-@interface ViewController ()
-@property (weak, nonatomic) IBOutlet UIImageView *imageView;
-
-@end
-
-@implementation ViewController
-
-- (void)viewDidLoad {
- [super viewDidLoad];
-
-// CGAffineTransform *mytransform = self.imageView.transform;
- self.imageView.userInteractionEnabled = YES;
- //1双击 恢复
- UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
- tap.numberOfTapsRequired = 2;
- tap.numberOfTouchesRequired = 1;
- [self.imageView addGestureRecognizer:tap];
-
- //2拖拽
- UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
-// pan.delegate = self;
- [self.imageView addGestureRecognizer:pan];
-
- //3捏合
- UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
- pinch.delegate = self;
- [self.imageView addGestureRecognizer:pinch];
-
- //4旋转
- UIRotationGestureRecognizer *rotaion = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotaion:)];
-// pinch.delegate = self;
- [self.imageView addGestureRecognizer:rotaion];
-
- // 长按
- UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
- longPress.numberOfTapsRequired = 0;
- longPress.minimumPressDuration = 1;
-// longPress.allowableMovement = 3;
- [self.imageView addGestureRecognizer:longPress];
-
-}
-- (void)tap:(UITapGestureRecognizer *)sender{
-
- NSLog(@"tap!");
-
- //恢复
- self.imageView.transform = CGAffineTransformIdentity;
-
-}
-- (void)pan:(UIPanGestureRecognizer *)sender{
-// CGPoint center = self.imageView.center;
-// if (center.x < 0){
-// center.x = 0;
-// }else{
-// center.x += [sender translationInView:self.view].x;
-// }
-//
-// center.y += [sender translationInView:self.view].y;
-// self.imageView.center = center;
- //将相对偏移量清零
-// [sender setTranslation:CGPointMake(0, 0) inView:self.view];
-
- self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, [sender translationInView:self.imageView].x, [sender translationInView:self.imageView].y);
- [sender setTranslation:CGPointZero inView:self.view];
-
-}
-
-- (void)pinch:(UIPinchGestureRecognizer *)sender{
-
- CGFloat scale = sender.scale;
- self.imageView.transform = CGAffineTransformScale(self.imageView.transform, scale, scale);
- [sender setScale:1];
-
-}
-
-- (void)rotaion:(UIRotationGestureRecognizer *)sender{
- //获取旋转弧度
- CGFloat rotation = sender.rotation;
- self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotation);
- sender.rotation = 0;
-
-// self.imageView.transform = CGAffineTransformMakeRotation(sender.rotation);
-
-}
-
-- (void)longPress:(UILongPressGestureRecognizer *)sender {
-
- NSLog(@"longPress:%@", sender);
-
- // 判断长按事件触发
- if (sender.state == UIGestureRecognizerStateBegan) {
- self.imageView.transform = CGAffineTransformMake(1, 0, 0, 1, 0, 0);
- }
-
-}
-
-
-//希望两个手势共存
-//遵守 UIGestureRecognizerDelegate 协议
-//实现方法 -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
-//将要同时实现的手势设置代理 pinch.delegate = self; pinch.delegate = self;
-
--(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
- return YES;
-}
-
-```
-
-有几点需要注意:
-
-- 给本身没有交互功能的控件()imagView, UIlabel, View等)添加手势,要设置`userInteractionEnabled`为**YES**,否则识别不了手势
-- 想要手势共存需要:
- - 遵守 `UIGestureRecognizerDelegate` 协议
- - 实现`-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer`方法,返回`YES`
- - 将要同时实现的手势设置代理 `pinch.delegate = self`; `pinch.delegate = self`
-
-# 在storyboard中添加手势
-
-在`storyboard`的控件栏中我们可以看到这些手势控件:
-
-![storyboard中的手势控件](http://ww1.sinaimg.cn/large/006tNc79gw1fb0j188yh1j30780evwfq.jpg)
-
-#### 使用方法:
-
-1. 直接将手势控件拖到要添加的视图上
-
- ![](http://ww3.sinaimg.cn/large/006tNc79gw1fb0ja1f8fnj30f206nwev.jpg)
-
-2. 关联手势事件
-
- ![](http://ww2.sinaimg.cn/large/006tNc79gw1fb0jaxllv6j30ol0be77b.jpg)
-
-3. 设置手势属性
-
- ![](http://ww2.sinaimg.cn/large/006tNc79gw1fb0jc5mon3j307c06ydgd.jpg)
-
-注意:若想同时识别多个手势,方法和上面相同,遵循协议,实现方法,设置代理,不过代理可以手动关联。
-
-![](http://ww4.sinaimg.cn/large/006tNc79gw1fb0jokip2vj30ej0aq3zz.jpg)
-
-
-
diff --git "a/_posts/2016-10-18-Xcode-Debug-\345\244\247\345\205\250.md" "b/_posts/2016-10-18-Xcode-Debug-\345\244\247\345\205\250.md"
deleted file mode 100644
index ce2e762f275..00000000000
--- "a/_posts/2016-10-18-Xcode-Debug-\345\244\247\345\205\250.md"
+++ /dev/null
@@ -1,249 +0,0 @@
----
-layout: post
-title: Xcode Debug 大全
-subtitle: iOS开发中利用 Xcode 各种调试Bug方法
-date: 2016-10-18
-author: BY
-header-img: img/post-bg-ios9-web.jpg
-catalog: true
-tags:
- - iOS
- - 开发技巧
- - Debug
----
-
-
-# 前言
-
->BUG,简单来说就是程序运行结果与预期的不同,下面来说说Xcode中的DEBUG方法
->
->[参考博文](http://www.cnblogs.com/daiweilai/p/4421340.html#quanjuduandian)
-
-
-
-# 断点调试
-
-- 普通断点
-- 全局断点
-- 条件断点
-
-#### 1.普通断点
-看图
-
-![](http://ww4.sinaimg.cn/large/65e4f1e6gw1f8rti38wlxj20ke0d3n0h.jpg)
-
-当程序运行到断点处时会停下,然后进行单步调试
-![](http://images.cnitblog.com/blog2015/680363/201504/131002381048966.png)
-
-
-#### 2.全局断点
-
-当程序运行出现崩溃时,就会自动断点到出现crash的代码行
-
-![](http://images.cnitblog.com/blog2015/680363/201504/130933043392329.png)
-
-#### 3.条件断点
-
-
-我们如果在一个循环里面使用了断点,如果这个循环执行了100万次,那你的断点要执行那么多次,你不觉得蛋蛋都凉了的忧伤么?所以我们这么做:
-
-编辑断点
-
-![](http://ww1.sinaimg.cn/large/65e4f1e6gw1f8rw64yys0j207i03laah.jpg)
-
-添加条件Condition
-
-![](http://ww2.sinaimg.cn/large/65e4f1e6gw1f8rw52q1tjj20ct04lmxo.jpg)
-
-
-![](http://ww3.sinaimg.cn/large/65e4f1e6gw1f8rw44p4ykj20ln0g10vg.jpg)
-
-还可以Action中在条件断点触发时执行事件
-
-![](http://ww3.sinaimg.cn/large/65e4f1e6gw1f8rwq16872j20cv07amyg.jpg)
-
-如:输出信息
-
-![](http://ww2.sinaimg.cn/large/65e4f1e6gw1f8rwms50t3j20dj07bjso.jpg)
-
-#### 4.方法断点
-
-# 打印调试(NSLog)
-
-尽管ARC已经让内存管理变得简单、省时和高效,但是在object的life-cycles中跟踪一些重要事件依然十分重要。毕竟ARC并没有完全排除内存泄露的可能性,或者试图访问一个被release的对象。
-
-- NSLog
-
-强化NSLog
-
-```
-//A better version of NSLog
-#define NSLog(format, ...) do { \
-fprintf(stderr, "<%s : %d> %s\n", \
-[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
-__LINE__, __func__); \
-(NSLog)((format), ##__VA_ARGS__); \
-fprintf(stderr, "-------\n"); \
-} while (0)
-```
-控制台输出
-
-```
- -[ViewController viewDidLoad]
-2016-10-14 17:33:31.022 DEUBG[12852:1238167] Hello World!
--------
-```
-
-利用NSString输出多种类型
-
-![](http://ww4.sinaimg.cn/large/65e4f1e6gw1f8rxvn6fqlj20nc05cgoh.jpg)
-
-- 开启僵尸对象
-
-Xcode可以把那些已经release掉得对象,变成“僵尸”,当我们访问一个Zombie对象时,Xcode可以告诉我们正在访问的对象是一个不应该存在的对象了。因为Xcode知道这个对象是什么,所以可以让我们知道这个对象在哪里,以及这是什么时候发生的。
-所以Zombies是你的好基友!他可以让你输出的信息更具体!
-
-具体这样做:(僵尸只能用在模拟器和OC语言)
-
-![](http://images.cnitblog.com/blog2015/680363/201504/130941016986159.png)
-
-# 控制台(lldb 命令)
-
-
-
-LLDB 是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部,存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它,你可以查看变量的值,执行自定的指令,并且按照你所认为合适的步骤来操作程序的进展。(这里有一个关于调试器如何工作的总体的解释。)
-
-你以前有可能已经使用过调试器,即使只是在 Xcode 的界面上加一些断点。但是通过一些小的技巧,你就可以做一些非常酷的事情。GDB to LLDB 参考是一个非常好的调试器可用命令的总览。你也可以安装 Chisel,它是一个开源的 LLDB 插件合辑,这会使调试变得更加有趣。
-
-参考:
-
-[与调试器共舞 - LLDB 的华尔兹](http://objccn.io/issue-19-2/)
-
-[LLDB调试命令初探](http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/)
-
-[About LLDB and Xcode](https://developer.apple.com/library/content/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/Introduction.html)
-
-[The LLDB Debugger](http://lldb.llvm.org/tutorial.html)
-
-#### 基础
-###### *help*
-在控制台输入`help`,显示控制台支持的lldb命令
-
-###### *print*
-打印值
-
-缩写`p`
-
-print是 `expression --` 的缩写
-
-
-![](http://ww4.sinaimg.cn/large/006y8lVagw1f8vakv88vuj30b204s74x.jpg)
-
-printk可以指定格式打印
-如
-`默认 p`
-
-`十六进制 p/x`、
-
-`二进制 p/t`
-
-```
-(lldb) p 16
-16
-
-(lldb) p/x 16
-0x10
-
-(lldb) p/t 16
-0b00000000000000000000000000010000
-
-(lldb) p/t (char)16
-0b00010000
-
-```
-你也可以使用 p/c 打印字符,或者 p/s 打印以空终止的字符串 p/d打印ACRSII(译者注:以 '\0' 结尾的字符串)。
-
-完整清单[点击查看](https://sourceware.org/gdb/onlinedocs/gdb/Output-Formats.html)
-
-
-###### *po*
-
-打印对象,是 `e -o --`的缩写
-
-###### *expression*
-
-#### 流程控制
-
-当你通过 Xcode 的源码编辑器的侧边槽 (或者通过下面的方法) 插入一个断点,程序到达断点时会就会停止运行。
-
-调试条上会出现四个你可以用来控制程序的执行流程的按钮。
-
-![](https://objccn.io/images/issues/issue-19/Image_2014-11-22_at_10.37.45_AM.png)
-
-从左到右,四个按钮分别是:continue,step over,step into,step out。
-
-第一个,continue 按钮,会取消程序的暂停,允许程序正常执行 (要么一直执行下去,要么到达下一个断点)。在 LLDB 中,你可以使用 process continue 命令来达到同样的效果,它的别名为 continue,或者也可以缩写为 c。
-
-第二个,step over 按钮,会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用,那么就不会跳进这个函数,而是会执行这个函数,然后继续。LLDB 则可以使用 thread step-over,next,或者 n 命令。
-
-如果你确实想跳进一个函数调用来调试或者检查程序的执行情况,那就用第三个按钮,step in,或者在LLDB中使用 thread step in,step,或者 s 命令。注意,当前行不是函数调用时,next 和 step 效果是一样的。
-
-大多数人知道 c,n 和 s,但是其实还有第四个按钮,step out。如果你曾经不小心跳进一个函数,但实际上你想跳过它,常见的反应是重复的运行 n 直到函数返回。其实这种情况,step out 按钮是你的救世主。它会继续执行到下一个返回语句 (直到一个堆栈帧结束) 然后再次停止。
-
-###### frame info
-
-会告诉你当前的行数和源码文件
-
-```
-(lldb) frame info
-frame #0: 0x000000010a53bcd4 DebuggerDance`main + 68 at main.m:17
-
-```
-
-###### Thread Return
-调试时,还有一个很棒的函数可以用来控制程序流程:thread return 。它有一个可选参数,在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧。这意味这函数剩余的部分不会被执行。这会给 ARC 的引用计数造成一些问题,或者会使函数内的清理部分失效。但是在函数的开头执行这个命令,是个非常好的隔离这个函数,伪造返回值的方式 。
-
-`(lldb) thread return NO`
-
-#### 不用断点调试
-
-在程序运行时,点击暂停按钮,即可进入调试状态,能对全局变量做操作
-
-![](http://ww4.sinaimg.cn/large/006y8lVagw1f8vd4vy66ej307300xjr8.jpg)
-
-
-# 工具调试(instruments)
-instruments Xcode自带许多工具供大家使用,打开方式如下图:
-
-![](http://ww1.sinaimg.cn/large/006y8lVagw1f8ve05g45cj30qd0f276o.jpg)
-
-**leaks**内存泄漏检查工具
-
-![](http://ww4.sinaimg.cn/large/006y8lVagw1f8ve5wnnr6j30li0c1wgd.jpg)
-
-运行后查看
-
-![](http://ww4.sinaimg.cn/large/006y8lVagw1f8vebiu6r5j30se0kdqcr.jpg)
-
-# 视图调试
-
-启用视图调试:运行app过程中,按下底部的Debug View Hierarchy 按钮,或者从菜单中选择Debug > View Debugging > Capture View Hierarchy 来启动视图调试。
-
-![](http://ww1.sinaimg.cn/large/006y8lVagw1f8vejy3rmgj30by01kmx8.jpg)
-
-启动视图调试后,Xcode会对应用程序的视图层次拍一个快照并展示三维原型视图来探究用户界面的层级。该三维视图除了展示app的视图层次外,还展示每个视图的位置、顺序和视图尺寸,以及视图间的交互方式。
-
-# 模拟器调试
-
-编译并运行应用程序,选中模拟器,从 Debug菜单中选择Color Blended Layers选项。
-
-![](http://ww2.sinaimg.cn/large/006y8lVagw1f8vezdqlh1j3092075dgz.jpg)
-
-然后会看到app的用户界面被红色和绿色覆盖,显示了哪些图层可以被叠加覆盖,以及哪些图层是透明的。混合层属于计算密集型视图,所以推荐尽可能地使用不透明的图层。
-
-![](http://ww3.sinaimg.cn/large/006y8lVagw1f8vf07u522j30ag0j1q36.jpg)
-
-# 结语
-目前所知道的调试方法大概就是上面这几种了,若有什么有趣的方法,请和我分享哈!
-
-
diff --git a/_posts/2016-10-26-BYPhoneNumTF.md b/_posts/2016-10-26-BYPhoneNumTF.md
deleted file mode 100644
index 74900bf67cb..00000000000
--- a/_posts/2016-10-26-BYPhoneNumTF.md
+++ /dev/null
@@ -1,397 +0,0 @@
----
-layout: post
-title: BYPhoneNumTF
-subtitle: 一个电话号码格式的文本框
-date: 2017-02-04
-author: BY
-header-img: img/post-bg-ios9-web.jpg
-catalog: true
-tags:
- - iOS
- - 轮子
----
-
->**BYPhoneNumTF** 一个电话号码格式的文本框
-
-# 功能
-
-当在`TextField`输入数字时,会自动分隔为:137 9922 2299 或 137-9922-2299
-
-限制文本输入个数
-
-限制只能输入数字
-# 效果:
-
-![](http://ww2.sinaimg.cn/large/7853084cgw1fa3cqnu8s2g207i0dc4qp.gif)
-
-
-# 实现方法
-
-要实现电话号码格式的输入看似简单,但是实现起来坑非常多,至于坑是什么只有各位动手写了才能体会~
-
-下面我们来实现该功能:
-
-首先要遵守协议``
-
-然后在`- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string` 方法中实现我们的逻辑
-
-代码:
-
-
-```
-#import "LoginVC.h"
-
-#define placeholder @" "
-
-@interface LoginVC ()
-
-@property (weak, nonatomic) IBOutlet UITextField *phoneNumberTF;
-
-@end
-
-
-- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
-
- NSString *phStr = placeholder;
- unichar phChar = ' ';
- if (phStr.length) {
- phChar = [phStr characterAtIndex:0];
- }
-
-
- if (textField) {
- NSString* text = textField.text;
- //删除
- if([string isEqualToString:@""]){
-
- //删除一位
- if(range.length == 1){
- //最后一位,遇到空格则多删除一次
- if (range.location == text.length - 1 ) {
- if ([text characterAtIndex:text.length - 1] == phChar) {
- [textField deleteBackward];
- }
- return YES;
- }
- //从中间删除
- else{
- NSInteger offset = range.location;
-
- if (range.location < text.length && [text characterAtIndex:range.location] == phChar && [textField.selectedTextRange isEmpty]) {
- [textField deleteBackward];
- offset --;
- }
- [textField deleteBackward];
- textField.text = [self _parseString:textField.text];
- UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset];
- textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos];
- return NO;
- }
- }
- else if (range.length > 1) {
- BOOL isLast = NO;
- //如果是从最后一位开始
- if(range.location + range.length == textField.text.length ){
- isLast = YES;
- }
- [textField deleteBackward];
- textField.text = [self _parseString:textField.text];
-
- NSInteger offset = range.location;
- if (range.location == 3 || range.location == 8) {
- offset ++;
- }
- if (isLast) {
- //光标直接在最后一位了
- }else{
- UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset];
- textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos];
- }
-
- return NO;
- }
-
- else{
- return YES;
- }
- }
-
- else if(string.length >0){
-
- //限制输入字符个数
- if (([self _noneSpaseString:textField.text].length + string.length - range.length > 11) ) {
- return NO;
- }
-
- //判断是否是纯数字(搜狗,百度输入法,数字键盘居然可以输入其他字符)
- if(![self _isNum:string]){
- return NO;
- }
- [textField insertText:string];
- textField.text = [self _parseString:textField.text];
-
- NSInteger offset = range.location + string.length;
- if (range.location == 3 || range.location == 8) {
- offset ++;
- }
- UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset];
- textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos];
- return NO;
- }else{
- return YES;
- }
-
- }
-
- return YES;
-
-
-}
-
-- (NSString*)_parseString:(NSString*)string{
-
- if (!string) {
- return nil;
- }
- NSMutableString* mStr = [NSMutableString stringWithString:[string stringByReplacingOccurrencesOfString:placeholder withString:@""]];
- if (mStr.length >3) {
- [mStr insertString:placeholder atIndex:3];
- }if (mStr.length > 8) {
- [mStr insertString:placeholder atIndex:8];
-
- }
-
- return mStr;
-
-}
-
-/** 获取正常电话号码(去掉空格) */
-- (NSString*)_noneSpaseString:(NSString*)string{
-
- return [string stringByReplacingOccurrencesOfString:placeholder withString:@""];
-
-}
-
-- (BOOL)_isNum:(NSString *)checkedNumString {
-
- if (!checkedNumString) {
- return NO;
- }
-
- checkedNumString = [checkedNumString stringByTrimmingCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]];
-
- if(checkedNumString.length > 0) {
- return NO;
- }
-
- return YES;
-
-}
-
-```
-
-
-###封装方法
-
-需要实现的代码就是要这么多,但这些代码写在ViewController显得太臃肿了,所以我对代码进行了封装:
-
-```
-//
-// BYPhoneNumTF.h
-//
-// Created by BY on 16/12/2.
-// Copyright © 2016年 BY. All rights reserved.
-// 电话号码类型的文本输入框,且只能输入数字
-// 输入显示:137 9922 1234 或 137-9922-1234
-// 使用方法:在XIB中的TextField继承该类即可
-// 修改占位符placeholder即可改变样式
-
-#import
-
-// @" " or @"-"
-#define placeholder @" "
-
-@interface BYPhoneNumTF : UITextField
-
-/** 去掉格式的电话号码 */
-@property (nonatomic, strong) NSString *plainPhoneNum;
-
-@end
-```
-
-```
-//
-// BYPhoneNumTF.m
-// dev-Jack
-//
-// Created by BY on 16/12/2.
-// Copyright © 2016年 Jack. All rights reserved.
-//
-
-#import "BYPhoneNumTF.h"
-
-
-@interface BYPhoneNumTF ()
-
-@end
-
-@implementation BYPhoneNumTF
-
-- (NSString *)plainPhoneNum {
- return [self _noneSpaseString:self.text];
-}
-
-- (void)awakeFromNib {
- [super awakeFromNib];
- self.delegate = self;
-}
-
-
-- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
-
- NSString *phStr = placeholder;
- unichar phChar = ' ';
- if (phStr.length) {
- phChar = [phStr characterAtIndex:0];
- }
-
-
- if (textField) {
- NSString* text = textField.text;
- //删除
- if([string isEqualToString:@""]){
-
- //删除一位
- if(range.length == 1){
- //最后一位,遇到空格则多删除一次
- if (range.location == text.length - 1 ) {
- if ([text characterAtIndex:text.length - 1] == phChar) {
- [textField deleteBackward];
- }
- return YES;
- }
- //从中间删除
- else{
- NSInteger offset = range.location;
-
- if (range.location < text.length && [text characterAtIndex:range.location] == phChar && [textField.selectedTextRange isEmpty]) {
- [textField deleteBackward];
- offset --;
- }
- [textField deleteBackward];
- textField.text = [self _parseString:textField.text];
- UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset];
- textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos];
- return NO;
- }
- }
- else if (range.length > 1) {
- BOOL isLast = NO;
- //如果是从最后一位开始
- if(range.location + range.length == textField.text.length ){
- isLast = YES;
- }
- [textField deleteBackward];
- textField.text = [self _parseString:textField.text];
-
- NSInteger offset = range.location;
- if (range.location == 3 || range.location == 8) {
- offset ++;
- }
- if (isLast) {
- //光标直接在最后一位了
- }else{
- UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset];
- textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos];
- }
-
- return NO;
- }
-
- else{
- return YES;
- }
- }
-
- else if(string.length >0){
-
- //限制输入字符个数
- if (([self _noneSpaseString:textField.text].length + string.length - range.length > 11) ) {
- return NO;
- }
-
- //判断是否是纯数字(搜狗,百度输入法,数字键盘居然可以输入其他字符)
- if(![self _isNum:string]){
- return NO;
- }
- [textField insertText:string];
- textField.text = [self _parseString:textField.text];
-
- NSInteger offset = range.location + string.length;
- if (range.location == 3 || range.location == 8) {
- offset ++;
- }
- UITextPosition *newPos = [textField positionFromPosition:textField.beginningOfDocument offset:offset];
- textField.selectedTextRange = [textField textRangeFromPosition:newPos toPosition:newPos];
- return NO;
- }else{
- return YES;
- }
-
- }
-
- return YES;
-
-
-}
-
-- (NSString*)_parseString:(NSString*)string{
-
- if (!string) {
- return nil;
- }
- NSMutableString* mStr = [NSMutableString stringWithString:[string stringByReplacingOccurrencesOfString:placeholder withString:@""]];
- if (mStr.length >3) {
- [mStr insertString:placeholder atIndex:3];
- }if (mStr.length > 8) {
- [mStr insertString:placeholder atIndex:8];
-
- }
-
- return mStr;
-
-}
-
-/** 获取正常电话号码(去掉空格) */
-- (NSString*)_noneSpaseString:(NSString*)string{
-
- return [string stringByReplacingOccurrencesOfString:placeholder withString:@""];
-
-}
-
-- (BOOL)_isNum:(NSString *)checkedNumString {
-
- if (!checkedNumString) {
- return NO;
- }
-
- checkedNumString = [checkedNumString stringByTrimmingCharactersInSet:[NSCharacterSet decimalDigitCharacterSet]];
-
- if(checkedNumString.length > 0) {
- return NO;
- }
-
- return YES;
-
-}
-
-
-@end
-
-```
-
-# 使用方法
-
-在storyboard中的`TextField`控件的Calss类型选择该类`BYPhoneNumTF`即可。
-
-代码及Demo下载地址:[BYPhoneNumTF](https://github.com/qiubaiying/BYPhoneNumTF)
\ No newline at end of file
diff --git "a/_posts/2016-10-26-JSON-\350\275\254\346\250\241\345\236\213-For-YYModel.md" "b/_posts/2016-10-26-JSON-\350\275\254\346\250\241\345\236\213-For-YYModel.md"
deleted file mode 100644
index f14e03dd9ac..00000000000
--- "a/_posts/2016-10-26-JSON-\350\275\254\346\250\241\345\236\213-For-YYModel.md"
+++ /dev/null
@@ -1,78 +0,0 @@
----
-layout: post
-title: JSON转模型 For YYModel
-subtitle: 使用 YYModel库 快速完成 JSON 转模型
-date: 2016-10-26
-author: BY
-header-img: img/post-bg-ios9-web.jpg
-catalog: true
-tags:
- - iOS
- - 开发技巧
----
-
->JSON转模型是我们做iOS开发的基础技能,本文将通过[YYModel](https://github.com/ibireme/YYModel)这个框架安全快速的完成JSON到模型的转换,其中还会介绍到一款好用的插件[ESJsonFormat](https://github.com/EnjoySR/ESJsonFormat-Xcode)。
-
-# 1、首先创建模型类
-创建模型类我们可以通过[ESJsonFormat](https://github.com/EnjoySR/ESJsonFormat-Xcode)这款插件快速完成。
-
-使用方法:
-
-将光标移动到代码行中 如下图的13行
-
-然后点击`Window`->`ESJsonFormat`->`Input JSON Window`调出窗口
-
-
-![](http://ww1.sinaimg.cn/large/006y8lVagw1f95tr49ed7j30no0csdir.jpg)
-
-在窗口中输入你要解析的JSON文本,如下图:
-
-![](http://ww4.sinaimg.cn/large/006y8lVagw1f97s13l4b9j30jv0e8dhp.jpg)
-
-按`Enter`继续,然后神奇的一幕发生了
-
-![](http://ww3.sinaimg.cn/large/006y8lVagw1f97s46k95tj30k30dydj9.jpg)
-
-![](http://ww1.sinaimg.cn/large/006y8lVagw1f97s6yp9hmj30iw0b840m.jpg)
-
-看到在.h中 所有的属性自动为你填上,而且帮你选好了类型
-
-.m 也为你声明了`list`中成员的类型,不过这里需要稍作修改,因为我们需要用到YYModel进行解析,所以方法名改成`modelContainerPropertyGenericClass`
-
-```
-+ (NSDictionary *)modelContainerPropertyGenericClass {
- return @{@"list" : [List class]};
-}
-
-```
-
-还有问题就是属性中出现关键字`id`,我们需要将id改为`teacherId`
-
-然后在.m的`implementation`中声明,将字典的的`id`
-
-```
-+ (NSDictionary *)modelCustomPropertyMapper {
- return @{@"teacherId" : @"id"};
-}
-```
-
-这样,模型的创建就完成了,剩下的就是用YYModel进行解析了
-
-# 2、使用YYModel进行解析
-
-解析很简单,就只需要一句话
-
-```
-// 将 JSON (NSData,NSString,NSDictionary) 转换为 Model:
-Model *model = [Model yy_modelWithJSON:json];
-
-// 或者
-Model *model = [[Model alloc] init];
-[model yy_modelSetWithDictionary:json];
-
-```
-
-到此,简便快速的完成了JSON到模型的转换。
-
-
-最后,[这里附上一篇YYModel的使用](http://www.jianshu.com/p/25e678fa43d3)
\ No newline at end of file
diff --git "a/_posts/2016-11-10-Objective-C Runtime\350\257\246\350\247\243.md" "b/_posts/2016-11-10-Objective-C Runtime\350\257\246\350\247\243.md"
deleted file mode 100644
index d68ad366cb9..00000000000
--- "a/_posts/2016-11-10-Objective-C Runtime\350\257\246\350\247\243.md"
+++ /dev/null
@@ -1,863 +0,0 @@
----
-layout: post
-title: Objective-C Runtime 详解
-subtitle: Runtime 详解
-date: 2017-02-04
-author: BY
-header-img: img/post-bg-ios9-web.jpg
-catalog: true
-tags:
- - Obj-C
- - Runtime
- - iOS
----
-
-# 前言
->最近在学习Runtime的知识,恰巧发现了这篇博客[《Objective-C Runtime》](http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/),在此基础上,进行了些许补充说明,如有错误或其他想法,欢迎提出交流。
-
-## 基础知识
-- 引言
-- 简介
-- 与Runtime交互
-- RunTime术语
-- 消息
-- 动态方法解析
-- 消息转发
-- 健壮的实例变量
-- 动态添加属性(Object-C Associated Objects)
-- 方法调剂(Method Swizzling)
-- 总结
-
-### 引言
-
-Objective-C的方法调用实则为“发送消息”,我们来看`[dog eat]`实际会被编译器转化为
-
-```
-objc_msgSend(dog, SEL)//SEL为eat方法的标识符@selector(@"eat")
-```
-
-若方法中函数参数,则为:
-
-```
-objc_msgSend(dog, SEL, arg1, arg2, ...)
-```
-
-如果消息的接收者能够找到对应的方法,那么就相当于直接执行了接收者这个对象的特定方法;否则,消息要么被转发,或是临时向接收者动态添加这个方法对应的实现内容,要么就干脆就crash掉。
-
-现在可以看出`[dog eat]`真的不是一个简简单单的方法调用。因为这只是在编译阶段确定了要向接收者发送`eat`这条消息,而`dog`将要如何响应这条消息,那就要看运行时发生的情况来决定了。
-
-Objective-C 的 Runtime 铸就了它动态语言的特性,这些深层次的知识虽然平时写代码用的少一些,但是却是每个 Objc 程序员需要了解的。
-
-### 简介
-
-因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。
-
-Runtime其实有两个版本:“modern”和 “legacy”。我们现在用的 Objective-C 2.0 采用的是现行(Modern)版的Runtime系统,只能运行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X较老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。
-
-Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在[这里](https://opensource.apple.com/source/objc4/)下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。
-
-### 与Runtime交互
-
-Objc 从`三种`不同的层级上与 Runtime 系统进行交互,分别是通过 `Objective-C 源代码`,通过 Foundation 框架的`NSObject类定义的方法`,通过对 `runtime 函数`的直接调用。
-
-#### Objective-C源代码
-
-大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。
-还记得引言中举的例子吧,消息的执行会使用到一些编译器为实现动态语言特性而创建的数据结构和函数,Objc中的类、方法和协议等在 runtime 中都由一些数据结构来定义,这些内容在后面会讲到。(比如`objc_msgSend`函数及其参数列表中的`id`和`SEL`都是啥)
-
-#### NSObject的方法
-
-Cocoa 中大多数类都继承于`NSObject`类,也就自然继承了它的方法。最特殊的例外是`NSProxy`,它是个抽象超类,它实现了一些消息转发有关的方法,可以通过继承它来实现一个其他类的替身类或是虚拟出一个不存在的类,说白了就是领导把自己展现给大家风光无限,但是把活儿都交给幕后小弟去干。
-
-有的`NSObject`中的方法起到了抽象接口的作用,比如`description`方法需要你重载它并为你定义的类提供描述内容。`NSObject`还有些方法能在运行时获得类的信息,并检查一些特性,比如`class`返回对象的类;`isKindOfClass`:和`isMemberOfClass:`则检查对象是否在指定的类继承体系中;`respondsToSelector:`检查对象能否响应指定的消息;`conformsToProtocol:`检查对象是否实现了指定协议类的方法;`methodForSelector:`则返回指定方法实现的地址。
-
-#### Runtime的函数
-
-Runtime 系统是一个由一系列函数和数据结构组成,具有公共接口的动态共享库。头文件存放于`/usr/include/objc`目录下。许多函数允许你用纯C代码来重复实现 Objc 中同样的功能。虽然有一些方法构成了`NSObject`类的基础,但是你在写 Objc 代码时一般不会直接用到这些函数的,除非是写一些 Objc 与其他语言的桥接或是底层的debug工作。在[Objective-C Runtime Reference](https://developer.apple.com/reference/objectivec/1657527-objective_c_runtime)中有对 Runtime 函数的详细文档。
-
-### Runtime术语
-
-还记得引言中的`objc_msgSend:`方法吧,它的真身是这样的
-
-```
-id objc_msgSend ( id self, SEL op, ... );
-```
-下面将会逐渐展开介绍一些术语,其实它们都对应着数据结构。
-
-#### SEL
-
-`objc_msgSend`函数第二个参数类型为`SEL`,它是`selector`在Objc中的表示类型(Swift中是`Selector`类)。`selector`是方法选择器,可以理解为区分方法的标识,而这个标识的数据结构是SEL:
-
-```
-typedef struct objc_selector *SEL;
-```
-
-本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。这个查找过程我们将在下面讨论。
-
-我们可以在运行时添加新的selector,也可以在运行时获取已存在的selector,我们可以通过下面三种方法来获取SEL:
-
-1. sel_registerName函数
-
-2. Objective-C编译器提供的@selector()
-
-3. NSSelectorFromString()方法
-
-#### id
-
-`objc_msgSend`第一个参数类型为`id`,大家对它都不陌生,它是一个指向类实例的指针:
-
-```
-typedef struct objc_object *id;
-```
-
-那`objc_object`又是啥呢:
-
-```
-struct objc_object { Class isa; };
-```
-`objc_object`结构体包含一个`isa`指针,根据`isa`指针就可以顺藤摸瓜找到对象所属的类。
-
-PS:`isa`指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用`class`方法来确定实例对象的类。因为KVO的实现机理就是将被观察对象的isa指针指向一个中间类而不是真实的类,这是一种叫做 **isa-swizzling** 的技术,详见[官方文档](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOImplementation.html#//apple_ref/doc/uid/20002307-BAJEAIEE)的这句段说明
-
->Key-Value Observing Implementation Details
-
->Automatic key-value observing is implemented using a technique called isa-swizzling.
-
->The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
-
->When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
-
->You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.
-
-#### Class
-
-之所以说`isa`是指针是因为`Class`其实是一个指向`objc_class`结构体的指针:
-
-```
-typedef struct objc_class *Class;
-```
-而`objc_class`就是我们摸到的那个瓜,里面的东西多着呢:
-
-```
-struct objc_class {
- Class isa OBJC_ISA_AVAILABILITY;
-
-#if !__OBJC2__
- Class super_class OBJC2_UNAVAILABLE;
- const char *name OBJC2_UNAVAILABLE;
- long version OBJC2_UNAVAILABLE;
- long info OBJC2_UNAVAILABLE;
- long instance_size OBJC2_UNAVAILABLE;
- struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
- struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
- struct objc_cache *cache OBJC2_UNAVAILABLE;
- struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
-#endif
-
-} OBJC2_UNAVAILABLE;
-```
-可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议
-
-PS:`OBJC2_UNAVAILABLE`之类的宏定义是苹果在 Objc 中对系统运行版本进行约束的黑魔法,为的是兼容非Objective-C 2.0的遗留逻辑,但我们仍能从中获得一些有价值的信息,有兴趣的可以查看源代码
-
-`Objective-C 2.0` 的头文件虽然没暴露出`objc_class`结构体更详细的设计,我们依然可以从`Objective-C 1.0` 的定义中小窥端倪
-
-在`objc_class`结构体中:`ivars`是`objc_ivar_list`指针;`methodLists`是指向`objc_method_list`指针的指针。也就是说可以动态修改`*methodLists`的值来添加成员方法,这也是Category实现的原理,同样解释了Category不能添加属性的原因。而最新版的 Runtime 源码对这一块的描述已经有很大变化,可以参考下美团技术团队的[深入理解Objective-C:Category](http://tech.meituan.com/DiveIntoCategory.html).
-
-PS:任性的话可以在Category中添加`@dynamic`的属性,并利用运行期动态提供存取方法或干脆动态转发;或者干脆使用关联度对象(AssociatedObject)
-
-其中`objc_ivar_list`和`objc_method_list`分别是成员变量列表和方法列表:
-
-```
-struct objc_ivar_list {
- int ivar_count OBJC2_UNAVAILABLE;
-#ifdef __LP64__
- int space OBJC2_UNAVAILABLE;
-#endif
- /* variable length structure */
- struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
-} OBJC2_UNAVAILABLE;
-
-struct objc_method_list {
- struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
-
- int method_count OBJC2_UNAVAILABLE;
-#ifdef __LP64__
- int space OBJC2_UNAVAILABLE;
-#endif
- /* variable length structure */
- struct objc_method method_list[1] OBJC2_UNAVAILABLE;
-}
-```
-如果你C语言不是特别好,可以理解为`objc_ivar_list`结构体存储着`objc_ivar`数组列表,而`objc_ivar`结构体存储了类的单个成员变量的信息;同理`objc_method_list`结构体存储着`objc_method`数组列表,而o`bjc_method`结构体存储了类的某个方法的信息。
-
-最后要提到的还有一个`objc_cache`,顾名思义它是缓存,它在`objc_class`的作用很重要,在后面会讲到。
-
-不知道你是否注意到了`objc_class`中也有一个`isa`对象,这是因为一个 ObjC 类本身同时也是一个对象,为了处理类和对象的关系,runtime 库创建了一种叫做元类 (Meta Class) 的东西,类对象所属类型就叫做元类,它用来表述类对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。当你发出一个类似`[NSObject alloc]`的消息时,你事实上是把这个消息发给了一个类对象 (Class Object) ,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。所有的元类最终都指向根元类为其超类。所有的元类的方法列表都有能够响应消息的类方法。所以当 `[NSObject alloc]` 这条消息发给类对象的时候,`objc_msgSend()`会去它的元类里面去查找能够响应消息的方法,如果找到了,然后对这个类对象执行方法调用。
-
-![](http://7ni3rk.com1.z0.glb.clouddn.com/Runtime/class-diagram.jpg)
-
-上图实线是 `super_class` 指针,虚线是`isa`指针。 有趣的是根元类的超类是`NSObjec`t,而`isa`指向了自己,而`NSObject`的超类为`nil`,也就是它没有超类
-
-####Method
-
-`Method`是一种代表类中的某个方法的类型。
-
-```
-typedef struct objc_method *Method;
-```
-而`objc_method`在上面的方法列表中提到过,它存储了方法名,方法类型和方法实现:
-
-```
-struct objc_method {
- SEL method_name OBJC2_UNAVAILABLE;
- char *method_types OBJC2_UNAVAILABLE;
- IMP method_imp OBJC2_UNAVAILABLE;
-} OBJC2_UNAVAILABLE;
-
-```
-- 方法名 `method_name` 类型为 `SEL`, 相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
-- 方法类型`method_types`是个`char`指针,存储着方法的 参数类型 和 返回值 类型。
-- `method_imp`指向了方法的实现,本质上是一个函数指针,后面会详细讲到。
-
-#### Ivar
-
-Ivar是一种代表类中实例变量的类型。定义如下:
-
-```
-typedef struct objc_ivar *Ivar;
-```
-它是一个指向objc_ivar结构体的指针,结构体有如下定义:
-
-```
-struct objc_ivar {
- char *ivar_name OBJC2_UNAVAILABLE;
- char *ivar_type OBJC2_UNAVAILABLE;
- int ivar_offset OBJC2_UNAVAILABLE;
-#ifdef __LP64__
- int space OBJC2_UNAVAILABLE;
-#endif
-} OBJC2_UNAVAILABLE;
-```
-这里我们注意第三个成员 `ivar_offset`。它表示基地址偏移字节。
-
-在编译我们的类时,编译器生成了一个 `ivar` 布局,显示了在类中从哪可以访问我们的 `ivars` 。
-
-我们对 ivar 的访问就可以通过 `对象地址` + `ivar偏移字节`的方法。
-
-但是当我们增加了父类的`ivar`,这个时候布局就出错了,我们就不得不重新编译子类来恢复兼容性。
-
-而Objective-C Runtime中使用了`Non Fragile ivars`来避免这个问题
-
-使用`Non Fragile ivars`时,Runtime会进行检测来调整类中新增的`ivar`的偏移量。 这样我们就可以通过 `对象地址 + 基类大小 + ivar偏移字节`的方法来计算出`ivar`相应的地址,并访问到相应的`ivar`。
-
-可以根据实例查找其在类中的名字,也就是“反射”:
-
-```
--(NSString *)nameWithInstance:(id)instance {
- unsigned int numIvars = 0;
- NSString *key=nil;
- Ivar * ivars = class_copyIvarList([self class], &numIvars);
- for(int i = 0; i < numIvars; i++) {
- Ivar thisIvar = ivars[i];
- const char *type = ivar_getTypeEncoding(thisIvar);
- NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
- if (![stringType hasPrefix:@"@"]) {
- continue;
- }
- if ((object_getIvar(self, thisIvar) == instance)) {//此处若 crash 不要慌!
- key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
- break;
- }
- }
- free(ivars);
- return key;
-}
-```
-`class_copyIvarList` 函数获取的不仅有实例变量,还有属性。但会在原本的属性名前加上一个下划线。(属性的本质就是 `_属性名+set+get方法`)
-
-#### IMP
-
-`IMP`在`objc.h`中的定义是:
-
-```
-typedef id (*IMP)(id, SEL, ...);
-```
-它就是一个[函数指针](http://yulingtianxia.com/blog/2014/04/17/han-shu-zhi-zhen-yu-zhi-zhen-han-shu/),这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 `IMP` 这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,这在后面会提到。
-
-我们再来看看objc_msgSend()的定义:` id objc_msgSend(id self, SEL op, ...)`
-
-你会发现`IMP`指向的方法与`objc_msgSend`函数类型相同,参数都包含id和SEL类型。每个方法名都对应一个SEL类型的方法选择器,而每个实例对象中的`SEL`对应的方法实现肯定是唯一的,通过一组`id`和`SEL`参数就能确定唯一的方法实现地址。
-
-#### Cache
-
-在`runtime.h`中Cache的定义如下:
-
-```
-typedef struct objc_cache *Cache
-```
-还记得之前 `objc_class` 结构体中有一个 `struct objc_cache *cache` 吧,它到底是缓存啥的呢,先看看 `objc_cache` 的实现:
-
-```
-struct objc_cache {
- unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
- unsigned int occupied OBJC2_UNAVAILABLE;
- Method buckets[1] OBJC2_UNAVAILABLE;
-};
-```
-`objc_cache` 的定义看起来很简单,它包含了下面三个变量:
-
-- `mask`:可以认为是当前能达到的最大index(从0开始的),所以缓存的size(total)是mask+1
-- `occupied`:被占用的槽位,因为缓存是以散列表的形式存在的,所以会有空槽,而occupied表示当前被占用的数目
-- `buckets`:用数组表示的hash表,cache_entry类型,每一个cache_entry代表一个方法缓存
-
-(buckets定义在objc_cache的最后,说明这是一个可变长度的数组)
-
-`Cache`为方法调用的性能进行优化,下面我们来看看`objc_msgSend`具体又是如何分发的呢? 我们来看下runtime层`objc_msgSend`的源码。
-
-在`objc-msg-arm.s`中,`objc_msgSend`的代码如下:
-
-ps:Apple为了高度优化objc_msgSend的性能,这个文件是汇编写成的,不过即使我们不懂汇编,详尽的注释也可以让我们一窥其真面目
-
-```
-ENTRY objc_msgSend
-# check whether receiver is nil
-teq a1, #0
- beq LMsgSendNilReceiver
-# save registers and load receiver's class for CacheLookup
-stmfd sp!, {a4,v1}
-ldr v1, [a1, #ISA]
-# receiver is non-nil: search the cache
-CacheLookup a2, v1, LMsgSendCacheMiss
-# cache hit (imp in ip) and CacheLookup returns with nonstret (eq) set, restore registers and call
-ldmfd sp!, {a4,v1}
-bx ip
-# cache miss: go search the method lists
-LMsgSendCacheMiss:
-ldmfd sp!, {a4,v1}
-b _objc_msgSend_uncached
-LMsgSendNilReceiver:
- mov a2, #0
- bx lr
-LMsgSendExit:
-END_ENTRY objc_msgSend
-STATIC_ENTRY objc_msgSend_uncached
-# Push stack frame
-stmfd sp!, {a1-a4,r7,lr}
-add r7, sp, #16
-# Load class and selector
-ldr a3, [a1, #ISA] /* class = receiver->isa */
-/* selector already in a2 */
-/* receiver already in a1 */
-# Do the lookup
-MI_CALL_EXTERNAL(__class_lookupMethodAndLoadCache3)
-MOVE ip, a1
-# Prep for forwarding, Pop stack frame and call imp
-teq v1, v1 /* set nonstret (eq) */
-ldmfd sp!, {a1-a4,r7,lr}
-bx ip
-```
-
-如果向更深入了解 `objc_cache` ,可以看看这篇博文[深入理解Objective-C:方法缓存](http://www.cocoachina.com/ios/20150818/13075.html)
-
-从上述代码中可以看到,`objc_msgSend`(就ARM平台而言)的消息分发分为以下几个步骤:
-
-1. 判断receiver是否为nil,也就是objc_msgSend的第一个参数self,也就是要调用的那个方法所属对象
-2. 从缓存里寻找,找到了则分发,否则
-3. 利用objc-class.mm中_class_lookupMethodAndLoadCache3(为什么有个这么奇怪的方法。本文末尾会解释)方法去寻找selector
-4. 如果支持GC,忽略掉非GC环境的方法(retain等)
-5. 从本class的method list寻找selector,如果找到,填充到缓存中,并返回selector,否则
-6. 寻找父类的method list,并依次往上寻找,直到找到selector,填充到缓存中,并返回selector,否则
-7. 调用_class_resolveMethod,如果可以动态resolve为一个selector,不缓存,方法返回,否则
-8. 转发这个selector,否则
-9. 报错,抛出异常
-
-从上面的分析中我们可以看到,当一个方法在比较“上层”的类中,用比较“下层”(继承关系上的上下层)对象去调用的时候,如果没有缓存,那么整个查找链是相当长的。就算方法是在这个类里面,当方法比较多的时候,每次都查找也是费事费力的一件事情。
-
-当我们需要去调用一个方法数十万次甚至更多地时候,查找方法的消耗会变的非常显著。就算我们平常的非大规模调用,`除非一个方法只会调用一次,否则缓存都是有用的。`在运行时,那么多对象,那么多方法调用,节省下来的时间也是非常可观的。可见缓存的重要性。
-
-方法缓存存在什么地方?
-
-让我们再去去翻看 `objc_class` 的定义,
-
-```
-struct objc_class {
- Class isa OBJC_ISA_AVAILABILITY;
-
-#if !__OBJC2__
- Class super_class OBJC2_UNAVAILABLE;
- const char *name OBJC2_UNAVAILABLE;
- long version OBJC2_UNAVAILABLE;
- long info OBJC2_UNAVAILABLE;
- long instance_size OBJC2_UNAVAILABLE;
- struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
- struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
- struct objc_cache *cache OBJC2_UNAVAILABLE;
- struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
-#endif
-
-} OBJC2_UNAVAILABLE;
-```
-
-我们看到在类的定义里就有`cache`字段,没错,类的所有缓存都存在metaclass上,所以每个类都只有一份方法缓存,而不是每一个类的object都保存一份。
-
-子类类即便是从父类取到的方法,也会存在类本身的方法缓存里。而当用一个父类对象去调用那个方法的时候,也会在父类的metaclass里缓存一份。
-
-#### Property
-
-`@property`标记了类中的属性,这个不必多说大家都很熟悉,它是一个指向objc_property结构体的指针:
-
-```
-typedef struct objc_property *Property;
-typedef struct objc_property *objc_property_t;//这个更常用
-```
-
-现在在类中声明声明属性和成员变量:
-
-```
-@interface ViewController ()
-{
- int age;
- NSString *name;
-}
-@property (nonatomic, strong) NSString *property1;
-@property (nonatomic, strong) NSString *property2;
-@property (nonatomic, assign) int age;//这里的age为属性,对应变量:_age
-@property (nonatomic, assign) long ID;
-
-@end
-```
-
-然后用下面的方法来获取类中属性列表:
-
-```
-id LenderClass = objc_getClass("ViewController");//获取calss
-//id LenderClass = [MyViewController class];//同上
-unsigned int outCount;//属性数量
-objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);//获取属性列表
-for (int i = 0; i < outCount; i++) {// 遍历
- objc_property_t property = properties[i];
- const char *propertyName = property_getName(property);
- const char *propertyAttributes = property_getAttributes(property);
- printf("propertyName:%s \n", propertyName);
- printf("propertyAttributes:%s\n--------\n", propertyAttributes);//属性名及描述
-}
-```
-
-控制台输出:
-
-```
-propertyName:property1
-propertyAttributes:T@"NSString",&,N,V_property1
---------
-propertyName:property2
-propertyAttributes:T@"NSString",&,N,V_property2
---------
-propertyName:age
-propertyAttributes:Ti,N,V_age
---------
-propertyName:ID
-propertyAttributes:Tq,N,V_ID
-```
-
-
-我们再来来看看获取成员变量的方法:
-
-```
-id selfClass = [self class];
-unsigned int numIvars = 0;
-Ivar *ivars = class_copyIvarList(selfClass, &numIvars);
-for(int i = 0; i < numIvars; i++) {
- Ivar ivar = ivars[i];
- const char *ivarType = ivar_getTypeEncoding(ivar);// 获取类型
- const char *ivarName = ivar_getName(ivar);
- printf("ivarName:%s\n", ivarName);
- printf("ivarType:%s\n------\n", ivarType);
-}
-```
-控制台输出:
-
-```
-ivarName:age
-ivarType:i
-------
-ivarName:name
-ivarType:@"NSString"
-------
-ivarName:_age
-ivarType:i
-------
-ivarName:_property1
-ivarType:@"NSString"
-------
-ivarName:_property2
-ivarType:@"NSString"
-------
-ivarName:_ID
-ivarType:q
-```
-
-我们会发现与 `class_copyIvarList` 函数不同,使用 `class_copyPropertyList` 函数只能获取类的属性,而不包含成员变量。但此时获取的属性名是不带下划线的,得到属性或者变量名后我们就可以使用KVC去修改访问类中的私有属性或变量。所以OC中没有真正意义上的私有变量,私有方法也是。
-
-### 消息
-前面做了这么多铺垫,现在终于说到了消息了。Objc 中发送消息是用中括号 `[]` 把接收者和消息括起来,而直到运行时才会把消息与方法实现绑定。
-
-有关消息发送和消息转发机制的原理,可以查看[这篇文章](http://yulingtianxia.com/blog/2016/06/15/Objective-C-Message-Sending-and-Forwarding/)。
-
-#### objc_msgSend函数
-
-在引言中已经对 `objc_msgSend` 进行了一点介绍,看起来像是 `objc_msgSend` 返回了数据,其实 `objc_msgSend` 从不返回数据而是你的方法被调用后返回了数据。下面详细叙述下消息发送步骤:
-
-1. 检测这个 消息 是不是要忽略的。比如 Mac OS X 开发,在ARC中有了垃圾回收就不理会MRC的 `retain`, `release` 这些函数了。
-2. 检测这个 目标对象 是不是 `nil` 对象。ObjC 的特性是允许对一个 `nil` 对象执行任何一个方法不会 Crash,因为会被忽略掉。
-3. 如果上面两个都过了,那就开始查找这个类的 `IMP`,先从 `cache` 里面找,完了找得到就跳到对应的函数去执行。
-4. 如果 `cache` 找不到就找一下方法分发表。
-5. 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
-6. 如果还找不到就要开始进入动态方法解析了,后面会提到。
-
-PS:这里说的分发表其实就是 `Class` 中的方法列表,它将方法选择器和方法实现地址联系起来。
-![](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Art/messaging1.gif)
-
-其实编译器会根据情况在`objc_msgSend`, `objc_msgSend_stret`, `objc_msgSendSuper`, 或 `objc_msgSendSuper_stret`四个方法中选择一个来调用。如果消息是传递给超类,那么会调用名字带有”Super”的函数;如果消息返回值是数据结构而不是简单值时,那么会调用名字带有`”stret”`的函数。排列组合正好四个方法
-
-PS:有木有发现这些函数的命名规律哦?带 `“Super”` 的是消息传递给超类;`“stret”`可分为`“st”`+`“ret”`两部分,分别代表 `“struct”` 和 `“return”` ;`“fpret”`就是 `“fp”` + `“ret”`,分别代表`“floating-point”`和 `“return”`。
-
-#### 方法中的隐藏参数
-我们经常在方法中使用 `self` 关键字来引用实例本身,但从没有想过为什么 `self` 就能取到调用当前方法的对象吧。其实 `self` 的内容是在方法运行时被偷偷的动态传入的
-
-当 `objc_msgSend` 找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:
-
-- 接收消息的对象(也就是`self`指向的内容)
-- 方法选择器(`_cmd`指向的内容)
-
-之所以说它们是隐藏的是因为在源代码方法的定义中并没有声明这两个参数。它们是在代码被编译时被插入实现中的。尽管这些参数没有被明确声明,在源代码中我们仍然可以引用它们。在下面的例子中,`self`引用了接收者对象,而`_cmd`引用了方法本身的选择器:
-
-```
-- strange
-{
- id target = getTheReceiver();
- SEL method = getTheMethod();
-
- if ( target == self || method == _cmd )
- return nil;
- return [target performSelector:method];
-}
-```
-在这两个参数中,`self` 更有用。实际上,它是在方法实现中访问消息接收者对象的实例变量的途径
-
-而当方法中的 `super` 关键字接收到消息时,编译器会创建一个 `objc_super` 结构体:
-
-```
-struct objc_super { id receiver; Class class; };
-```
-这个结构体指明了消息应该被传递给特定父类的定义。但`receiver`仍然是`self`本身,这点需要注意,因为当我们想通过`[super class]`获取超类时,编译器只是将指向`self`的`id`指针和`class`的`SEL`传递给了o`bjc_msgSendSuper`函数,因为只有在`NSObject`类才能找到`class`方法,然后`class`方法调用`object_getClass()`,接着调用`objc_msgSend(objc_super->receiver`, `@selector(class))`,传入的第一个参数是指向`self`的`id`指针,与调用`[self class]`相同,所以我们得到的永远都是`self`的类型。
-
-#### 获取方法地址
-
-在 `IMP` 那节提到过可以避开消息绑定而直接获取方法的地址并调用方法。这种做法很少用,除非是需要持续大量重复调用某方法的极端情况,避开消息发送泛滥而直接调用该方法会更高效。
-NSObject类中有个`methodForSelector:`实例方法,你可以用它来获取某个方法选择器对应的 `IMP` ,举个栗子:
-
-```
-void (*imp)(id, SEL, BOOL);//定义一个函数指针
-imp = (void (*)(id, SEL, BOOL))[self methodForSelector:@selector(setFilled:)];//获取setFilled:函数的IMP
-```
-
-### 动态方法解析
-
-你可以动态地提供一个方法的实现。例如我们可以用 `@dynamic` 关键字在类的实现文件中修饰一个属性:
-
-```
-@dynamic propertyName;
-```
-这表明我们会为这个属性提供存取方法,也就是说编译器不会默认为我们生成 `setPropertyName:`和 `prepertyName` 方法,而需要我们自己提供动态方法。我们可以通过分别重载 `resolveIntanceMethod:` 和 `resolvrClassMethod:` 方法分别添加实例方法实现和类方法实现。因为当 Runtime 系统在 `Cache` 和方法分发表中(包括父类)找不到要执行的方法时,Runtime会调用 `resolveIntanceMethod:` 和 `resolvrClassMethod:` 来给我们一次动态添加实现的机会。我们需要 `class_addMethod`函数完成向特定类添加特定方法实现的操作:
-
-```
-void dynamicMethodIMP(id self, SEL _cmd) {
- // implementation ....
-}
-@implementation MyClass
-+ (BOOL)resolveInstanceMethod:(SEL)aSEL
-{
- if (aSEL == @selector(resolveThisMethodDynamically)) {
- class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
- return YES;
- }
- return [super resolveInstanceMethod:aSEL];
-}
-@end
-
-```
-上面的例子为resolveThisMethodDynamically方法添加了实现内容,也就是dynamicMethodIMP方法中的代码。其中 “v@:” 表示返回值和参数,这个符号涉及 [Type Encoding](https://developer.apple.com/library/mac/DOCUMENTATION/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)
-
-PS:动态方法解析会在消息转发机制浸入前执行。如果 `respondsToSelector:` 或 `instancesRespondToSelector:` 方法被执行,动态方法解析器将会被首先给予一个提供该方法选择器对应的 `IMP` 的机会。如果你想让该方法选择器被传送到转发机制,那么就让`resolveInstanceMethod:` 返回 `NO` 。
-
-### 消息转发
-#### 重定向
-在消息转发机制执行前,Runtime 系统会再给我们一次偷梁换柱的机会,即通过重载`- (id)forwardingTargetForSelector:(SEL)aSelector` 方法替换消息的接受者为其他对象:
-
-```
-- (id)forwardingTargetForSelector:(SEL)aSelector
-{
- if(aSelector == @selector(mysteriousMethod:)){
- return alternateObject;
- }
- return [super forwardingTargetForSelector:aSelector];
-}
-```
-
-毕竟消息转发要耗费更多时间,抓住这次机会将消息重定向给别人是个不错的选择,不过千万别返回`self`,因为那样会死循环
-
-#### 转发
-
-当动态方法解析不作处理返回NO时,消息转发机制会被触发。在这时`forwardInvocation:`方法会被执行,我们可以重写这个方法来定义我们的转发逻辑:
-
-```
-- (void)forwardInvocation:(NSInvocation *)anInvocation
-{
- if ([someOtherObject respondsToSelector:
- [anInvocation selector]])
- [anInvocation invokeWithTarget:someOtherObject];
- else
- [super forwardInvocation:anInvocation];
-}
-```
-
-该消息的唯一参数是个`NSInvocation`类型的对象——该对象封装了原始的消息和消息的参数。我们可以实现`forwardInvocation:`方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。
-
-当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过 `forwardInvocation:` 消息通知该对象。每个对象都从NSObject类中继承了 `forwardInvocation:` 方法。然而,NSObject中的方法实现只是简单地调用了 `doesNotRecognizeSelector:` 。通过实现我们自己的 `forwardInvocation:` 方法,我们可以在该方法实现中将消息转发给其它对象。
-
-`forwardInvocation:` 方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的”吃掉“某些消息,因此没有响应也没有错误。`forwardInvocation:`方法也可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。
-
-注意: `forwardInvocation:` 方法只有在消息接收对象中无法正常响应消息时才会被调用。 所以,如果我们希望一个对象将negotiate消息转发给其它对象,则这个对象不能有`negotiate`方法。否则,`forwardInvocation:`将不可能会被调用。
-
-#### 转发和多继承
-转发和继承相似,可以用于为Objc编程添加一些多继承的效果。就像下图那样,一个对象把消息转发出去,就好似它把另一个对象中的方法借过来或是“继承”过来一样。
-
-![](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Art/forwarding.gif)
-
-这使得不同继承体系分支下的两个类可以“继承”对方的方法,在上图中 `Warrior` 和 `Diplomat` 没有继承关系,但是 `Warrior` 将`negotiate` 消息转发给了 `Diplomat` 后,就好似 `Diplomat` 是 `Warrior` 的超类一样。
-消息转发弥补了 Objc 不支持多继承的性质,也避免了因为多继承导致单个类变得臃肿复杂。它将问题分解得很细,只针对想要借鉴的方法才转发,而且转发机制是透明的
-
-#### 替代者对象(Surrogate Objects)
-转发不仅能模拟多继承,也能使轻量级对象代表重量级对象。弱小的女人背后是强大的男人,毕竟女人遇到难题都把它们转发给男人来做了。这里有一些适用案例,可以参看[官方文档](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105-SW11)。
-
-#### 转发于继承
-尽管转发很像继承,但是NSObject类不会将两者混淆。像 `respondsToSelector:` 和 `isKindOfClass:` 这类方法只会考虑继承体系,不会考虑转发链。比如上图中一个 `Warrior` 对象如果被问到是否能响应 `negotiate` 消息:
-
-```
-if ( [aWarrior respondsToSelector:@selector(negotiate)] )
- ...
-```
-结果是 `NO` ,尽管它能够接受 `negotiate` 消息而不报错,因为它靠转发消息给 `Diplomat` 类来响应消息。
-
-如果你为了某些意图偏要“弄虚作假”让别人以为`Warrior` 继承到了 `Diplomat` 的 `negotiate` 方法,你得重新实现 `respondsToSelector: ` 和 `isKindOfClass:` 来加入你的转发算法:
-
-```
-- (BOOL)respondsToSelector:(SEL)aSelector
-{
- if ( [super respondsToSelector:aSelector] )
- return YES;
- else {
- /* Here, test whether the aSelector message can *
- * be forwarded to another object and whether that *
- * object can respond to it. Return YES if it can. */
- }
- return NO;
-}
-```
-
-除了`respondsToSelector: `和 `isKindOfClass:`之外,`instancesRespondToSelector:`中也应该写一份转发算法。如果使用了协议,`conformsToProtocol:`同样也要加入到这一行列中。类似地,如果一个对象转发它接受的任何远程消息,它得给出一个`methodSignatureForSelector:`来返回准确的方法描述,这个方法会最终响应被转发的消息。比如一个对象能给它的替代者对象转发消息,它需要像下面这样实现`methodSignatureForSelector:`:
-
-```
-- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
-{
- NSMethodSignature* signature = [super methodSignatureForSelector:selector];
- if (!signature) {
- signature = [surrogate methodSignatureForSelector:selector];
- }
- return signature;
-}
-```
-
-### 健壮的实例变量(Non Fragile ivars)
-
-在 Runtime 的现行版本中,最大的特点就是健壮的实例变量。当一个类被编译时,实例变量的布局也就形成了,它表明访问类的实例变量的位置。从对象头部地址开始,实例变量依次根据自己所占空间而产生位移:
-
-再翻出Ivar的定义:
-
-```
-struct objc_ivar {
- char *ivar_name OBJC2_UNAVAILABLE;
- char *ivar_type OBJC2_UNAVAILABLE;
- int ivar_offset OBJC2_UNAVAILABLE;
-#ifdef __LP64__
- int space OBJC2_UNAVAILABLE;
-#endif
-} OBJC2_UNAVAILABLE;
-```
-
-`ivar` 的访问可以通过 `对象地址` + `ivar偏移字节(ivar_offset)`的方法。
-
-当我们增加了父类的`ivar`,这个时候布局就出错了,我们就不得不重新编译子类来恢复兼容性。
-
-在健壮的实例变量下编译器生成的实例变量布局跟以前一样,但是当 runtime 系统检测到与超类有部分重叠时它会调整你新添加的实例变量的位移,那样你在子类中新添加的成员就被保护起来了
-
-需要注意的是在健壮的实例变量下,不要使用 `sizeof(SomeClass)`,而是用 `class_getInstanceSize([SomeClass class])` 代替;也不要使用 `offsetof(SomeClass, SomeIvar)` ,而要用 `ivar_getOffset(class_getInstanceVariable([SomeClass class], "SomeIvar"))` 来代替。
-
-```
-/* 定义一个Student类 */
-@interface Student : NSObject
-{
-@private
- int age;
-}
-@end
-
-@implementation Student
-// 重写%@输出方法
-- (NSString *)description
-{
- NSLog(@"current pointer = %p", self);
- NSLog(@"age pointer = %p", &age);
- return [NSString stringWithFormat:@"age = %d", age];
-}
-
-@end
-
-int main(int argc, const char * argv[]) {
- @autoreleasepool {
- // insert code here...
-
- Student *student = [[Student alloc] init];
- Ivar age_ivar = class_getInstanceVariable(object_getClass(student), "age");//获取"age"的ivar
- int *age_pointer = (int *)((__bridge void *)(student) + ivar_getOffset(age_ivar));//定义一个指向age_ivar的指针:指向地址为 student对象地址 + age_ivar的偏移量(ivar_offset)
- NSLog(@"age ivar offset = %td", ivar_getOffset(age_ivar));//输出offset偏移量
- *age_pointer = 10;//对指针age_pointer指向的变量(age_ivar)赋值
- NSLog(@"%@", student);//输出重写的description方法
-
- }
- return 0;
-}
-```
-观察控制台输出:
-
-```
-2016-11-11 16:22:56.364 Iavr_offset[1501:928608] age ivar offset = 8
-2016-11-11 16:22:56.365 Iavr_offset[1501:928608] current pointer = 0x100400170
-2016-11-11 16:22:56.365 Iavr_offset[1501:928608] age pointer = 0x100400178
-2016-11-11 16:22:56.366 Iavr_offset[1501:928608] age = 10
-```
-
-我们发现`age pointer = current pointer + age ivar offset`
-
-### Objective-C Associated Objects
-
-在 OS X 10.6 之后,Runtime系统让Objc支持向对象动态添加变量。涉及到的函数有以下三个:
-
-```
- void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );
- id objc_getAssociatedObject ( id object, const void *key );
- void objc_removeAssociatedObjects ( id object );
-```
-这些方法以键值对的形式动态地向对象添加、获取或删除关联值。其中关联政策是一组枚举常量:
-
-```
-enum {
- OBJC_ASSOCIATION_ASSIGN = 0,
- OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
- OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
- OBJC_ASSOCIATION_RETAIN = 01401,
- OBJC_ASSOCIATION_COPY = 01403
-};
-```
-这些常量对应着引用关联值的政策,也就是 Objc 内存管理的引用计数机制。
-
-### Method Swizzling
-
-之前所说的消息转发虽然功能强大,但需要我们了解并且能更改对应类的源代码,因为我们需要实现自己的转发逻辑。当我们无法触碰到某个类的源代码,却想更改这个类某个方法的实现时,该怎么办呢?可能继承类并重写方法是一种想法,但是有时无法达到目的。这里介绍的是 Method Swizzling ,它通过重新映射方法对应的实现来达到“偷天换日”的目的。跟消息转发相比,Method Swizzling 的做法更为隐蔽,甚至有些冒险,也增大了debug的难度。
-
-这里摘抄一个 NSHipster 的例子
-
-```
-#import
-
-@implementation UIViewController (Tracking)
-
-+ (void)load {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- Class class = [self class];
- // When swizzling a class method, use the following:
- // Class class = object_getClass((id)self);
- SEL originalSelector = @selector(viewWillAppear:);
- SEL swizzledSelector = @selector(xxx_viewWillAppear:);
- Method originalMethod = class_getInstanceMethod(class, originalSelector);
- Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
- BOOL didAddMethod =
- class_addMethod(class,
- originalSelector,
- method_getImplementation(swizzledMethod),
- method_getTypeEncoding(swizzledMethod));
- if (didAddMethod) {
- class_replaceMethod(class,
- swizzledSelector,
- method_getImplementation(originalMethod),
- method_getTypeEncoding(originalMethod));
- } else {
- method_exchangeImplementations(originalMethod, swizzledMethod);
- }
- });
-}
-#pragma mark - Method Swizzling
-- (void)xxx_viewWillAppear:(BOOL)animated {
- [self xxx_viewWillAppear:animated];
- NSLog(@"viewWillAppear: %@", self);
-}
-@end
-```
-上面的代码通过添加一个 `Tracking` 类别到 `UIViewController` 类中,将 `UIViewController` 类的 `viewWillAppear:` 方法和 `Tracking` 类别中 `xxx_viewWillAppear:` 方法的实现相互调换。`Swizzling` 应该在 `+load` 方法中实现,因为 `+load` 是在一个类最开始加载时调用。`dispatch_once` 是GCD中的一次性方法,它保证了代码块只执行一次,并让其为一个原子操作,线程安全是很重要的。
-
-先用 `class_addMethod` 和 `class_replaceMethod` 函数将两个方法的实现进行调换,如果类中已经有了 `viewWillAppear:` 方法的实现,那么就调用 `method_exchangeImplementations` 函数交换了两个方法的 `IMP` ,这是苹果提供给我们用于实现 `Method Swizzling` 的便捷方法。
-最后 `xxx_viewWillAppear:` 方法的定义看似是递归调用引发死循环,其实不会的。因为 `[self xxx_viewWillAppear:animated]` 消息会动态找到 `xxx_viewWillAppear:` 方法的实现,而它的实现已经被我们与 `viewWillAppear:`方法实现进行了互换,所以这段代码不仅不会死循环,如果你把 `[self xxx_viewWillAppear:animated]` 换成 `[self viewWillAppear:animated]` 反而会引发死循环。
-看到有人说 `+load`方法本身就是线程安全的,因为它在程序刚开始就被调用,很少会碰到并发问题,于是 `stackoverflow` 上也有大神给出了另一个 `Method Swizzling` 的实现:
-
-```
-- (void)replacementReceiveMessage:(const struct BInstantMessage *)arg1 {
- NSLog(@"arg1 is %@", arg1);
- [self replacementReceiveMessage:arg1];
-}
-+ (void)load {
- SEL originalSelector = @selector(ReceiveMessage:);
- SEL overrideSelector = @selector(replacementReceiveMessage:);
- Method originalMethod = class_getInstanceMethod(self, originalSelector);
- Method overrideMethod = class_getInstanceMethod(self, overrideSelector);
- if (class_addMethod(self, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
- class_replaceMethod(self, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
- } else {
- method_exchangeImplementations(originalMethod, overrideMethod);
- }
-}
-```
-其实也就是去掉了`dispatch_once`的部分罢了。
-
-`Method Swizzling` 的确是一个值得深入研究的话题,`Method Swizzling` 的最佳实现是什么呢?小弟才疏学浅理解的不深刻,找了几篇不错的资源推荐给大家:
-
-- [Objective-C的hook方案(一): Method Swizzling](http://blog.csdn.net/yiyaaixuexi/article/details/9374411)
-- [Method Swizzling](http://nshipster.com/method-swizzling/)
-- [How do I implement method swizzling?](http://stackoverflow.com/questions/5371601/how-do-i-implement-method-swizzling)
-- [What are the Dangers of Method Swizzling in Objective C?](http://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c)
-- [JRSwizzle](https://github.com/rentzsch/jrswizzle)
-
-### 总结
-
-我们之所以让自己的类继承 `NSObject` 不仅仅因为苹果帮我们完成了复杂的内存分配问题,更是因为这使得我们能够用上 Runtime 系统带来的便利。深入理解 Runtime 系统的细节更有利于我们利用消息机制写出功能更强大的代码,比如 `Method Swizzling` 等。
-
-
-
-参考链接
-
-- 原文:[Objective-C Runtime](http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/)
-- Apple官方文档:[Objective-C Runtime Programming Guide](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048)
-- Apple开源代码:[Objective-C Runtime源码](https://opensource.apple.com/source/objc4/)
-- [Objective-C runtime之运行时的基本特点](http://blog.csdn.net/wzzvictory/article/details/8615569)
-- [Understanding the Objective-C Runtime](http://cocoasamurai.blogspot.jp/2010/01/understanding-objective-c-runtime.html)
\ No newline at end of file
diff --git "a/_posts/2016-11-15-iOS\345\231\252\351\237\263\350\256\241\346\250\241\345\236\213.md" "b/_posts/2016-11-15-iOS\345\231\252\351\237\263\350\256\241\346\250\241\345\236\213.md"
deleted file mode 100644
index c641d55a4a3..00000000000
--- "a/_posts/2016-11-15-iOS\345\231\252\351\237\263\350\256\241\346\250\241\345\236\213.md"
+++ /dev/null
@@ -1,118 +0,0 @@
----
-layout: post
-title: iOS噪音计
-subtitle: 一个iOS噪音计模型、以及测量原理及分贝计算
-date: 2016-11-15
-author: BY
-header-img: img/post-bg-ios9-web.jpg
-catalog: true
-tags:
- - iOS
- - Demo
----
-
-
-# 前言
-
-最近在办公室觉得有点吵,然后忽然想做一个噪音计测试一下噪音,在App Store下载了几款测噪音软件,使用原来都大同小异。于是决定自己实现测噪音的原理。
-
-## 分贝dB
-首先要测量噪音,必须知道噪音的大小的参考的单位为分贝(dB),分贝的定义如下:
-
-```
-SPL = 20lg[p(e)/p(ref)]
-```
-`p(e)`为待测的有效声压,`p(ref)`为参考声压,一般取2*10E-5帕,这是人耳能分辨的最小声压(1KHz)。
-
-就是说噪音每增加20dB,声压增强了10倍。
-
-## iOS测噪音原理
-
-iOS设备测量噪音原理非常简单:调用系统麦克风,根据麦克风输入强度计算转化为对应的dB值。但是,实现的过程可是坑满满。
-
-找到了一篇博客介绍iOS硬件的调用:[iOS开发系列--音频播放、录音、视频播放、拍照、视频录制](http://www.cnblogs.com/kenshincui/p/4186022.html)
-
-iOS的`AVFoundation`框架中有一个`AVAudioRecorder`类专门处理录音操作,详见[Apple文档](https://developer.apple.com/reference/avfoundation/1668872-av_foundation_audio_settings_con)
-
-在`AVAudioRecorder.h`中找到下列方法
-
-```
-- (void)updateMeters; /* call to refresh meter values */ 更新麦克风测量值
-- (float)peakPowerForChannel:(NSUInteger)channelNumber; /* returns peak power in decibels for a given channel */ 获取峰值
-- (float)averagePowerForChannel:(NSUInteger)channelNumber; /* returns average power in decibels for a given channel */ 获取平局值
-```
-
-`- (float)averagePowerForChannel:(NSUInteger)channelNumber;`文档中描述:
-
->Return Value
-
->The current average power, in decibels, for the sound being recorded. A return value of 0 dB indicates full scale, or maximum power; a return value of -160 dB indicates minimum power (that is, near silence).
-
->If the signal provided to the audio recorder exceeds ±full scale, then the return value may exceed 0 (that is, it may enter the positive range).
-
->Discussion
-
->To obtain a current average power value, you must call the updateMeters method before calling this method.
-
-也就是说获取的麦克风测量值返回值范围为 `-160dB ~ 0dB`,并且注意最后那句话返回值可能超过0。
-
-## 转化公式
-
-获取的的测量值为 `-160 ~ 0dB` ,如何转化为我们所要的噪音值呢?在网上找了很多资料都没有结果,于是就自己摸索转化公式。
-
-刚开始想到的是利用分贝计算公式`SPL = 20lg[p(e)/p(ref)]`进行计算,后来直接放弃这个方案,因为这是一个对数运算,获取到的值非常稳定,几乎不会波动,与其他的测噪软件所得的分贝值出入太大。
-
-然后发现有个App在麦克风没有输入时显示-55dB
-
-![](http://ww2.sinaimg.cn/large/7853084cgw1f9u0nu3xv3j205n0a0glq.jpg)
-
-于是思路就有了。
-
-其他测噪音软件的量程均为`0~110dB`,而我们获取的的测量值为 `-160 ~ 0dB`,两者之间差了`50dB`,也就是说以麦克风的测量值的`-160dB+50dB = -110dB`作为起点,`0dB`作为Max值,恰好量程为`0~110dB`.
-
-问题看似结束,但是直接以`50dB`作为补偿测量结果会偏大。最后选择了分段进行处理,代码如下
-
-```
-
--(void)audioPowerChange{
-
- [self.audioRecorder updateMeters];//更新测量值
- float power = [self.audioRecorder averagePowerForChannel:0];// 均值
- float powerMax = [self.audioRecorder peakPowerForChannel:0];// 峰值
- NSLog(@"power = %f, powerMax = %f",power, powerMax);
-
- CGFloat progress = (1.0 / 160.0) * (power + 160.0);
-
- // 关键代码
- power = power + 160 - 50;
-
- int dB = 0;
- if (power < 0.f) {
- dB = 0;
- } else if (power < 40.f) {
- dB = (int)(power * 0.875);
- } else if (power < 100.f) {
- dB = (int)(power - 15);
- } else if (power < 110.f) {
- dB = (int)(power * 2.5 - 165);
- } else {
- dB = 110;
- }
-
- NSLog(@"progress = %f, dB = %d", progress, dB);
- self.powerLabel.text = [NSString stringWithFormat:@"%ddB", dB];
- [self.audioPowerProgress setProgress:progress];
-
-}
-
-```
-
-# 效果
-
-效果如下:
-
-![](http://ww4.sinaimg.cn/large/7853084cgw1f9u1gqgqieg20k00zk7d8.gif)
-
-# 下载地址
-
-Demo下载地址:[Noise-meter-Demo](https://github.com/qiubaiying/Noise-meter-Demo)
diff --git "a/_posts/2016-11-18-Objective-C-Runtime-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/_posts/2016-11-18-Objective-C-Runtime-\345\237\272\346\234\254\344\275\277\347\224\250.md"
deleted file mode 100644
index 199109bbc52..00000000000
--- "a/_posts/2016-11-18-Objective-C-Runtime-\345\237\272\346\234\254\344\275\277\347\224\250.md"
+++ /dev/null
@@ -1,724 +0,0 @@
----
-layout: post
-title: Objective-C Runtime 基本使用
-subtitle: Runtime 使用案例
-date: 2017-02-04
-author: BY
-header-img: img/post-bg-ios9-web.jpg
-catalog: true
-tags:
- - Obj-C
- - Runtime
- - iOS
----
-
-# 前言
-
->在上一篇文章[《Objective-C Runtime详解》](http://www.jianshu.com/p/a36bfc976b8e)中我们探讨了Runtime的基本原理,这篇文章我们将总结一下Runtime的一些基本使用
-
-# 使用方法
-
-
-- 查询方法
-- 给分类添加属性
-- 更换代码的实现方法
-- 动态添加方法
-- 字典转属性
-
-# 准备
-
- 先创建两个类
-
-`ClassA.h`
-
-```
-#import
-
-@interface ClassA : NSObject {
- // 公有变量
- NSString *_publicVar1;
- NSString *_publicVar2;
-
-}
-// 公有属性
-@property(nonatomic,copy) NSString *publicProperty1;
-@property(nonatomic,copy) NSString *publicProperty2;
-
-/* 公有方法 */
--(void)methodAOfClassAWithArg:(NSString *)arg;
-
-@end
-
-```
-
-`ClassA.m`
-
-```
-#import "ClassA.h"
-
-@interface ClassA()
-// 私有属性
-@property(nonatomic,copy) NSString *privateProperty1;
-@property(nonatomic,copy) NSString *privateProperty2;
-
-@end
-
-@implementation ClassA {
- // 私有变量
- NSString *_privateVar1;
- NSString *_privateVar2;
-}
-
-/* 公有方法 */
--(void)methodAOfClassAWithArg:(NSString *)arg {
- NSLog(@" methodAOfClassA arg = %@", arg);
-}
-
-/* 私有方法 */
--(void)MethodBOfClassAWithArg:(NSString *)arg {
- NSLog(@" methodBOfClassA arg = %@", arg);
-}
-@end
-```
-
-`ClassB.h`
-
-```
-#import
-
-@interface ClassB : NSObject
-
-/* 公有方法 */
--(void)methodAOfClassBWithArg:(NSString *)arg;
-
-@end
-```
-
-`ClassB.m`
-
-```
-#import "ClassB.h"
-
-@implementation ClassB
-- (void)methodAOfClassBWithArg:(NSString *)arg {
- NSLog(@" methodAOfClassB arg = %@", arg);
-}
-
--(void)methodBOfClassBWithArg:(NSString *)arg {
- NSLog(@" methodBOfClassB arg = %@", arg);
-}
-
-@end
-```
-
-## 查询方法
----
-
-在Objective-C Runtime下没有真正意义上的私有变量和方法,因为这些私有变量和方法都可以通过Runtime方法获取,这当然包括系统的私有API。接下来我们来一一介绍获取类中属性和方法的方法。当然不要忘了`#import `.
-
-#### 获取类的名称
-
-方法:`const char *object_getClassName(id obj)`,使用比较简单,传入对象即可得到对应分类名。
-
-```
-ClassA *classA = [[ClassA alloc] init];
-const char *className = object_getClassName(classA);
-NSLog(@"className = %@", [NSString stringWithUTF8String:className]);
-
-//输出
-className = ClassA
-```
-
-#### 获取类中的方法
-
-方法:`Method *class_copyMethodList(Class cls, unsigned int *outCount) `
-
-上代码:
-
-```
-UInt32 count;
-char dst;
-Method *methods = class_copyMethodList([classA class], &count);//获取方法列表
-for (int i = 0; i < count; i++) {
- Method method = methods[i];// 获取方法
- SEL methodName = method_getName(method);// 获取方法名
- method_getReturnType(method, &dst, sizeof(char));// 获取方法返回类型
- const char *methodType = method_getTypeEncoding(method);// 获取方法参数类型和返回类型
- NSLog(@"methodName = %@",NSStringFromSelector(methodName));
- NSLog(@"dst = %c", dst);
-}
-
- // 输出
- methodName = methodAOfClassAWithArg:
- dst = v
- methodType = v24@0:8@16
- methodName = MethodBOfClassAWithArg:
- dst = v
- methodType = v24@0:8@16
- methodName = publicProperty1
- dst = @
- methodType = @16@0:8
- methodName = setPublicProperty1:
- dst = v
- methodType = v24@0:8@16
- methodName = publicProperty2
- dst = @
- methodType = @16@0:8
- methodName = setPublicProperty2:
- dst = v
- methodType = v24@0:8@16
- methodName = privateProperty1
- dst = @
- methodType = @16@0:8
- methodName = setPrivateProperty1:
- dst = v
- methodType = v24@0:8@16
- methodName = privateProperty2
- dst = @
- methodType = @16@0:8
- methodName = setPrivateProperty2:
- dst = v
- methodType = v24@0:8@16
- methodName = .cxx_destruct
- dst = v
- methodType = v16@0:8
-
-```
-
-`class_copyMethodList([classA class], &count)` 传入元类和计数器地址,返回方法列表。这里注意,返回的是`Method`结构体类型的C数组,`Method`类型我们在[上篇文章](http://www.jianshu.com/p/a36bfc976b8e)中已经详细说明,
-
-```
-typedef struct objc_method *Method;
-
-struct objc_method {
- SEL method_name OBJC2_UNAVAILABLE;
- char *method_types OBJC2_UNAVAILABLE;
- IMP method_imp OBJC2_UNAVAILABLE;
-}
-```
-
-但要区分`Method *methods`与`Method method`的区别,这是比较基础C语言知识。还有`Uint32`是OC定义的`unsigned int`类型`typedef unsigned int UInt32;`
-
-这里我们来看看 `method_getReturnType(method, &dst, sizeof(char))` 方法简单输出返回值类型,输出为 `v` 和 `@` ,参考Apple文档可知道返回类型为 `void` 和 `id`
-
-```
-A void v
-A method selector (SEL) :
-An object (whether statically typed or typed id) @
-```
-
-`method_getTypeEncoding(method)`方法可以输出返回值,参数类型以及接收器类型。我们看输出的`v24@0:8@16`,分析上面的说明就可以知道: `v24`返回类型为`viod`,`@0`接收器类型为`id`,`@16`参数类型为`id`
-
-至于类型后面的值观察可以发现都是相差8,我认为是在method中的位置,分别以8bit存储不同类型的数据。
-
-若有两个参数返回值为 `v32@0:8@16@24` ,对比可以猜测,在method中各个成员的排列是这样的: `接收器|SEl标识|参数1|参数2|...|返回值`,然后由 `method_getTypeEncoding(method)` 输出的顺序为: `返回值类型|接收器类型|SEL标识|参数1|参数2|...` 此处为个人见解,如有错误或不同意见欢迎提出探讨。
-
-最后发现了一个奇怪的方法 `.cxx_destruct` ,在中[这篇文章](http://my.safaribooksonline.com/book/programming/objective-c/9780132908641/3dot-memory-management/ch03)中:
->ARC actually creates a -.cxx_destruct method to handle freeing instance variables. This method was originally created for calling C++ destructors automatically when an object was destroyed.
-
-和《Effective Objective-C 2.0》中提到的:
->When the compiler saw that an object contained C++ objects, it would generate a method called .cxx_destruct. ARC piggybacks on this method and emits the required cleanup code within it.
-
-可以了解到,`.cxx_destruct` 方法原本是为了C++对象析构的,ARC借用了这个方法插入代码实现了自动内存释放的工作
-
-关于 `.cxx_destruct` 可以参考这篇文章:[ARC下dealloc过程及.cxx_destruct的探究](http://blog.jobbole.com/65028/)
-
-#### 获取类中的属性
-
-在 [上篇文章](http://www.jianshu.com/p/a36bfc976b8e) 的 `Property` 中我们也提到了获取类中的属性的方法,如下:
-
-```
-id LenderClass = objc_getClass("ClassA");//获取classA 的元类同[ClassA class]
-unsigned int outCount;//属性数量
-// 获取属性列表
-objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
-
-// 遍历
-for (int i = 0; i < outCount; i++) {
-
- objc_property_t property = properties[i];
-
- const char *propertyName = property_getName(property);// 获取属性名
- const char *propertyAttributes = property_getAttributes(property);// 获取属性描述
-
- printf("propertyName:%s \n", propertyName);
- printf("propertyAttributes:%s\n--------\n", propertyAttributes);//属性名及描述
-}
-```
-
-```
-// 输出
-propertyName:privateProperty1
-propertyAttributes:T@"NSString",C,N,V_privateProperty1
---------
-propertyName:privateProperty2
-propertyAttributes:T@"NSString",C,N,V_privateProperty2
---------
-propertyName:publicProperty1
-propertyAttributes:T@"NSString",C,N,V_publicProperty1
---------
-propertyName:publicProperty2
-propertyAttributes:T@"NSString",C,N,V_publicProperty2
---------
-```
-发现会输出公有属性以及私有属性。
-
-#### 获取类中的成员变量
-
-我们可以发现获取类中的方法,属性过程基本一致:通过元类获取方法列表或属性列表,然后在进行遍历。获取成员变量也一样:
-
-```
-id selfClass = [Calss class];
-unsigned int numIvars = 0;
-Ivar *ivars = class_copyIvarList(selfClass, &numIvars);
-for(int i = 0; i < numIvars; i++) {
- Ivar ivar = ivars[i];
- const char *ivarName = ivar_getName(ivar);
- const char *ivarType = ivar_getTypeEncoding(ivar);// 获取类型
-
- printf("ivarName:%s\n", ivarName);
- printf("ivarType:%s\n------\n", ivarType);
-}
-```
-
-```
-// 输出
-ivarName:_publicVar1
-ivarType:@"NSString"
-------
-ivarName:_publicVar2
-ivarType:@"NSString"
-------
-ivarName:_privateVar1
-ivarType:@"NSString"
-------
-ivarName:_privateVar2
-ivarType:@"NSString"
-------
-ivarName:_publicProperty1
-ivarType:@"NSString"
-------
-ivarName:_publicProperty2
-ivarType:@"NSString"
-------
-ivarName:_privateProperty1
-ivarType:@"NSString"
-------
-ivarName:_privateProperty2
-ivarType:@
-```
-可以发现输出了所有的成员变量,包括属性声明的 `_+属性名` 变量。
-
-## 给分类添加属性
----
-
-众所周知,分类中是不能声明属性的。
-
-我们创建一个 `CalssA` 的分类 `ClassA+CategoryA` ,在 `ClassA+CategoryA` 中添加一个属性 `name`
-
-```
-#import "ClassA.h"
-
-@interface ClassA (CategoryA)
-
-@property (nonatomic, strong) NSString *name;
-
-@end
-
-```
-
-若在我们调用`CalssA`分类的`name` 将会crash,原因是分类中使用 `@property` 声明属性并不会生成`setter`和`getter`方法,但是我们会想,我们可以自己实现呀,没错,看下面的代码
-
-```
-#import "ClassA+CategoryA.h"
-#import
-
-@implementation ClassA (CategoryA)
-
-- (NSString *)name {
- return name;
-}
-
-- (void)setName:(NSString *)name {
- _name = name;
-}
-
-@end
-```
-这里会报编译错误,因为分类中使用 `@property` 声明属性也不会生成成员变量 _name,并且手动声明也不行
-
-![](http://ww1.sinaimg.cn/large/7853084cgw1f9zsknx42yj20fw033gm0.jpg)
-
-编译错误,提示实例变量无法添加到分类中,用正常的方法确实无法在分类中添加属性。
-
-但是可以通过Runtim机制进行“添加”。其本质是给这个类添加属性关联,而非把这个属性添加到类中。
-
-```
-#import "ClassA+CategoryA.h"
-#import
-
-
-@implementation ClassA (CategoryA)
-
-- (NSString *)name {
- return objc_getAssociatedObject(self, @selector(name));
-}
-
-- (void)setName:(NSString *)name {
- objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
-}
-
-@end
-```
-
-调用:
-
-```
-classA.name = @"邱帅";
-NSLog(@"%@",classA.name);
-
-// 输出
-2016-11-21 16:18:48.084 UseRuntime[4392:1325037] 邱帅
-```
-可以看出添加属性成功!
-
-我们来看看关联属性的这几个方法:
-
-```
-OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
- OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
-
-OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
- OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
-
-OBJC_EXPORT void objc_removeAssociatedObjects(id object)
- OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
-
-```
-`objc_setAssociatedObject()` 方法为关联属性,参数如下:
-
-- `object`:属性关联的源对象,这里使用了`self`,代表关联本类的对象
-- `key`:区分属性的唯一标识,因为关联的属性可能不止一个,我们使用了`- (NSString *)name`方法的`SEL` `@selector(name)`作为唯一标示,当然也可以用下面的方法来生成Key :
-
-```
-//利用静态变量地址唯一不变的特性
-1、static void *strKey = &strKey;
-
-2、static NSString *strKey = @"strKey";
-
-3、static char strKey;
-```
-
-- `value`:关联的属性值
-- `policy`:设置关联对象的`copy`、`story`、`nonatomic`等参数:
-
-这些常量对应着引用关联值的政策,也就是 Objc 内存管理的引用计数机制。
-
-```
-typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
- OBJC_ASSOCIATION_ASSIGN = 0,
- OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
- OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
- OBJC_ASSOCIATION_RETAIN = 01401,
- OBJC_ASSOCIATION_COPY = 01403
-};
-```
-`objc_getAssociatedObject()` 方法通过 `object` 与 `Key` 直接获取关联的属性值
-
-`objc_removeAssociatedObjects()` 移除关联
-
-我们使用上面的获取类中属性和成员变量的方法,发现输出:
-
-```
-// 有属性输出
-propertyName:name
-propertyAttributes:T@"NSString",&,N
-```
-没有成员变量 `_name`,进一步说明分类中不能添加成员变量!其本质是添加属性与分类之间关联。
-
-
-## 更换代码实现方法(Method Swizzling)
----
-在[上篇](http://www.jianshu.com/p/a36bfc976b8e)中详细介绍了`Method Swizzling`的原理,其本质是更换了 `selector` 的 `IMP` 。
-
-```
-#import "ViewController.h"
-#import
-#import "ClassA.h"
-#import "ClassB.h"
-
-@interface ViewController ()
-
-@end
-
-@implementation ViewController
-
-+ (void)load {
- Method classA_method = class_getInstanceMethod([ClassA class], @selector(methodAOfClassAWithArg:));
- Method classB_method = class_getInstanceMethod([ClassB class], @selector(methodAOfClassBWithArg:));
- method_exchangeImplementations(classA_method, classB_method);
-}
-- (void)viewDidLoad {
- [super viewDidLoad];
-
- [classA methodAOfClassAWithArg:@"classA 发出的 A方法"];
- [classB methodAOfClassBWithArg:@"classB 发出的 A方法"];
-}
-
-// 输出
-
-2016-11-22 13:07:15.151 UseRuntime[1015:533335] methodAOfClassB arg = classA 发出的 A方法
-2016-11-22 13:07:15.151 UseRuntime[1015:533335] methodAOfClassA arg = classB 发出的 A方法
-```
-
-首先交换方法写在 `+(void)load`,在程序的一开始就调用执行,你将不会碰到并发问题。
-
-我们可以发现两个方法的实现过程以及对换。
-
-当然,平时使用我们并不会这么做,当我们要在系统提供的方法上再扩充功能时(不能重写系统方法),就可以使用`Method Swizzling`.
-
-我们给`NSArray`添加一个分类`AddLog`,给 `arrayByAddingObject:`方法添加一个输出方法:
-
-```
-#import "NSArray+AddLog.h"
-#import
-
-@implementation NSArray (AddLog)
-
-+ (void)load {
-
- SEL ori_selector = @selector(arrayByAddingObject:);
- SEL my_selector = @selector(my_arrayByAddingObject:);
-
- Method ori_method = class_getInstanceMethod([NSArray class], ori_selector);
- Method my_method = class_getInstanceMethod([NSArray class], my_selector);
-
- if (([NSArray class], ori_selector, method_getImplementation(my_method), method_getTypeEncoding(my_method))) {
-
- class_replaceMethod([NSArray class], my_selector, method_getImplementation(ori_method), method_getTypeEncoding(ori_method));
-
- } else {
- method_exchangeImplementations(ori_method, my_method);
- }
-
-}
-
-- (NSArray *)my_arrayByAddingObject:(id)anObject {
-
- NSArray *array = [self my_arrayByAddingObject:anObject];
- NSLog(@"添加了一个元素 %@", anObject);
- return array;
-}
-
-@end
-
-```
-我们来看看这三个方法:
-
-- `class_addMethod()`:给一个方法添加新的方法和实现
-- `class_replaceMethod()`:取代了对于一个给定的类的实现方法
-- `method_exchangeImplementations()`:交换两个类的实现方法
-
-这里我们先使用 `class_addMethod()` 在类中添加方法,若返回Yes说明类中没有该方法,然后再使用 `class_replaceMethod()` 方法进行取代;若返回NO,说明类中有该方法,使用`method_exchangeImplementations()`直接交换两者的 `IMP`.
-
-其实在这里直接使用`method_exchangeImplementations()`进行交换就可以了。因为类中必定有`arrayByAddingObject:`方法。
-
-
-我给我们自己的方法命名为`my_arrayByAddingObject:`,在原来的方法名上加上前缀,既可以防止命名冲突,又方便阅读,在我们`my_arrayByAddingObject:`方法中调用本身
-
-```
-NSArray *array = [self my_arrayByAddingObject:anObject];
-```
-
-看似会陷入递归调用,其实则不会,因为我们已经在`+ (void)load `方法中更换了`IMP`,他会调用`arrayByAddingObject:`方法,然后在后面添加我们需要添加的功能。
-
-`arrayByAddingObject:`方法的调用不变;
-
-
-```
-NSArray *arr1 = @[@"one", @"two"];
-NSArray *arr2 = [arr1 arrayByAddingObject:@"three"];
-NSLog(@"arr2 = %@", arr2);
-```
-
-```
-// 输出
-2016-11-22 13:57:00.021 UseRuntime[1147:743449] 添加了一个元素 three
-2016-11-22 13:57:00.021 UseRuntime[1147:743449] arr2 = (
- one,
- two,
- three
-)
-```
-
-## 动态添加方法
-
-动态添加方法就是在消息转发前在`+ (BOOL)resolveInstanceMethod:(SEL)sel`方法中使用`class_addMethod()` 添加方法。
-
-下面我面添加一个名为`resolveThisMethodDynamically`的方法:
-
-```
-void dynamicMethodIMP(id self, SEL _cmd) {
- // implementation ....
- printf("执行了dynamicMethodIMP!!!!");
-}
-
-+ (BOOL)resolveInstanceMethod:(SEL)sel {
-
- if (sel == @selector(resolveThisMethodDynamically)) {
- class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
- return YES;
- }
- return [super resolveInstanceMethod:sel];
-}
-```
-调用:
-
-```
-performSelector:@selector(resolveThisMethodDynamically)];
-
-// 输出
-执行了dynamicMethodIMP!!!!
-```
-对于上面添加的的方法 `resolveThisMethodDynamically` ,使用 `[self performSelector:@selector(resolveThisMethodDynamically)]` 进行调用,不能使用`[self resolveThisMethodDynamically]`,因为压根就没有声明 `-(void)resolveThisMethodDynamically`,会报编译错误。
-
-整个过程就是,`performSelector:`调用`resolveThisMethodDynamically`方法,然后在列表中找不到(因为类中根本就没有注册该方法),然后跳入 `+ (BOOL)resolveInstanceMethod:` 中,我们再为`resolveThisMethodDynamically`方法添加具体实现。
-
-## 字典转属性
-
-将字典转化为模型,是在我们iOS开发中最为常用的技能。iOS的模型框架如`JSONModel`,`MJExtension`,`MJExtension`等皆是利用了runtime,将字典转为模型,不过兼顾的细节更多。下面我们来实现一个简易的字典转模型框架。
-
-先上代码:
-
-```
-#import "NSObject+BYModel.h"
-#import
-#import
-
-@implementation NSObject (BYModel)
-
-- (void)by_modelSetDictionary:(NSDictionary *)dic {
-
- Class cls = [self class];
-
- // 遍历本类和父类的变量
- while (cls) {
- //获取所有成员变量
- unsigned int outCount = 0;
- Ivar *ivars = class_copyIvarList(cls, &outCount);
-
- for (int i = 0; i < outCount; i++) {
- Ivar ivar = ivars[i];
-
- // 获取变量名
- NSMutableString *ivar_Name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
-
- [ivar_Name replaceCharactersInRange:NSMakeRange(0, 1) withString:@""];// _ivar -> ivar
-
- //
- NSString *key = [ivar_Name copy];
- if ([key isEqualToString:@"dece"]) {
- key = @"description";
- }
- if ([key isEqualToString:@"ID"]) {
- key = @"id";
- }
-
- id value = dic[key];
- if (!value) continue;
-
- // 拼接SEL ivar -> setIvar:
-
- NSString *cap = [ivar_Name substringToIndex:1];
- cap = cap.uppercaseString; // a->A
- [ivar_Name replaceCharactersInRange:NSMakeRange(0, 1) withString:cap];
- [ivar_Name insertString:@"set" atIndex:0];
- [ivar_Name appendString:@":"];
-
- SEL selector = NSSelectorFromString(ivar_Name);
-
- // 判断类型并发送消息
- NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
-
- if ([type hasPrefix:@"@"]) { // 对象类型
- objc_msgSend(self, selector, value);
- } else { // 非对象类型
- if ([type isEqualToString:@"d"]) {
- objc_msgSend(self, selector, [value doubleValue]);
- } else if ([type isEqualToString:@"f"]) {
- objc_msgSend(self, selector, [value floatValue]);
- } else if ([type isEqualToString:@"i"]) {
- objc_msgSend(self, selector, [value intValue]);
- } else {
- objc_msgSend(self, selector, [value longLongValue]);
- }
- }
-
-
- }
- // 获取父类进行遍历变量
- cls = class_getSuperclass(cls);
- }
-
-}
-
-```
-
-这个这个段代码可能出现编译错误:
-
-![](http://ww3.sinaimg.cn/large/7853084cgw1fa3b5fbvsqj20k001fjrn.jpg)
-
-解决办法很简单:
-
-将项目 Project -> Build Settings -> Enable strct checking of objc_msgSend Calls 设置为 **NO** 即可
-
-![](http://ww1.sinaimg.cn/large/7853084cgw1fa3b6mm9h7j20oh0aj0v8.jpg)
-
-接下来我们创建一个模型类`Student`
-
-```
-#import
-
-@interface Student : NSObject
-
-@property (nonatomic, strong) NSString *name;
-@property (nonatomic, assign) int age;
-@property (nonatomic, assign) int idNumber;
-
-@end
-
-
-```
-
-使用我们的转模型方法:
-
-```
-NSDictionary *dic = @{ @"name":@"邱帅", @"age": @(23), @"idNumber":@(1234567)};
-
-Student *stu = [Student new];
-[stu by_modelSetDictionary:dic];
-
-NSLog(@"%@", [NSString stringWithFormat:@"%@, %d, %d", stu.name, stu.age, stu.idNumber]);
-
-// 输出
-2016-11-24 15:32:46.351 Demo_字典转模型(Runtime)[2131:884627] 邱帅, 23, 1234567
-```
-
-该方法先利用我们上面介绍的`class_copyIvarList()`获取类中的成员变量列表,然后进行遍历,拼接字符串`setIvar:`,最后调用`objc_msgSend()`直接发送设置变量的消息,完成属性的赋值。
-
-```
-while (cls) {
-
- //code..
-
- cls = class_getSuperclass(cls);
-}
-```
-这个循环是则获取父类中的属性:当前类的属性遍历结束之后,指向父类,若父类存在则在继续遍历属性,否则就退出循环。
-
-当然,这个方法只是介绍了利用runtime进行字典转模型的原理,实际中还有很多需要考虑的细节,项目中我还是推荐使用像[YYModel](https://github.com/ibireme/YYModel)这些比较成熟而且安全的模型框架。
-
-关于快速字典转模型可以参考我写的一篇[《快速完成JSON\字典转模型 For YYModel》](http://www.jianshu.com/p/b7d8cf650722)。
-
-
-
diff --git a/_posts/2016-11-28-Objective-C-RunLoop.md b/_posts/2016-11-28-Objective-C-RunLoop.md
deleted file mode 100644
index cd0d55bcc82..00000000000
--- a/_posts/2016-11-28-Objective-C-RunLoop.md
+++ /dev/null
@@ -1,724 +0,0 @@
----
-layout: post
-title: Objective-C:RunLoop
-subtitle: 深入理解RunLoop
-date: 2016-11-28
-author: BY
-header-img: img/post-bg-ios9-web.jpg
-catalog: true
-tags:
- - iOS
- - RunLoop
- - Obj-C
----
-
-# 深入理解RunLoop
->本文转自:[《深入理解RunLoop》](http://blog.ibireme.com/2015/05/18/runloop/)
-
-# 前言
-
-RunLoop 是 iOS 和 OSX 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理。之后会介绍一下在 iOS 中,苹果是如何利用 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的。
-
-# 目录
-
-- RunLoop 的概念
-- RunLoop 与线程的关系
-- RunLoop 对外的接口
-- RunLoop 的 Mode
-- RunLoop 的内部逻辑
-- RunLoop 的底层实现
-- 苹果用 RunLoop 实现的功能
- - AutoreleasePool
- - 事件响应
- - 手势识别
- - 界面更新
- - 定时器
- - PerformSelecter
- - 关于GCD
- - 关于网络请求
-- RunLoop 的实际应用举例
- - AFNetworking
- - AsyncDisplayKit
-
-
-# 正文
-## RunLoop 的概念
-
-一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:
-
-```
-function loop() {
- initialize();
- do {
- var message = get_next_message();
- process_message(message);
- } while (message != quit);
-}
-```
-
-这种模型通常被称作 [Event Loop](https://en.wikipedia.org/wiki/Event_loop)。 Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。
-
-所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。
-
-OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。
-
-CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
-
-NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
-
-CFRunLoopRef 的代码是[开源](https://opensource.apple.com/source/CF/CF-855.17/CFRunLoop.c)的,你可以在[这里](http://opensource.apple.com/tarballs/CF/)下载到整个 CoreFoundation 的源码来查看。
-
-(Update: Swift 开源后,苹果又维护了一个跨平台的 CoreFoundation 版本:,这个版本的源码可能和现有 iOS 系统中的实现略不一样,但更容易编译,而且已经适配了 Linux/Windows。)
-
-## RunLoop 与线程的关系
-
-首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份[文档](http://www.fenestrated.net/~macman/mirrors/Apple%20Technotes%20(As%20of%202002)/tn/tn2028.html)标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 `pthread_main_thread_np()` 或 `[NSThread mainThread]` 来获取主线程;也可以通过 `pthread_self()` 或 `[NSThread currentThread]` 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。
-
-苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:`CFRunLoopGetMain()` 和 `CFRunLoopGetCurrent()`。 这两个函数内部的逻辑大概是下面这样:
-
-
-```
-/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
-static CFMutableDictionaryRef loopsDic;
-/// 访问 loopsDic 时的锁
-static CFSpinLock_t loopsLock;
-
-/// 获取一个 pthread 对应的 RunLoop。
-CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
- OSSpinLockLock(&loopsLock);
-
- if (!loopsDic) {
- // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
- loopsDic = CFDictionaryCreateMutable();
- CFRunLoopRef mainLoop = _CFRunLoopCreate();
- CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
- }
-
- /// 直接从 Dictionary 里获取。
- CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
-
- if (!loop) {
- /// 取不到时,创建一个
- loop = _CFRunLoopCreate();
- CFDictionarySetValue(loopsDic, thread, loop);
- /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
- _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
- }
-
- OSSpinLockUnLock(&loopsLock);
- return loop;
-}
-
-CFRunLoopRef CFRunLoopGetMain() {
- return _CFRunLoopGet(pthread_main_thread_np());
-}
-
-CFRunLoopRef CFRunLoopGetCurrent() {
- return _CFRunLoopGet(pthread_self());
-}
-```
-从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
-
-## RunLoop 对外的接口
-
-在 CoreFoundation 里面关于 RunLoop 有5个类:
-
-- CFRunLoopRef
-- CFRunLoopModeRef
-- CFRunLoopSourceRef
-- CFRunLoopTimerRef
-- CFRunLoopObserverRef
-
-其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:
-
-![RunLoop 有5个类的关系](http://blog.ibireme.com/wp-content/uploads/2015/05/RunLoop_0.png)
-
-一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
-
-**CFRunLoopSourceRef** 是事件产生的地方。Source 有两个版本:Source0 和 Source1。
-
-- Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 `CFRunLoopSourceSignal(source)`,将这个 Source 标记为待处理,然后手动调用 `CFRunLoopWakeUp(runloop)` 来唤醒 RunLoop,让其处理这个事件。
-- Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。
-
-**CFRunLoopTimerRef** 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
-
-**CFRunLoopObserverRef** 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:
-
-```
-typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
- kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
- kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
- kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
- kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
- kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
- kCFRunLoopExit = (1UL << 7), // 即将退出Loop
-};
-```
-上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环
-
-## RunLoop 的 Mode
-
-CFRunLoopMode 和 CFRunLoop 的结构大致如下:
-
-```
-struct __CFRunLoopMode {
- CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
- CFMutableSetRef _sources0; // Set
- CFMutableSetRef _sources1; // Set
- CFMutableArrayRef _observers; // Array
- CFMutableArrayRef _timers; // Array
- ...
-};
-
-struct __CFRunLoop {
- CFMutableSetRef _commonModes; // Set
- CFMutableSetRef _commonModeItems; // Set