谈谈 MVX 中的 Controller
+在前两篇文章中,我们已经对 iOS 中的 Model 层以及 View 层进行了分析,划分出了它们的具体职责,其中 Model 层除了负责数据的持久存储、缓存工作,还要负责所有 HTTP... »
+diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 50dcf7d..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.md linguist-language=Objective-C diff --git a/.github/ISSUE_REPLY_TEMPLATE.md b/.github/ISSUE_REPLY_TEMPLATE.md new file mode 100644 index 0000000..944c73f --- /dev/null +++ b/.github/ISSUE_REPLY_TEMPLATE.md @@ -0,0 +1,3 @@ +# 注意 + +由于评论维护的问题,所有在 GitHub Issue 中提的问题都不会得到作者的回复,请到对应[博客](http://draveness.me)下面的 Disqus 评论系统留言,谢谢。 diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..944c73f --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,3 @@ +# 注意 + +由于评论维护的问题,所有在 GitHub Issue 中提的问题都不会得到作者的回复,请到对应[博客](http://draveness.me)下面的 Disqus 评论系统留言,谢谢。 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfc4d70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + +fastlane/report.xml + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control +# +Pods/ + +.DS_Store diff --git a/README.md b/README.md index d107b5f..c4ee9dd 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,51 @@ -# iOS-Source-Code-Analyze +# Analyze
-
+
Banner designed by Levine
在前两篇文章中,我们已经对 iOS 中的 Model 层以及 View 层进行了分析,划分出了它们的具体职责,其中 Model 层除了负责数据的持久存储、缓存工作,还要负责所有 HTTP... »
+ > 所有继承自 `NSObject` 的类实例化后的对象都会包含一个类型为 `isa_t` 的结构体。 @@ -40,14 +39,12 @@ struct objc_class : objc_object { 当**实例方法**被调用时,它要通过自己持有的 `isa` 来查找对应的类,然后在这里的 `class_data_bits_t` 结构体中查找对应方法的实现。同时,每一个 `objc_class` 也有一个**指向自己的父类的指针** `super_class` 用来查找继承的方法。 -> 关于如何在 `class_data_bits_t` 中查找对应方法会在之后的文章中讲到。这里只需要知道,它会在这个结构体中查找到对应方法的实现就可以了。[深入解析 ObjC 中方法的结构](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/深入解析%20ObjC%20中方法的结构.md) +> 关于如何在 `class_data_bits_t` 中查找对应方法会在之后的文章中讲到。这里只需要知道,它会在这个结构体中查找到对应方法的实现就可以了。[深入解析 ObjC 中方法的结构](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/深入解析%20ObjC%20中方法的结构.md) -
 但是,这样就有一个问题,类方法的实现又是如何查找并且调用的呢?这时,就需要引入*元类*来保证无论是类还是对象都能**通过相同的机制查找方法的实现**。 -
 @@ -58,7 +55,6 @@ struct objc_class : objc_object { 下面这张图介绍了对象,类与元类之间的关系,笔者认为已经觉得足够清晰了,所以不在赘述。 -
 > 图片来自 [objc_explain_Classes_and_metaclasses](http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html) @@ -111,7 +107,6 @@ union isa_t { `isa_t` 是一个 `union` 类型的结构体,对 `union` 不熟悉的读者可以看这个 stackoverflow 上的[回答](http://stackoverflow.com/questions/252552/why-do-we-need-c-unions). 也就是说其中的 `isa_t`、`cls`、 `bits` 还有结构体共用同一块地址空间。而 `isa` 总共会占据 64 位的内存空间(决定于其中的结构体) -
 ```objectivec @@ -173,7 +168,6 @@ inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 我们可以把它转换成二进制的数据,然后看一下哪些属性对应的位被这行代码初始化了(标记为红色): -
 从图中了解到,在使用 `ISA_MAGIC_VALUE` 设置 `isa_t` 结构体之后,实际上只是设置了 `indexed` 以及 `magic` 这两部分的值。 @@ -224,7 +218,6 @@ inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) isa.has_cxx_dtor = hasCxxDtor; ``` -
 ### `shiftcls` @@ -242,7 +235,6 @@ isa.shiftcls = (uintptr_t)cls >> 3; 而 ObjC 中的类指针的地址后三位也为 0,在 `_class_createInstanceFromZone` 方法中打印了调用这个方法传入的类指针: -
 可以看到,这里打印出来的**所有类指针十六进制地址的最后一位都为 8 或者 0**。也就是说,类指针的后三位都为 0,所以,我们在上面存储 `Class` 指针时右移三位是没有问题的。 @@ -253,7 +245,6 @@ isa.shiftcls = (uintptr_t)cls >> 3; 如果再尝试打印对象指针的话,会发现所有对象内存地址的**后四位**都是 0,说明 ObjC 在初始化内存时是以 16 个字节对齐的, 分配的内存地址后四位都是 0。 -
 > 使用整个指针大小的内存来存储 `isa` 指针有些浪费,尤其在 64 位的 CPU 上。在 `ARM64` 运行的 iOS 只使用了 33 位作为指针(与结构体中的 33 位无关,Mac OS 上为 47 位),而剩下的 31 位用于其它目的。类的指针也同样根据字节对齐了,每一个类指针的地址都能够被 8 整除,也就是使最后 3 bits 为 0,为 `isa` 留下 34 位用于性能的优化。 @@ -263,7 +254,6 @@ isa.shiftcls = (uintptr_t)cls >> 3; 我尝试运行了下面的代码将 `NSObject` 的类指针和对象的 `isa` 打印出来,具体分析一下 -
 ``` diff --git "a/objc/\344\273\216\346\272\220\344\273\243\347\240\201\347\234\213 ObjC \344\270\255\346\266\210\346\201\257\347\232\204\345\217\221\351\200\201.md" "b/contents/objc/\344\273\216\346\272\220\344\273\243\347\240\201\347\234\213 ObjC \344\270\255\346\266\210\346\201\257\347\232\204\345\217\221\351\200\201.md" similarity index 96% rename from "objc/\344\273\216\346\272\220\344\273\243\347\240\201\347\234\213 ObjC \344\270\255\346\266\210\346\201\257\347\232\204\345\217\221\351\200\201.md" rename to "contents/objc/\344\273\216\346\272\220\344\273\243\347\240\201\347\234\213 ObjC \344\270\255\346\266\210\346\201\257\347\232\204\345\217\221\351\200\201.md" index 7af5783..24f8734 100644 --- "a/objc/\344\273\216\346\272\220\344\273\243\347\240\201\347\234\213 ObjC \344\270\255\346\266\210\346\201\257\347\232\204\345\217\221\351\200\201.md" +++ "b/contents/objc/\344\273\216\346\272\220\344\273\243\347\240\201\347\234\213 ObjC \344\270\255\346\266\210\346\201\257\347\232\204\345\217\221\351\200\201.md" @@ -10,10 +10,10 @@ 2. `[receiver message]` 会被翻译为 `objc_msgSend(receiver, @selector(message))` 3. 在消息的响应链中**可能**会调用 `- resolveInstanceMethod:` `- forwardInvocation:` 等方法 4. 关于选择子 SEL 的知识 - + > 如果对于上述的知识不够了解,可以看一下这篇文章 [Objective-C Runtime](http://tech.glowing.com/cn/objective-c-runtime/),但是其中关于 `objc_class` 的结构体的代码已经过时了,不过不影响阅读以及理解。 -5. 方法在内存中存储的位置,[深入解析 ObjC 中方法的结构](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/深入解析%20ObjC%20中方法的结构.md)(可选) +5. 方法在内存中存储的位置,[深入解析 ObjC 中方法的结构](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/深入解析%20ObjC%20中方法的结构.md)(可选) > 文章中不会刻意区别方法和函数、消息传递和方法调用之间的区别。 @@ -26,7 +26,6 @@ 由于这个系列的文章都是对 Objective-C 源代码的分析,所以会**从 Objective-C 源代码中分析并合理地推测一些关于消息传递的问题**。 -
 ## 关于 @selector() 你需要知道的 @@ -87,7 +86,6 @@ int main(int argc, const char * argv[]) { 在主函数任意位置打一个断点, 比如 `-> [object hello];` 这里,然后在 lldb 中输入: -
 这里面我们打印了两个选择子的地址` @selector(hello)` 以及 `@selector(undefined_hello_method)`,需要注意的是: @@ -96,7 +94,6 @@ int main(int argc, const char * argv[]) { 如果我们修改程序的代码: -
 在这里,由于我们在代码中显示地写出了 `@selector(undefined_hello_method)`,所以在 lldb 中再次打印这个 `sel` 内存地址跟之前相比有了很大的改变。 @@ -111,7 +108,6 @@ int main(int argc, const char * argv[]) { 在运行时初始化之前,打印 `hello` 选择子的的内存地址: -
 ## message.h 文件 @@ -119,10 +115,10 @@ int main(int argc, const char * argv[]) { Objective-C 中 `objc_msgSend` 的实现并没有开源,它只存在于 `message.h` 这个头文件中。 ```objectivec -/** +/** * @note When it encounters a method call, the compiler generates a call to one of the * functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret. - * Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper; + * Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper; * other messages are sent using \c objc_msgSend. Methods that have data structures as return values * are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret. */ @@ -177,7 +173,6 @@ int main(int argc, const char * argv[]) { 在调用 `hello` 方法的这一行打一个断点,当我们尝试进入(Step in)这个方法只会直接跳入这个方法的实现,而不会进入 `objc_msgSend`: -
 因为 `objc_msgSend` 是一个私有方法,我们没有办法进入它的实现,但是,我们却可以在 `objc_msgSend` 的调用栈中“截下”这个函数调用的过程。 @@ -188,7 +183,6 @@ int main(int argc, const char * argv[]) { 在 `objc-runtime-new.mm` 文件中有一个函数 `lookUpImpOrForward`,这个函数的作用就是查找方法的实现,于是运行程序,在运行到 `hello` 这一行时,激活 `lookUpImpOrForward` 函数中的断点。 -
> 由于转成 gif 实在是太大了,笔者试着用各种方法生成动图,然而效果也不是很理想,只能贴一个 Youtube 的视频链接,不过对于能够翻墙的开发者们,应该也不是什么问题吧(手动微笑)
@@ -203,7 +197,6 @@ int main(int argc, const char * argv[]) {
在 `-> [object hello]` 这里增加一个断点,**当程序运行到这一行时**,再向 `lookUpImpOrForward` 函数的第一行添加断点,确保是捕获 `@selector(hello)` 的调用栈,而不是调用其它选择子的调用栈。
-
 由图中的变量区域可以了解,传入的选择子为 `"hello"`,对应的类是 `XXObject`。所以我们可以确信这就是当调用 `hello` 方法时执行的函数。在 Xcode 左侧能看到方法的调用栈: @@ -221,7 +214,7 @@ int main(int argc, const char * argv[]) { ```objectivec IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls) { - return lookUpImpOrForward(cls, sel, obj, + return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/); } ``` @@ -291,12 +284,10 @@ imp = cache_getImp(cls, sel); if (imp) goto done; ``` -
 不过 `cache_getImp` 的实现目测是不开源的,同时也是汇编写的,在我们尝试 step in 的时候进入了如下的汇编代码。 -
 它会进入一个 `CacheLookup` 的标签,获取实现,使用汇编的原因还是因为要加速整个实现查找的过程,其原理推测是在类的 `cache` 中寻找对应的实现,只是做了一些性能上的优化。 @@ -316,8 +307,8 @@ if (meth) { ```objectivec static method_t *getMethodNoSuper_nolock(Class cls, SEL sel) { - for (auto mlists = cls->data()->methods.beginLists(), - end = cls->data()->methods.endLists(); + for (auto mlists = cls->data()->methods.beginLists(), + end = cls->data()->methods.endLists(); mlists != end; ++mlists) { @@ -336,7 +327,7 @@ static method_t *search_method_list(const method_list_t *mlist, SEL sel) { int methodListIsFixedUp = mlist->isFixedUp(); int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); - + if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) { return findMethodInSortedMethodList(sel, mlist); } else { @@ -434,11 +425,11 @@ void _class_resolveMethod(Class cls, SEL sel, id inst) { if (! cls->isMetaClass()) { _class_resolveInstanceMethod(cls, sel, inst); - } + } else { _class_resolveClassMethod(cls, sel, inst); - if (!lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) + if (!lookUpImpOrNil(cls, sel, inst, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); } @@ -450,7 +441,7 @@ void _class_resolveMethod(Class cls, SEL sel, id inst) ```objectivec static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) { - if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, + if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { // 没有找到 resolveInstanceMethod: 方法,直接返回。 return; @@ -460,7 +451,7 @@ static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) { bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); // 缓存结果,以防止下次在调用 resolveInstanceMethod: 方法影响性能。 - IMP imp = lookUpImpOrNil(cls, sel, inst, + IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); } ``` @@ -486,7 +477,6 @@ cache_fill(cls, sel, imp, inst); 这样就结束了整个方法第一次的调用过程,缓存没有命中,但是在当前类的方法列表中找到了 `hello` 方法的实现,调用了该方法。 -
 @@ -507,7 +497,6 @@ int main(int argc, const char * argv[]) { 然后在第二次调用 `hello` 方法时,加一个断点: -
 `objc_msgSend` 并没有走 `lookupImpOrForward` 这个方法,而是直接结束,打印了另一个 `hello` 字符串。 @@ -518,7 +507,6 @@ int main(int argc, const char * argv[]) { 好,现在重新运行程序至第二个 `hello` 方法调用之前: -
 打印缓存中 bucket 的内容: @@ -568,12 +556,10 @@ int main(int argc, const char * argv[]) { } ``` -
 这样 `XXObject` 中就不存在 `hello` 方法对应实现的缓存了。然后继续运行程序: -
 虽然第二次调用 `hello` 方法,但是因为我们清除了 `hello` 的缓存,所以,会再次进入 `lookupImpOrForward` 方法。 @@ -604,12 +590,10 @@ int main(int argc, const char * argv[]) { 在第一个 `hello` 方法调用之前将实现加入缓存: -
 然后继续运行代码: -
 可以看到,我们虽然没有改变 `hello` 方法的实现,但是在 **objc_msgSend** 的消息发送链路中,使用错误的缓存实现 `cached_imp` 拦截了实现的查找,打印出了 `Cached Hello`。 @@ -625,7 +609,7 @@ int main(int argc, const char * argv[]) { 这篇文章与其说是讲 ObjC 中的消息发送的过程,不如说是讲方法的实现是如何查找的。 Objective-C 中实现查找的路径还是比较符合直觉的: - + 1. 缓存命中 2. 查找当前类的缓存及方法 3. 查找父类的缓存及方法 @@ -636,10 +620,8 @@ Objective-C 中实现查找的路径还是比较符合直觉的: ## 参考资料 -+ [深入解析 ObjC 中方法的结构](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/深入解析%20ObjC%20中方法的结构.md) ++ [深入解析 ObjC 中方法的结构](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/深入解析%20ObjC%20中方法的结构.md) + [Objective-C Runtime](http://tech.glowing.com/cn/objective-c-runtime/) + [Let's Build objc_msgSend](https://www.mikeash.com/pyblog/friday-qa-2012-11-16-lets-build-objc_msgsend.html) Follow: [@Draveness](https://github.com/Draveness) - - diff --git "a/objc/\344\275\240\347\234\237\347\232\204\344\272\206\350\247\243 load \346\226\271\346\263\225\344\271\210\357\274\237.md" "b/contents/objc/\344\275\240\347\234\237\347\232\204\344\272\206\350\247\243 load \346\226\271\346\263\225\344\271\210\357\274\237.md" similarity index 96% rename from "objc/\344\275\240\347\234\237\347\232\204\344\272\206\350\247\243 load \346\226\271\346\263\225\344\271\210\357\274\237.md" rename to "contents/objc/\344\275\240\347\234\237\347\232\204\344\272\206\350\247\243 load \346\226\271\346\263\225\344\271\210\357\274\237.md" index a8135eb..60dd61e 100644 --- "a/objc/\344\275\240\347\234\237\347\232\204\344\272\206\350\247\243 load \346\226\271\346\263\225\344\271\210\357\274\237.md" +++ "b/contents/objc/\344\275\240\347\234\237\347\232\204\344\272\206\350\247\243 load \346\226\271\346\263\225\344\271\210\357\274\237.md" @@ -52,7 +52,7 @@ int main(int argc, const char * argv[]) { 代码总共只实现了一个 `XXObject` 的 `+ load` 方法,主函数中也没有任何的东西: -
 + 虽然在主函数中什么方法都没有调用,但是运行之后,依然打印了 `XXObject load` 字符串,也就是说调用了 `+ load` 方法。 @@ -62,13 +62,13 @@ int main(int argc, const char * argv[]) { > 注意这里 `+` 和 `[` 之间没有空格 -
 + > 为什么要加一个符号断点呢?因为这样看起来比较高级。 重新运行程序。这时,代码会停在 `NSLog(@"XXObject load");` 这一行的实现上: -
 + 左侧的调用栈很清楚的告诉我们,哪些方法被调用了: @@ -128,7 +128,7 @@ load_images(enum dyld_image_states state, uint32_t infoCount, 这里就会遇到一个问题:镜像到底是什么,我们用一个断点打印出所有加载的镜像: -
 + 从控制台输出的结果大概就是这样的,我们可以看到镜像并不是一个 Objective-C 的代码文件,它应该是一个 target 的编译产物。 @@ -160,7 +160,7 @@ load_images(enum dyld_image_states state, uint32_t infoCount, 但是如果进入最下面的这个目录,会发现它是一个**可执行文件**,它的运行结果与 Xcode 中的运行结果相同: -
 + ### 准备 + load 方法 @@ -267,7 +267,7 @@ void call_load_methods(void) 方法的调用流程大概是这样的: -
 + 其中 `call_class_loads` 会从一个待加载的类列表 `loadable_classes` 中寻找对应的类,然后找到 `@selector(load)` 的实现并执行。 @@ -310,7 +310,7 @@ ObjC 对于加载的管理,主要使用了两个列表,分别是 `loadable_c 方法的调用过程也分为两个部分,准备 `load` 方法和调用 `load` 方法,我更觉得这两个部分比较像生产者与消费者: -
 + `add_class_to_loadable_list` 方法负责将类加入 `loadable_classes` 集合,而 `call_class_loads` 负责消费集合中的元素。 diff --git "a/objc/\345\205\263\350\201\224\345\257\271\350\261\241 AssociatedObject \345\256\214\345\205\250\350\247\243\346\236\220.md" "b/contents/objc/\345\205\263\350\201\224\345\257\271\350\261\241 AssociatedObject \345\256\214\345\205\250\350\247\243\346\236\220.md" similarity index 99% rename from "objc/\345\205\263\350\201\224\345\257\271\350\261\241 AssociatedObject \345\256\214\345\205\250\350\247\243\346\236\220.md" rename to "contents/objc/\345\205\263\350\201\224\345\257\271\350\261\241 AssociatedObject \345\256\214\345\205\250\350\247\243\346\236\220.md" index cfc3acb..059cd09 100644 --- "a/objc/\345\205\263\350\201\224\345\257\271\350\261\241 AssociatedObject \345\256\214\345\205\250\350\247\243\346\236\220.md" +++ "b/contents/objc/\345\205\263\350\201\224\345\257\271\350\261\241 AssociatedObject \345\256\214\345\205\250\350\247\243\346\236\220.md" @@ -457,7 +457,7 @@ inline void objc_object::setHasAssociatedObjects() {  -> 如果想要了解关于 isa 的知识,可以阅读[从 NSObject 的初始化了解 isa](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/从%20NSObject%20的初始化了解%20isa.md) +> 如果想要了解关于 isa 的知识,可以阅读[从 NSObject 的初始化了解 isa](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/从%20NSObject%20的初始化了解%20isa.md) ### objc_getAssociatedObject diff --git "a/contents/objc/\345\257\271\350\261\241\346\230\257\345\246\202\344\275\225\345\210\235\345\247\213\345\214\226\347\232\204\357\274\210iOS\357\274\211.md" "b/contents/objc/\345\257\271\350\261\241\346\230\257\345\246\202\344\275\225\345\210\235\345\247\213\345\214\226\347\232\204\357\274\210iOS\357\274\211.md" new file mode 100644 index 0000000..0bb16a1 --- /dev/null +++ "b/contents/objc/\345\257\271\350\261\241\346\230\257\345\246\202\344\275\225\345\210\235\345\247\213\345\214\226\347\232\204\357\274\210iOS\357\274\211.md" @@ -0,0 +1,157 @@ +# 对象是如何初始化的(iOS) + +在之前,我们已经讨论了非常多的问题了,关于 objc 源代码系列的文章也快结束了,其实关于对象是如何初始化的这篇文章本来是我要写的第一篇文章,但是由于有很多前置内容不得不说,所以留到了这里。 + +`+ alloc` 和 `- init` 这一对我们在 iOS 开发中每天都要用到的初始化方法一直困扰着我, 于是笔者仔细研究了一下 objc 源码中 `NSObject` 如何进行初始化。 + +在具体分析对象的初始化过程之前,我想先放出结论,以免文章中的细枝末节对读者的理解有所影响;整个对象的初始化过程其实只是**为一个分配内存空间,并且初始化 isa_t 结构体的过程**。 + +## alloc 方法分析 + +先来看一下 `+ alloc` 方法的调用栈(在调用栈中省略了很多不必要的方法的调用): + +```objectivec +id _objc_rootAlloc(Class cls) +└── static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) + └── id class_createInstance(Class cls, size_t extraBytes) + └── id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct, size_t *outAllocatedSize) + ├── size_t instanceSize(size_t extraBytes) + ├── void *calloc(size_t, size_t) + └── inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) +``` + +这个调用栈中的方法涉及了多个文件中的代码,在下面的章节中会对调用的方法逐步进行分析,如果这个调用栈让你觉得很头疼,也不是什么问题。 + +### alloc 的实现 + +```objectivec ++ (id)alloc { + return _objc_rootAlloc(self); +} +``` + +`alloc` 方法的实现真的是非常的简单, 它直接调用了另一个私有方法 `id _objc_rootAlloc(Class cls)` + +```objectivec +id _objc_rootAlloc(Class cls) { + return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); +} +``` + +这就是上帝类 `NSObject` 对 `callAlloc` 的实现,我们省略了非常多的代码,展示了最常见的执行路径: + +```objectivec +static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { + id obj = class_createInstance(cls, 0); + return obj; +} + +id class_createInstance(Class cls, size_t extraBytes) { + return _class_createInstanceFromZone(cls, extraBytes, nil); +} +``` + +对象初始化中最重要的操作都在 `_class_createInstanceFromZone` 方法中执行: + +```objectivec +static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { + size_t size = cls->instanceSize(extraBytes); + + id obj = (id)calloc(1, size); + if (!obj) return nil; + obj->initInstanceIsa(cls, hasCxxDtor); + + return obj; +} +``` + +### 对象的大小 + +在使用 `calloc` 为对象分配一块内存空间之前,我们要先获取对象在内存的大小: + +```objectivec +size_t instanceSize(size_t extraBytes) { + size_t size = alignedInstanceSize() + extraBytes; + if (size < 16) size = 16; + return size; +} + +uint32_t alignedInstanceSize() { + return word_align(unalignedInstanceSize()); +} + +uint32_t unalignedInstanceSize() { + assert(isRealized()); + return data()->ro->instanceSize; +} +``` + +实例大小 `instanceSize` 会存储在类的 `isa_t` 结构体中,然后经过对齐最后返回。 + +> Core Foundation 需要所有的对象的大小都必须大于或等于 16 字节。 + +在获取对象大小之后,直接调用 `calloc` 函数就可以为对象分配内存空间了。 + +### isa 的初始化 + +在对象的初始化过程中除了使用 `calloc` 来分配内存之外,还需要根据类初始化 `isa_t` 结构体: + +```objectivec +inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) { + if (!indexed) { + isa.cls = cls; + } else { + isa.bits = ISA_MAGIC_VALUE; + isa.has_cxx_dtor = hasCxxDtor; + isa.shiftcls = (uintptr_t)cls >> 3; + } +} +``` + +上面的代码只是对 `isa_t` 结构体进行初始化而已: + +```objectivec +union isa_t { + isa_t() { } + isa_t(uintptr_t value) : bits(value) { } + + Class cls; + uintptr_t bits; + + struct { + uintptr_t indexed : 1; + uintptr_t has_assoc : 1; + uintptr_t has_cxx_dtor : 1; + uintptr_t shiftcls : 44; + uintptr_t magic : 6; + uintptr_t weakly_referenced : 1; + uintptr_t deallocating : 1; + uintptr_t has_sidetable_rc : 1; + uintptr_t extra_rc : 8; + }; +}; +``` + +> 在这里并不想过多介绍关于 `isa_t` 结构体的内容,你可以看[从 NSObject 的初始化了解 isa](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/从%20NSObject%20的初始化了解%20isa.md) 来了解你想知道的关于 `isa_t` 的全部内容。 + +## init 方法 + +`NSObject` 的 `- init` 方法只是调用了 `_objc_rootInit` 并返回了当前对象: + +```objectivec +- (id)init { + return _objc_rootInit(self); +} + +id _objc_rootInit(id obj) { + return obj; +} +``` + +## 总结 + +在 iOS 中一个对象的初始化过程很符合直觉,只是分配内存空间、然后初始化 `isa_t` 结构体,其实现也并不复杂,这篇文章也是这个系列文章中较为简单并且简短的一篇。 + +> Follow: [Draveness · Github](https://github.com/Draveness) + + diff --git "a/objc/\346\207\222\346\203\260\347\232\204 initialize \346\226\271\346\263\225.md" "b/contents/objc/\346\207\222\346\203\260\347\232\204 initialize \346\226\271\346\263\225.md" similarity index 97% rename from "objc/\346\207\222\346\203\260\347\232\204 initialize \346\226\271\346\263\225.md" rename to "contents/objc/\346\207\222\346\203\260\347\232\204 initialize \346\226\271\346\263\225.md" index baa20b8..4ed7ab6 100644 --- "a/objc/\346\207\222\346\203\260\347\232\204 initialize \346\226\271\346\263\225.md" +++ "b/contents/objc/\346\207\222\346\203\260\347\232\204 initialize \346\226\271\346\263\225.md" @@ -6,7 +6,7 @@ 这篇文章可能是对 Objective-C 源代码解析系列文章中最短的一篇了,在 Objective-C 中,我们总是会同时想到 `load`、`initialize` 这两个类方法。而这两个方法也经常在一起比较: -在上一篇介绍 `load` 方法的[文章](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/你真的了解%20load%20方法么?.md)中,已经对 `load` 方法的调用时机、调用顺序进行了详细地分析,所以对于 `load` 方法,这里就不在赘述了。 +在上一篇介绍 `load` 方法的[文章](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/你真的了解%20load%20方法么?.md)中,已经对 `load` 方法的调用时机、调用顺序进行了详细地分析,所以对于 `load` 方法,这里就不在赘述了。 这篇文章会~~假设你知道:~~假设你是 iOS 开发者。 @@ -80,7 +80,7 @@ int main(int argc, const char * argv[]) { 6 start ``` -直接来看调用栈中的 `lookUpImpOrForward` 方法,`lookUpImpOrForward` 方法**只会在向对象发送消息,并且在类的缓存中没有找到消息的选择子时**才会调用,具体可以看这篇文章,[从源代码看 ObjC 中消息的发送](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/从源代码看%20ObjC%20中消息的发送.md)。 +直接来看调用栈中的 `lookUpImpOrForward` 方法,`lookUpImpOrForward` 方法**只会在向对象发送消息,并且在类的缓存中没有找到消息的选择子时**才会调用,具体可以看这篇文章,[从源代码看 ObjC 中消息的发送](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/从源代码看%20ObjC%20中消息的发送.md)。 在这里,我们知道 `lookUpImpOrForward` 方法是 `objc_msgSend` 触发的就够了。 diff --git "a/objc/\346\267\261\345\205\245\350\247\243\346\236\220 ObjC \344\270\255\346\226\271\346\263\225\347\232\204\347\273\223\346\236\204.md" "b/contents/objc/\346\267\261\345\205\245\350\247\243\346\236\220 ObjC \344\270\255\346\226\271\346\263\225\347\232\204\347\273\223\346\236\204.md" similarity index 96% rename from "objc/\346\267\261\345\205\245\350\247\243\346\236\220 ObjC \344\270\255\346\226\271\346\263\225\347\232\204\347\273\223\346\236\204.md" rename to "contents/objc/\346\267\261\345\205\245\350\247\243\346\236\220 ObjC \344\270\255\346\226\271\346\263\225\347\232\204\347\273\223\346\236\204.md" index ecf91b6..17d666a 100644 --- "a/objc/\346\267\261\345\205\245\350\247\243\346\236\220 ObjC \344\270\255\346\226\271\346\263\225\347\232\204\347\273\223\346\236\204.md" +++ "b/contents/objc/\346\267\261\345\205\245\350\247\243\346\236\220 ObjC \344\270\255\346\226\271\346\263\225\347\232\204\347\273\223\346\236\204.md" @@ -2,7 +2,7 @@ > 因为 ObjC 的 runtime 只能在 Mac OS 下才能编译,所以文章中的代码都是在 Mac OS,也就是 `x86_64` 架构下运行的,对于在 arm64 中运行的代码会特别说明。 -在上一篇分析 `isa` 的文章[从 NSObject 的初始化了解 isa](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/从%20NSObject%20的初始化了解%20isa.md) 中曾经说到过实例方法被调用时,会通过其持有 `isa` 指针寻找对应的类,然后在其中的 `class_data_bits_t` 中查找对应的方法,在这一篇文章中会介绍方法在 ObjC 中是如何存储方法的。 +在上一篇分析 `isa` 的文章[从 NSObject 的初始化了解 isa](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/从%20NSObject%20的初始化了解%20isa.md) 中曾经说到过实例方法被调用时,会通过其持有 `isa` 指针寻找对应的类,然后在其中的 `class_data_bits_t` 中查找对应的方法,在这一篇文章中会介绍方法在 ObjC 中是如何存储方法的。 这篇文章的首先会根据 ObjC 源代码来分析方法在内存中的存储结构,然后在 lldb 调试器中一步一步验证分析的正确性。 @@ -10,7 +10,6 @@ 先来了解一下 ObjC 中类的结构图: -
 + `isa` 是指向元类的指针,不了解元类的可以看 [Classes and Metaclasses](http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html) @@ -24,7 +23,6 @@ 下面就是 ObjC 中 `class_data_bits_t` 的结构体,其中只含有一个 64 位的 `bits` 用于存储与类有关的信息: -
 在 `objc_class` 结构体中的注释写到 `class_data_bits_t` 相当于 `class_rw_t` 指针加上 rr/alloc 的标志。 @@ -47,7 +45,6 @@ class_rw_t* data() { 因为 `class_rw_t *` 指针只存于第 `[3, 47]` 位,所以可以使用最后三位来存储关于当前类的其他信息: -
 @@ -126,7 +123,6 @@ struct class_ro_t { **在编译期间**类的结构中的 `class_data_bits_t *data` 指向的是一个 `class_ro_t *` 指针: -
 @@ -147,7 +143,6 @@ cls->setData(rw); 下图是 `realizeClass` 方法执行过后的类所占用内存的布局,你可以与上面调用方法前的内存布局对比以下,看有哪些更改: -
 但是,在这段代码运行之后 `class_rw_t` 中的方法,属性以及协议列表均为空。这时需要 `realizeClass` 调用 `methodizeClass` 方法来**将类自己实现的方法(包括分类)、属性和遵循的协议加载到 `methods`、 `properties` 和 `protocols` 列表中**。 @@ -181,7 +176,6 @@ cls->setData(rw); > 这段代码是运行在 Mac OS X 10.11.3 (x86_64)版本中,而不是运行在 iPhone 模拟器或者真机上的,如果你在 iPhone 或者真机上运行,可能有一定差别。 -
 这是主程序的代码: @@ -209,7 +203,6 @@ int main(int argc, const char * argv[]) { 接下来,在整个 ObjC 运行时初始化之前,也就是 `_objc_init` 方法中加入一个断点: -
 然后在 lldb 中输入以下命令: @@ -240,7 +233,6 @@ warning: could not load any Objective-C class information. This will significant } ``` -
 现在我们获取了类经过编译器处理后的只读属性 `class_ro_t`: @@ -281,7 +273,6 @@ The process has been returned to the state before expression evaluation. (lldb) ``` -
 使用 `$5->get(0)` 时,成功获取到了 `-[XXObject hello]` 方法的结构体 `method_t`。而尝试获取下一个方法时,断言提示我们当前类只有一个方法。 @@ -299,14 +290,12 @@ static Class realizeClass(Class cls) 上面就是这个方法的签名,我们需要在这个方法中打一个条件断点,来判断当前类是否为 `XXObject`: -
 这里直接判断两个指针是否相等,而不使用 `[NSStringFromClass(cls) isEqualToString:@"XXObject"]` 是因为在这个时间点,这些方法都不能调用,在 ObjC 中没有这些方法,所以只能通过判断类指针是否相等的方式来确认当前类是 `XXObject`。 > 直接与指针比较是因为类在内存中的位置是编译期确定的,只要代码不改变,类在内存中的位置就会不变(已经说过很多遍了)。 -
 @@ -316,13 +305,11 @@ static Class realizeClass(Class cls) 在这时打印类结构体中的 `data` 的值,发现其中的布局依旧是这样的: -
 在运行完这段代码之后: -
 我们再来打印类的结构: @@ -400,7 +387,6 @@ Assertion failed: (i < count), function get, file /Users/apple/Desktop/objc-runt (lldb) ``` -
 > 最后一个操作实在是截取不到了 @@ -415,7 +401,6 @@ cls->setData(rw); 在上述的代码运行之后,类的只读指针 `class_ro_t` 以及可读写指针 `class_rw_t` 都被正确的设置了。但是到这里,其 `class_rw_t` 部分的方法等成员的指针 `methods`、 `protocols` 和 `properties` 均为空,这些会在 `methodizeClass` 中进行设置: -
 在这里调用了 `method_array_t` 的 `attachLists` 方法,将 `baseMethods` 中的方法添加到 `methods` 数组之后。我们访问 `methods` 才会获取当前类的实例方法。 @@ -434,7 +419,6 @@ struct method_t { 其中包含方法名,类型还有方法的实现指针 `IMP`: -

上面的 `-[XXObject hello]` 方法的结构体是这样的:
diff --git "a/objc/\350\207\252\345\212\250\351\207\212\346\224\276\346\261\240\347\232\204\345\211\215\344\270\226\344\273\212\347\224\237.md" "b/contents/objc/\350\207\252\345\212\250\351\207\212\346\224\276\346\261\240\347\232\204\345\211\215\344\270\226\344\273\212\347\224\237.md"
similarity index 99%
rename from "objc/\350\207\252\345\212\250\351\207\212\346\224\276\346\261\240\347\232\204\345\211\215\344\270\226\344\273\212\347\224\237.md"
rename to "contents/objc/\350\207\252\345\212\250\351\207\212\346\224\276\346\261\240\347\232\204\345\211\215\344\270\226\344\273\212\347\224\237.md"
index 1717739..9f222be 100644
--- "a/objc/\350\207\252\345\212\250\351\207\212\346\224\276\346\261\240\347\232\204\345\211\215\344\270\226\344\273\212\347\224\237.md"
+++ "b/contents/objc/\350\207\252\345\212\250\351\207\212\346\224\276\346\261\240\347\232\204\345\211\215\344\270\226\344\273\212\347\224\237.md"
@@ -2,8 +2,8 @@
> 由于 Objective-C 中的内存管理是一个比较大的话题,所以会分为两篇文章来对内存管理中的一些机制进行剖析,一部分分析自动释放池以及 `autorelease` 方法,另一部分分析 `retain`、`release` 方法的实现以及自动引用计数。
-+ [自动释放池的前世今生](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/自动释放池的前世今生.md)
-+ [黑箱中的 retain 和 release](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/黑箱中的%20retain%20和%20release.md)
++ [自动释放池的前世今生](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/自动释放池的前世今生.md)
++ [黑箱中的 retain 和 release](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/黑箱中的%20retain%20和%20release.md)
## 写在前面
diff --git "a/objc/\351\273\221\347\256\261\344\270\255\347\232\204 retain \345\222\214 release.md" "b/contents/objc/\351\273\221\347\256\261\344\270\255\347\232\204 retain \345\222\214 release.md"
similarity index 98%
rename from "objc/\351\273\221\347\256\261\344\270\255\347\232\204 retain \345\222\214 release.md"
rename to "contents/objc/\351\273\221\347\256\261\344\270\255\347\232\204 retain \345\222\214 release.md"
index 6996cc3..f8471a8 100644
--- "a/objc/\351\273\221\347\256\261\344\270\255\347\232\204 retain \345\222\214 release.md"
+++ "b/contents/objc/\351\273\221\347\256\261\344\270\255\347\232\204 retain \345\222\214 release.md"
@@ -2,8 +2,8 @@
> 由于 Objective-C 中的内存管理是一个比较大的话题,所以会分为两篇文章来对内存管理中的一些机制进行剖析,一部分分析自动释放池以及 `autorelease` 方法,另一部分分析 `retain`、`release` 方法的实现以及自动引用计数。
-+ [自动释放池的前世今生](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/自动释放池的前世今生.md)
-+ [黑箱中的 retain 和 release](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/黑箱中的%20retain%20和%20release.md)
++ [自动释放池的前世今生](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/自动释放池的前世今生.md)
++ [黑箱中的 retain 和 release](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/黑箱中的%20retain%20和%20release.md)
## 写在前面
@@ -359,7 +359,7 @@ inline uintptr_t objc_object::rootRetainCount() {
+ Objective-C 使用 `isa` 中的 `extra_rc` 和 `SideTable` 来存储对象的引用计数
+ 在对象的引用计数归零时,会调用 `dealloc` 方法回收对象
-有关于自动释放池实现的介绍,可以看[自动释放池的前世今生](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/objc/自动释放池的前世今生.md)。
+有关于自动释放池实现的介绍,可以看[自动释放池的前世今生](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/自动释放池的前世今生.md)。
> Follow: [Draveness · Github](https://github.com/Draveness)
diff --git a/contents/rack/images/rack-thin/event-callback.png b/contents/rack/images/rack-thin/event-callback.png
new file mode 100644
index 0000000..97e6423
Binary files /dev/null and b/contents/rack/images/rack-thin/event-callback.png differ
diff --git a/contents/rack/images/rack-thin/eventmachine-select.png b/contents/rack/images/rack-thin/eventmachine-select.png
new file mode 100644
index 0000000..8673de9
Binary files /dev/null and b/contents/rack/images/rack-thin/eventmachine-select.png differ
diff --git a/contents/rack/images/rack-thin/reactor-eventloop.png b/contents/rack/images/rack-thin/reactor-eventloop.png
new file mode 100644
index 0000000..eed3171
Binary files /dev/null and b/contents/rack/images/rack-thin/reactor-eventloop.png differ
diff --git a/contents/rack/images/rack-thin/selectable-and-subclasses.png b/contents/rack/images/rack-thin/selectable-and-subclasses.png
new file mode 100644
index 0000000..af1891c
Binary files /dev/null and b/contents/rack/images/rack-thin/selectable-and-subclasses.png differ
diff --git a/contents/rack/images/rack-thin/thin-handle-request.png b/contents/rack/images/rack-thin/thin-handle-request.png
new file mode 100644
index 0000000..aaec961
Binary files /dev/null and b/contents/rack/images/rack-thin/thin-handle-request.png differ
diff --git a/contents/rack/images/rack-thin/thin-initialize-server.png b/contents/rack/images/rack-thin/thin-initialize-server.png
new file mode 100644
index 0000000..515c9d0
Binary files /dev/null and b/contents/rack/images/rack-thin/thin-initialize-server.png differ
diff --git a/contents/rack/images/rack-thin/thin-io-model.png b/contents/rack/images/rack-thin/thin-io-model.png
new file mode 100644
index 0000000..a5846b3
Binary files /dev/null and b/contents/rack/images/rack-thin/thin-io-model.png differ
diff --git a/contents/rack/images/rack-thin/thin-send-response.png b/contents/rack/images/rack-thin/thin-send-response.png
new file mode 100644
index 0000000..0685d5b
Binary files /dev/null and b/contents/rack/images/rack-thin/thin-send-response.png differ
diff --git a/contents/rack/images/rack-thin/thin-start-server.png b/contents/rack/images/rack-thin/thin-start-server.png
new file mode 100644
index 0000000..ebdc972
Binary files /dev/null and b/contents/rack/images/rack-thin/thin-start-server.png differ
diff --git a/contents/rack/images/rack-unicorn/unicorn-daemonize.png b/contents/rack/images/rack-unicorn/unicorn-daemonize.png
new file mode 100644
index 0000000..2d4bb0b
Binary files /dev/null and b/contents/rack/images/rack-unicorn/unicorn-daemonize.png differ
diff --git a/contents/rack/images/rack-unicorn/unicorn-io-model.png b/contents/rack/images/rack-unicorn/unicorn-io-model.png
new file mode 100644
index 0000000..b25d078
Binary files /dev/null and b/contents/rack/images/rack-unicorn/unicorn-io-model.png differ
diff --git a/contents/rack/images/rack-unicorn/unicorn-multi-processes.png b/contents/rack/images/rack-unicorn/unicorn-multi-processes.png
new file mode 100644
index 0000000..423398a
Binary files /dev/null and b/contents/rack/images/rack-unicorn/unicorn-multi-processes.png differ
diff --git a/contents/rack/images/rack-unicorn/unicorn.jpeg b/contents/rack/images/rack-unicorn/unicorn.jpeg
new file mode 100644
index 0000000..6df4100
Binary files /dev/null and b/contents/rack/images/rack-unicorn/unicorn.jpeg differ
diff --git a/contents/rack/images/rack-webrick/mounttable-and-applications.png b/contents/rack/images/rack-webrick/mounttable-and-applications.png
new file mode 100644
index 0000000..0a3b6b3
Binary files /dev/null and b/contents/rack/images/rack-webrick/mounttable-and-applications.png differ
diff --git a/contents/rack/images/rack-webrick/webrick-io-model.png b/contents/rack/images/rack-webrick/webrick-io-model.png
new file mode 100644
index 0000000..4092d1b
Binary files /dev/null and b/contents/rack/images/rack-webrick/webrick-io-model.png differ
diff --git a/contents/rack/images/rack/rack-and-web-servers-frameworks.png b/contents/rack/images/rack/rack-and-web-servers-frameworks.png
new file mode 100644
index 0000000..51163df
Binary files /dev/null and b/contents/rack/images/rack/rack-and-web-servers-frameworks.png differ
diff --git a/contents/rack/images/rack/rack-app.png b/contents/rack/images/rack/rack-app.png
new file mode 100644
index 0000000..c2c7966
Binary files /dev/null and b/contents/rack/images/rack/rack-app.png differ
diff --git a/contents/rack/images/rack/rack-logo.png b/contents/rack/images/rack/rack-logo.png
new file mode 100644
index 0000000..f5bba1a
Binary files /dev/null and b/contents/rack/images/rack/rack-logo.png differ
diff --git a/contents/rack/images/rack/rack-protocol.png b/contents/rack/images/rack/rack-protocol.png
new file mode 100644
index 0000000..b5ae951
Binary files /dev/null and b/contents/rack/images/rack/rack-protocol.png differ
diff --git a/contents/rack/images/rack/rails-application.png b/contents/rack/images/rack/rails-application.png
new file mode 100644
index 0000000..0fe07d9
Binary files /dev/null and b/contents/rack/images/rack/rails-application.png differ
diff --git a/contents/rack/images/rack/server-app-call-stack.png b/contents/rack/images/rack/server-app-call-stack.png
new file mode 100644
index 0000000..8dde02d
Binary files /dev/null and b/contents/rack/images/rack/server-app-call-stack.png differ
diff --git a/contents/rack/images/rack/wrapped-app.png b/contents/rack/images/rack/wrapped-app.png
new file mode 100644
index 0000000..2b0e12a
Binary files /dev/null and b/contents/rack/images/rack/wrapped-app.png differ
diff --git a/contents/rack/rack-thin.md b/contents/rack/rack-thin.md
new file mode 100644
index 0000000..99ceaf9
--- /dev/null
+++ b/contents/rack/rack-thin.md
@@ -0,0 +1,786 @@
+# 浅谈 Thin 的事件驱动模型
+
++ [谈谈 Rack 协议与实现](https://draveness.me/rack)
++ [浅谈 WEBrick 的实现](https://draveness.me/rack-webrick)
++ [浅谈 Thin 的事件驱动模型](https://draveness.me/rack-thin)
++ [浅谈 Unicorn 的多进程模型](https://draveness.me/rack-unicorn)
++ [浅谈 Puma 的实现](https://draveness.me/rack-puma)
+
+在上一篇文章中我们已经介绍了 WEBrick 的实现,它的 handler 是写在 Rack 工程中的,而在这篇文章介绍的 webserver [thin](https://github.com/macournoyer/thin) 的 Rack 处理器也是写在 Rack 中的;与 WEBrick 相同,Thin 的实现也非常简单,官方对它的介绍是:
+
+> A very fast & simple Ruby web server.
+
+它将 [Mongrel](https://zedshaw.com/archive/ragel-state-charts/)、[Event Machine](https://github.com/eventmachine/eventmachine) 和 [Rack](http://rack.github.io) 三者进行组合,在其中起到胶水的作用,所以在理解 Thin 的实现的过程中我们也需要分析 EventMachine 到底是如何工作的。
+
+## Thin 的实现
+
+在这一节中我们将从源代码来分析介绍 Thin 的实现原理,因为部分代码仍然是在 Rack 工程中实现的,所以我们要从 Rack 工程的代码开始理解 Thin 的实现。
+
+### 从 Rack 开始
+
+Thin 的处理器 `Rack::Handler::Thin` 与其他遵循 Rack 协议的 webserver 一样都实现了 `.run` 方法,接受 Rack 应用和 `options` 作为输入:
+
+```ruby
+module Rack
+ module Handler
+ class Thin
+ def self.run(app, options={})
+ environment = ENV['RACK_ENV'] || 'development'
+ default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
+
+ host = options.delete(:Host) || default_host
+ port = options.delete(:Port) || 8080
+ args = [host, port, app, options]
+ args.pop if ::Thin::VERSION::MAJOR < 1 && ::Thin::VERSION::MINOR < 8
+ server = ::Thin::Server.new(*args)
+ yield server if block_given?
+ server.start
+ end
+ end
+ end
+end
+```
+
+上述方法仍然会从 `options` 中取出 ip 地址和端口号,然后初始化一个 `Thin::Server` 的实例后,执行 `#start` 方法在 8080 端口上监听来自用户的请求。
+
+### 初始化服务
+
+Thin 服务的初始化由以下的代码来处理,首先会处理在 `Rack::Handler::Thin.run` 中传入的几个参数 `host`、`port`、`app` 和 `options`,将 Rack 应用存储在临时变量中:
+
+```ruby
+From: lib/thin/server.rb @ line 100:
+Owner: Thin::Server
+
+def initialize(*args, &block)
+ host, port, options = DEFAULT_HOST, DEFAULT_PORT, {}
+
+ args.each do |arg|
+ case arg
+ when 0.class, /^\d+$/ then port = arg.to_i
+ when String then host = arg
+ when Hash then options = arg
+ else
+ @app = arg if arg.respond_to?(:call)
+ end
+ end
+
+ @backend = select_backend(host, port, options)
+ @backend.server = self
+ @backend.maximum_connections = DEFAULT_MAXIMUM_CONNECTIONS
+ @backend.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
+ @backend.timeout = options[:timeout] || DEFAULT_TIMEOUT
+
+ @app = Rack::Builder.new(&block).to_app if block
+end
+```
+
+在初始化服务的过程中,总共只做了三件事情,处理参数、选择并配置 `backend`,创建新的应用:
+
+
+
+处理参数的过程自然不用多说,只是这里判断的方式并不是按照顺序处理的,而是按照参数的类型;在初始化器的最后,如果向初始化器传入了 block,那么就会使用 `Rack::Builder` 和 block 中的代码初始化一个新的 Rack 应用。
+
+### 选择后端
+
+在选择后端时 Thin 使用了 `#select_backend` 方法,这里使用 `case` 语句替代多个 `if`、`else`,也是一个我们可以使用的小技巧:
+
+```ruby
+From: lib/thin/server.rb @ line 261:
+Owner: Thin::Server
+
+def select_backend(host, port, options)
+ case
+ when options.has_key?(:backend)
+ raise ArgumentError, ":backend must be a class" unless options[:backend].is_a?(Class)
+ options[:backend].new(host, port, options)
+ when options.has_key?(:swiftiply)
+ Backends::SwiftiplyClient.new(host, port, options)
+ when host.include?('/')
+ Backends::UnixServer.new(host)
+ else
+ Backends::TcpServer.new(host, port)
+ end
+end
+```
+
+在大多数时候,我们只会选择 `UnixServer` 和 `TcpServer` 两种后端中的一个,而后者又是两者中使用更为频繁的后端:
+
+```ruby
+From: lib/thin/backends/tcp_server.rb @ line 8:
+Owner: Thin::Backends::TcpServer
+
+def initialize(host, port)
+ @host = host
+ @port = port
+ super()
+end
+
+From: lib/thin/backends/base.rb @ line 47:
+Owner: Thin::Backends::Base
+
+def initialize
+ @connections = {}
+ @timeout = Server::DEFAULT_TIMEOUT
+ @persistent_connection_count = 0
+ @maximum_connections = Server::DEFAULT_MAXIMUM_CONNECTIONS
+ @maximum_persistent_connections = Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
+ @no_epoll = false
+ @ssl = nil
+ @threaded = nil
+ @started_reactor = false
+end
+```
+
+初始化的过程中只是对属性设置默认值,比如 `host`、`port` 以及超时时间等等,并没有太多值得注意的代码。
+
+### 启动服务
+
+在启动服务时会直接调用 `TcpServer#start` 方法并在其中传入一个用于处理信号的 block:
+
+```ruby
+From: lib/thin/server.rb @ line 152:
+Owner: Thin::Server
+
+def start
+ raise ArgumentError, 'app required' unless @app
+
+ log_info "Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
+ log_debug "Debugging ON"
+ trace "Tracing ON"
+
+ log_info "Maximum connections set to #{@backend.maximum_connections}"
+ log_info "Listening on #{@backend}, CTRL+C to stop"
+
+ @backend.start { setup_signals if @setup_signals }
+end
+```
+
+虽然这里的 `backend` 其实已经被选择成了 `TcpServer`,但是该子类并没有覆写 `#start` 方法,这里执行的方法其实是从父类继承的:
+
+```ruby
+From: lib/thin/backends/base.rb @ line 60:
+Owner: Thin::Backends::Base
+
+def start
+ @stopping = false
+ starter = proc do
+ connect
+ yield if block_given?
+ @running = true
+ end
+
+ # Allow for early run up of eventmachine.
+ if EventMachine.reactor_running?
+ starter.call
+ else
+ @started_reactor = true
+ EventMachine.run(&starter)
+ end
+end
+```
+
+上述方法在构建一个 `starter` block 之后,将该 block 传入 `EventMachine.run` 方法,随后执行的 `#connect` 会启动一个 `EventMachine` 的服务器用于处理用户的网络请求:
+
+```ruby
+From: lib/thin/backends/tcp_server.rb @ line 15:
+Owner: Thin::Backends::TcpServer
+
+def connect
+ @signature = EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
+ binary_name = EventMachine.get_sockname( @signature )
+ port_name = Socket.unpack_sockaddr_in( binary_name )
+ @port = port_name[0]
+ @host = port_name[1]
+ @signature
+end
+```
+
+在 EventMachine 的文档中,`.start_server` 方法被描述成一个在指定的地址和端口上初始化 TCP 服务的方法,正如这里所展示的,它经常在 `.run` 方法的 block 中执行;该方法的参数 `Connection` 作为处理 TCP 请求的类,会实现不同的方法接受各种各样的回调,传入的 `initialize_connection` block 会在有请求需要处理时对 `Connection` 对象进行初始化:
+
+> `Connection` 对象继承自 `EventMachine::Connection`,是 EventMachine 与外界的接口,在 EventMachine 中的大部分事件都会调用 `Connection` 的一个实例方法来传递数据和参数。
+
+```ruby
+From: lib/thin/backends/base.rb @ line 145:
+Owner: Thin::Backends::Base
+
+def initialize_connection(connection)
+ connection.backend = self
+ connection.app = @server.app
+ connection.comm_inactivity_timeout = @timeout
+ connection.threaded = @threaded
+ connection.start_tls(@ssl_options) if @ssl
+
+ if @persistent_connection_count < @maximum_persistent_connections
+ connection.can_persist!
+ @persistent_connection_count += 1
+ end
+ @connections[connection.__id__] = connection
+end
+```
+
+### 处理请求的连接
+
+`Connection` 类中有很多的方法 `#post_init`、`#receive_data` 方法等等都是由 EventMachine 在接收到请求时调用的,当 Thin 的服务接收到来自客户端的数据时就会调用 `#receive_data` 方法:
+
+```ruby
+From: lib/thin/connection.rb @ line 36:
+Owner: Thin::Connection
+
+def receive_data(data)
+ @idle = false
+ trace data
+ process if @request.parse(data)
+rescue InvalidRequest => e
+ log_error("Invalid request", e)
+ post_process Response::BAD_REQUEST
+end
+```
+
+在这里我们看到了与 WEBrick 在处理来自客户端的原始数据时使用的方法 `#parse`,它会解析客户端请求的原始数据并执行 `#process` 来处理 HTTP 请求:
+
+```ruby
+From: lib/thin/connection.rb @ line 47:
+Owner: Thin::Connection
+
+def process
+ if threaded?
+ @request.threaded = true
+ EventMachine.defer { post_process(pre_process) }
+ else
+ @request.threaded = false
+ post_process(pre_process)
+ end
+end
+```
+
+如果当前的连接允许并行处理多个用户的请求,那么就会在 `EventMachine.defer` 的 block 中执行两个方法 `#pre_process` 和 `#post_process`:
+
+```ruby
+From: lib/thin/connection.rb @ line 63:
+Owner: Thin::Connection
+
+def pre_process
+ @request.remote_address = remote_address
+ @request.async_callback = method(:post_process)
+
+ response = AsyncResponse
+ catch(:async) do
+ response = @app.call(@request.env)
+ end
+ response
+rescue Exception => e
+ unexpected_error(e)
+ can_persist? && @request.persistent? ? Response::PERSISTENT_ERROR : Response::ERROR
+end
+```
+
+在 `#pre_process` 中没有做太多的事情,只是调用了 Rack 应用的 `#call` 方法,得到一个三元组 `response`,在这之后将这个数组传入 `#post_process` 方法:
+
+```ruby
+From: lib/thin/connection.rb @ line 95:
+Owner: Thin::Connection
+
+def post_process(result)
+ return unless result
+ result = result.to_a
+ return if result.first == AsyncResponse.first
+
+ @response.status, @response.headers, @response.body = *result
+ @response.each do |chunk|
+ send_data chunk
+ end
+rescue Exception => e
+ unexpected_error(e)
+ close_connection
+ensure
+ if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
+ @response.body.callback { terminate_request }
+ @response.body.errback { terminate_request }
+ else
+ terminate_request unless result && result.first == AsyncResponse.first
+ end
+end
+```
+
+`#post_response` 方法将传入的数组赋值给 `response` 的 `status`、`headers` 和 `body` 这三部分,在这之后通过 `#send_data` 方法将 HTTP 响应以块的形式写回 Socket;写回结束后可能会调用对应的 `callback` 并关闭持有的 `request` 和 `response` 两个实例变量。
+
+> 上述方法中调用的 `#send_data` 继承自 `EventMachine::Connection` 类。
+
+### 小结
+
+到此为止,我们对于 Thin 是如何处理来自用户的 HTTP 请求的就比较清楚了,我们可以看到 Thin 本身并没有做一些类似解析 HTTP 数据包以及发送数据的问题,它使用了来自 Rack 和 EventMachine 两个开源框架中很多已有的代码逻辑,确实只做了一些胶水的事情。
+
+对于 Rack 是如何工作的我们在前面的文章 [谈谈 Rack 协议与实现](https://draveness.me/rack) 中已经介绍过了;虽然我们看到了很多与 EventMachine 相关的代码,但是到这里我们仍然对 EventMachine 不是太了解。
+
+## EventMachine 和 Reactor 模式
+
+为了更好地理解 Thin 的工作原理,在这里我们会介绍一个 EventMachine 和 Reactor 模式。
+
+EventMachine 其实是一个使用 Ruby 实现的事件驱动的并行框架,它使用 Reactor 模式提供了事件驱动的 IO 模型,如果你对 Node.js 有所了解的话,那么你一定对事件驱动这个词并不陌生,EventMachine 的出现主要是为了解决两个核心问题:
+
++ 为生产环境提供更高的可伸缩性、更好的性能和稳定性;
++ 为上层提供了一些能够减少高性能的网络编程复杂性的 API;
+
+其实 EventMachine 的主要作用就是将所有同步的 IO 都变成异步的,调度都通过事件来进行,这样用于监听用户请求的进程不会被其他代码阻塞,能够同时为更多的客户端提供服务;在这一节中,我们需要了解一下在 Thin 中使用的 EventMachine 中几个常用方法的实现。
+
+### 启动事件循环
+
+EventMachine 其实就是一个事件循环(Event Loop),当我们想使用 EventMachine 来处理某些任务时就一定需要调用 `.run` 方法启动这个事件循环来接受外界触发的各种事件:
+
+```ruby
+From: lib/eventmachine.rb @ line 149:
+Owner: #