├── .gitattributes ├── .gitignore ├── README.md └── contents ├── AFNetworking ├── AFNetworkReachabilityManager 监控网络状态(四).md ├── AFNetworking 概述(一).md ├── AFNetworking 的核心 AFURLSessionManager(二).md ├── 处理请求和响应 AFURLSerialization(三).md └── 验证 HTTPS 请求的证书(五).md ├── Alamofire └── iOS 源代码分析 ---- Alamofire.md ├── AsyncDisplayKit ├── images │ ├── CRT.png │ ├── apple-a9.jpg │ ├── asdk-hierarchy.png │ ├── asdk-logo.png │ ├── async-node-calculate.jpeg │ ├── box-layout.jpg │ ├── cpu-gpu.jpg │ ├── how-to-solve-tearing-problem.jpg │ ├── lag-vsync.png │ ├── layout-header.jpg │ ├── layout-hierarchy.png │ ├── layout-phase.png │ ├── lcd.png │ ├── masonry.jpg │ ├── normal-vsync.png │ ├── performance-chart-100-1000.jpeg │ ├── performance-layout-10-90.jpeg │ ├── performance-loss.jpeg │ ├── performance-nested-autolayout-frame.jpeg │ ├── phone-in-hand.jpg │ ├── placeholder-layer.png │ ├── screen-tearing.jpg │ ├── scrollview-demo.png │ ├── stack.jpg │ ├── view-demonstrate.png │ └── view-layer-cg-compare.png ├── 从 Auto Layout 的布局算法谈性能.md ├── 使用 ASDK 性能调优 - 提升 iOS 界面的渲染性能.md └── 提升 iOS 界面的渲染性能 .md ├── BlocksKit ├── 神奇的 BlocksKit (一).md └── 神奇的 BlocksKit (二).md ├── DKNightVersion └── 成熟的夜间模式解决方案.md ├── FBRetainCycleDetector ├── iOS 中的 block 是如何持有对象的.md ├── images │ ├── after-dispose-helper.png │ ├── before-dispose-helper.jpeg │ ├── block-capture-strong-weak-order.png │ ├── block-capture-var-layout.png │ ├── block-superclass.png │ ├── block.jpg │ ├── filtered-ivars.png │ ├── get-ivar-layout.png │ ├── get-ivars.png │ └── retain-objects.png ├── 如何在 iOS 中解决循环引用的问题.md ├── 如何实现 iOS 中的 Associated Object.md └── 检测 NSObject 对象持有的强指针.md ├── IQKeyboardManager ├── images │ ├── IQKeyboardManager-Hierarchy.png │ ├── IQKeyboardManager-hide-keyboard.png │ ├── IQToolBar.png │ ├── IQToolBarItem.png │ ├── UITextView-Notification-IQKeyboardManager.png │ ├── easiest-integration-demo.png │ └── notification-IQKeyboardManager.png └── 『零行代码』解决键盘遮挡问题(iOS).md ├── MBProgressHUD └── iOS 源代码分析 --- MBProgressHUD.md ├── Masonry └── iOS 源代码分析 --- Masonry.md ├── OHHTTPStubs ├── iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求.md ├── images │ ├── OHHTTPStubs-test.png │ ├── URL-loading-system.png │ ├── http-mock-test.png │ └── intercept.png └── 如何进行 HTTP Mock(iOS).md ├── ProtocolKit ├── images │ ├── protocol-demo.jpeg │ └── protocol-recordings.jpeg └── 如何在 Objective-C 中实现协议扩展.md ├── SDWebImage └── iOS 源代码分析 --- SDWebImage.md ├── fishhook ├── images │ ├── fishbook-printf.png │ ├── fishhook-before-after.png │ ├── fishhook-hello-breakpoint.png │ ├── fishhook-hello.png │ ├── fishhook-imp.png │ ├── fishhook-mach-o.png │ ├── fishhook-result.png │ └── fishhook-symbol.png └── 动态修改 C 语言函数的实现.md ├── images ├── AFURLResponseSerialization.png ├── PendingInitializeMap.png ├── afnetworking-arch.png ├── afnetworking-logo.png ├── afnetworking-plist.png ├── banner.png ├── blockskit.png ├── logo.png ├── obj-method-struct.png ├── objc-ao-associateobjcect.png ├── objc-ao-isa-struct.png ├── objc-ao-warning-category-property.png ├── objc-autorelease-AutoreleasePoolPage-linked-list.png ├── objc-autorelease-AutoreleasePoolPage.png ├── objc-autorelease-after-insert-to-page.png ├── objc-autorelease-breakpoint-main.png ├── objc-autorelease-main-cpp-struct.png ├── objc-autorelease-main-cpp.png ├── objc-autorelease-main.png ├── objc-autorelease-page-in-memory.png ├── objc-autorelease-pop-stack.png ├── objc-autorelease-pop-string.png ├── objc-autorelease-print-pool-content.png ├── objc-hashtable-copy-class-list.png ├── objc-hashtable-hash-state-init.png ├── objc-hashtable-hashstate-next.gif ├── objc-hashtable-insert-empty.gif ├── objc-hashtable-insert-many.gif.gif ├── objc-hashtable-insert-one.gif.gif ├── objc-hashtable-instrument.png ├── objc-hashtable-nsarray-instrument.png ├── objc-initialize-breakpoint-lookup-imp-or-forward.png ├── objc-initialize-breakpoint.png ├── objc-initialize-class_rw_t_-bits-flag.png ├── objc-initialize-print-initialize.png ├── objc-initialize-print-nothing.png ├── objc-initialize-print-selector.png ├── objc-isa-class-diagram.png ├── objc-isa-class-object.png ├── objc-isa-class-pointer.png ├── objc-isa-isat-bits-has-css-dtor.png ├── objc-isa-isat-bits.png ├── objc-isa-isat-class-highlight-bits.png ├── objc-isa-isat.png ├── objc-isa-meta-class.png ├── objc-isa-print-class-object.png ├── objc-isa-print-cls.png ├── objc-isa-print-object.png ├── objc-load-break-after-add-breakpoint.png ├── objc-load-diagram.png ├── objc-load-image-binary.png ├── objc-load-print-image-info.png ├── objc-load-print-load.png ├── objc-load-producer-consumer-diagram.png ├── objc-load-symbolic-breakpoint.png ├── objc-message-add-imp-to-cache.png ├── objc-message-after-flush-cache-trap-in-lookup-again.png ├── objc-message-after-flush-cache.png ├── objc-message-before-flush-cache.png ├── objc-message-cache-struct.png ├── objc-message-core.png ├── objc-message-find-selector-before-init.png ├── objc-message-first-call-hello.png ├── objc-message-objc-msgSend-with-cache.gif ├── objc-message-run-after-add-cache.png ├── objc-message-selector-undefined.png ├── objc-message-selector.png ├── objc-message-step-in-cache-getimp.png ├── objc-message-wrong-step-in.gif ├── objc-message-youtube-preview.jpg ├── objc-method-after-compile.png ├── objc-method-after-methodizeClass.png ├── objc-method-after-realize-breakpoint.png ├── objc-method-after-realize-class.png ├── objc-method-before-realize.png ├── objc-method-breakpoint-before-set-rw.png ├── objc-method-class-data-bits-t.png ├── objc-method-class.png ├── objc-method-class_data_bits_t.png ├── objc-method-compile-class.png ├── objc-method-lldb-breakpoint.png ├── objc-method-lldb-print-before-realize.png ├── objc-method-lldb-print-method-list.png ├── objc-method-print-class-struct-after-realize.png ├── objc-method-target.png └── objc-rr-isa-struct.png ├── libextobjc └── 如何在 Objective-C 的环境下实现 defer.md └── objc ├── README.md ├── 上古时代 Objective-C 中哈希表的实现.md ├── 从 NSObject 的初始化了解 isa.md ├── 从源代码看 ObjC 中消息的发送.md ├── 你真的了解 load 方法么?.md ├── 关联对象 AssociatedObject 完全解析.md ├── 对象是如何初始化的(iOS).md ├── 懒惰的 initialize 方法.md ├── 深入解析 ObjC 中方法的结构.md ├── 自动释放池的前世今生.md └── 黑箱中的 retain 和 release.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-language=Objective-C 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | fastlane/report.xml 21 | 22 | # CocoaPods 23 | # 24 | # We recommend against adding the Pods directory to your .gitignore. However 25 | # you should judge for yourself, the pros and cons are mentioned at: 26 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 27 | # 28 | Pods/ 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS-Source-Code-Analyze 2 | 3 |

4 | 5 | Banner designed by Levine 6 |

7 | 8 | ## 为什么要建这个仓库 9 | 10 | 世人都说阅读开源框架的源代码对于功力有显著的提升,所以我也尝试阅读开源框架的源代码,并对其内容进行详细地分析和理解。在这里将自己阅读开源框架源代码的心得记录下来,希望能对各位开发者有所帮助。我会不断更新这个仓库中的文章,如果想要关注可以点 `star`。 11 | 12 | ## 目录 13 | 14 | Latest:[从 Auto Layout 的布局算法谈性能](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AsyncDisplayKit/从%20Auto%20Layout%20的布局算法谈性能.md) 15 | 16 | 17 | | Project | Version | Article | 18 | |:-------:|:-------:|:------| 19 | | AsyncDisplayKit | 1.9.81 | [提升 iOS 界面的渲染性能](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AsyncDisplayKit/提升%20iOS%20界面的渲染性能%20.md)
[从 Auto Layout 的布局算法谈性能](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AsyncDisplayKit/从%20Auto%20Layout%20的布局算法谈性能.md)| 20 | | OHHTTPStubs | 5.1.0 | [iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/OHHTTPStubs/iOS%20开发中使用%20NSURLProtocol%20拦截%20HTTP%20请求.md)
[如何进行 HTTP Mock(iOS)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/OHHTTPStubs/如何进行%20HTTP%20Mock(iOS).md) | 21 | | ProtocolKit | | [如何在 Objective-C 中实现协议扩展](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/ProtocolKit/如何在%20Objective-C%20中实现协议扩展.md) | 22 | | FBRetainCycleDetector | 0.1.2 | [如何在 iOS 中解决循环引用的问题](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/FBRetainCycleDetector/如何在%20iOS%20中解决循环引用的问题.md)
[检测 NSObject 对象持有的强指针](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/FBRetainCycleDetector/检测%20NSObject%20对象持有的强指针.md)
[如何实现 iOS 中的 Associated Object](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/FBRetainCycleDetector/如何实现%20iOS%20中的%20Associated%20Object.md)
[iOS 中的 block 是如何持有对象的](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/FBRetainCycleDetector/iOS%20中的%20block%20是如何持有对象的.md)| 23 | | fishhook | 0.2 |[动态修改 C 语言函数的实现](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/fishhook/动态修改%20C%20语言函数的实现.md) | 24 | | libextobjc | |[如何在 Objective-C 的环境下实现 defer](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/libextobjc/如何在%20Objective-C%20的环境下实现%20defer.md) | 25 | | IQKeyboardManager | 4.0.3 |[『零行代码』解决键盘遮挡问题(iOS)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/IQKeyboardManager/『零行代码』解决键盘遮挡问题(iOS).md) | 26 | | ObjC | | [从 NSObject 的初始化了解 isa](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/从%20NSObject%20的初始化了解%20isa.md)
[深入解析 ObjC 中方法的结构](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/深入解析%20ObjC%20中方法的结构.md)
[从源代码看 ObjC 中消息的发送](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/从源代码看%20ObjC%20中消息的发送.md)
[你真的了解 load 方法么?](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/你真的了解%20load%20方法么?.md)
[上古时代 Objective-C 中哈希表的实现](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/上古时代%20Objective-C%20中哈希表的实现.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)
[关联对象 AssociatedObject 完全解析](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/关联对象%20AssociatedObject%20完全解析.md)
[懒惰的 initialize 方法](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/懒惰的%20initialize%20方法.md)
[对象是如何初始化的(iOS)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/对象是如何初始化的(iOS).md)| 27 | | DKNightVersion | 2.3.0 | [成熟的夜间模式解决方案](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/DKNightVersion/成熟的夜间模式解决方案.md) | 28 | | AFNetworking | 3.0.4 | [AFNetworking 概述(一)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworking%20概述(一).md)
[AFNetworking 的核心 AFURLSessionManager(二)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworking%20的核心%20AFURLSessionManager(二).md)
[处理请求和响应 AFURLSerialization(三)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/处理请求和响应%20AFURLSerialization(三).md)
[AFNetworkReachabilityManager 监控网络状态(四)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworkReachabilityManager%20监控网络状态(四).md)
[验证 HTTPS 请求的证书(五)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/验证%20HTTPS%20请求的证书(五).md) | 29 | | BlocksKit | 2.2.5 | [神奇的 BlocksKit(一)遍历、KVO 和分类](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/BlocksKit/神奇的%20BlocksKit%20(一).md)
[神奇的 BlocksKit(二)动态代理的实现 ](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/BlocksKit/神奇的%20BlocksKit%20(二).md) | 30 | | Alamofire | | [iOS 源代码分析 --- Alamofire](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/Alamofire/iOS%20源代码分析%20----%20Alamofire.md) | 31 | | SDWebImage | | [iOS 源代码分析 --- SDWebImage](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/SDWebImage/iOS%20源代码分析%20---%20SDWebImage.md) | 32 | | MBProgressHUD | | [iOS 源代码分析 --- MBProgressHUD](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/MBProgressHUD/iOS%20源代码分析%20---%20MBProgressHUD.md) | 33 | | Masonry | | [iOS 源代码分析 --- Masonry](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/Masonry/iOS%20源代码分析%20---%20Masonry.md) | 34 | 35 | 36 | ## 勘误 37 | 38 | + 如果在文章中发现了问题,欢迎提交 PR 或者 issue 39 | 40 | ## 转载 41 | 42 | 知识共享许可协议
作品Draveness 创作,采用知识共享署名 4.0 国际许可协议进行许可。 43 | 44 | -------------------------------------------------------------------------------- /contents/AFNetworking/AFNetworkReachabilityManager 监控网络状态(四).md: -------------------------------------------------------------------------------- 1 | # AFNetworkReachabilityManager 监控网络状态(四) 2 | 3 | Blog: [Draveness](http://draveness.me) 4 | 5 | 6 | 7 | `AFNetworkReachabilityManager` 是对 `SystemConfiguration` 模块的封装,苹果的文档中也有一个类似的项目 [Reachability](https://developer.apple.com/library/ios/samplecode/reachability/) 这里对网络状态的监控跟苹果官方的实现几乎是完全相同的。 8 | 9 | 同样在 github 上有一个类似的项目叫做 [Reachability](https://github.com/tonymillion/Reachability) 不过这个项目**由于命名的原因可能会在审核时被拒绝**。 10 | 11 | 无论是 `AFNetworkReachabilityManager`,苹果官方的项目或者说 github 上的 Reachability,它们的实现都是类似的,而在这里我们会以 `AFNetworking` 中的 `AFNetworkReachabilityManager` 为例来说明在 iOS 开发中,我们是怎样监控网络状态的。 12 | 13 | ## AFNetworkReachabilityManager 的使用和实现 14 | 15 | `AFNetworkReachabilityManager` 的使用还是非常简单的,只需要三个步骤,就基本可以完成对网络状态的监控。 16 | 17 | 1. [初始化 `AFNetworkReachabilityManager`](#init) 18 | 2. [调用 `startMonitoring` 方法开始对网络状态进行监控](#monitor) 19 | 3. [设置 `networkReachabilityStatusBlock` 在每次网络状态改变时, 调用这个 block](#block) 20 | 21 | ### 初始化 AFNetworkReachabilityManager 22 | 23 | 在初始化方法中,使用 `SCNetworkReachabilityCreateWithAddress` 或者 `SCNetworkReachabilityCreateWithName` 生成一个 `SCNetworkReachabilityRef` 的引用。 24 | 25 | ```objectivec 26 | + (instancetype)managerForDomain:(NSString *)domain { 27 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]); 28 | 29 | AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; 30 | 31 | return manager; 32 | } 33 | 34 | + (instancetype)managerForAddress:(const void *)address { 35 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address); 36 | AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; 37 | 38 | return manager; 39 | } 40 | ``` 41 | 42 | 1. 这两个方法会通过一个**域名**或者一个 `sockaddr_in` 的指针生成一个 `SCNetworkReachabilityRef` 43 | 2. 调用 `- [AFNetworkReachabilityManager initWithReachability:]` 将生成的 `SCNetworkReachabilityRef` 引用传给 `networkReachability` 44 | 3. 设置一个默认的 `networkReachabilityStatus` 45 | 46 | 47 | ```objectivec 48 | - (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability { 49 | self = [super init]; 50 | if (!self) { 51 | return nil; 52 | } 53 | 54 | self.networkReachability = CFBridgingRelease(reachability); 55 | self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; 56 | 57 | return self; 58 | } 59 | ``` 60 | 61 | > 当调用 `CFBridgingRelease(reachability)` 后,会把 `reachability` 桥接成一个 NSObject 对象赋值给 `self.networkReachability`,然后释放原来的 CoreFoundation 对象。 62 | 63 | ### 监控网络状态 64 | 65 | 在初始化 `AFNetworkReachabilityManager` 后,会调用 `startMonitoring` 方法开始监控网络状态。 66 | 67 | ```objectivec 68 | - (void)startMonitoring { 69 | [self stopMonitoring]; 70 | 71 | if (!self.networkReachability) { 72 | return; 73 | } 74 | 75 | __weak __typeof(self)weakSelf = self; 76 | AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { 77 | __strong __typeof(weakSelf)strongSelf = weakSelf; 78 | 79 | strongSelf.networkReachabilityStatus = status; 80 | if (strongSelf.networkReachabilityStatusBlock) { 81 | strongSelf.networkReachabilityStatusBlock(status); 82 | } 83 | 84 | }; 85 | 86 | id networkReachability = self.networkReachability; 87 | SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; 88 | SCNetworkReachabilitySetCallback((__bridge SCNetworkReachabilityRef)networkReachability, AFNetworkReachabilityCallback, &context); 89 | SCNetworkReachabilityScheduleWithRunLoop((__bridge SCNetworkReachabilityRef)networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); 90 | 91 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{ 92 | SCNetworkReachabilityFlags flags; 93 | if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) { 94 | AFPostReachabilityStatusChange(flags, callback); 95 | } 96 | }); 97 | } 98 | ``` 99 | 100 | 1. 先调用 `- stopMonitoring` 方法,如果之前设置过对网络状态的监听,使用 `SCNetworkReachabilityUnscheduleFromRunLoop` 方法取消之前在 Main Runloop 中的监听 101 | 102 | - (void)stopMonitoring { 103 | if (!self.networkReachability) { 104 | return; 105 | } 106 | 107 | SCNetworkReachabilityUnscheduleFromRunLoop((__bridge SCNetworkReachabilityRef)self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); 108 | } 109 | 110 | 2. 创建一个在每次网络状态改变时的回调 111 | 112 | __weak __typeof(self)weakSelf = self; 113 | AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { 114 | __strong __typeof(weakSelf)strongSelf = weakSelf; 115 | 116 | strongSelf.networkReachabilityStatus = status; 117 | if (strongSelf.networkReachabilityStatusBlock) { 118 | strongSelf.networkReachabilityStatusBlock(status); 119 | } 120 | 121 | }; 122 | 123 | + 每次回调被调用时 124 | + 重新设置 `networkReachabilityStatus` 属性 125 | + 调用 `networkReachabilityStatusBlock` 126 | 3. 创建一个 `SCNetworkReachabilityContext` 127 | 128 | typedef struct { 129 | CFIndex version; 130 | void * __nullable info; 131 | const void * __nonnull (* __nullable retain)(const void *info); 132 | void (* __nullable release)(const void *info); 133 | CFStringRef __nonnull (* __nullable copyDescription)(const void *info); 134 | } SCNetworkReachabilityContext; 135 | 136 | SCNetworkReachabilityContext context = { 137 | 0, 138 | (__bridge void *)callback, 139 | AFNetworkReachabilityRetainCallback, 140 | AFNetworkReachabilityReleaseCallback, 141 | NULL 142 | }; 143 | 144 | + 其中的 `callback` 就是上一步中的创建的 block 对象 145 | + 这里的 `AFNetworkReachabilityRetainCallback` 和 `AFNetworkReachabilityReleaseCallback` 都是非常简单的 block,在回调被调用时,只是使用 `Block_copy` 和 `Block_release` 这样的宏 146 | + 传入的 `info` 会以参数的形式在 `AFNetworkReachabilityCallback` 执行时传入 147 | 148 | static const void * AFNetworkReachabilityRetainCallback(const void *info) { 149 | return Block_copy(info); 150 | } 151 | 152 | static void AFNetworkReachabilityReleaseCallback(const void *info) { 153 | if (info) { 154 | Block_release(info); 155 | } 156 | } 157 | 158 | 159 | 4. 当目标的网络状态改变时,会调用传入的回调 160 | 161 | SCNetworkReachabilitySetCallback( 162 | (__bridge SCNetworkReachabilityRef)networkReachability, 163 | AFNetworkReachabilityCallback, 164 | &context 165 | ); 166 | 167 | 168 | 5. 在 Main Runloop 中对应的模式开始监控网络状态 169 | 170 | SCNetworkReachabilityScheduleWithRunLoop( 171 | (__bridge SCNetworkReachabilityRef)networkReachability, 172 | CFRunLoopGetMain(), 173 | kCFRunLoopCommonModes 174 | ); 175 | 176 | 177 | 6. 获取当前的网络状态,调用 callback 178 | 179 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{ 180 | SCNetworkReachabilityFlags flags; 181 | if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) { 182 | AFPostReachabilityStatusChange(flags, callback); 183 | } 184 | }); 185 | 186 | 187 | 在下一节中会介绍上面所提到的一些 C 函数以及各种回调。 188 | 189 | ### 设置 networkReachabilityStatusBlock 以及回调 190 | 191 | 在 Main Runloop 中对网络状态进行监控之后,在每次网络状态改变,就会调用 `AFNetworkReachabilityCallback` 函数: 192 | 193 | ```objectivec 194 | static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { 195 | AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info); 196 | } 197 | ``` 198 | 199 | 这里会从 `info` 中取出之前存在 `context` 中的 `AFNetworkReachabilityStatusBlock`。 200 | 201 | ```objectivec 202 | __weak __typeof(self)weakSelf = self; 203 | AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { 204 | __strong __typeof(weakSelf)strongSelf = weakSelf; 205 | 206 | strongSelf.networkReachabilityStatus = status; 207 | if (strongSelf.networkReachabilityStatusBlock) { 208 | strongSelf.networkReachabilityStatusBlock(status); 209 | } 210 | 211 | }; 212 | ``` 213 | 214 | 取出这个 block 之后,传入 `AFPostReachabilityStatusChange` 函数: 215 | 216 | ```objectivec 217 | static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) { 218 | AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); 219 | dispatch_async(dispatch_get_main_queue(), ^{ 220 | if (block) { 221 | block(status); 222 | } 223 | NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; 224 | NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) }; 225 | [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo]; 226 | }); 227 | } 228 | ``` 229 | 230 | 1. 调用 `AFNetworkReachabilityStatusForFlags` 获取当前的网络可达性状态 231 | 2. **在主线程中异步执行**上面传入的 `callback` block(设置 `self` 的网络状态,调用 `networkReachabilityStatusBlock`) 232 | 3. 发送 `AFNetworkingReachabilityDidChangeNotification` 通知. 233 | 234 | ```objectivec 235 | static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) { 236 | BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); 237 | BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); 238 | BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)); 239 | BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0); 240 | BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction)); 241 | 242 | AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown; 243 | if (isNetworkReachable == NO) { 244 | status = AFNetworkReachabilityStatusNotReachable; 245 | } 246 | #if TARGET_OS_IPHONE 247 | else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) { 248 | status = AFNetworkReachabilityStatusReachableViaWWAN; 249 | } 250 | #endif 251 | else { 252 | status = AFNetworkReachabilityStatusReachableViaWiFi; 253 | } 254 | 255 | return status; 256 | } 257 | ``` 258 | 259 | 因为 `flags` 是一个 `SCNetworkReachabilityFlags`,它的不同位代表了不同的网络可达性状态,通过 `flags` 的位操作,获取当前的状态信息 `AFNetworkReachabilityStatus`。 260 | 261 | ```objectivec 262 | typedef CF_OPTIONS(uint32_t, SCNetworkReachabilityFlags) { 263 | kSCNetworkReachabilityFlagsTransientConnection = 1<<0, 264 | kSCNetworkReachabilityFlagsReachable = 1<<1, 265 | kSCNetworkReachabilityFlagsConnectionRequired = 1<<2, 266 | kSCNetworkReachabilityFlagsConnectionOnTraffic = 1<<3, 267 | kSCNetworkReachabilityFlagsInterventionRequired = 1<<4, 268 | kSCNetworkReachabilityFlagsConnectionOnDemand = 1<<5, // __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_0) 269 | kSCNetworkReachabilityFlagsIsLocalAddress = 1<<16, 270 | kSCNetworkReachabilityFlagsIsDirect = 1<<17, 271 | #if TARGET_OS_IPHONE 272 | kSCNetworkReachabilityFlagsIsWWAN = 1<<18, 273 | #endif // TARGET_OS_IPHONE 274 | 275 | kSCNetworkReachabilityFlagsConnectionAutomatic = kSCNetworkReachabilityFlagsConnectionOnTraffic 276 | }; 277 | ``` 278 | 279 | 这里就是在 `SystemConfiguration` 中定义的全部的网络状态的标志位。 280 | 281 | ## 与 AFNetworking 协作 282 | 283 | 其实这个类与 `AFNetworking` 整个框架并没有太多的耦合。正相反,它在整个框架中作为一个**即插即用**的类使用,每一个 `AFURLSessionManager` 都会持有一个 `AFNetworkReachabilityManager` 的实例。 284 | 285 | ```objectivec 286 | self.reachabilityManager = [AFNetworkReachabilityManager sharedManager]; 287 | ``` 288 | 289 | 这是整个框架中除了 `AFNetworkReachabilityManager.h/m` 文件,**唯一一个**引用到这个类的地方。 290 | 291 | 在实际的使用中,我们也可以直接操作 `AFURLSessionManager` 的 `reachabilityManager` 来获取当前的网络可达性状态,而不是自己手动初始化一个实例,当然这么做也是没有任何问题的。 292 | 293 | ## 总结 294 | 295 | 1. `AFNetworkReachabilityManager` 实际上只是一个对底层 `SystemConfiguration` 库中的 C 函数封装的类,它为我们隐藏了 C 语言的实现,提供了统一的 Objective-C 语言接口 296 | 2. 它是 `AFNetworking` 中一个即插即用的模块 297 | 298 | ## 相关文章 299 | 300 | 关于其他 AFNetworking 源代码分析的其他文章: 301 | 302 | + [AFNetworking 概述(一)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworking%20概述(一).md) 303 | + [AFNetworking 的核心 AFURLSessionManager(二)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworking%20的核心%20AFURLSessionManager(二).md) 304 | + [处理请求和响应 AFURLSerialization(三)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/处理请求和响应%20AFURLSerialization(三).md) 305 | + [AFNetworkReachabilityManager 监控网络状态(四)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworkReachabilityManager%20监控网络状态(四).md) 306 | + [验证 HTTPS 请求的证书(五)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/验证%20HTTPS%20请求的证书(五).md) 307 | 308 | 309 | Follow: [@Draveness](https://github.com/Draveness) 310 | 311 | 312 | -------------------------------------------------------------------------------- /contents/AFNetworking/AFNetworking 概述(一).md: -------------------------------------------------------------------------------- 1 | # AFNetworking 概述(一) 2 | 3 | ![afnetworking-logo](../images/afnetworking-logo.png) 4 | 5 | Blog: [Draveness](http://draveness.me) 6 | 7 | 8 | 9 | 在这一系列的文章中,我会对 AFNetworking 的源代码进行分析,深入了解一下它是如何构建的,如何在日常中完成发送 HTTP 请求、构建网络层这一任务。 10 | 11 | [AFNetworking](https://github.com/AFNetworking/AFNetworking) 是如今 iOS 开发中不可缺少的组件之一。它的 github 配置上是如下介绍的: 12 | 13 | > Perhaps the most important feature of all, however, is the amazing community of developers who use and contribute to AFNetworking every day. AFNetworking powers some of the most popular and critically-acclaimed apps on the iPhone, iPad, and Mac. 14 | 15 | 可以说**使用 AFNetworking 的工程师构成的社区**才使得它变得非常重要。 16 | 17 | ## 概述 18 | 19 | 我们今天是来深入研究一下这个与我们日常开发密切相关的框架是如何实现的。 20 | 21 | 这是我对 AFNetworking 整个架构的理解,随后一系列的文章也会逐步分析这些模块。 22 | 23 | ![afnetworking-arch](../images/afnetworking-arch.png) 24 | 25 | 26 | 在这篇文章中,我们有两个问题需要了解: 27 | 28 | 1. 如何使用 NSURLSession 发出 HTTP 请求 29 | 2. 如何使用 AFNetworking 发出 HTTP 请求 30 | 31 | ## NSURLSession 32 | 33 | `NSURLSession` 以及与它相关的类为我们提供了下载内容的 API,这个 API 提供了一系列的代理方法来支持身份认证,并且支持后台下载。 34 | 35 | 使用 `NSURLSession` 来进行 HTTP 请求并且获得数据总共有五个步骤: 36 | 37 | 1. 实例化一个 `NSURLRequest/NSMutableURLRequest`,设置 URL 38 | 2. 通过 `- sharedSession` 方法获取 `NSURLSession` 39 | 3. 在 session 上调用 `- dataTaskWithRequest:completionHandler:` 方法返回一个 `NSURLSessionDataTask` 40 | 4. 向 data task 发送消息 `- resume`,开始执行这个任务 41 | 5. 在 completionHandler 中将数据编码,返回字符串 42 | 43 | ```objectivec 44 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"https://github.com"]]; 45 | NSURLSession *session = [NSURLSession sharedSession]; 46 | NSURLSessionDataTask *task = [session dataTaskWithRequest:request 47 | completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 48 | NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 49 | NSLog(@"%@", dataStr); 50 | }]; 51 | [task resume]; 52 | ``` 53 | 54 | 这一段代码可以说是使用 `NSURLSession` 发送请求最简单的一段代码了,当你运行这段代码会在控制台看到一坨 [github](github.com) 首页的 html。 55 | 56 | ```html 57 | 58 | 59 | 60 | 61 | ... 62 | 63 | ... 64 | 65 | ``` 66 | 67 | ## AFNetworking 68 | 69 | AFNetworking 的使用也是比较简单的,使用它来发出 HTTP 请求有两个步骤 70 | 71 | 1. 以服务器的**主机地址或者域名**生成一个 AFHTTPSessionManager 的实例 72 | 2. 调用 `- GET:parameters:progress:success:failure:` 方法 73 | 74 | ```objectivec 75 | AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[[NSURL alloc] initWithString:@"hostname"]]; 76 | [manager GET:@"relative_url" parameters:nil progress:nil 77 | success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 78 | NSLog(@"%@" ,responseObject); 79 | } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 80 | NSLog(@"%@", error); 81 | }]; 82 | ``` 83 | 84 | > 注意:在 iOS9 中,苹果默认全局 HTTPs,如果你要发送不安全的 HTTP 请求,需要在 info.plist 中加入如下键值对才能发出不安全的 HTTP 请求 85 | 86 | > ![afnetworking-plist](../images/afnetworking-plist.png) 87 | 88 | > 还有一件事情是要注意的是,AFNetworking 默认接收 json 格式的响应(因为这是在 iOS 平台上的框架,一般不需要 text/html),如果想要返回 html,需要设置 `acceptableContentTypes` 89 | 90 | ## AFNetworking 的调用栈 91 | 92 | 在这一节中我们要分析一下在上面两个方法的调用栈,首先来看的是 `AFHTTPSessionManager` 的初始化方法 `- initWithBaseURL:` 93 | 94 | ```objectivec 95 | - [AFHTTPSessionManager initWithBaseURL:] 96 | - [AFHTTPSessionManager initWithBaseURL:sessionConfiguration:] 97 | - [AFURLSessionManager initWithSessionConfiguration:] 98 | - [NSURLSession sessionWithConfiguration:delegate:delegateQueue:] 99 | - [AFJSONResponseSerializer serializer] // 负责序列化响应 100 | - [AFSecurityPolicy defaultPolicy] // 负责身份认证 101 | - [AFNetworkReachabilityManager sharedManager] // 查看网络连接情况 102 | - [AFHTTPRequestSerializer serializer] // 负责序列化请求 103 | - [AFJSONResponseSerializer serializer] // 负责序列化响应 104 | ``` 105 | 106 | 从这个初始化方法的调用栈,我们可以非常清晰地了解这个框架的结构: 107 | 108 | + 其中 `AFURLSessionManager` 是 `AFHTTPSessionManager` 的父类 109 | + `AFURLSessionManager` 负责生成 `NSURLSession` 的实例,管理 `AFSecurityPolicy` 和 `AFNetworkReachabilityManager`,来保证请求的安全和查看网络连接情况,它有一个 `AFJSONResponseSerializer` 的实例来序列化 HTTP 响应 110 | + `AFHTTPSessionManager` 有着**自己的** `AFHTTPRequestSerializer` 和 `AFJSONResponseSerializer` 来管理请求和响应的序列化,同时**依赖父类提供的接口**保证安全、监控网络状态,实现发出 HTTP 请求这一核心功能 111 | 112 | 初始化方法很好地揭示了 AFNetworking 整个框架的架构,接下来我们要通过分析另一个方法 `- GET:parameters:process:success:failure:` 的调用栈,看一下 HTTP 请求是如何发出的: 113 | 114 | ```objectivec 115 | - [AFHTTPSessionManager GET:parameters:process:success:failure:] 116 | - [AFHTTPSessionManager dataTaskWithHTTPMethod:parameters:uploadProgress:downloadProgress:success:failure:] // 返回 NSURLSessionDataTask #1 117 | - [AFHTTPRequestSerializer requestWithMethod:URLString:parameters:error:] // 返回 NSMutableURLRequest 118 | - [AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:] // 返回 NSURLSessionDataTask #2 119 | - [NSURLSession dataTaskWithRequest:] // 返回 NSURLSessionDataTask #3 120 | - [AFURLSessionManager addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler:] 121 | - [AFURLSessionManagerTaskDelegate init] 122 | - [AFURLSessionManager setDelegate:forTask:] 123 | - [NSURLSessionDataTask resume] 124 | ``` 125 | 126 | 在这里 `#1` `#2` `#3` 处返回的是同一个 data task,我们可以看到,在 `#3` 处调用的方法 `- [NSURLSession dataTaskWithRequest:]` 和只使用 `NSURLSession` 发出 HTTP 请求时调用的方法 `- [NSURLSession dataTaskWithRequest:completionHandler:]` 差不多。在这个地方返回 data task 之后,我们再调用 `- resume` 方法执行请求,并在某些事件执行时通知代理 `AFURLSessionManagerTaskDelegate` 127 | 128 | ## 小结 129 | 130 | AFNetworking 实际上只是对 `NSURLSession` 高度地封装, 提供一些简单易用的 API 方便我们在 iOS 开发中发出网络请求并在其上更快地构建网络层组件并提供合理的接口. 131 | 132 | 到这里,这一篇文章从上到下对 AFNetworking 是如何调用的进行了一个简单的概述,我会在随后的文章中会具体介绍 AFNetworking 中的每一个模块,了解它们是如何工作,并且如何合理地组织到一起的。 133 | 134 | ## 相关文章 135 | 136 | 关于其他 AFNetworking 源代码分析的其他文章: 137 | 138 | + [AFNetworking 概述(一)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworking%20概述(一).md) 139 | + [AFNetworking 的核心 AFURLSessionManager(二)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworking%20的核心%20AFURLSessionManager(二).md) 140 | + [处理请求和响应 AFURLSerialization(三)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/处理请求和响应%20AFURLSerialization(三).md) 141 | + [AFNetworkReachabilityManager 监控网络状态(四)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworkReachabilityManager%20监控网络状态(四).md) 142 | + [验证 HTTPS 请求的证书(五)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/验证%20HTTPS%20请求的证书(五).md) 143 | 144 | 145 | Follow: [@Draveness](https://github.com/Draveness) 146 | 147 | 148 | -------------------------------------------------------------------------------- /contents/Alamofire/iOS 源代码分析 ---- Alamofire.md: -------------------------------------------------------------------------------- 1 | # iOS 源代码分析 --- Alamofire 2 | 3 | 4 | 已经有几个月没有阅读著名开源项目的源代码了, 最近才有时间来做这件事情. 5 | 6 | 下面是 Github 主页上对 [Alamofire](https://github.com/Alamofire/Alamofire) 的描述 7 | 8 | > Elegant HTTP Networking in Swift 9 | 10 | 为什么这次我选择阅读 Alamofire 的源代码而不是 AFNetworking 呢, 其实有两点原因. 11 | 12 | 1. AFNetworking 作为一个有着很多年的历史的框架, 它虽然有着强大的社区, 不过因为时间太久了, 可能有一些历史上的包袱. 而 Alamofire 是在 Swift 诞生之后才开始出现的, 到现在为止也并没有多长时间, 它的源代码都是**新鲜**的. 13 | 2. 由于最近在写 Swift 的项目, 所以没有选择 AFNetworking. 14 | 15 | 在阅读 Alamofire 的源代码之前, 我先粗略的查看了一下 Alamofire 实现的代码行数: 16 | 17 | ```shell 18 | $ find Source -name "*.swift" | xargs cat |wc -l 19 | > 3363 20 | ``` 21 | 22 | 也就是说 Alamofire 在包含注释以及空行的情况下, 只使用了 3000 多行代码就实现了一个用于处理 HTTP 请求的框架. 23 | 24 | 所以它描述中的 `Elegant` 也可以说是名副其实. 25 | 26 | ## 目录结构 27 | 28 | 首先, 我们来看一下 Alamofire 中的目录结构, 来了解一下它是如何组织各个文件的. 29 | 30 | ``` 31 | - Source 32 | - Alamore.swift 33 | - Core 34 | - Manager.swift 35 | - ParameterEncoding.swift 36 | - Request.swift 37 | - Features 38 | - Download.swift 39 | - MultipartFromData.swift 40 | - ResponseSeriallization.swift 41 | - Upload.swift 42 | - Validation.swift 43 | ``` 44 | 45 | 框架中最核心并且我们最值得关注的就是 `Alamore.swift` `Manager.swift` 和 `Request.swift` 这三个文件. 也是在这篇 post 中主要介绍的三个文件. 46 | 47 | ### Alamofire 48 | 49 | 在 Alamofire 中并没有找到 `Alamofire` 这个类, 相反这仅仅是一个命名空间, 在 `Alamofire.swift` 这个文件中不存在 `class Alamofire` 这种关键字, 这只是为了使得方法名更简洁的一种手段. 50 | 51 | 我们在使用 Alamofire 时, 往往都会采用这种方式: 52 | 53 | ```swift 54 | Alamofire.request(.GET, "http://httpbin.org/get") 55 | ``` 56 | 57 | 有了 Alamofire 作为命名空间, 就不用担心 `request` 方法与其他同名方法的冲突了. 58 | 59 | 在 `Alamofire.swift` 文件中为我们提供了三类方法: 60 | 61 | * request 62 | * upload 63 | * download 64 | 65 | 这三种方法都是通过调用 `Manager` 对应的操作来完成请求, 上传和下载的操作, 并返回一个 `Request` 的实例. 66 | 67 | 下面是 `request` 方法的一个实现: 68 | 69 | ```swift 70 | public func request(method: Method, URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, headers: [String: String]? = nil) -> Request { 71 | return Manager.sharedInstance.request(method, URLString, parameters: parameters, encoding: encoding, headers: headers) 72 | } 73 | ``` 74 | 75 | 这也就是 `Alamofire.request(.GET, "http://httpbin.org/get")` 所调用的方法. 而这个方法实际上就是通过这些参数调用 `Manager` 的具体方法, 我们所使用的 `request` 也好 `download` 也好, 都是对 `Manager` 方法的一个包装罢了. 76 | 77 | ### Manager 78 | 79 | Alamofire 中的几乎所有操作都是通过 `Manager` 来控制, 而 `Manager` 也可以说是 Alamofire 的核心部分, 它负责与 `Request` 交互完成网络操作: 80 | 81 | > Responsible for creating and managing `Request` objects, as well as their underlying `NSURLSession`. 82 | 83 | #### Manager.sharedInstance 84 | 85 | `Manager` 在 Alamofire 中有着极其重要的地位, 而在 `Manager` 方法的设计中, 一般也使用 `sharedInstance` 来获取 `Manager` 的单例: 86 | 87 | ```swift 88 | public static let sharedInstance: Manager = { 89 | let configuration = NSURLSessionConfiguration.defaultSessionConfiguration() 90 | configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders 91 | 92 | return Manager(configuration: configuration) 93 | }() 94 | ``` 95 | 96 | 对于其中 `defaultHTTPHeaders` 和 `Manager` 的初始化方法, 在这里就不多提了, 但是在这里有必要说明一下 `SessionDelegate` 这个类, 在 `Manager` 的初始化方法中, 调用了 `SessionDelegate` 的初始化方法, 返回了一个它的实例. 97 | 98 | #### SessionDelegate 99 | 100 | > Responsible for handling all delegate callbacks for the underlying session. 101 | 102 | 这个类的主要作用就是处理对应 session 的所有代理回调, 它持有两个属性: 103 | 104 | ```swift 105 | private var subdelegates: [Int: Request.TaskDelegate] = [:] 106 | private let subdelegateQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT) 107 | ``` 108 | 109 | `subdelegates` 以 task 标识符为键, 存储了所有的回调. `subdelegateQueue` 是一个异步的队列, 用于处理任务的回调. 110 | 111 | #### Manager.sharedInstace.request 112 | 113 | `Manager` 有两个返回 `Request` 实例的 `request` 方法: 114 | 115 | * `public func request(method: Method, _ URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, headers: [String: String]? = nil) -> Request` 116 | * `public func request(URLRequest: URLRequestConvertible) -> Request` 117 | 118 | 第一个方法的实现非常的简单: 119 | 120 | ```swift 121 | public func request(method: Method, _ URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, headers: [String: String]? = nil) -> Request { 122 | let mutableURLRequest = URLRequest(method, URLString, headers: headers) 123 | let encodedURLRequest = encoding.encode(mutableURLRequest, parameters: parameters).0 124 | return request(encodedURLRequest) 125 | } 126 | ``` 127 | 128 | 方法中首先调用了 `URLRequest` 方法: 129 | 130 | ```swift 131 | func URLRequest(method: Method, URLString: URLStringConvertible, headers: [String: String]? = nil) -> NSMutableURLRequest { 132 | let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: URLString.URLString)!) 133 | mutableURLRequest.HTTPMethod = method.rawValue 134 | 135 | if let headers = headers { 136 | for (headerField, headerValue) in headers { 137 | mutableURLRequest.setValue(headerValue, forHTTPHeaderField: headerField) 138 | } 139 | } 140 | 141 | return mutableURLRequest 142 | } 143 | ``` 144 | 145 | 首先创建一个 `NSMutableURLRequest` 设置它的 HTTP 请求方法和 HTTP header, 然后返回这个请求. 146 | 147 | 在请求被返回之后, 就进入了下一个环节 `encode`. 148 | 149 | ```swift 150 | let encodedURLRequest = encoding.encode(mutableURLRequest, parameters: parameters).0 151 | ``` 152 | 153 | #### ParameterEncoding.encoding 154 | 155 | `ParameterEncoding` 是一个用来处理一系列的参数是如何被"添加"到 URL 请求上的. 156 | 157 | > Used to specify the way in which a set of parameters are applied to a URL request. 158 | 159 | `ParameterEncoding` 类型中有四种不同的编码方法: 160 | 161 | * URL 162 | * JSON 163 | * PropertyList 164 | * Custom 165 | 166 | 其中 `encode` 方法就根据 `ParameterEncoding` 类型的不同返回不同的 `NSMutableURLRequest` 167 | 168 | 如果 `PatameterEncoding` 的类型为 `URL`, 那么就会把这次请求的参数以下面这种形式添加到请求的 `URL` 上 169 | 170 | ``` 171 | foo[]=1&foo[]=2 172 | ``` 173 | 174 | 在完成对参数的编码之后, 就会调用另一个同名的 `request` 方法 175 | 176 | ```swift 177 | request(encodedURLRequest) 178 | ``` 179 | 180 | #### Manager.sharedInstace.request(URLRequestConvertible) 181 | 182 | `request` 方法根据指定的 URL 请求返回一个 `Request` 183 | 184 | > Creates a request for the specified URL request. 185 | 186 | 它使用 `dispatch_sync` 把一个 `NSURLRequest` 请求同步加到一个串行队列中, 返回一个 `NSURLSessionDataTask`. 并通过 `session` 和 `dataTask` 生成一个 `Request` 的实例. 187 | 188 | ```swift 189 | public func request(URLRequest: URLRequestConvertible) -> Request { 190 | var dataTask: NSURLSessionDataTask! 191 | 192 | dispatch_sync(queue) { 193 | dataTask = self.session.dataTaskWithRequest(URLRequest.URLRequest) 194 | } 195 | 196 | let request = Request(session: session, task: dataTask) 197 | delegate[request.delegate.task] = request.delegate 198 | 199 | if startRequestsImmediately { 200 | request.resume() 201 | } 202 | 203 | return request 204 | } 205 | ``` 206 | 207 | 这段代码还是很直观的, 它的主要作用就是创建 `Request` 实例, 并发送请求. 208 | 209 | #### Request.init 210 | 211 | `Request` 这个类的 `init` 方法根据传入的 `task` 类型的不同, 生成了不用类型的 `TaskDelegate`, 可以说是 Swift 中对于反射的运用: 212 | 213 | ```swift 214 | init(session: NSURLSession, task: NSURLSessionTask) { 215 | self.session = session 216 | 217 | switch task { 218 | case is NSURLSessionUploadTask: 219 | self.delegate = UploadTaskDelegate(task: task) 220 | case is NSURLSessionDataTask: 221 | self.delegate = DataTaskDelegate(task: task) 222 | case is NSURLSessionDownloadTask: 223 | self.delegate = DownloadTaskDelegate(task: task) 224 | default: 225 | self.delegate = TaskDelegate(task: task) 226 | } 227 | } 228 | ``` 229 | 230 | 在 `UploadTaskDelegate` `DataTaskDelegate` `DownloadTaskDelegate` 和 `TaskDelegate` 几个类的作用是处理对应任务的回调, 在 `Request` 实例初始化之后, 会把对应的 `delegate` 添加到 `manager` 持有的 `delegate` 数组中, 方便之后在对应的时间节点通知代理事件的发生. 231 | 232 | 在最后返回 `request`, 到这里一次网络请求就基本完成了. 233 | 234 | ### ResponseSerialization 235 | 236 | `ResponseSerialization` 是用来对 `Reponse` 返回的值进行序列化显示的一个 extension. 237 | 238 | 它的设计非常的巧妙, 同时可以处理 `Data` `String` 和 `JSON` 格式的数据, 239 | 240 | #### ResponseSerializer 协议 241 | 242 | Alamofire 在这个文件的开头定义了一个所有 responseSerializer 都必须遵循的 `protocol`, 这个 protocol 的内容十分简单, 其中最重要的就是: 243 | 244 | ```swift 245 | var serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?) -> Result { get } 246 | ``` 247 | 248 | 所有的 responseSerializer 都必须包含 `serializeResponse` 这个闭包, 它的作用就是处理 response. 249 | 250 | #### GenericResponseSerializer 251 | 252 | 为了同时处理不同类型的数据, Alamofire 使用泛型创建了 `GenericResponseSerializer`, 这个结构体为处理 `JSON` `XML` 和 `NSData` 等数据的 responseSerializer 提供了一个骨架. 253 | 254 | 它在结构体中遵循了 `ResponseSerializer` 协议, 然后提供了一个 `init` 方法 255 | 256 | ```swift 257 | public init(serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?) -> Result) { 258 | self.serializeResponse = serializeResponse 259 | } 260 | ``` 261 | 262 | #### response 方法 263 | 264 | 在 Alamofire 中, 如果我们调用了 reponse 方法, 就会在 request 结束时, 添加一个处理器来处理服务器的 reponse. 265 | 266 | 这个方法有两个版本, 第一个版本是不对返回的数据进行处理: 267 | 268 | ```swift 269 | public func response( 270 | queue queue: dispatch_queue_t? = nil, 271 | completionHandler: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Void) 272 | -> Self 273 | { 274 | delegate.queue.addOperationWithBlock { 275 | dispatch_async(queue ?? dispatch_get_main_queue()) { 276 | completionHandler(self.request, self.response, self.delegate.data, self.delegate.error) 277 | } 278 | } 279 | 280 | return self 281 | } 282 | ``` 283 | 284 | 该方法的实现将一个 block 追加到 request 所在的队列中, 其它的部分过于简单, 在这里就不多说了. 285 | 286 | 另一个版本的 response 的作用就是处理多种类型的数据. 287 | 288 | ```swift 289 | public func response( 290 | queue queue: dispatch_queue_t? = nil, 291 | responseSerializer: T, 292 | completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result) -> Void) 293 | -> Self 294 | { 295 | delegate.queue.addOperationWithBlock { 296 | var result = responseSerializer.serializeResponse(self.request, self.response, self.delegate.data) 297 | 298 | if let error = self.delegate.error { 299 | result = .Failure(self.delegate.data, error) 300 | } 301 | 302 | dispatch_async(queue ?? dispatch_get_main_queue()) { 303 | completionHandler(self.request, self.response, result) 304 | } 305 | } 306 | 307 | return self 308 | } 309 | ``` 310 | 311 | 它会直接调用参数中 `responseSerializer` 所持有的闭包 `serializeResponse`, 然后返回对应的数据. 312 | 313 | #### 多种类型的 response 数据 314 | 315 | 有了高级的抽象方法 `response`, 我们现在就可以直接向这个方法中传入不同的 `responseSerializer` 来产生不同数据类型的 `handler` 316 | 317 | 比如说 `NSData` 318 | 319 | ```swift 320 | public static func dataResponseSerializer() -> GenericResponseSerializer { 321 | return GenericResponseSerializer { _, _, data in 322 | guard let validData = data else { 323 | let failureReason = "Data could not be serialized. Input data was nil." 324 | let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason) 325 | return .Failure(data, error) 326 | } 327 | 328 | return .Success(validData) 329 | } 330 | } 331 | 332 | public func responseData(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result) -> Void) -> Self { 333 | return response(responseSerializer: Request.dataResponseSerializer(), completionHandler: completionHandler) 334 | } 335 | ``` 336 | 337 | 在 `ResponseSerialization.swift` 这个文件中, 你还可以看到其中对于 `String` `JSON` `propertyList` 数据处理的 `responseSerializer`. 338 | 339 | ### URLStringConvertible 340 | 341 | 在 ALamofire 的实现中还有一些我们可以学习的地方. 因为 Alamofire 是一个 Swift 的框架, 而且 Swift 是静态语言, 所以有一些坑是必须要解决的, 比如说 `NSURL` 和 `String` 之间的相互转换. 在 Alamofire 中用了一种非常优雅的解决方案, 我相信能够给很多人带来一定的启发. 342 | 343 | 首先我们先定义了一个 `protocol` `URLStringConvertible` (注释部分已经省略) : 344 | 345 | ```swift 346 | public protocol URLStringConvertible { 347 | var URLString: String { get } 348 | } 349 | ``` 350 | 351 | 这个 `protocol` 只定义了一个 `var`, 遵循这个协议的类必须实现 `URLString` 返回 `String` 的这个**功能**. 352 | 353 | 接下来让所有可以转化为 `String` 的类全部遵循这个协议, 这个方法虽然我以前知道, 不过我还是第一次见到在实际中的使用, 真的非常的优雅: 354 | 355 | ```swift 356 | extension String: URLStringConvertible { 357 | public var URLString: String { 358 | return self 359 | } 360 | } 361 | 362 | extension NSURL: URLStringConvertible { 363 | public var URLString: String { 364 | return absoluteString! 365 | } 366 | } 367 | 368 | extension NSURLComponents: URLStringConvertible { 369 | public var URLString: String { 370 | return URL!.URLString 371 | } 372 | } 373 | 374 | extension NSURLRequest: URLStringConvertible { 375 | public var URLString: String { 376 | return URL!.URLString 377 | } 378 | } 379 | ``` 380 | 381 | 这样 `String` `NSURL` `NSURLComponents` 和 `NSURLRequest` 都可以调用 `URLString` 方法了. 我们也就可以**直接在方法的签名中使用 `URLStringConvertible` 类型**. 382 | 383 | ## End 384 | 385 | 到目前为止关于 Alamofire 这个框架就大致介绍完了, 框架的实现还是非常简洁和优雅的, 这篇 post 从开始写到现在也过去了好久, 写的也不是十分的详细具体. 如果你对这个框架的实现有兴趣, 那么看一看这个框架的源代码也未尝不可. 386 | 387 | 388 | 389 | Follow: [@Draveness](https://github.com/Draveness) 390 | 391 | 392 | -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/CRT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/CRT.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/apple-a9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/apple-a9.jpg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/asdk-hierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/asdk-hierarchy.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/asdk-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/asdk-logo.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/async-node-calculate.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/async-node-calculate.jpeg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/box-layout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/box-layout.jpg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/cpu-gpu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/cpu-gpu.jpg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/how-to-solve-tearing-problem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/how-to-solve-tearing-problem.jpg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/lag-vsync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/lag-vsync.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/layout-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/layout-header.jpg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/layout-hierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/layout-hierarchy.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/layout-phase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/layout-phase.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/lcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/lcd.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/masonry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/masonry.jpg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/normal-vsync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/normal-vsync.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/performance-chart-100-1000.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/performance-chart-100-1000.jpeg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/performance-layout-10-90.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/performance-layout-10-90.jpeg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/performance-loss.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/performance-loss.jpeg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/performance-nested-autolayout-frame.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/performance-nested-autolayout-frame.jpeg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/phone-in-hand.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/phone-in-hand.jpg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/placeholder-layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/placeholder-layer.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/screen-tearing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/screen-tearing.jpg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/scrollview-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/scrollview-demo.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/stack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/stack.jpg -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/view-demonstrate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/view-demonstrate.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/images/view-layer-cg-compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/AsyncDisplayKit/images/view-layer-cg-compare.png -------------------------------------------------------------------------------- /contents/AsyncDisplayKit/从 Auto Layout 的布局算法谈性能.md: -------------------------------------------------------------------------------- 1 | # 从 Auto Layout 的布局算法谈性能 2 | 3 | > 这是使用 ASDK 性能调优系列的第二篇文章,前一篇文章中讲到了如何提升 iOS 应用的渲染性能,你可以点击 [这里](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AsyncDisplayKit/提升%20iOS%20界面的渲染性能.md) 了解这部分的内容。 4 | 5 | 在上一篇文章中,我们提到了 iOS 界面的渲染过程以及如何对渲染过程进行优化。ASDK 的做法是将渲染绘制的工作抛到后台线程进行,并在每次 Runloop 结束时,将绘制结果交给 `CALayer` 进行展示。 6 | 7 | 而这篇文章就要从 iOS 中影响性能的另一大杀手,也就是万恶之源 Auto Layout(自动布局)来分析如何对 iOS 应用的性能进行优化以及 Auto Layout 到底为什么会影响性能? 8 | 9 | ![box-layout](images/box-layout.jpg) 10 | 11 | ## 把 Auto Layout 批判一番 12 | 13 | 由于在 2012 年苹果发布了 4.0 寸的 iPhone5,在 iOS 平台上出现了不同尺寸的移动设备,使得原有的 `frame` 布局方式无法很好地适配不同尺寸的屏幕,所以,为了解决这一问题 Auto Layout 就诞生了。 14 | 15 | Auto Layout 的诞生并没有如同苹果的其它框架一样收到开发者的好评,它自诞生的第一天起就饱受 iOS 开发者的批评,其蹩脚、冗长的语法使得它在刚刚面世就被无数开发者吐槽,写了几个屏幕的代码都不能完成一个简单的布局,哪怕是 VFL(Visual Format Language)也拯救不了它。 16 | 17 | 真正使 Auto Layout 大规模投入使用的应该还是 [Masonry](https://github.com/SnapKit/Masonry),它使用了链式的语法对 Auto Layout 进行了很好的封装,使得 Auto Layout 更加简单易用;时至今日,开发者也在日常使用中发现了 Masonry 的各种问题,于是出现了各种各样的布局框架,不过这都是后话了。 18 | 19 | ![masonry](images/masonry.jpg) 20 | 21 | ## Auto Layout 的原理和 Cassowary 22 | 23 | Auto Layout 的原理其实非常简单,在这里通过一个例子先简单的解释一下: 24 | 25 | ![view-demonstrate](images/view-demonstrate.png) 26 | 27 | iOS 中视图所需要的布局信息只有两个,分别是 `origin/center` 和 `size`,在这里我们以 `origin & size` 为例,也就是 `frame` 时代下布局的需要的两个信息;这两个信息由四部分组成: 28 | 29 | + `x` & `y` 30 | + `width` & `height` 31 | 32 | 以左上角的 `(0, 0)` 为坐标的原点,找到坐标 `(x, y)`,然后绘制一个大小为 `(width, height)` 的矩形,这样就完成了一个最简单的布局。而 Auto Layout 的布局方式与上面所说的 `frame` 有些不同,`frame` 的原理是与父视图之间的绝对距离,但是 Auto Layout 中大部分的约束都是**描述性的**,表示视图间相对距离,以上图为例: 33 | 34 | ```objectivec 35 | A.left = Superview.left + 50 36 | A.top = Superview.top + 30 37 | A.width = 100 38 | A.height = 100 39 | 40 | B.left = (A.left + A.width)/(A.right) + 30 41 | B.top = A.top 42 | B.width = A.width 43 | B.height = A.height 44 | ``` 45 | 46 | 虽然上面的约束很好的表示了各个视图之间的关系,但是 Auto Layout 实际上并没有改变原有的 Hard-Coded 形式的布局方式,只是将原有没有太多意义的 `(x, y)` 值,变成了描述性的代码。我们仍然需要知道布局信息所需要的四部分 `x`、`y`、`width` 以及 `height`。换句话说,我们要求解上述的**八元一次**方程组,将每个视图所需要的信息解出来;Cocoa 会在运行时求解上述的方程组,最终使用 `frame` 来绘制视图。 47 | 48 | ![layout-phase](images/layout-phase.png) 49 | 50 | ### Cassowary 算法 51 | 52 | 在上世纪 90 年代,一个名叫 [Cassowary](https://en.wikipedia.org/wiki/Cassowary_(software)) 的布局算法解决了用户界面的布局问题,它通过将布局问题抽象成线性等式和不等式约束来进行求解。 53 | 54 | Auto Layout 其实就是对 Cassowary 算法的一种实现,但是这里并不会对这里进行展开介绍,有兴趣的读者可以在文章最后的 Reference 中了解一下 Cassowary 算法相关的文章。 55 | 56 | > Auto Layout 的原理就是对**线性方程组或者不等式**的求解。 57 | 58 | ## Auto Layout 的性能 59 | 60 | 在我们使用 Auto Layout 进行布局时,可以指定一系列的约束,比如视图的高度、宽度是多少等等。而每一个约束其实都是一个简单的线性等式或不等式,整个界面上的所有约束在一起就**明确地(没有冲突)**定义了整个系统的布局。 61 | 62 | > 在涉及冲突发生时,Auto Layout 会尝试 break 一些优先级低的约束,能够尽量满足最多并且优先级最高的约束。 63 | 64 | 因为布局系统在最后仍然需要通过 `frame` 来进行,所以 Auto Layout 虽然为开发者在描述布局时带来了一些好处,不过它相当于在原有的布局系统中加了从约束计算 `frame` 的过程,而在这里,我们需要了解 Auto Layout 的性能到底是怎么样的。 65 | 66 | ![performance-loss](images/performance-loss.jpeg) 67 | 68 | 因为使用 Cassowary 算法解决约束问题就是对线性等式或不等式求解,所以其时间复杂度就是**多项式时间**的,不难推测出,在处理极其复杂的 UI 界面时,会造成性能上的巨大损失。 69 | 70 | 在这里我们会对 Auto Layout 的性能进行测试,为了更明显的展示 Auto Layout 的性能,我们通过 `frame` 的性能建立一条基准线**以消除对象的创建和销毁、视图的渲染、视图层级的改变带来的影响**。 71 | 72 | 代码分别使用 Auto Layout 和 `frame` 对 N 个视图进行布局,测算其执行时间。 73 | 74 | 使用 AutoLayout 时,每个视图会随机选择两个视图对它的 `top` 和 `left` 进行约束,随机生成一个数字作为 `offset`;同时,还会用几个优先级高的约束保证视图的布局不会超出整个 `keyWindow`。 75 | 76 | 而下图就是对 100~1000 个视图布局所需要的时间的折线图。 77 | 78 | > 这里的数据是在 OS X EL Captain,Macbook Air (13-inch Mid 2013)上的 iPhone 6s Plus 模拟器上采集的, Xcode 版本为 7.3.1。在其他设备上可能不会获得一致的信息,由于笔者的 iPhone 升级到了 iOS 10,所以没有办法真机测试,最后的结果可能会有一定的偏差。 79 | 80 | ![performance-chart-100-1000](images/performance-chart-100-1000.jpeg) 81 | 82 | 从图中可以看到,使用 Auto Layout 进行布局的时间会是只使用 `frame` 的 **16 倍**左右,如果去掉设置 `frame` 的过程消耗的时间,Auto Layout 过程进行的计算量也是非常巨大的。 83 | 84 | 在上一篇文章中,我们曾经提到,想要让 iOS 应用的视图保持 60 FPS 的刷新频率,我们必须在 **1/60 = 16.67 ms** 之内完成包括布局、绘制以及渲染等操作。 85 | 86 | 也就是说如果当前界面上的视图大于 100 的话,使用 Auto Layout 是很难达到绝对流畅的要求的;而在使用 `frame` 时,同一个界面下哪怕有 500 个视图,也是可以在 16.67 ms 之内完成布局的。不过在一般情况下,在 iOS 的整个 UIWindow 中也不会一次性出现如此多的视图。 87 | 88 | 我们更关心的是,在日常开发中难免会使用 Auto Layout 进行布局,既然有 16.67 ms 这个限制,那么在界面上出现了多少个视图时,我才需要考虑其它的布局方式呢?在这里,我们将需要布局的视图数量减少一个量级,重新绘制一个图表: 89 | 90 | ![performance-layout-10-90](images/performance-layout-10-90.jpeg) 91 | 92 | 从图中可以看出,当对 **30 个左右视图**使用 Auto Layout 进行布局时,所需要的时间就会在 16.67 ms 左右,当然这里不排除一些其它因素的影响;到目前为止,会得出一个大致的结论,使用 Auto Layout 对复杂的 UI 界面进行布局时(大于 30 个视图)就会对性能有严重的影响。 93 | 94 | 上述对 Auto Layout 的使用还是比较简单的,而在日常使用中,使用嵌套的视图层级有非常正常。 95 | 96 | > 在笔者对嵌套视图层级中使用 Auto Layout 进行布局时,当视图的数量超过了 500 时,模拟器直接就 crash 了,所以这里没有超过 500 个视图的数据。 97 | 98 | 我们对嵌套视图数量在 100~500 之间布局时间进行测量,并与 Auto Layout 进行比较: 99 | 100 | ![performance-nested-autolayout-frame](images/performance-nested-autolayout-frame.jpeg) 101 | 102 | 在视图数量大于 200 之后,随着视图数量的增加,使用 Auto Layout 对嵌套视图进行布局的时间相比非嵌套的布局成倍增长。 103 | 104 | 虽然说 Auto Layout 为开发者在多尺寸布局上提供了遍历,而且支持跨越视图层级的约束,但是由于其实现原理导致其时间复杂度为**多项式时间**,其性能损耗是仅使用 `frame` 的十几倍,所以在处理庞大的 UI 界面时表现差强人意。 105 | 106 | > 在三年以前,有一篇关于 Auto Layout 性能分析的文章,可以点击这里了解这篇文章的内容 [Auto Layout Performance on iOS](http://floriankugler.com/2013/04/22/auto-layout-performance-on-ios/)。 107 | 108 | ## ASDK 的布局引擎 109 | 110 | Auto Layout 不止在复杂 UI 界面布局的表现不佳,它还会强制视图在主线程上布局;所以在 ASDK 中提供了另一种可以在后台线程中运行的布局引擎,它的结构大致是这样的: 111 | 112 | ![layout-hierarchy](images/layout-hierarchy.png) 113 | 114 | `ASLayoutSpec` 与下面的所有的 Spec 类都是继承关系,在视图需要布局时,会调用 `ASLayoutSpec` 或者它的子类的 `- measureWithSizeRange:` 方法返回一个用于布局的对象 [ASLayout](#aslayout)。 115 | 116 | > `ASLayoutable` 是 ASDK 中一个协议,遵循该协议的类实现了一系列的布局方法。 117 | 118 | 当我们使用 ASDK 布局时,需要做下面四件事情中的一件: 119 | 120 | + 提供 `layoutSpecBlock` 121 | + 覆写 `- layoutSpecThatFits:` 方法 122 | + 覆写 `- calculateSizeThatFits:` 方法 123 | + 覆写 `- calculateLayoutThatFits:` 方法 124 | 125 | 只有做上面四件事情中的其中一件才能对 ASDK 中的视图或者说结点进行布局。 126 | 127 | 方法 `- calculateSizeThatFits:` 提供了手动布局的方式,通过在该方法内对 `frame` 进行计算,返回一个当前视图的 `CGSize`。 128 | 129 | 而 `- layoutSpecThatFits:` 与 `layoutSpecBlock` 其实没什么不同,只是前者通过覆写方法返回 `ASLayoutSpec`;后者通过 block 的形式提供一种不需要子类化就可以完成布局的方法,这两者可以看做是完全等价的。 130 | 131 | `- calculateLayoutThatFits:` 方法有一些不同,它把上面的两种布局方式:手动布局和 Spec 布局封装成了一个接口,无论是 `CGSize` 还是 `ASLayoutSpec` 最后都会以 `ASLayout` 的形式返回给方法调用者。 132 | 133 | ### 手动布局 134 | 135 | 这里简单介绍一下手动布局使用的方法 `-[ASDisplayNode calculatedSizeThatFits:]` 方法,这个方法与 `UIView` 中的 `-[UIView sizeThatFits:]` 非常相似,其区别只是在 ASDK 中,所有的计算出的大小都会通过缓存来提升性能。 136 | 137 | ```objectivec 138 | - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { 139 | return _preferredFrameSize; 140 | } 141 | ``` 142 | 143 | 子类可以在这个方法中进行计算,通过覆写这个方法返回一个合适的大小,不过一般情况下都不会使用手动布局的方式。 144 | 145 | ### 使用 ASLayoutSpec 布局 146 | 147 | 在 ASDK 中,更加常用的是使用 `ASLayoutSpec` 布局,在上面提到的 `ASLayout` 是一个保存布局信息的媒介,而真正计算视图布局的代码都在 `ASLayoutSpec` 中;所有 ASDK 中的布局(手动 / Spec)都是由 `-[ASLayoutable measureWithSizeRange:]` 方法触发的,在这里我们以 `ASDisplayNode` 的调用栈为例: 148 | 149 | ```objectivec 150 | -[ASDisplayNode measureWithSizeRange:] 151 | -[ASDisplayNode shouldMeasureWithSizeRange:] 152 | -[ASDisplayNode calculateLayoutThatFits:] 153 | -[ASDisplayNode layoutSpecThatFits:] 154 | -[ASLayoutSpec measureWithSizeRange:] 155 | +[ASLayout layoutWithLayoutableObject:constrainedSizeRange:size:sublayouts:] 156 | -[ASLayout filteredNodeLayoutTree] 157 | ``` 158 | 159 | ASDK 的文档中推荐在子类中覆写 `- layoutSpecThatFits:` 方法,返回一个用于布局的 `ASLayoutSpec` 对象,然后使用 `ASLayoutSpec` 中的 `- measureWithSizeRange:` 方法对它指定的视图进行布局,不过通过覆写 [ASDK 的布局引擎](#asdk-的布局引擎) 一节中的其它方法也都是可以的。 160 | 161 | 如果我们使用 `ASStackLayoutSpec` 对视图进行布局的话,方法调用栈大概是这样的: 162 | 163 | ```objectivec 164 | -[ASDisplayNode measureWithSizeRange:] 165 | -[ASDisplayNode shouldMeasureWithSizeRange:] 166 | -[ASDisplayNode calculateLayoutThatFits:] 167 | -[ASDisplayNode layoutSpecThatFits:] 168 | -[ASStackLayoutSpec measureWithSizeRange:] 169 | ASStackUnpositionedLayout::compute 170 | ASStackPositionedLayout::compute ASStackBaselinePositionedLayout::compute +[ASLayout layoutWithLayoutableObject:constrainedSizeRange:size:sublayouts:] 171 | -[ASLayout filteredNodeLayoutTree] 172 | ``` 173 | 174 | 这里只是执行了 `ASStackLayoutSpec` 对应的 `- measureWithSizeRange:` 方法,对其中的视图进行布局。在 `- measureWithSizeRange:` 中调用了一些 C++ 方法 `ASStackUnpositionedLayout`、`ASStackPositionedLayout` 以及 `ASStackBaselinePositionedLayout` 的 `compute` 方法,这些方法完成了对 `ASStackLayoutSpec` 中视图的布局。 175 | 176 | 相比于 Auto Layout,ASDK 实现了一种完全不同的布局方式;比较类似与前端开发中的 `Flexbox` 模型,而 ASDK 其实就实现 `Flexbox` 的一个子集。 177 | 178 | 在 ASDK 1.0 时代,很多开发者都表示希望 ASDK 中加入 ComponentKit 的布局引擎;而现在,ASDK 布局引擎的大部分代码都是从 [ComponentKit](http://componentkit.org) 中移植过来的(ComponentKit 是另一个 Facebook 团队开发的用于布局的框架)。 179 | 180 | #### ASLayout 181 | 182 | `ASLayout` 表示当前的结点在布局树中的大小和位置;当然,它还有一些其它的奇怪的属性: 183 | 184 | ```objectivec 185 | @interface ASLayout : NSObject 186 | 187 | @property (nonatomic, weak, readonly) id layoutableObject; 188 | @property (nonatomic, readonly) CGSize size; 189 | @property (nonatomic, readwrite) CGPoint position; 190 | @property (nonatomic, readonly) NSArray *sublayouts; 191 | @property (nonatomic, readonly) CGRect frame; 192 | 193 | ... 194 | 195 | @end 196 | ``` 197 | 198 | 代码中的 `layoutableObject` 表示当前的对象,`sublayouts` 表示当前视图的子布局 `ASLayout` 数组。 199 | 200 | 整个类的实现都没有什么值得多说的,除了大量的构造方法,唯一一个做了一些事情的就是 `-[ASLayout filteredNodeLayoutTree]` 方法了: 201 | 202 | ```objectivec 203 | - (ASLayout *)filteredNodeLayoutTree { 204 | NSMutableArray *flattenedSublayouts = [NSMutableArray array]; 205 | struct Context { 206 | ASLayout *layout; 207 | CGPoint absolutePosition; 208 | }; 209 | std::queue queue; 210 | queue.push({self, CGPointMake(0, 0)}); 211 | while (!queue.empty()) { 212 | Context context = queue.front(); 213 | queue.pop(); 214 | 215 | if (self != context.layout && context.layout.type == ASLayoutableTypeDisplayNode) { 216 | ASLayout *layout = [ASLayout layoutWithLayout:context.layout position:context.absolutePosition]; 217 | layout.flattened = YES; 218 | [flattenedSublayouts addObject:layout]; 219 | } 220 | 221 | for (ASLayout *sublayout in context.layout.sublayouts) { 222 | if (sublayout.isFlattened == NO) queue.push({sublayout, context.absolutePosition + sublayout.position}); 223 | } 224 | 225 | return [ASLayout layoutWithLayoutableObject:_layoutableObject 226 | constrainedSizeRange:_constrainedSizeRange 227 | size:_size 228 | sublayouts:flattenedSublayouts]; 229 | } 230 | ``` 231 | 232 | 而这个方法也只是将 `sublayouts` 中的内容展平,然后实例化一个新的 `ASLayout` 对象。 233 | 234 | #### ASLayoutSpec 235 | 236 | `ASLayoutSpec` 的作用更像是一个抽象类,在真正使用 ASDK 的布局引擎时,都不会直接使用这个类,而是会用类似 `ASStackLayoutSpec`、`ASRelativeLayoutSpec`、`ASOverlayLayoutSpec` 以及 `ASRatioLayoutSpec` 等子类。 237 | 238 | 笔者不打算一行一行代码深入讲解其内容,简单介绍一下最重要的 `ASStackLayoutSpec`。 239 | 240 | ![stack](images/stack.jpg) 241 | 242 | `ASStackLayoutSpec` 从 `Flexbox` 中获得了非常多的灵感,比如说 `justifyContent`、`alignItems` 等属性,它和苹果的 `UIStackView` 比较类似,不过底层并没有使用 Auto Layout 进行计算。如果没有接触过 `ASStackLayoutSpec` 的开发者,可以通过这个小游戏 [Foggy-ASDK-Layout](http://nguyenhuy.github.io/froggy-asdk-layout/) 快速学习 `ASStackLayoutSpec` 的使用。 243 | 244 | ### 关于缓存以及异步并发 245 | 246 | 因为计算视图的 `CGRect` 进行布局是一种非常昂贵的操作,所以 ASDK 在这里面加入了缓存机制,在每次执行 `- measureWithSizeRange:` 方法时,都会通过 `-shouldMeasureWithSizeRange:` 判断是否需要重新计算布局: 247 | 248 | ```objectivec 249 | - (BOOL)shouldMeasureWithSizeRange:(ASSizeRange)constrainedSize { 250 | return [self _hasDirtyLayout] || !ASSizeRangeEqualToSizeRange(constrainedSize, _calculatedLayout.constrainedSizeRange); 251 | } 252 | 253 | - (BOOL)_hasDirtyLayout { 254 | return _calculatedLayout == nil || _calculatedLayout.isDirty; 255 | } 256 | ``` 257 | 258 | 在一般情况下,只有当前结点被标记为 `dirty` 或者这一次布局传入的 `constrainedSize` 不同时,才需要进行重新计算。在不需要重新计算布局的情况下,只需要直接返回 `_calculatedLayout` 布局对象就可以了。 259 | 260 | 因为 ASDK 实现的布局引擎其实只是对 `frame` 的计算,所以无论是在主线程还是后台的异步并发进程中都是可以执行的,也就是说,你可以在任意线程中调用 `- measureWithSizeRange:` 方法,ASDK 中的一些 `ViewController` 比如:`ASDataViewController` 就会在后台并发进程中执行该方法: 261 | 262 | ```objectivec 263 | - (NSArray *)_layoutNodesFromContexts:(NSArray *)contexts { 264 | ... 265 | 266 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 267 | dispatch_apply(nodeCount, queue, ^(size_t i) { 268 | ASIndexedNodeContext *context = contexts[i]; 269 | ASCellNode *node = [context allocateNode]; 270 | if (node == nil) node = [[ASCellNode alloc] init]; 271 | 272 | CGRect frame = CGRectZero; 273 | frame.size = [node measureWithSizeRange:context.constrainedSize].size; 274 | node.frame = frame; 275 | 276 | [ASDataController _didLayoutNode]; 277 | }); 278 | 279 | ... 280 | 281 | return nodes; 282 | } 283 | ``` 284 | 285 | > 上述代码做了比较大的修改,将原有一些方法调用放到了当前方法中,并省略了大量的代码。 286 | 287 | ### 关于性能的对比 288 | 289 | 由于 ASDK 的布局引擎的问题,其性能比较难以测试,在这里只对 ASDK 使用 `ASStackLayoutSpec` 的**布局计算时间**进行了测试,不包括视图的渲染以及其它时间: 290 | 291 | ![async-node-calculate](images/async-node-calculate.jpeg) 292 | 293 | 测试结果表明 `ASStackLayoutSpec` 花费的布局时间与结点的数量成正比,哪怕计算 100 个视图的布局也只需要 **8.89 ms**,虽然这里没有包括视图的渲染时间,不过与 Auto Layout 相比性能还是有比较大的提升。 294 | 295 | ## 总结 296 | 297 | 其实 ASDK 的布局引擎大部分都是对 ComponentKit 的封装,不过由于摆脱了 Auto Layout 这一套低效但是通用的布局方式,ASDK 的布局计算不仅在后台并发线程中进行、而且通过引入 `Flexbox` 的概念提升了布局的性能,但是 ASDK 的使用相对比较复杂,如果只想对布局性能进行优化,更推荐单独使用 ComponentKit 框架进行。 298 | 299 | ## References 300 | 301 | + [Cassowary, Cocoa Auto Layout, and enaml constraints](http://stacks.11craft.com/cassowary-cocoa-Auto Layout-and-enaml-constraints.html) 302 | + [Solving constraint systems](http://cassowary.readthedocs.io/en/latest/topics/theory.html) 303 | + [Auto Layout Performance on iOS](http://floriankugler.com/2013/04/22/auto-layout-performance-on-ios/) 304 | + [The Cassowary Linear Arithmetic Constraint Solving Algorithm: Interface and Implementation](https://constraints.cs.washington.edu/cassowary/cassowary-tr.pdf) 305 | + [The Cassowary Linear Arithmetic Constraint Solving Algorithm](http://constraints.cs.washington.edu/solvers/cassowary-tochi.pdf) 306 | + [Solving Linear Arithmetic Constraints for User Interface Applications](http://constraints.cs.washington.edu/solvers/uist97.pdf) 307 | + [AsyncDisplayKit 介绍(二)布局系统](https://medium.com/@jasonyuh/asyncdisplaykit介绍-二-布局系统-1f1a674cf644#.8jskykm15) 308 | 309 | > Github Repo:[iOS-Source-Code-Analyze](https://github.com/draveness/iOS-Source-Code-Analyze) 310 | > 311 | > Follow: [Draveness · GitHub](https://github.com/Draveness) 312 | > 313 | > Source: http://draveness.me/layout-performance 314 | 315 | 316 | -------------------------------------------------------------------------------- /contents/DKNightVersion/成熟的夜间模式解决方案.md: -------------------------------------------------------------------------------- 1 | # 成熟的夜间模式解决方案 2 | 3 | 从开始写 [DKNightVersion](https://github.com/Draveness/DKNightVersion) 这个框架到现在已经将近一年了,目前整个框架的设计也趋于稳定。 4 | 5 | 其实夜间模式的实现就是相当于**多主题加颜色管理**。而最新版本的 [DKNightVersion](https://github.com/Draveness/DKNightVersion) 已经很好的解决了这个问题。 6 | 7 | 在正式介绍目前版本的实现之前,我会先简单介绍一下 1.0 时代的 DKNightVersion 的实现,为各位读者带来一些新的思路,也确实想梳理一下这个框架是如何演变的。 8 | 9 | > 我们会以对 `backgroundColor` 为例说明整个框架的工作原理。 10 | 11 |

12 | 13 |

14 | 15 | ## 方法调剂的版本 16 | 17 | 如何在不改变原有的架构,甚至不改变原有的代码的基础上,为应用优雅地添加夜间模式成为很多开发者不得不面对的问题。这也是 1.0 时代的 DKNightVersion 想要实现的目标。 18 | 19 | 其核心思路就是**使用方法调剂修改 `backgroundColor` 的存取方法**。 20 | 21 | ### 使用 nightBackgroundColor 22 | 23 | 在思考之后,我想到,想要在不改动原有代码的基础上实现夜间模式只能通过在**分类**中添加 `nightBackgroundColor` 属性,并且使用方法调剂改变 `backgroundColor` 的 setter 方法。 24 | 25 | ```objectivec 26 | - (void)hook_setBackgroundColor:(UIColor*)backgroundColor { 27 | if ([DKNightVersionManager currentThemeVersion] == DKThemeVersionNormal) { 28 | [self setNormalBackgroundColor:backgroundColor]; 29 | } 30 | [self hook_setBackgroundColor:backgroundColor]; 31 | } 32 | ``` 33 | 34 | 在当前主题为 `DKThemeVersionNormal` 时,将颜色保存至 `normalBackgroundColor` 中,然后再调用原 `backgroundColor` 的 setter 方法,更新视图的颜色。 35 | 36 | ### DKNightVersionManager 37 | 38 | 这里只解决了颜色设置的问题,下面会说明,如果在主题改变时,实时更新颜色,而不用重新进入当前页面。 39 | 40 | 整个 DKNightVersion 都是由一个 `DKNightVersionManager` 的单例来管理的,而它的主要工作就是负责**改变应用的主题**、并在主题改变时**通知其它视图更新颜色**: 41 | 42 | ```objectivec 43 | - (void)changeColor:(id )object { 44 | if ([object respondsToSelector:@selector(changeColor)]) { 45 | [object changeColor]; 46 | } 47 | if ([object respondsToSelector:@selector(subviews)]) { 48 | if (![object subviews]) { 49 | // Basic case, do nothing. 50 | return; 51 | } else { 52 | for (id subview in [object subviews]) { 53 | // recursive darken all the subviews of current view. 54 | [self changeColor:subview]; 55 | if ([subview respondsToSelector:@selector(changeColor)]) { 56 | [subview changeColor]; 57 | } 58 | } 59 | } 60 | } 61 | } 62 | ``` 63 | 64 | 如果主题更新,那么就会递归地调用 `changeColor` 方法,刷新全部的视图颜色,而这个方法的实现比较简单: 65 | 66 | ```objectivec 67 | - (void)changeColor { 68 | if ([DKNightVersionManager currentThemeVersion] == DKThemeVersionNormal) { 69 | self.backgroundColor = self.normalBackgroundColor; 70 | } else { 71 | self.backgroundColor = self.nightBackgroundColor; 72 | } 73 | } 74 | ``` 75 | 76 | 上面就是整个框架在 1.0 版本时的实现思路。不过这个版本的 DKNightVersion 在实际应用中会有比较多的问题: 77 | 78 | 1. 在高速滚动的 `scrollView` 上面来回切换夜间模式,会出现颜色错乱的问题 79 | 2. 由于对 `backgroundColor` 属性进行**不合适的**方法调剂,其行为无法预测,比如:在设置颜色后,再取出,不一定与设置时传入的颜色相同 80 | 2. 无法适配第三方 UI 控件 81 | 82 | ## 使用色表的版本 83 | 84 | 为了解决 1.0 中的各种问题,我决定在 2.0 版本中放弃对 `nightBackgroundColor` 的使用,并且重新设计底层的实现,转而使用更为**稳定**、**安全**的方法实现夜间模式,先看一下效果图: 85 | 86 |

87 | 88 |

89 |

90 | 新的实现不仅能够支持夜间模式,而且能够支持多主题。 91 |

92 | 93 | ### DKColorPicker 94 | 95 | 与上一个版本实现上的不同,在 2.0 中删除了全部的 `nightBackgroundColor`,使用一个名为 `dk_backgroundColorPicker` 的属性取代它。 96 | 97 | ```objectivec 98 | @property (nonatomic, copy) DKColorPicker dk_backgroundColorPicker; 99 | ``` 100 | 101 | 这个属性其实就是一个 block,它接收参数 `DKThemeVersion *themeVersion`,但是会返回一个 `UIColor *`: 102 | 103 | > 在第一次传入 picker 或者每次主题改变时,都会将当前主题 `DKThemeVersion` 传入 picker 并执行,然后,将得到的 `UIColor` 赋值给对应的属性 `backgroundColor` 更新视图颜色。 104 | 105 | ```objectivec 106 | typedef UIColor *(^DKColorPicker)(DKThemeVersion *themeVersion); 107 | ``` 108 | 109 | 比如下面使用 `DKColorPickerWithRGB` 创建一个临时的 `DKColorPicker`: 110 | 111 | 1. 在 `DKThemeVersionNormal` 时返回 `0xffffff` 112 | 2. 在 `DKThemeVersionNight` 时返回 `0x343434` 113 | 3. 在自定义的主题下返回 `0xfafafa` (这里的顺序与色表中主题的顺序有关) 114 | 115 | ```objectivec 116 | cell.dk_backgroundColorPicker = DKColorPickerWithRGB(0xffffff, 0x343434, 0xfafafa); 117 | ``` 118 | 119 | 同时,每一个对象还持有一个 `pickers` 数组,来存储自己的全部 `DKColorPicker`: 120 | 121 | ```objectivec 122 | @interface NSObject () 123 | 124 | @property (nonatomic, strong) NSMutableDictionary *pickers; 125 | 126 | @end 127 | ``` 128 | 129 | 在第一次使用这个属性时,当前对象注册为 `DKNightVersionThemeChangingNotificaiton` 通知的观察者。 130 | 131 | 在每次收到通知时,都会调用 `night_update` 方法,将当前主题传入 `DKColorPicker`,并再次执行,并将结果传入对应的属性 `[self performSelector:sel withObject:result]`。 132 | 133 | ```objectivec 134 | - (void)night_updateColor { 135 | [self.pickers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull selector, DKColorPicker _Nonnull picker, BOOL * _Nonnull stop) { 136 | SEL sel = NSSelectorFromString(selector); 137 | id result = picker(self.dk_manager.themeVersion); 138 | [UIView animateWithDuration:DKNightVersionAnimationDuration 139 | animations:^{ 140 | #pragma clang diagnostic push 141 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 142 | [self performSelector:sel withObject:result]; 143 | #pragma clang diagnostic pop 144 | }]; 145 | }]; 146 | } 147 | ``` 148 | 149 | 也就是说,在每次改变主题的时候,都会发出通知。 150 | 151 | ### DKColorTable 152 | 153 | 虽然我们在上面临时创建了一些 `DKColorPicker`。不过在 `DKNightVersion` 中,我更推荐使用色表,来减少相同的 `DKColorPicker` 的创建,并且能够更好地管理整个应用中的颜色: 154 | 155 | ```objectivec 156 | NORMAL NIGHT RED 157 | #ffffff #343434 #fafafa BG 158 | #aaaaaa #313131 #aaaaaa SEP 159 | #0000ff #ffffff #fa0000 TINT 160 | #000000 #ffffff #000000 TEXT 161 | #ffffff #444444 #ffffff BAR 162 | ``` 163 | 164 | 上面就是默认色表文件 `DKColorTable.txt` 中的内容,其中,第一行表示主题,`NORMAL` 主题必须存在,而且必须为第一列,而最右面的 `BG`、`SEP` 就是对应 `DKColorPicker` 的 key。 165 | 166 | ```objectivec 167 | self.tableView.dk_backgroundColorPicker = DKColorPickerWithKey(BG); 168 | ``` 169 | 170 | 在使用时,上面的代码就相当于返回了一个在 `NORMAL` 时返回 `#ffffff`、`NIGHT` 时返回 `#343434` 以及 `RED` 时返回 `#fafafa` 的 `DKColorPicker`。 171 | 172 | ### pickerify 173 | 174 | 虽然说,我们使用色表以及 `DKColorPicker` 解决了,但是,到目前为止我们还没有解决第三方框架的问题。 175 | 176 | 比如我们使用了某个第三方框架,或者自己添加了某个 `color` 属性,比如说: 177 | 178 | ```objectivec 179 | @interface DKView () 180 | 181 | @property (nonatomic, strong) UIColor *weirdColor; 182 | 183 | @end 184 | ``` 185 | 186 | `weirdColor` 并没有对应的 `DKColorPicker`,但是,我们可以通过 `pickerify` 在想要使用 `dk_weirdColorPicker` 的地方生成这个对应的 picker: 187 | 188 | ```objectivec 189 | @pickerify(DKView, weirdColor); 190 | ``` 191 | 192 | 然后,我们就可以使用 `dk_weirdColorPicker` 属性了: 193 | 194 | ```objectivec 195 | view.dk_weirdColorPicker = DKColorPickerWithKey(BG); 196 | ``` 197 | 198 | `pickerify` 其实是一个宏: 199 | 200 | ```objectivec 201 | #define pickerify(KLASS, PROPERTY) interface \ 202 | KLASS (Night) \ 203 | @property (nonatomic, copy, setter = dk_set ## PROPERTY ## Picker:) DKColorPicker dk_ ## PROPERTY ## Picker; \ 204 | @end \ 205 | @interface \ 206 | KLASS () \ 207 | @property (nonatomic, strong) NSMutableDictionary *pickers; \ 208 | @end \ 209 | @implementation \ 210 | KLASS (Night) \ 211 | - (DKColorPicker)dk_ ## PROPERTY ## Picker { \ 212 | return objc_getAssociatedObject(self, @selector(dk_ ## PROPERTY ## Picker)); \ 213 | } \ 214 | - (void)dk_set ## PROPERTY ## Picker:(DKColorPicker)picker { \ 215 | objc_setAssociatedObject(self, @selector(dk_ ## PROPERTY ## Picker), picker, OBJC_ASSOCIATION_COPY_NONATOMIC); \ 216 | [self setValue:picker(self.dk_manager.themeVersion) forKeyPath:@keypath(self, PROPERTY)];\ 217 | [self.pickers setValue:[picker copy] forKey:_DKSetterWithPROPERTYerty(@#PROPERTY)]; \ 218 | } \ 219 | @end 220 | ``` 221 | 222 | 这个宏根据传入的类和属性名,为我们生成了对应 `picker` 的存取方法,它也可以说是一种元编程的手段。 223 | 224 | > 这里生成的 setter 方法不是标准意义上的驼峰命名法 `dk_setweirdColorPicker:`,因为我不知道怎么才能让大写首字母之后的属性添加到这里(如果各位读者有解决方案,欢迎提 PR 或者 issue)。 225 | 226 | ## 嵌入式 Ruby 227 | 228 | 由于框架中很多的代码,都是重复的,所以在这里使用了**嵌入式 Ruby 模板**来生成对应的文件 `color.m.irb`: 229 | 230 | ```objectivec 231 | // 232 | // <%= klass.name %>+Night.m 233 | // <%= klass.name %>+Night 234 | // 235 | // Copyright (c) 2015 Draveness. All rights reserved. 236 | // 237 | // These files are generated by ruby script, if you want to modify code 238 | // in this file, you are supposed to update the ruby code, run it and 239 | // test it. And finally open a pull request. 240 | 241 | #import "<%= klass.name %>+Night.h" 242 | #import "DKNightVersionManager.h" 243 | #import 244 | 245 | @interface <%= klass.name %> () 246 | 247 | @property (nonatomic, strong) NSMutableDictionary *pickers; 248 | 249 | @end 250 | 251 | @implementation <%= klass.name %> (Night) 252 | 253 | <% klass.properties.each do |property| %><%= """ 254 | - (DKColorPicker)dk_#{property.name}Picker { 255 | return objc_getAssociatedObject(self, @selector(dk_#{property.name}Picker)); 256 | } 257 | 258 | - (void)dk_set#{property.cap_name}Picker:(DKColorPicker)picker { 259 | objc_setAssociatedObject(self, @selector(dk_#{property.name}Picker), picker, OBJC_ASSOCIATION_COPY_NONATOMIC); 260 | self.#{property.name} = picker(self.dk_manager.themeVersion); 261 | [self.pickers setValue:[picker copy] forKey:@\"#{property.setter}\"]; 262 | } 263 | """ %><% end %> 264 | 265 | @end 266 | ``` 267 | 268 | 这部分的实现并不在这篇文章的讨论范围之内,如果,对这部分看兴趣,可以看一下仓库中的 `generator` 文件夹,其中包含了代码生成器的全部代码。 269 | 270 | ## 小结 271 | 272 | 如果你对 DKNightVersion 的使用有兴趣,可以查看仓库的 [README](https://github.com/Draveness/DKNightVersion) 文件,有人会说不要在项目中 ObjC runtime,我个人觉得是没有问题,`AFNetworking`、 `BlocksKit` 也使用方法调剂来改变原有方法的实现,不能因为它强大就不使用它;正相反,有时候,使用 runtime 才能优雅地解决问题。 273 | 274 | > 关注仓库,及时获得更新:[iOS-Source-Code-Analyze](https://github.com/draveness/iOS-Source-Code-Analyze) 275 | > Follow: [Draveness · Github](https://github.com/Draveness) 276 | 277 | 278 | -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/iOS 中的 block 是如何持有对象的.md: -------------------------------------------------------------------------------- 1 | # iOS 中的 block 是如何持有对象的 2 | 3 | > Follow: [Draveness · Github](https://github.com/Draveness) 4 | 5 | Block 是 Objective-C 中笔者最喜欢的特性,它为 Objective-C 这门语言提供了强大的函数式编程能力,而最近苹果推出的很多新的 API 都已经开始原生的支持 block 语法,可见它在 Objective-C 中变得越来越重要。 6 | 7 | ![](images/block.jpg) 8 | 9 | 这篇文章并不会详细介绍 block 在内存中到底是以什么形式存在的,主要会介绍 block 是如何持有并且释放对象的。文章中的代码都出自 Facebook 开源的**用于检测循环引用**的框架 [FBRetainCycleDetector](https://github.com/facebook/FBRetainCycleDetector),这是分析该框架文章中的最后一篇,也是笔者觉得最有意思的一部分。 10 | 11 | > 如果你希望了解 FBRetainCycleDetector 的原理可以阅读[如何在 iOS 中解决循环引用的问题](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/FBRetainCycleDetector/如何在%20iOS%20中解决循环引用的问题.md)以及后续文章。 12 | 13 | ## 为什么会谈到 block 14 | 15 | 可能很多读者会有这样的疑问,本文既然是对 `FBRetainCycleDetector` 解析的文章,为什么会提到 block?原因其实很简单,因为在 iOS 开发中大多数的循环引用都是因为 block 使用不当导致的,由于 block 会 retain 它持有的对象,这样就很容易造成循环引用,最终导致内存泄露。 16 | 17 | 在 `FBRetainCycleDetector` 中存在这样一个类 `FBObjectiveCBlock`,这个类的 `- allRetainedObjects` 方法就会返回所有 block 持有的强引用,这也是文章需要关注的重点。 18 | 19 | ```objectivec 20 | - (NSSet *)allRetainedObjects { 21 | NSMutableArray *results = [[[super allRetainedObjects] allObjects] mutableCopy]; 22 | 23 | __attribute__((objc_precise_lifetime)) id anObject = self.object; 24 | 25 | void *blockObjectReference = (__bridge void *)anObject; 26 | NSArray *allRetainedReferences = FBGetBlockStrongReferences(blockObjectReference); 27 | 28 | for (id object in allRetainedReferences) { 29 | FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, object, self.configuration); 30 | if (element) { 31 | [results addObject:element]; 32 | } 33 | } 34 | 35 | return [NSSet setWithArray:results]; 36 | } 37 | ``` 38 | 39 | 这部分代码中的大部分都不重要,只是在开头调用父类方法,在最后将获取的对象包装成一个系列 `FBObjectiveCGraphElement`,最后返回一个数组,也就是当前对象 block 持有的全部强引用了。 40 | 41 | ## Block 是什么? 42 | 43 | 对 block 稍微有了解的人都知道,block 其实是一个结构体,其结构大概是这样的: 44 | 45 | ```objectivec 46 | struct BlockLiteral { 47 | void *isa; 48 | int flags; 49 | int reserved; 50 | void (*invoke)(void *, ...); 51 | struct BlockDescriptor *descriptor; 52 | }; 53 | 54 | struct BlockDescriptor { 55 | unsigned long int reserved; 56 | unsigned long int size; 57 | void (*copy_helper)(void *dst, void *src); 58 | void (*dispose_helper)(void *src); 59 | const char *signature; 60 | }; 61 | ``` 62 | 63 | 在 `BlockLiteral` 结构体中有一个 `isa` 指针,而对 `isa`了解的人也都知道,这里的 `isa` 其实指向了一个类,每一个 block 指向的类可能是 `__NSGlobalBlock__`、`__NSMallocBlock__` 或者 `__NSStackBlock__`,但是这些 block,它们继承自一个共同的父类,也就是 `NSBlock`,我们可以使用下面的代码来获取这个类: 64 | 65 | ```objectivec 66 | static Class _BlockClass() { 67 | static dispatch_once_t onceToken; 68 | static Class blockClass; 69 | dispatch_once(&onceToken, ^{ 70 | void (^testBlock)() = [^{} copy]; 71 | blockClass = [testBlock class]; 72 | while(class_getSuperclass(blockClass) && class_getSuperclass(blockClass) != [NSObject class]) { 73 | blockClass = class_getSuperclass(blockClass); 74 | } 75 | [testBlock release]; 76 | }); 77 | return blockClass; 78 | } 79 | ``` 80 | 81 | Objective-C 中的三种 block `__NSMallocBlock__`、`__NSStackBlock__` 和 `__NSGlobalBlock__` 会在下面的情况下出现: 82 | 83 | | | ARC | 非 ARC | 84 | |------------|:----------------------------:|-----------------------------| 85 | | 捕获外部变量 | `__NSMallocBlock__`
`__NSStackBlock__` | `__NSStackBlock__`| 86 | | 未捕获外部变量 | `__NSGlobalBlock__`| `__NSGlobalBlock__` | 87 | 88 | 89 | + 在 ARC 中,捕获外部了变量的 block 的类会是 `__NSMallocBlock__` 或者 `__NSStackBlock__`,如果 block 被赋值给了某个变量在这个过程中会执行 `_Block_copy` 将原有的 `__NSStackBlock__` 变成 `__NSMallocBlock__`;但是如果 block 没有被赋值给某个变量,那它的类型就是 `__NSStackBlock__`;没有捕获外部变量的 block 的类会是 `__NSGlobalBlock__` 即不在堆上,也不在栈上,它类似 C 语言函数一样会在代码段中。 90 | + 在非 ARC 中,捕获了外部变量的 block 的类会是 `__NSStackBlock__`,放置在栈上,没有捕获外部变量的 block 时与 ARC 环境下情况相同。 91 | 92 | 如果我们不断打印一个 block 的 `superclass` 的话最后就会在继承链中找到 `NSBlock` 的身影: 93 | 94 | ![block-superclass](images/block-superclass.png) 95 | 96 | 然后可以通过这种办法来判断当前对象是不是 block: 97 | 98 | ```objectivec 99 | BOOL FBObjectIsBlock(void *object) { 100 | Class blockClass = _BlockClass(); 101 | 102 | Class candidate = object_getClass((__bridge id)object); 103 | return [candidate isSubclassOfClass:blockClass]; 104 | } 105 | ``` 106 | 107 | ## Block 如何持有对象 108 | 109 | 在这一小节,我们将讨论 block 是**如何持有对象**的,我们会通过对 FBRetainCycleDetector 的源代码进行分析最后尽量详尽地回答这一问题。 110 | 111 | 重新回到文章开头提到的 `- allRetainedObjects` 方法: 112 | 113 | ```objectivec 114 | - (NSSet *)allRetainedObjects { 115 | NSMutableArray *results = [[[super allRetainedObjects] allObjects] mutableCopy]; 116 | 117 | __attribute__((objc_precise_lifetime)) id anObject = self.object; 118 | 119 | void *blockObjectReference = (__bridge void *)anObject; 120 | NSArray *allRetainedReferences = FBGetBlockStrongReferences(blockObjectReference); 121 | 122 | for (id object in allRetainedReferences) { 123 | FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, object, self.configuration); 124 | if (element) { 125 | [results addObject:element]; 126 | } 127 | } 128 | 129 | return [NSSet setWithArray:results]; 130 | } 131 | ``` 132 | 133 | 通过函数的符号我们也能够猜测出,上述方法中通过 `FBGetBlockStrongReferences` 获取 block 持有的所有强引用: 134 | 135 | ```objectivec 136 | NSArray *FBGetBlockStrongReferences(void *block) { 137 | if (!FBObjectIsBlock(block)) { 138 | return nil; 139 | } 140 | 141 | NSMutableArray *results = [NSMutableArray new]; 142 | 143 | void **blockReference = block; 144 | NSIndexSet *strongLayout = _GetBlockStrongLayout(block); 145 | [strongLayout enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { 146 | void **reference = &blockReference[idx]; 147 | 148 | if (reference && (*reference)) { 149 | id object = (id)(*reference); 150 | 151 | if (object) { 152 | [results addObject:object]; 153 | } 154 | } 155 | }]; 156 | 157 | return [results autorelease]; 158 | } 159 | ``` 160 | 161 | 而 `FBGetBlockStrongReferences` 是对另一个私有函数 `_GetBlockStrongLayout` 的封装,也是实现最有意思的部分。 162 | 163 | ### 几个必要的概念 164 | 165 | 在具体介绍 `_GetBlockStrongLayout` 函数的源代码之前,我希望先对其原理有一个简单的介绍,便于各位读者的理解;在这里有三个概念需要介绍,首先是 block 持有的对象都存在的位置。 166 | 167 | #### 如何持有对象 168 | 169 | 在文章的上面曾经出现过 block 的结构体,不知道各位读者是否还有印象: 170 | 171 | ```objectivec 172 | struct BlockLiteral { 173 | void *isa; 174 | int flags; 175 | int reserved; 176 | void (*invoke)(void *, ...); 177 | struct BlockDescriptor *descriptor; 178 | // imported variables 179 | }; 180 | ``` 181 | 182 | 在每个 block 结构体的下面就会存放当前 block 持有的所有对象,无论强弱。我们可以做一个小实验来验证这个观点,我们在程序中声明这样一个 block: 183 | 184 | ```objectivec 185 | NSObject *firstObject = [NSObject new]; 186 | __attribute__((objc_precise_lifetime)) NSObject *object = [NSObject new]; 187 | __weak NSObject *secondObject = object; 188 | NSObject *thirdObject = [NSObject new]; 189 | 190 | __unused void (^block)() = ^{ 191 | __unused NSObject *first = firstObject; 192 | __unused NSObject *second = secondObject; 193 | __unused NSObject *third = thirdObject; 194 | }; 195 | ``` 196 | 197 | 然后在代码中打一个断点: 198 | 199 | ![block-capture-var-layout](images/block-capture-var-layout.png) 200 | 201 | 202 | > 上面代码中 block 由于被变量引用,执行了 `_Block_copy`,所以其类型为 `__NSMallocBlock__`,没有被变量引用的 block 都是 `__NSStackBlock__`。 203 | 204 | 1. 首先打印 block 变量的大小,因为 block 变量其实只是一个指向结构体的指针,所以大小为 8,而结构体的大小为 32; 205 | 2. 以 block 的地址为基址,偏移 32,得到一个指针 206 | 3. 使用 `$3[0]` `$3[1]` `$3[2]` 依次打印地址为 `0x1001023b0` `0x1001023b8` `0x1001023c0` 的内容,可以发现它们就是 block 捕获的全部引用,前两个是强引用,最后的是弱引用 207 | 208 | 这可以得出一个结论:block 将其捕获的引用存放在结构体的下面,但是为什么这里的顺序并不是按照引用的顺序呢?接下来增加几个变量,再做另一次实验: 209 | 210 | ![block-capture-strong-weak-orde](images/block-capture-strong-weak-order.png) 211 | 212 | 在代码中多加入了几个对象之后,block 对持有的对象的布局的顺序依然是**强引用在前、弱引用在后**,我们不妨做一个假设:**block 会将强引用的对象排放在弱引用对象的前面**。但是这个假设能够帮助我们在**只有 block 但是没有上下文信息的情况下**区分哪些是强引用么?我觉得并不能,因为我们没有办法知道它们之间的分界线到底在哪里。 213 | 214 | #### dispose_helper 215 | 216 | 第二个需要介绍的是 `dispose_helper`,这是 `BlockDescriptor` 结构体中的一个指针: 217 | 218 | ```objectivec 219 | struct BlockDescriptor { 220 | unsigned long int reserved; // NULL 221 | unsigned long int size; 222 | // optional helper functions 223 | void (*copy_helper)(void *dst, void *src); // IFF (1<<25) 224 | void (*dispose_helper)(void *src); // IFF (1<<25) 225 | const char *signature; // IFF (1<<30) 226 | }; 227 | ``` 228 | 229 | 上面的结构体中有两个函数指针,`copy_helper` 用于 block 的拷贝,`dispose_helper` 用于 block 的 `dispose` 也就是 block 在析构的时候会调用这个函数指针,销毁自己持有的对象,而这个原理也是区别强弱引用的关键,因为在 `dispose_helper` 会对强引用发送 `release` 消息,对弱引用不会做任何的处理。 230 | 231 | #### FBBlockStrongRelationDetector 232 | 233 | 最后就是用于从 `dispose_helper` 接收消息的类 `FBBlockStrongRelationDetector` 了;它的实例在接受 `release` 消息时,并不会真正的释放,只会将标记 `_strong` 为 YES: 234 | 235 | ```objectivec 236 | - (oneway void)release { 237 | _strong = YES; 238 | } 239 | 240 | - (oneway void)trueRelease { 241 | [super release]; 242 | } 243 | ``` 244 | 245 | 只有真正执行 `trueRelease` 的时候才会向对象发送 `release` 消息。 246 | 247 | 因为这个文件覆写了 `release` 方法,所以要在非 ARC 下编译: 248 | 249 | ```objectivec 250 | #if __has_feature(objc_arc) 251 | #error This file must be compiled with MRR. Use -fno-objc-arc flag. 252 | #endif 253 | ``` 254 | 255 | 如果 block 持有了另一个 block 对象,`FBBlockStrongRelationDetector` 也可以将自身 fake 成为一个假的 block 防止在接收到关于 block 释放的消息时发生 crash: 256 | 257 | ```objectivec 258 | struct _block_byref_block; 259 | @interface FBBlockStrongRelationDetector : NSObject { 260 | // __block fakery 261 | void *forwarding; 262 | int flags; //refcount; 263 | int size; 264 | void (*byref_keep)(struct _block_byref_block *dst, struct _block_byref_block *src); 265 | void (*byref_dispose)(struct _block_byref_block *); 266 | void *captured[16]; 267 | } 268 | ``` 269 | 270 | 该类的实例在初始化时,会设置 `forwarding`、`byref_keep` 和 `byref_dispose`,后两个方法的实现都是空的,只是为了防止 crash: 271 | 272 | ```objectivec 273 | + (id)alloc { 274 | FBBlockStrongRelationDetector *obj = [super alloc]; 275 | 276 | // Setting up block fakery 277 | obj->forwarding = obj; 278 | obj->byref_keep = byref_keep_nop; 279 | obj->byref_dispose = byref_dispose_nop; 280 | 281 | return obj; 282 | } 283 | 284 | static void byref_keep_nop(struct _block_byref_block *dst, struct _block_byref_block *src) {} 285 | static void byref_dispose_nop(struct _block_byref_block *param) {} 286 | ``` 287 | 288 | ### 获取 block 强引用的对象 289 | 290 | 到现在为止,获取 block 强引用对象所需要的知识都介绍完了,接下来可以对私有方法 `_GetBlockStrongLayout` 进行分析了: 291 | 292 | ```objectivec 293 | static NSIndexSet *_GetBlockStrongLayout(void *block) { 294 | struct BlockLiteral *blockLiteral = block; 295 | 296 | if ((blockLiteral->flags & BLOCK_HAS_CTOR) 297 | || !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) { 298 | return nil; 299 | } 300 | 301 | ... 302 | } 303 | ``` 304 | 305 | + 如果 block 有 Cpp 的构造器/析构器,说明它**持有的对象很有可能没有按照指针大小对齐**,我们很难检测到所有的对象 306 | + 如果 block 没有 `dispose` 函数,说明它无法 `retain` 对象,也就是说我们也没有办法测试其强引用了哪些对象 307 | 308 | ```objectivec 309 | static NSIndexSet *_GetBlockStrongLayout(void *block) { 310 | ... 311 | void (*dispose_helper)(void *src) = blockLiteral->descriptor->dispose_helper; 312 | const size_t ptrSize = sizeof(void *); 313 | const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize; 314 | 315 | void *obj[elements]; 316 | void *detectors[elements]; 317 | 318 | for (size_t i = 0; i < elements; ++i) { 319 | FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new]; 320 | obj[i] = detectors[i] = detector; 321 | } 322 | 323 | @autoreleasepool { 324 | dispose_helper(obj); 325 | } 326 | ... 327 | } 328 | ``` 329 | 330 | 1. 从 `BlockDescriptor` 取出 `dispose_helper` 以及 `size`(block 持有的所有对象的大小) 331 | 2. 通过 `(blockLiteral->descriptor->size + ptrSize - 1) / ptrSize` 向上取整,获取 block 持有的指针的数量 332 | 3. 初始化两个包含 `elements` 个 `FBBlockStrongRelationDetector` 实例的数组,其中第一个数组用于传入 `dispose_helper`,第二个数组用于检测 `_strong` 是否被标记为 `YES` 333 | 4. 在自动释放池中执行 `dispose_helper(obj)`,释放 block 持有的对象 334 | 335 | ```objectivec 336 | static NSIndexSet *_GetBlockStrongLayout(void *block) { 337 | ... 338 | NSMutableIndexSet *layout = [NSMutableIndexSet indexSet]; 339 | 340 | for (size_t i = 0; i < elements; ++i) { 341 | FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]); 342 | if (detector.isStrong) { 343 | [layout addIndex:i]; 344 | } 345 | 346 | [detector trueRelease]; 347 | } 348 | 349 | return layout; 350 | } 351 | ``` 352 | 353 | 因为 `dispose_helper` 只会调用 `release` 方法,但是这并不会导致我们的 `FBBlockStrongRelationDetector` 实例被释放掉,反而会标记 `_string` 属性,在这里我们只需要判断这个属性的真假,将对应索引加入数组,最后再调用 `trueRelease` 真正的释放对象。 354 | 355 | 我们可以执行下面的代码,分析其工作过程: 356 | 357 | ```objectivec 358 | NSObject *firstObject = [NSObject new]; 359 | __attribute__((objc_precise_lifetime)) NSObject *object = [NSObject new]; 360 | __weak NSObject *secondObject = object; 361 | NSObject *thirdObject = [NSObject new]; 362 | 363 | __unused void (^block)() = ^{ 364 | __unused NSObject *first = firstObject; 365 | __unused NSObject *second = secondObject; 366 | __unused NSObject *third = thirdObject; 367 | }; 368 | 369 | FBRetainCycleDetector *detector = [FBRetainCycleDetector new]; 370 | [detector addCandidate:block]; 371 | [detector findRetainCycles]; 372 | ``` 373 | 374 | 在 `dispose_helper` 调用之前: 375 | 376 | ![before-dispose-helpe](images/before-dispose-helper.jpeg) 377 | 378 | `obj` 数组中的每一个位置都存储了 `FBBlockStrongRelationDetector` 的实例,但是在 `dispose_helper` 调用之后: 379 | 380 | ![after-dispose-helpe](images/after-dispose-helper.png) 381 | 382 | 索引为 4 和 5 处的实例已经被清空了,这里对应的 `FBBlockStrongRelationDetector` 实例的 `strong` 已经被标记为 `YES`、加入到数组中并返回;最后也就获取了所有强引用的索引,同时得到了 block 强引用的对象。 383 | 384 | ## 总结 385 | 386 | 其实最开始笔者对这个 `dispose_helper` 实现的机制并不是特别的肯定,只是有一个猜测,但是在询问了 `FBBlockStrongRelationDetector` 的作者之后,才确定 `dispose_helper` 确实会负责向所有捕获的变量发送 `release` 消息,如果有兴趣可以看这个 [issue](https://github.com/facebook/FBRetainCycleDetector/issues/15)。这部分的代码其实最开始源于 mikeash 大神的 [Circle](https://github.com/mikeash/Circle),不过对于他是如何发现这一点的,笔者并不清楚,如果各位有相关的资料或者合理的解释,可以随时联系我。 387 | 388 | > Follow: [Draveness · Github](https://github.com/Draveness) 389 | 390 | 391 | -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/images/after-dispose-helper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/FBRetainCycleDetector/images/after-dispose-helper.png -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/images/before-dispose-helper.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/FBRetainCycleDetector/images/before-dispose-helper.jpeg -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/images/block-capture-strong-weak-order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/FBRetainCycleDetector/images/block-capture-strong-weak-order.png -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/images/block-capture-var-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/FBRetainCycleDetector/images/block-capture-var-layout.png -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/images/block-superclass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/FBRetainCycleDetector/images/block-superclass.png -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/images/block.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/FBRetainCycleDetector/images/block.jpg -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/images/filtered-ivars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/FBRetainCycleDetector/images/filtered-ivars.png -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/images/get-ivar-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/FBRetainCycleDetector/images/get-ivar-layout.png -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/images/get-ivars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/FBRetainCycleDetector/images/get-ivars.png -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/images/retain-objects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/FBRetainCycleDetector/images/retain-objects.png -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/如何在 iOS 中解决循环引用的问题.md: -------------------------------------------------------------------------------- 1 | # 如何在 iOS 中解决循环引用的问题 2 | 3 | 稍有常识的人都知道在 iOS 开发时,我们经常会遇到循环引用的问题,比如两个强指针相互引用,但是这种简单的情况作为稍有经验的开发者都会轻松地查找出来。 4 | 5 | 但是遇到下面这样的情况,如果只看其实现代码,也很难仅仅凭借肉眼上的观察以及简单的推理就能分析出其中存在的循环引用问题,更何况真实情况往往比这复杂的多: 6 | 7 | ```objectivec 8 | testObject1.object = testObject2; 9 | testObject1.secondObject = testObject3; 10 | testObject2.object = testObject4; 11 | testObject2.secondObject = testObject5; 12 | testObject3.object = testObject1; 13 | testObject5.object = testObject6; 14 | testObject4.object = testObject1; 15 | testObject5.secondObject = testObject7; 16 | testObject7.object = testObject2; 17 | ``` 18 | 19 | 上述代码确实是存在循环引用的问题: 20 | 21 | ![detector-retain-objects](images/retain-objects.png) 22 | 23 | 这一次分享的内容就是用于检测循环引用的框架 [FBRetainCycleDetector]([https://github.com/facebook/FBRetainCycleDetector]) 我们会分几个部分来分析 FBRetainCycleDetector 是如何工作的: 24 | 25 | 1. 检测循环引用的基本原理以及过程 26 | 2. [检测涉及 NSObject 对象的循环引用问题](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/FBRetainCycleDetector/检测%20NSObject%20对象持有的强指针.md) 27 | 2. [检测涉及 Associated Object 关联对象的循环引用问题](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/FBRetainCycleDetector/如何实现%20iOS%20中的%20Associated%20Object.md) 28 | 3. 检测涉及 Block 的循环引用问题 29 | 30 | 这是四篇文章中的第一篇,我们会以类 `FBRetainCycleDetector` 的 `- findRetainCycles` 方法为入口,分析其实现原理以及运行过程。 31 | 32 | 简单介绍一下 `FBRetainCycleDetector` 的使用方法: 33 | 34 | ```objectivec 35 | _RCDTestClass *testObject = [_RCDTestClass new]; 36 | testObject.object = testObject; 37 | 38 | FBRetainCycleDetector *detector = [FBRetainCycleDetector new]; 39 | [detector addCandidate:testObject]; 40 | NSSet *retainCycles = [detector findRetainCycles]; 41 | 42 | NSLog(@"%@", retainCycles); 43 | ``` 44 | 45 | 1. 初始化一个 `FBRetainCycleDetector` 的实例 46 | 2. 调用 `- addCandidate:` 方法添加潜在的泄露对象 47 | 3. 执行 `- findRetainCycles` 返回 `retainCycles` 48 | 49 | 在控制台中的输出是这样的: 50 | 51 | ```c 52 | 2016-07-29 15:26:42.043 xctest[30610:1003493] {( 53 | ( 54 | "-> _object -> _RCDTestClass " 55 | ) 56 | )} 57 | ``` 58 | 59 | 说明 `FBRetainCycleDetector` 在代码中发现了循环引用。 60 | 61 | ## findRetainCycles 的实现 62 | 63 | 在具体开始分析 `FBRetainCycleDetector` 代码之前,我们可以先观察一下方法 `findRetainCycles` 的调用栈: 64 | 65 | ```objectivec 66 | - (NSSet *> *)findRetainCycles 67 | └── - (NSSet *> *)findRetainCyclesWithMaxCycleLength:(NSUInteger)length 68 | └── - (NSSet *> *)_findRetainCyclesInObject:(FBObjectiveCGraphElement *)graphElement stackDepth:(NSUInteger)stackDepth 69 | └── - (instancetype)initWithObject:(FBObjectiveCGraphElement *)object 70 | └── - (FBNodeEnumerator *)nextObject 71 | ├── - (NSArray *)_unwrapCycle:(NSArray *)cycle 72 | ├── - (NSArray *)_shiftToUnifiedCycle:(NSArray *)array 73 | └── - (void)addObject:(ObjectType)anObject; 74 | ``` 75 | 76 | 调用栈中最上面的两个简单方法的实现都是比较容易理解的: 77 | 78 | ```objectivec 79 | - (NSSet *> *)findRetainCycles { 80 | return [self findRetainCyclesWithMaxCycleLength:kFBRetainCycleDetectorDefaultStackDepth]; 81 | } 82 | 83 | - (NSSet *> *)findRetainCyclesWithMaxCycleLength:(NSUInteger)length { 84 | NSMutableSet *> *allRetainCycles = [NSMutableSet new]; 85 | for (FBObjectiveCGraphElement *graphElement in _candidates) { 86 | NSSet *> *retainCycles = [self _findRetainCyclesInObject:graphElement 87 | stackDepth:length]; 88 | [allRetainCycles unionSet:retainCycles]; 89 | } 90 | [_candidates removeAllObjects]; 91 | 92 | return allRetainCycles; 93 | } 94 | ``` 95 | 96 | `- findRetainCycles` 调用了 `- findRetainCyclesWithMaxCycleLength:` 传入了 `kFBRetainCycleDetectorDefaultStackDepth` 参数来限制查找的深度,如果超过该深度(默认为 10)就不会继续处理下去了(查找的深度的增加会对性能有非常严重的影响)。 97 | 98 | 在 `- findRetainCyclesWithMaxCycleLength:` 中,我们会遍历所有潜在的内存泄露对象 `candidate`,执行整个框架中最核心的方法 `- _findRetainCyclesInObject:stackDepth:`,由于这个方法的实现太长,这里会分几块对其进行介绍,并会省略其中的注释: 99 | 100 | ```objectivec 101 | - (NSSet *> *)_findRetainCyclesInObject:(FBObjectiveCGraphElement *)graphElement 102 | stackDepth:(NSUInteger)stackDepth { 103 | NSMutableSet *> *retainCycles = [NSMutableSet new]; 104 | FBNodeEnumerator *wrappedObject = [[FBNodeEnumerator alloc] initWithObject:graphElement]; 105 | 106 | NSMutableArray *stack = [NSMutableArray new]; 107 | 108 | NSMutableSet *objectsOnPath = [NSMutableSet new]; 109 | 110 | ... 111 | } 112 | ``` 113 | 114 | 其实整个对象的相互引用情况可以看做一个**有向图**,对象之间的引用就是图的 `Edge`,每一个对象就是 `Vertex`,**查找循环引用的过程就是在整个有向图中查找环的过程**,所以在这里我们使用 DFS 来扫面图中的环,这些环就是对象之间的循环引用。 115 | 116 | > 文章中并不会介绍 DFS 的原理,如果对 DFS 不了解的读者可以看一下这个[视频]([https://www.youtube.com/watch?v=tlPuVe5Otio]),或者找以下相关资料了解一下 DFS 的实现。 117 | 118 | 接下来就是 DFS 的实现: 119 | 120 | ```objectivec 121 | - (NSSet *> *)_findRetainCyclesInObject:(FBObjectiveCGraphElement *)graphElement 122 | stackDepth:(NSUInteger)stackDepth { 123 | ... 124 | [stack addObject:wrappedObject]; 125 | 126 | while ([stack count] > 0) { 127 | @autoreleasepool { 128 | FBNodeEnumerator *top = [stack lastObject]; 129 | [objectsOnPath addObject:top]; 130 | 131 | FBNodeEnumerator *firstAdjacent = [top nextObject]; 132 | if (firstAdjacent) { 133 | 134 | BOOL shouldPushToStack = NO; 135 | 136 | if ([objectsOnPath containsObject:firstAdjacent]) { 137 | NSUInteger index = [stack indexOfObject:firstAdjacent]; 138 | NSInteger length = [stack count] - index; 139 | 140 | if (index == NSNotFound) { 141 | shouldPushToStack = YES; 142 | } else { 143 | NSRange cycleRange = NSMakeRange(index, length); 144 | NSMutableArray *cycle = [[stack subarrayWithRange:cycleRange] mutableCopy]; 145 | [cycle replaceObjectAtIndex:0 withObject:firstAdjacent]; 146 | 147 | [retainCycles addObject:[self _shiftToUnifiedCycle:[self _unwrapCycle:cycle]]]; 148 | } 149 | } else { 150 | shouldPushToStack = YES; 151 | } 152 | 153 | if (shouldPushToStack) { 154 | if ([stack count] < stackDepth) { 155 | [stack addObject:firstAdjacent]; 156 | } 157 | } 158 | } else { 159 | [stack removeLastObject]; 160 | [objectsOnPath removeObject:top]; 161 | } 162 | } 163 | } 164 | return retainCycles; 165 | } 166 | ``` 167 | 168 | 这里其实就是对 DFS 的具体实现,其中比较重要的有两点,一是使用 `nextObject` 获取下一个需要遍历的对象,二是对查找到的环进行处理和筛选;在这两点之中,第一点相对重要,因为 `nextObject` 的实现是调用 `allRetainedObjects` 方法获取被当前对象持有的对象,如果没有这个方法,我们就无法获取当前对象的邻接结点,更无从谈起遍历了: 169 | 170 | ```objectivec 171 | - (FBNodeEnumerator *)nextObject { 172 | if (!_object) { 173 | return nil; 174 | } else if (!_retainedObjectsSnapshot) { 175 | _retainedObjectsSnapshot = [_object allRetainedObjects]; 176 | _enumerator = [_retainedObjectsSnapshot objectEnumerator]; 177 | } 178 | 179 | FBObjectiveCGraphElement *next = [_enumerator nextObject]; 180 | 181 | if (next) { 182 | return [[FBNodeEnumerator alloc] initWithObject:next]; 183 | } 184 | 185 | return nil; 186 | } 187 | ``` 188 | 189 | 基本上所有图中的对象 `FBObjectiveCGraphElement` 以及它的子类 `FBObjectiveCBlock` `FBObjectiveCObject` 和 `FBObjectiveCNSCFTimer` 都实现了这个方法返回其持有的对象数组。获取数组之后,就再把其中的对象包装成新的 `FBNodeEnumerator` 实例,也就是下一个 `Vertex`。 190 | 191 | 因为使用 `- subarrayWithRange:` 方法获取的数组中的对象都是 `FBNodeEnumerator` 的实例,还需要一定的处理才能返回: 192 | 193 | 1. - (NSArray *)_unwrapCycle:(NSArray *)cycle 194 | 2. - (NSArray *)_shiftToUnifiedCycle:(NSArray *)array 195 | 196 | 197 | `- _unwrapCycle:` 的作用是将数组中的每一个 `FBNodeEnumerator` 实例转换成 `FBObjectiveCGraphElement`: 198 | 199 | ```objectivec 200 | - (NSArray *)_unwrapCycle:(NSArray *)cycle { 201 | NSMutableArray *unwrappedArray = [NSMutableArray new]; 202 | for (FBNodeEnumerator *wrapped in cycle) { 203 | [unwrappedArray addObject:wrapped.object]; 204 | } 205 | 206 | return unwrappedArray; 207 | } 208 | ``` 209 | 210 | `- _shiftToUnifiedCycle:` 方法将每一个环中的元素按照**地址递增以及字母顺序**来排序,方法签名很好的说明了它们的功能,两个方法的代码就不展示了,它们的实现没有什么值得注意的地方: 211 | 212 | ```objectivec 213 | - (NSArray *)_shiftToUnifiedCycle:(NSArray *)array { 214 | return [self _shiftToLowestLexicographically:[self _shiftBufferToLowestAddress:array]]; 215 | } 216 | ``` 217 | 218 | 方法的作用是防止出现**相同环的不同表示方式**,比如说下面的两个环其实是完全相同的: 219 | 220 | ``` 221 | -> object1 -> object2 222 | -> object2 -> object1 223 | ``` 224 | 225 | 在获取图中的环并排序好之后,就可以讲这些环 union 一下,去除其中重复的元素,最后返回所有查找到的循环引用了。 226 | 227 | ## 总结 228 | 229 | 到目前为止整个 `FBRetainCycleDetector` 的原理介绍大概就结束了,其原理完全是基于 DFS 算法:把整个对象的之间的引用情况当做图进行处理,查找其中的环,就找到了循环引用。不过原理真的很简单,如果这个 lib 的实现仅仅是这样的话,我也不会写几篇文章来专门分析这个框架,真正让我感兴趣的还是 `- allRetainedObjects` 方法**在各种对象以及 block 中获得它们强引用的对象的过程**,这也是之后的文章要分析的主要内容。 230 | 231 | > Follow: [Draveness · Github](https://github.com/Draveness) 232 | 233 | 234 | -------------------------------------------------------------------------------- /contents/FBRetainCycleDetector/如何实现 iOS 中的 Associated Object.md: -------------------------------------------------------------------------------- 1 | # 如何实现 iOS 中的 Associated Object 2 | 3 | 这一篇文章是对 [FBRetainCycleDetector]([https://github.com/facebook/FBRetainCycleDetector]) 中实现的关联对象机制的分析;因为追踪的需要, FBRetainCycleDetector 重新实现了关联对象,本文主要就是对其实现关联对象的方法进行分析。 4 | 5 | 文章中涉及的类主要就是 `FBAssociationManager`: 6 | 7 | > FBAssociationManager is a tracker of object associations. For given object it can return all objects that are being retained by this object with objc_setAssociatedObject & retain policy. 8 | 9 | FBRetainCycleDetector 在对关联对象进行追踪时,修改了底层处理关联对象的两个 C 函数,`objc_setAssociatedObject` 和 `objc_removeAssociatedObjects`,在这里不会分析它是如何修改底层 C 语言函数实现的,如果想要了解相关的内容,可以阅读下面的文章。 10 | 11 | > 关于如何动态修改 C 语言函数实现可以看[动态修改 C 语言函数的实现]([https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/fishhook/动态修改%20C%20语言函数的实现.md])这篇文章,使用的第三方框架是 [fishhook]([https://github.com/facebook/fishhook])。 12 | 13 | ## FBAssociationManager 14 | 15 | 在 `FBAssociationManager` 的类方法 `+ hook` 调用时,fishhook 会修改 `objc_setAssociatedObject` 和 `objc_removeAssociatedObjects` 方法: 16 | 17 | ```objectivec 18 | + (void)hook { 19 | #if _INTERNAL_RCD_ENABLED 20 | std::lock_guard l(*FB::AssociationManager::hookMutex); 21 | rcd_rebind_symbols((struct rcd_rebinding[2]){ 22 | { 23 | "objc_setAssociatedObject", 24 | (void *)FB::AssociationManager::fb_objc_setAssociatedObject, 25 | (void **)&FB::AssociationManager::fb_orig_objc_setAssociatedObject 26 | }, 27 | { 28 | "objc_removeAssociatedObjects", 29 | (void *)FB::AssociationManager::fb_objc_removeAssociatedObjects, 30 | (void **)&FB::AssociationManager::fb_orig_objc_removeAssociatedObjects 31 | }}, 2); 32 | FB::AssociationManager::hookTaken = true; 33 | #endif //_INTERNAL_RCD_ENABLED 34 | } 35 | ``` 36 | 37 | 将它们的实现替换为 `FB::AssociationManager:: fb_objc_setAssociatedObject` 以及 `FB::AssociationManager::fb_objc_removeAssociatedObjects` 这两个 Cpp 静态方法。 38 | 39 | 上面的两个方法实现都位于 `FB::AssociationManager` 的命名空间中: 40 | 41 | ```objectivec 42 | namespace FB { namespace AssociationManager { 43 | using ObjectAssociationSet = std::unordered_set; 44 | using AssociationMap = std::unordered_map; 45 | 46 | static auto _associationMap = new AssociationMap(); 47 | static auto _associationMutex = new std::mutex; 48 | 49 | static std::mutex *hookMutex(new std::mutex); 50 | static bool hookTaken = false; 51 | 52 | ... 53 | } 54 | ``` 55 | 56 | 命名空间中有两个用于存储关联对象的数据结构: 57 | 58 | + `AssociationMap` 用于存储从对象到 `ObjectAssociationSet *` 指针的映射 59 | + `ObjectAssociationSet` 用于存储某对象所有关联对象的集合 60 | 61 | 其中还有几个比较重要的成员变量: 62 | 63 | + `_associationMap` 就是 `AssociationMap` 的实例,是一个用于存储所有关联对象的数据结构 64 | + `_associationMutex` 用于在修改关联对象时加锁,防止出现线程竞争等问题,导致不可预知的情况发生 65 | + `hookMutex` 以及 `hookTaken` 都是在类方法 `+ hook` 调用时使用的,用于保证 hook 只会执行一次并保证线程安全 66 | 67 | 用于追踪关联对象的静态方法 `fb_objc_setAssociatedObject` 只会追踪强引用: 68 | 69 | ```objectivec 70 | static void fb_objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) { 71 | { 72 | std::lock_guard l(*_associationMutex); 73 | if (policy == OBJC_ASSOCIATION_RETAIN || 74 | policy == OBJC_ASSOCIATION_RETAIN_NONATOMIC) { 75 | _threadUnsafeSetStrongAssociation(object, key, value); 76 | } else { 77 | // We can change the policy, we need to clear out the key 78 | _threadUnsafeResetAssociationAtKey(object, key); 79 | } 80 | } 81 | 82 | fb_orig_objc_setAssociatedObject(object, key, value, policy); 83 | } 84 | ``` 85 | 86 | `std::lock_guard l(*_associationMutex)` 对 `fb_objc_setAssociatedObject` 过程加锁,防止死锁问题,不过 `_associationMutex` 会在作用域之外被释放。 87 | 88 | 通过输入的 `policy` 我们可以判断哪些是强引用对象,然后调用 `_threadUnsafeSetStrongAssociation` 追踪它们,如果不是强引用对象,通过 `_threadUnsafeResetAssociationAtKey` 将 `key` 对应的 `value` 删除,保证追踪的正确性: 89 | 90 | ```objectivec 91 | void _threadUnsafeSetStrongAssociation(id object, void *key, id value) { 92 | if (value) { 93 | auto i = _associationMap->find(object); 94 | ObjectAssociationSet *refs; 95 | if (i != _associationMap->end()) { 96 | refs = i->second; 97 | } else { 98 | refs = new ObjectAssociationSet; 99 | (*_associationMap)[object] = refs; 100 | } 101 | refs->insert(key); 102 | } else { 103 | _threadUnsafeResetAssociationAtKey(object, key); 104 | } 105 | } 106 | ``` 107 | 108 | `_threadUnsafeSetStrongAssociation` 会以 object 作为键,查找或者创建一个 `ObjectAssociationSet *` 集合,将新的 `key` 插入到集合中,当然,如果 `value == nil` 或者上面 `fb_objc_setAssociatedObject` 方法中传入的 `policy` 是非 `retain` 的就会调用 `_threadUnsafeResetAssociationAtKey ` 重置 `ObjectAssociationSet` 中的关联对象: 109 | 110 | ```objectivec 111 | void _threadUnsafeResetAssociationAtKey(id object, void *key) { 112 | auto i = _associationMap->find(object); 113 | 114 | if (i == _associationMap->end()) { 115 | return; 116 | } 117 | 118 | auto *refs = i->second; 119 | auto j = refs->find(key); 120 | if (j != refs->end()) { 121 | refs->erase(j); 122 | } 123 | } 124 | ``` 125 | 126 | 同样在查找到对应的 `ObjectAssociationSet` 之后会擦除 `key` 对应的值,`_threadUnsafeRemoveAssociations` 的实现与这个方法也差不多,相较于 reset 方法移除某一个对象的**所有**关联对象,该方法仅仅移除了某一个 `key` 对应的值。 127 | 128 | ```objectivec 129 | void _threadUnsafeRemoveAssociations(id object) { 130 | if (_associationMap->size() == 0 ){ 131 | return; 132 | } 133 | 134 | auto i = _associationMap->find(object); 135 | if (i == _associationMap->end()) { 136 | return; 137 | } 138 | 139 | auto *refs = i->second; 140 | delete refs; 141 | _associationMap->erase(i); 142 | } 143 | ``` 144 | 145 | 146 | 调用 `_threadUnsafeRemoveAssociations` 的方法 `fb_objc_removeAssociatedObjects` 的实现也很简单,利用了上面的方法,并在执行结束后,使用原 `obj_removeAssociatedObjects` 方法对应的函数指针 `fb_orig_objc_removeAssociatedObjects` 移除关联对象: 147 | 148 | ```objectivec 149 | static void fb_objc_removeAssociatedObjects(id object) { 150 | { 151 | std::lock_guard l(*_associationMutex); 152 | _threadUnsafeRemoveAssociations(object); 153 | } 154 | 155 | fb_orig_objc_removeAssociatedObjects(object); 156 | } 157 | ``` 158 | 159 | ## FBObjectiveCGraphElement 获取关联对象 160 | 161 | 因为在获取某一个对象持有的所有强引用时,不可避免地需要获取其强引用的关联对象;因此我们也就需要使用 `FBAssociationManager` 提供的 `+ associationsForObject:` 接口获取所有**强引用**关联对象: 162 | 163 | ```objectivec 164 | - (NSSet *)allRetainedObjects { 165 | NSArray *retainedObjectsNotWrapped = [FBAssociationManager associationsForObject:_object]; 166 | NSMutableSet *retainedObjects = [NSMutableSet new]; 167 | 168 | for (id obj in retainedObjectsNotWrapped) { 169 | FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, obj, _configuration, @[@"__associated_object"]); 170 | if (element) { 171 | [retainedObjects addObject:element]; 172 | } 173 | } 174 | 175 | return retainedObjects; 176 | } 177 | ``` 178 | 179 | 这个接口调用我们在上一节中介绍的 `_associationMap`,最后得到某一个对象的所有关联对象的强引用: 180 | 181 | ```objectivec 182 | + (NSArray *)associationsForObject:(id)object { 183 | return FB::AssociationManager::associations(object); 184 | } 185 | 186 | NSArray *associations(id object) { 187 | std::lock_guard l(*_associationMutex); 188 | if (_associationMap->size() == 0 ){ 189 | return nil; 190 | } 191 | 192 | auto i = _associationMap->find(object); 193 | if (i == _associationMap->end()) { 194 | return nil; 195 | } 196 | 197 | auto *refs = i->second; 198 | 199 | NSMutableArray *array = [NSMutableArray array]; 200 | for (auto &key: *refs) { 201 | id value = objc_getAssociatedObject(object, key); 202 | if (value) { 203 | [array addObject:value]; 204 | } 205 | } 206 | 207 | return array; 208 | } 209 | ``` 210 | 211 | 这部分的代码没什么好解释的,遍历所有的 `key`,检测是否真的存在关联对象,然后加入可变数组,最后返回。 212 | 213 | ## 总结 214 | 215 | FBRetainCycleDetector 为了追踪某一 `NSObject` 对关联对象的引用,重新实现了关联对象模块,不过其实现与 ObjC 运行时中对关联对象的实现其实所差无几,如果对运行时中的关联对象实现原理有兴趣的话,可以看[关联对象 AssociatedObject 完全解析](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/关联对象%20AssociatedObject%20完全解析.md)这篇文章,它介绍了底层运行时中的关联对象的实现。 216 | 217 | 这是 FBRetainCycleDetector 系列文章中的第三篇,第四篇也是最后一篇文章会介绍 FBRetainCycleDetector 是如何获取 block 持有的强引用的,这也是我觉得整个框架中实现最精彩的一部分。 218 | 219 | > Follow: [Draveness · Github](https://github.com/Draveness) 220 | 221 | 222 | -------------------------------------------------------------------------------- /contents/IQKeyboardManager/images/IQKeyboardManager-Hierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/IQKeyboardManager/images/IQKeyboardManager-Hierarchy.png -------------------------------------------------------------------------------- /contents/IQKeyboardManager/images/IQKeyboardManager-hide-keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/IQKeyboardManager/images/IQKeyboardManager-hide-keyboard.png -------------------------------------------------------------------------------- /contents/IQKeyboardManager/images/IQToolBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/IQKeyboardManager/images/IQToolBar.png -------------------------------------------------------------------------------- /contents/IQKeyboardManager/images/IQToolBarItem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/IQKeyboardManager/images/IQToolBarItem.png -------------------------------------------------------------------------------- /contents/IQKeyboardManager/images/UITextView-Notification-IQKeyboardManager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/IQKeyboardManager/images/UITextView-Notification-IQKeyboardManager.png -------------------------------------------------------------------------------- /contents/IQKeyboardManager/images/easiest-integration-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/IQKeyboardManager/images/easiest-integration-demo.png -------------------------------------------------------------------------------- /contents/IQKeyboardManager/images/notification-IQKeyboardManager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/IQKeyboardManager/images/notification-IQKeyboardManager.png -------------------------------------------------------------------------------- /contents/MBProgressHUD/iOS 源代码分析 --- MBProgressHUD.md: -------------------------------------------------------------------------------- 1 | # iOS 源代码分析 --- MBProgressHUD 2 | 3 | [MBProgressHUD]() 是一个为 iOS app 添加透明浮层 HUD 的第三方框架. 作为一个 UI 层面的框架, 它的实现很简单, 但是其中也有一些非常有意思的代码. 4 | 5 | ## MBProgressHUD 6 | 7 | `MBProgressHUD` 是一个 `UIView` 的子类, 它提供了一系列的创建 `HUD` 的方法. 我们在这里会主要介绍三种使用 `HUD` 的方法. 8 | 9 | + `+ showHUDAddedTo:animated:` 10 | + `- showAnimated:whileExecutingBlock:onQueue:completionBlock:` 11 | + `- showWhileExecuting:onTarget:withObject:` 12 | 13 | ## `+ showHUDAddedTo:animated:` 14 | 15 | `MBProgressHUD` 提供了一对类方法 `+ showHUDAddedTo:animated:` 和 `+ hideHUDForView:animated:` 来创建和隐藏 `HUD`, 这是创建和隐藏 `HUD` 最简单的一组方法 16 | 17 | ```objectivec 18 | + (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { 19 | MBProgressHUD *hud = [[self alloc] initWithView:view]; 20 | hud.removeFromSuperViewOnHide = YES; 21 | [view addSubview:hud]; 22 | [hud show:animated]; 23 | return MB_AUTORELEASE(hud); 24 | } 25 | ``` 26 | 27 | ### `- initWithView:` 28 | 29 | 首先调用 `+ alloc` `- initWithView:` 方法返回一个 `MBProgressHUD` 的实例, `- initWithView:` 方法会调用当前类的 `- initWithFrame:` 方法. 30 | 31 | 通过 `- initWithFrame:` 方法的执行, 会为 `MBProgressHUD` 的一些属性设置一系列的默认值. 32 | 33 | ```objectivec 34 | - (id)initWithFrame:(CGRect)frame { 35 | self = [super initWithFrame:frame]; 36 | if (self) { 37 | // Set default values for properties 38 | self.animationType = MBProgressHUDAnimationFade; 39 | self.mode = MBProgressHUDModeIndeterminate; 40 | ... 41 | // Make it invisible for now 42 | self.alpha = 0.0f; 43 | 44 | [self registerForKVO]; 45 | ... 46 | } 47 | return self; 48 | } 49 | ``` 50 | 51 | 在 `MBProgressHUD` 初始化的过程中, 有一个需要注意的方法 `- registerForKVO`, 我们会在之后查看该方法的实现. 52 | 53 | ### `- show:` 54 | 55 | 在初始化一个 `HUD` 并添加到 `view` 上之后, 这时 `HUD` 并没有显示出来, 因为在初始化时, `view.alpha` 被设置为 0. 所以我们接下来会调用 `- show:` 方法使 `HUD` 显示到屏幕上. 56 | 57 | ```objectivec 58 | - (void)show:(BOOL)animated { 59 | NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread."); 60 | useAnimation = animated; 61 | // If the grace time is set postpone the HUD display 62 | if (self.graceTime > 0.0) { 63 | NSTimer *newGraceTimer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; 64 | [[NSRunLoop currentRunLoop] addTimer:newGraceTimer forMode:NSRunLoopCommonModes]; 65 | self.graceTimer = newGraceTimer; 66 | } 67 | // ... otherwise show the HUD imediately 68 | else { 69 | [self showUsingAnimation:useAnimation]; 70 | } 71 | } 72 | ``` 73 | 74 | 因为在 iOS 开发中, 对于 `UIView` 的处理必须在主线程中, 所以在这里我们要先用 `[NSThread isMainThread]` 来确认当前前程为主线程. 75 | 76 | 如果 `graceTime` 为 `0`, 那么直接调用 `- showUsingAnimation:` 方法, 否则会创建一个 `newGraceTimer` 当然这个 `timer` 对应的 `selector` 最终调用的也是 `- showUsingAnimation:` 方法. 77 | 78 | ### `- showUsingAnimation:` 79 | 80 | ```objectivec 81 | - (void)showUsingAnimation:(BOOL)animated { 82 | // Cancel any scheduled hideDelayed: calls 83 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 84 | [self setNeedsDisplay]; 85 | 86 | if (animated && animationType == MBProgressHUDAnimationZoomIn) { 87 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); 88 | } else if (animated && animationType == MBProgressHUDAnimationZoomOut) { 89 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); 90 | } 91 | self.showStarted = [NSDate date]; 92 | // Fade in 93 | if (animated) { 94 | [UIView beginAnimations:nil context:NULL]; 95 | [UIView setAnimationDuration:0.30]; 96 | self.alpha = 1.0f; 97 | if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) { 98 | self.transform = rotationTransform; 99 | } 100 | [UIView commitAnimations]; 101 | } 102 | else { 103 | self.alpha = 1.0f; 104 | } 105 | } 106 | ``` 107 | 108 | 这个方法的核心功能就是根据 `animationType` 为 `HUD` 的出现添加合适的动画. 109 | 110 | ```objectivec 111 | typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) { 112 | /** Opacity animation */ 113 | MBProgressHUDAnimationFade, 114 | /** Opacity + scale animation */ 115 | MBProgressHUDAnimationZoom, 116 | MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom, 117 | MBProgressHUDAnimationZoomIn 118 | }; 119 | ``` 120 | 121 | 它在方法刚调用时会通过 `- cancelPreviousPerformRequestsWithTarget:` 移除附加在 `HUD` 上的所有 `selector`, 这样可以保证该方法不会多次调用. 122 | 123 | 同时也会保存 `HUD` 的出现时间. 124 | 125 | ```objectivec 126 | self.showStarted = [NSDate date] 127 | ``` 128 | 129 | ### `+ hideHUDForView:animated:` 130 | 131 | ```objectivec 132 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { 133 | MBProgressHUD *hud = [self HUDForView:view]; 134 | if (hud != nil) { 135 | hud.removeFromSuperViewOnHide = YES; 136 | [hud hide:animated]; 137 | return YES; 138 | } 139 | return NO; 140 | } 141 | ``` 142 | 143 | `+ hideHUDForView:animated:` 方法的实现和 `+ showHUDAddedTo:animated:` 差不多, `+ HUDForView:` 方法会返回对应 `view` 最上层的 `MBProgressHUD` 的实例. 144 | 145 | ```objectivec 146 | + (MB_INSTANCETYPE)HUDForView:(UIView *)view { 147 | NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; 148 | for (UIView *subview in subviewsEnum) { 149 | if ([subview isKindOfClass:self]) { 150 | return (MBProgressHUD *)subview; 151 | } 152 | } 153 | return nil; 154 | } 155 | ``` 156 | 157 | 然后调用的 `- hide:` 方法和 `- hideUsingAnimation:` 方法也没有什么特别的, 只有在 `HUD` 隐藏之后 `- done` 负责隐藏执行 `completionBlock` 和 `delegate` 回调. 158 | 159 | ```objectivec 160 | - (void)done { 161 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 162 | isFinished = YES; 163 | self.alpha = 0.0f; 164 | if (removeFromSuperViewOnHide) { 165 | [self removeFromSuperview]; 166 | } 167 | #if NS_BLOCKS_AVAILABLE 168 | if (self.completionBlock) { 169 | self.completionBlock(); 170 | self.completionBlock = NULL; 171 | } 172 | #endif 173 | if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { 174 | [delegate performSelector:@selector(hudWasHidden:) withObject:self]; 175 | } 176 | } 177 | ``` 178 | 179 | ### `- showAnimated:whileExecutingBlock:onQueue:completionBlock:` 180 | 181 | > 当 `block` 指定的队列执行时, 显示 `HUD`, 并在 `HUD` 消失时, 调用 `completion`. 182 | 183 | 同时 `MBProgressHUD` 也提供一些其他的便利方法实现这一功能: 184 | 185 | ```objectivec 186 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block; 187 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(MBProgressHUDCompletionBlock)completion; 188 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue; 189 | ``` 190 | 191 | 该方法会**异步**在指定 `queue` 上运行 `block` 并在 `block` 执行结束调用 `- cleanUp`. 192 | 193 | ``` 194 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue 195 | completionBlock:(MBProgressHUDCompletionBlock)completion { 196 | self.taskInProgress = YES; 197 | self.completionBlock = completion; 198 | dispatch_async(queue, ^(void) { 199 | block(); 200 | dispatch_async(dispatch_get_main_queue(), ^(void) { 201 | [self cleanUp]; 202 | }); 203 | }); 204 | [self show:animated]; 205 | } 206 | ``` 207 | 208 | 关于 `- cleanUp` 我们会在下一段中介绍. 209 | 210 | ### `- showWhileExecuting:onTarget:withObject:` 211 | 212 | > 当一个后台任务在新线程中执行时, 显示 `HUD`. 213 | 214 | ```objectivec 215 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { 216 | methodForExecution = method; 217 | targetForExecution = MB_RETAIN(target); 218 | objectForExecution = MB_RETAIN(object); 219 | // Launch execution in new thread 220 | self.taskInProgress = YES; 221 | [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil]; 222 | // Show HUD view 223 | [self show:animated]; 224 | } 225 | ``` 226 | 227 | 在保存 `methodForExecution` `targetForExecution` 和 `objectForExecution` 之后, 会在新的线程中调用方法. 228 | 229 | ```objectivec 230 | - (void)launchExecution { 231 | @autoreleasepool { 232 | #pragma clang diagnostic push 233 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 234 | // Start executing the requested task 235 | [targetForExecution performSelector:methodForExecution withObject:objectForExecution]; 236 | #pragma clang diagnostic pop 237 | // Task completed, update view in main thread (note: view operations should 238 | // be done only in the main thread) 239 | [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO]; 240 | } 241 | } 242 | ``` 243 | 244 | `- launchExecution` 会创建一个自动释放池, 然后再这个自动释放池中调用方法, 并在方法调用结束之后在主线程执行 `- cleanUp`. 245 | 246 | ## Trick 247 | 248 | 在 `MBProgressHUD` 中有很多神奇的魔法来解决一些常见的问题. 249 | 250 | ### ARC 251 | 252 | `MBProgressHUD` 使用了一系列神奇的宏定义来兼容 `MRC`. 253 | 254 | ```objectivec 255 | #ifndef MB_INSTANCETYPE 256 | #if __has_feature(objc_instancetype) 257 | #define MB_INSTANCETYPE instancetype 258 | #else 259 | #define MB_INSTANCETYPE id 260 | #endif 261 | #endif 262 | 263 | #ifndef MB_STRONG 264 | #if __has_feature(objc_arc) 265 | #define MB_STRONG strong 266 | #else 267 | #define MB_STRONG retain 268 | #endif 269 | #endif 270 | 271 | #ifndef MB_WEAK 272 | #if __has_feature(objc_arc_weak) 273 | #define MB_WEAK weak 274 | #elif __has_feature(objc_arc) 275 | #define MB_WEAK unsafe_unretained 276 | #else 277 | #define MB_WEAK assign 278 | #endif 279 | #endif 280 | ``` 281 | 282 | 通过宏定义 `__has_feature` 来判断当前环境是否启用了 ARC, 使得不同环境下宏不会出错. 283 | 284 | ### KVO 285 | 286 | `MBProgressHUD` 通过 `@property` 生成了一系列的属性. 287 | 288 | ```objectivec 289 | - (NSArray *)observableKeypaths { 290 | return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", @"labelColor", 291 | @"detailsLabelText", @"detailsLabelFont", @"detailsLabelColor", @"progress", @"activityIndicatorColor", nil]; 292 | } 293 | ``` 294 | 295 | 这些属性在改变的时候不会, 重新渲染整个 `view`, 我们在一般情况下覆写 `setter` 方法, 然后再 `setter` 方法中刷新对应的属性, 在 `MBProgressHUD` 中使用 KVO 来解决这个问题. 296 | 297 | ```objectivec 298 | - (void)registerForKVO { 299 | for (NSString *keyPath in [self observableKeypaths]) { 300 | [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; 301 | } 302 | } 303 | 304 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 305 | if (![NSThread isMainThread]) { 306 | [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO]; 307 | } else { 308 | [self updateUIForKeypath:keyPath]; 309 | } 310 | } 311 | 312 | - (void)updateUIForKeypath:(NSString *)keyPath { 313 | if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"] || 314 | [keyPath isEqualToString:@"activityIndicatorColor"]) { 315 | [self updateIndicators]; 316 | } else if ([keyPath isEqualToString:@"labelText"]) { 317 | label.text = self.labelText; 318 | } else if ([keyPath isEqualToString:@"labelFont"]) { 319 | label.font = self.labelFont; 320 | } else if ([keyPath isEqualToString:@"labelColor"]) { 321 | label.textColor = self.labelColor; 322 | } else if ([keyPath isEqualToString:@"detailsLabelText"]) { 323 | detailsLabel.text = self.detailsLabelText; 324 | } else if ([keyPath isEqualToString:@"detailsLabelFont"]) { 325 | detailsLabel.font = self.detailsLabelFont; 326 | } else if ([keyPath isEqualToString:@"detailsLabelColor"]) { 327 | detailsLabel.textColor = self.detailsLabelColor; 328 | } else if ([keyPath isEqualToString:@"progress"]) { 329 | if ([indicator respondsToSelector:@selector(setProgress:)]) { 330 | [(id)indicator setValue:@(progress) forKey:@"progress"]; 331 | } 332 | return; 333 | } 334 | [self setNeedsLayout]; 335 | [self setNeedsDisplay]; 336 | } 337 | ``` 338 | 339 | `- observeValueForKeyPath:ofObject:change:context:` 方法中的代码是为了保证 UI 的更新一定是在主线程中, 而 `- updateUIForKeypath:` 方法负责 UI 的更新. 340 | 341 | ## End 342 | 343 | `MBProgressHUD` 由于是一个 UI 的第三方库, 所以它的实现还是挺简单的. 344 | 345 | 346 | 347 | Follow: [@Draveness](https://github.com/Draveness) 348 | 349 | 350 | -------------------------------------------------------------------------------- /contents/OHHTTPStubs/iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求.md: -------------------------------------------------------------------------------- 1 | ![](images/intercept.png) 2 | 3 | # iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求 4 | 5 | 这篇文章会提供一种在 Cocoa 层拦截所有 HTTP 请求的方法,其实标题已经说明了拦截 HTTP 请求需要的了解的就是 `NSURLProtocol`。 6 | 7 | 由于文章的内容较长,会分成两部分,这篇文章介绍 `NSURLProtocol` 拦截 HTTP 请求的原理,另一篇文章[如何进行 HTTP Mock(iOS)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/OHHTTPStubs/如何进行%20HTTP%20Mock(iOS).md) 8 | 介绍这个原理在 `OHHTTPStubs` 中的应用,它是如何 Mock(伪造)某个 HTTP 请求对应的响应的。 9 | 10 | ## NSURLProtocol 11 | 12 | `NSURLProtocol` 是苹果为我们提供的 [URL Loading System](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html) 的一部分,这是一张从官方文档贴过来的图片: 13 | 14 | ![URL-loading-syste](images/URL-loading-system.png) 15 | 16 | 官方文档对 `NSURLProtocol` 的描述是这样的: 17 | 18 | > An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports. 19 | 20 | 在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的 `NSURLProtocol` 对象处理对应的 URL 请求,而我们需要做的就是写一个继承自 `NSURLProtocol` 的类,并通过 `- registerClass:` 方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。 21 | 22 | 这样,我们需要解决的核心问题就变成了如何使用 `NSURLProtocol` 来处理所有的网络请求,这里使用苹果官方文档中的 [CustomHTTPProtocol](https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/CustomHTTPProtocol.zip) 进行介绍,你可以点击[这里](https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/CustomHTTPProtocol.zip)下载源代码。 23 | 24 | 在这个工程中 `CustomHTTPProtocol.m` 是需要重点关注的文件,`CustomHTTPProtocol` 就是 `NSURLProtocol` 的子类: 25 | 26 | ```objectivec 27 | @interface CustomHTTPProtocol : NSURLProtocol 28 | 29 | ... 30 | 31 | @end 32 | ``` 33 | 34 | 现在重新回到需要解决的问题,也就是 **如何使用 NSURLProtocol 拦截 HTTP 请求?**,有这个么几个问题需要去解决: 35 | 36 | + 如何决定哪些请求需要当前协议对象处理? 37 | + 对当前的请求对象需要进行哪些处理? 38 | + `NSURLProtocol` 如何实例化? 39 | + 如何发出 HTTP 请求并且将响应传递给调用者? 40 | 41 | 上面的这几个问题其实都可以通过 `NSURLProtocol` 为我们提供的 API 来解决,决定请求是否需要当前协议对象处理的方法是:`+ canInitWithRequest`: 42 | 43 | ```objectivec 44 | + (BOOL)canInitWithRequest:(NSURLRequest *)request { 45 | BOOL shouldAccept; 46 | NSURL *url; 47 | NSString *scheme; 48 | 49 | shouldAccept = (request != nil); 50 | if (shouldAccept) { 51 | url = [request URL]; 52 | shouldAccept = (url != nil); 53 | } 54 | return shouldAccept; 55 | } 56 | ``` 57 | 58 | 因为项目中的这个方法是大约有 60 多行,在这里只粘贴了其中的一部分,只为了说明该方法的作用:每一次请求都会有一个 `NSURLRequest` 实例,上述方法会拿到所有的请求对象,我们就可以根据对应的请求选择是否处理该对象;而上面的代码只会处理所有 `URL` 不为空的请求。 59 | 60 | 请求经过 `+ canInitWithRequest:` 方法过滤之后,我们得到了所有要处理的请求,接下来需要对请求进行一定的操作,而这都会在 `+ canonicalRequestForRequest:` 中进行,虽然它与 `+ canInitWithRequest:` 方法传入的 request 对象都是一个,但是最好不要在 `+ canInitWithRequest:` 中操作对象,可能会有语义上的问题;所以,我们需要覆写 `+ canonicalRequestForRequest:` 方法提供一个标准的请求对象: 61 | 62 | ```objectivec 63 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { 64 | return request; 65 | } 66 | ``` 67 | 68 | 这里对请求不做任何修改,直接返回,当然你也可以给这个请求加个 header,只要最后返回一个 `NSURLRequest` 对象就可以。 69 | 70 | 在得到了需要的请求对象之后,就可以初始化一个 `NSURLProtocol` 对象了: 71 | 72 | ```objectivec 73 | - (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id )client { 74 | return [super initWithRequest:request cachedResponse:cachedResponse client:client]; 75 | } 76 | ``` 77 | 78 | 在这里直接调用 `super` 的指定构造器方法,实例化一个对象,然后就进入了发送网络请求,获取数据并返回的阶段了: 79 | 80 | ```objectivec 81 | - (void)startLoading { 82 | NSURLSession *session = [NSURLSession sessionWithConfiguration:[[NSURLSessionConfiguration alloc] init] delegate:self delegateQueue:nil]; 83 | NSURLSessionDataTask *task = [session dataTaskWithRequest:self.request]; 84 | [task resume]; 85 | } 86 | ``` 87 | 88 | > 这里使用简化了 CustomHTTPClient 中的项目代码,可以达到几乎相同的效果。 89 | 90 | 你可以在 `- startLoading` 中使用任何方法来对协议对象持有的 `request` 进行转发,包括 `NSURLSession`、 `NSURLConnection` 甚至使用 AFNetworking 等网络库,只要你能在回调方法中把数据传回 `client`,帮助其正确渲染就可以,比如这样: 91 | 92 | ```objectivec 93 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { 94 | [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed]; 95 | 96 | completionHandler(NSURLSessionResponseAllow); 97 | } 98 | 99 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { 100 | [[self client] URLProtocol:self didLoadData:data]; 101 | } 102 | ``` 103 | 104 | > 当然这里省略后的代码只会保证大多数情况下的正确执行,只是给你一个对获取响应数据粗略的认知,如果你需要更加详细的代码,我觉得最好还是查看一下 `CustomHTTPProtocol` 中对 HTTP 响应处理的代码,也就是 `NSURLSessionDelegate` 协议实现的部分。 105 | 106 | `client` 你可以理解为当前网络请求的发起者,所有的 `client` 都实现了 `NSURLProtocolClient` 协议,协议的作用就是在 HTTP 请求发出以及接受响应时向其它对象传输数据: 107 | 108 | ```objectivec 109 | @protocol NSURLProtocolClient 110 | ... 111 | - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy; 112 | 113 | - (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data; 114 | 115 | - (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol; 116 | ... 117 | @end 118 | ``` 119 | 120 | 当然这个协议中还有很多其他的方法,比如 HTTPS 验证、重定向以及响应缓存相关的方法,你需要在合适的时候调用这些代理方法,对信息进行传递。 121 | 122 | 如果你只是继承了 `NSURLProtocol` 并且实现了上述方法,依然不能达到预期的效果,完成对 HTTP 请求的拦截,你还需要在 URL 加载系统中注册当前类: 123 | 124 | ```objectivec 125 | [NSURLProtocol registerClass:self]; 126 | ``` 127 | 128 | > 需要注意的是 `NSURLProtocol` 只能拦截 `UIURLConnection`、`NSURLSession` 和 `UIWebView` 中的请求,对于 `WKWebView` 中发出的网络请求也无能为力,如果真的要拦截来自 `WKWebView` 中的请求,还是需要实现 `WKWebView` 对应的 `WKNavigationDelegate`,并在代理方法中获取请求。 129 | > 无论是 `NSURLProtocol`、`NSURLConnection` 还是 `NSURLSession` 都会走底层的 socket,但是 `WKWebView` 可能由于基于 WebKit,并不会执行 C socket 相关的函数对 HTTP 请求进行处理,具体会执行什么代码暂时不是很清楚,如果对此有兴趣的读者,可以联系笔者一起讨论。 130 | 131 | ## 总结 132 | 133 | 如果你只想了解如何对 HTTP 请求进行拦截,其实看到这里就可以了,不过如果你想应用文章中的内容或者希望了解如何伪造 HTTP 响应,可以看下一篇文章[如何进行 HTTP Mock(iOS)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/OHHTTPStubs/如何进行%20HTTP%20Mock(iOS).md) 134 | 。 135 | 136 | > Follow: [Draveness · Github](https://github.com/Draveness) 137 | 138 | ## References 139 | 
+ [NSURLProtocol]([http://nshipster.com/nsurlprotocol/]) 140 | [如何进行 HTTP Mock(iOS)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/OHHTTPStubs/如何进行%20HTTP%20Mock(iOS).md) 141 | 142 | -------------------------------------------------------------------------------- /contents/OHHTTPStubs/images/OHHTTPStubs-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/OHHTTPStubs/images/OHHTTPStubs-test.png -------------------------------------------------------------------------------- /contents/OHHTTPStubs/images/URL-loading-system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/OHHTTPStubs/images/URL-loading-system.png -------------------------------------------------------------------------------- /contents/OHHTTPStubs/images/http-mock-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/OHHTTPStubs/images/http-mock-test.png -------------------------------------------------------------------------------- /contents/OHHTTPStubs/images/intercept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/OHHTTPStubs/images/intercept.png -------------------------------------------------------------------------------- /contents/ProtocolKit/images/protocol-demo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/ProtocolKit/images/protocol-demo.jpeg -------------------------------------------------------------------------------- /contents/ProtocolKit/images/protocol-recordings.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/ProtocolKit/images/protocol-recordings.jpeg -------------------------------------------------------------------------------- /contents/ProtocolKit/如何在 Objective-C 中实现协议扩展.md: -------------------------------------------------------------------------------- 1 | # 如何在 Objective-C 中实现协议扩展 2 | 3 | ![protocol-recordings](images/protocol-recordings.jpeg) 4 | 5 | Swift 中的协议扩展为 iOS 开发带来了非常多的可能性,它为我们提供了一种类似多重继承的功能,帮助我们减少一切可能导致重复代码的地方。 6 | 7 | ## 关于 Protocol Extension 8 | 9 | 在 Swift 中比较出名的 Then 就是使用了协议扩展为所有的 `AnyObject` 添加方法,而且不需要调用 runtime 相关的 API,其实现简直是我见过最简单的开源框架之一: 10 | 11 | ```swift 12 | public protocol Then {} 13 | 14 | extension Then where Self: AnyObject { 15 | public func then(@noescape block: Self -> Void) -> Self { 16 | block(self) 17 | return self 18 | } 19 | } 20 | 21 | extension NSObject: Then {} 22 | ``` 23 | 24 | 只有这么几行代码,就能为所有的 `NSObject` 添加下面的功能: 25 | 26 | ```swift 27 | let titleLabel = UILabel().then { 28 | $0.textColor = .blackColor() 29 | $0.textAlignment = .Center 30 | } 31 | ``` 32 | 33 | 这里没有调用任何的 runtime 相关 API,也没有在 `NSObject` 中进行任何的方法声明,甚至 `protocol Then {}` 协议本身都只有一个大括号,整个 Then 框架就是基于协议扩展来实现的。 34 | 35 | 在 Objective-C 中同样有协议,但是这些协议只是相当于接口,遵循某个协议的类只表明实现了这些接口,每个类都需要**对这些接口有单独的实现**,这就很可能会导致重复代码的产生。 36 | 37 | 而协议扩展可以调用协议中声明的方法,以及 `where Self: AnyObject` 中的 `AnyObject` 的类/实例方法,这就大大提高了可操作性,便于开发者写出一些意想不到的扩展。 38 | 39 | > 如果读者对 Protocol Extension 兴趣或者不了解协议扩展,可以阅读最后的 [Reference](#reference) 了解相关内容。 40 | 41 | ## ProtocolKit 42 | 43 | 其实协议扩展的强大之处就在于它能为遵循协议的类添加一些方法的实现,而不只是一些接口,而今天为各位读者介绍的 [ProtocolKit]([https://github.com/forkingdog/ProtocolKit]) 就实现了这一功能,为遵循协议的类添加方法。 44 | 45 | ### ProtocolKit 的使用 46 | 47 | 我们先来看一下如何使用 ProtocolKit,首先定义一个协议: 48 | 49 | ```objectivec 50 | @protocol TestProtocol 51 | 52 | @required 53 | 54 | - (void)fizz; 55 | 56 | @optional 57 | 58 | - (void)buzz; 59 | 60 | @end 61 | ``` 62 | 63 | 在协议中定义了两个方法,必须实现的方法 `fizz` 以及可选实现 `buzz`,然后使用 ProtocolKit 提供的接口 `defs` 来定义协议中方法的实现了: 64 | 65 | ```objectivec 66 | @defs(TestProtocol) 67 | 68 | - (void)buzz { 69 | NSLog(@"Buzz"); 70 | } 71 | 72 | @end 73 | ``` 74 | 75 | 这样所有遵循 `TestProtocol` 协议的对象都可以调用 `buzz` 方法,哪怕它们没有实现: 76 | 77 | ![protocol-demo](images/protocol-demo.jpeg) 78 | 79 | 上面的 `XXObject` 虽然没有实现 `buzz` 方法,但是该方法仍然成功执行了。 80 | 81 | ### ProtocolKit 的实现 82 | 83 | ProtocolKit 的主要原理仍然是 runtime 以及宏的;通过宏的使用来**隐藏类的声明以及实现的代码**,然后在 main 函数运行之前,**将类中的方法实现加载到内存**,使用 runtime 将实现**注入到目标类**中。 84 | 85 | > 如果你对上面的原理有所疑惑也不是太大的问题,这里只是给你一个 ProtocolKit 原理的简单描述,让你了解它是如何工作的。 86 | 87 | ProtocolKit 中有两条重要的执行路线: 88 | 89 | + `_pk_extension_load` 将协议扩展中的方法实现加载到了内存 90 | + `_pk_extension_inject_entry` 负责将扩展协议注入到实现协议的类 91 | 92 | #### 加载实现 93 | 94 | 首先要解决的问题是如何将方法实现加载到内存中,这里可以先了解一下上面使用到的 `defs` 接口,它其实只是一个调用了其它宏的**超级宏**~~这名字是我编的~~: 95 | 96 | ```objectivec 97 | #define defs _pk_extension 98 | 99 | #define _pk_extension($protocol) _pk_extension_imp($protocol, _pk_get_container_class($protocol)) 100 | 101 | #define _pk_extension_imp($protocol, $container_class) \ 102 | protocol $protocol; \ 103 | @interface $container_class : NSObject <$protocol> @end \ 104 | @implementation $container_class \ 105 | + (void)load { \ 106 | _pk_extension_load(@protocol($protocol), $container_class.class); \ 107 | } \ 108 | 109 | #define _pk_get_container_class($protocol) _pk_get_container_class_imp($protocol, __COUNTER__) 110 | #define _pk_get_container_class_imp($protocol, $counter) _pk_get_container_class_imp_concat(__PKContainer_, $protocol, $counter) 111 | #define _pk_get_container_class_imp_concat($a, $b, $c) $a ## $b ## _ ## $c 112 | ``` 113 | 114 | > 使用 `defs` 作为接口的是因为它是一个保留的 keyword,Xcode 会将它渲染成与 `@property` 等其他关键字相同的颜色。 115 | 116 | 上面的这一坨宏并不需要一个一个来分析,只需要看一下最后展开会变成什么: 117 | 118 | ```objectivec 119 | @protocol TestProtocol; 120 | 121 | @interface __PKContainer_TestProtocol_0 : NSObject 122 | 123 | @end 124 | 125 | @implementation __PKContainer_TestProtocol_0 126 | 127 | + (void)load { 128 | _pk_extension_load(@protocol(TestProtocol), __PKContainer_TestProtocol_0.class); 129 | } 130 | ``` 131 | 132 | 根据上面宏的展开结果,这里可以介绍上面的一坨宏的作用: 133 | 134 | + `defs` 这货没什么好说的,只是 `_pk_extension` 的别名,为了提供一个更加合适的名字作为接口 135 | + `_pk_extension` 向 `_pk_extension_imp ` 中传入 `$protocol` 和 `_pk_get_container_class($protocol)` 参数 136 | + `_pk_get_container_class` 的执行生成一个类名,上面生成的类名就是 `__PKContainer_TestProtocol_0`,这个类名是 `__PKContainer_`、 `$protocol` 和 `__COUNTER__` 拼接而成的(`__COUNTER__` 只是一个计数器,可以理解为每次调用时加一) 137 | + `_pk_extension_imp` 会以传入的类名生成一个遵循当前 `$protocol` 协议的类,然后在 `+ load` 方法中执行 `_pk_extension_load` 加载扩展协议 138 | 139 | 通过宏的运用成功隐藏了 `__PKContainer_TestProtocol_0` 类的声明以及实现,还有 `_pk_extension_load` 函数的调用: 140 | 141 | ```objectivec 142 | void _pk_extension_load(Protocol *protocol, Class containerClass) { 143 | 144 | pthread_mutex_lock(&protocolsLoadingLock); 145 | 146 | if (extendedProtcolCount >= extendedProtcolCapacity) { 147 | size_t newCapacity = 0; 148 | if (extendedProtcolCapacity == 0) { 149 | newCapacity = 1; 150 | } else { 151 | newCapacity = extendedProtcolCapacity << 1; 152 | } 153 | allExtendedProtocols = realloc(allExtendedProtocols, sizeof(*allExtendedProtocols) * newCapacity); 154 | extendedProtcolCapacity = newCapacity; 155 | } 156 | 157 | ... 158 | 159 | pthread_mutex_unlock(&protocolsLoadingLock); 160 | } 161 | ``` 162 | 163 | ProtocolKit 使用了 `protocolsLoadingLock` 来保证静态变量 `allExtendedProtocols` 以及 `extendedProtcolCount` `extendedProtcolCapacity` 不会因为线程竞争导致问题: 164 | 165 | + `allExtendedProtocols` 用于保存所有的 `PKExtendedProtocol` 结构体 166 | + 后面的两个变量确保数组不会越界,并在数组满的时候,将内存占用地址翻倍 167 | 168 | 方法的后半部分会在静态变量中寻找或创建传入的 `protocol` 对应的 `PKExtendedProtocol` 结构体: 169 | 170 | ```objectivec 171 | size_t resultIndex = SIZE_T_MAX; 172 | for (size_t index = 0; index < extendedProtcolCount; ++index) { 173 | if (allExtendedProtocols[index].protocol == protocol) { 174 | resultIndex = index; 175 | break; 176 | } 177 | } 178 | 179 | if (resultIndex == SIZE_T_MAX) { 180 | allExtendedProtocols[extendedProtcolCount] = (PKExtendedProtocol){ 181 | .protocol = protocol, 182 | .instanceMethods = NULL, 183 | .instanceMethodCount = 0, 184 | .classMethods = NULL, 185 | .classMethodCount = 0, 186 | }; 187 | resultIndex = extendedProtcolCount; 188 | extendedProtcolCount++; 189 | } 190 | 191 | _pk_extension_merge(&(allExtendedProtocols[resultIndex]), containerClass); 192 | ``` 193 | 194 | 这里调用的 `_pk_extension_merge` 方法非常重要,不过在介绍 `_pk_extension_merge` 之前,首先要了解一个用于保存协议扩展信息的私有结构体 `PKExtendedProtocol`: 195 | 196 | ```objectivec 197 | typedef struct { 198 | Protocol *__unsafe_unretained protocol; 199 | Method *instanceMethods; 200 | unsigned instanceMethodCount; 201 | Method *classMethods; 202 | unsigned classMethodCount; 203 | } PKExtendedProtocol; 204 | ``` 205 | 206 | `PKExtendedProtocol` 结构体中保存了协议的指针、实例方法、类方法、实例方法数以及类方法数用于框架记录协议扩展的状态。 207 | 208 | 回到 `_pk_extension_merge` 方法,它会将新的扩展方法追加到 `PKExtendedProtocol` 结构体的数组 `instanceMethods` 以及 `classMethods` 中: 209 | 210 | ```objectivec 211 | void _pk_extension_merge(PKExtendedProtocol *extendedProtocol, Class containerClass) { 212 | // Instance methods 213 | unsigned appendingInstanceMethodCount = 0; 214 | Method *appendingInstanceMethods = class_copyMethodList(containerClass, &appendingInstanceMethodCount); 215 | Method *mergedInstanceMethods = _pk_extension_create_merged(extendedProtocol->instanceMethods, 216 | extendedProtocol->instanceMethodCount, 217 | appendingInstanceMethods, 218 | appendingInstanceMethodCount); 219 | free(extendedProtocol->instanceMethods); 220 | extendedProtocol->instanceMethods = mergedInstanceMethods; 221 | extendedProtocol->instanceMethodCount += appendingInstanceMethodCount; 222 | 223 | // Class methods 224 | ... 225 | } 226 | ``` 227 | 228 | > 因为类方法的追加与实例方法几乎完全相同,所以上述代码省略了向结构体中的类方法追加方法的实现代码。 229 | 230 | 实现中使用 `class_copyMethodList` 从 `containerClass` 拉出方法列表以及方法数量;通过 `_pk_extension_create_merged` 返回一个合并之后的方法列表,最后在更新结构体中的 `instanceMethods` 以及 `instanceMethodCount` 成员变量。 231 | 232 | `_pk_extension_create_merged` 只是重新 `malloc` 一块内存地址,然后使用 `memcpy` 将所有的方法都复制到了这块内存地址中,最后返回首地址: 233 | 234 | ```objectivec 235 | Method *_pk_extension_create_merged(Method *existMethods, unsigned existMethodCount, Method *appendingMethods, unsigned appendingMethodCount) { 236 | 237 | if (existMethodCount == 0) { 238 | return appendingMethods; 239 | } 240 | unsigned mergedMethodCount = existMethodCount + appendingMethodCount; 241 | Method *mergedMethods = malloc(mergedMethodCount * sizeof(Method)); 242 | memcpy(mergedMethods, existMethods, existMethodCount * sizeof(Method)); 243 | memcpy(mergedMethods + existMethodCount, appendingMethods, appendingMethodCount * sizeof(Method)); 244 | return mergedMethods; 245 | } 246 | ``` 247 | 248 | 这一节的代码从使用宏生成的类中抽取方法实现,然后以结构体的形式加载到内存中,等待之后的方法注入。 249 | 250 | #### 注入方法实现 251 | 252 | 注入方法的时间点在 main 函数执行之前议实现的注入并不是在 `+ load` 方法 `+ initialize` 方法调用时进行的,而是使用的编译器指令(compiler directive) `__attribute__((constructor))` 实现的: 253 | 254 | ```objectivec 255 | __attribute__((constructor)) static void _pk_extension_inject_entry(void); 256 | ``` 257 | 258 | 使用上述编译器指令的函数会在 shared library 加载的时候执行,也就是 main 函数之前,可以看 StackOverflow 上的这个问题 [How exactly does __attribute__((constructor)) work?](http://stackoverflow.com/questions/2053029/how-exactly-does-attribute-constructor-work)。 259 | 260 | ```objectivec 261 | __attribute__((constructor)) static void _pk_extension_inject_entry(void) { 262 | #1:加锁 263 | unsigned classCount = 0; 264 | Class *allClasses = objc_copyClassList(&classCount); 265 | 266 | @autoreleasepool { 267 | for (unsigned protocolIndex = 0; protocolIndex < extendedProtcolCount; ++protocolIndex) { 268 | PKExtendedProtocol extendedProtcol = allExtendedProtocols[protocolIndex]; 269 | for (unsigned classIndex = 0; classIndex < classCount; ++classIndex) { 270 | Class class = allClasses[classIndex]; 271 | if (!class_conformsToProtocol(class, extendedProtcol.protocol)) { 272 | continue; 273 | } 274 | _pk_extension_inject_class(class, extendedProtcol); 275 | } 276 | } 277 | } 278 | #2:解锁并释放 allClasses、allExtendedProtocols 279 | } 280 | ``` 281 | 282 | `_pk_extension_inject_entry` 会在 main 执行之前遍历内存中的**所有** `Class`(整个遍历过程都是在一个自动释放池中进行的),如果某个类遵循了`allExtendedProtocols` 中的协议,调用 `_pk_extension_inject_class` 向类中注射(inject)方法实现: 283 | 284 | ```objectivec 285 | static void _pk_extension_inject_class(Class targetClass, PKExtendedProtocol extendedProtocol) { 286 | 287 | for (unsigned methodIndex = 0; methodIndex < extendedProtocol.instanceMethodCount; ++methodIndex) { 288 | Method method = extendedProtocol.instanceMethods[methodIndex]; 289 | SEL selector = method_getName(method); 290 | 291 | if (class_getInstanceMethod(targetClass, selector)) { 292 | continue; 293 | } 294 | 295 | IMP imp = method_getImplementation(method); 296 | const char *types = method_getTypeEncoding(method); 297 | class_addMethod(targetClass, selector, imp, types); 298 | } 299 | 300 | #1: 注射类方法 301 | } 302 | ``` 303 | 304 | 如果类中没有实现该实例方法就会通过 runtime 中的 `class_addMethod` 注射该实例方法;而类方法的注射有些不同,因为类方法都是保存在元类中的,而一些类方法由于其特殊地位最好不要改变其原有实现,比如 `+ load` 和 `+ initialize` 这两个类方法就比较特殊,如果想要了解这两个方法的相关信息,可以在 [Reference](#reference) 中查看相关的信息。 305 | 306 | ```objectivec 307 | Class targetMetaClass = object_getClass(targetClass); 308 | for (unsigned methodIndex = 0; methodIndex < extendedProtocol.classMethodCount; ++methodIndex) { 309 | Method method = extendedProtocol.classMethods[methodIndex]; 310 | SEL selector = method_getName(method); 311 | 312 | if (selector == @selector(load) || selector == @selector(initialize)) { 313 | continue; 314 | } 315 | if (class_getInstanceMethod(targetMetaClass, selector)) { 316 | continue; 317 | } 318 | 319 | IMP imp = method_getImplementation(method); 320 | const char *types = method_getTypeEncoding(method); 321 | class_addMethod(targetMetaClass, selector, imp, types); 322 | } 323 | ``` 324 | 325 | 实现上的不同仅仅在获取元类、以及跳过 `+ load` 和 `+ initialize` 方法上。 326 | 327 | ## 总结 328 | 329 | ProtocolKit 通过宏和 runtime 实现了类似协议扩展的功能,其实现代码总共也只有 200 多行,还是非常简洁的;在另一个叫做 [libextobjc](https://github.com/jspahrsummers/libextobjc) 的框架中也实现了类似的功能,有兴趣的读者可以查看 [EXTConcreteProtocol.h · libextobjc]([https://github.com/jspahrsummers/libextobjc/blob/master/contents/extobjc/EXTConcreteProtocol.h]) 这个文件。 330 | 331 | ## Reference 332 | 333 | + [Protocols · Apple Doc](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift\_Programming\_Language/Extensions.html#//apple\_ref/doc/uid/TP40014097-CH24-ID151) 334 | + [EXTConcreteProtocol.h · libextobjc](https://github.com/jspahrsummers/libextobjc/blob/master/contents/extobjc/EXTConcreteProtocol.h) 335 | + [\_\_attribute__ · NSHipster](http://nshipster.com/__attribute__/) 336 | + [你真的了解 load 方法么?](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/你真的了解%20load%20方法么?.md) 337 | + [懒惰的 initialize 方法](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/懒惰的%20initialize%20方法.md) 338 | 339 | 340 | -------------------------------------------------------------------------------- /contents/fishhook/images/fishbook-printf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/fishhook/images/fishbook-printf.png -------------------------------------------------------------------------------- /contents/fishhook/images/fishhook-before-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/fishhook/images/fishhook-before-after.png -------------------------------------------------------------------------------- /contents/fishhook/images/fishhook-hello-breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/fishhook/images/fishhook-hello-breakpoint.png -------------------------------------------------------------------------------- /contents/fishhook/images/fishhook-hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/fishhook/images/fishhook-hello.png -------------------------------------------------------------------------------- /contents/fishhook/images/fishhook-imp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/fishhook/images/fishhook-imp.png -------------------------------------------------------------------------------- /contents/fishhook/images/fishhook-mach-o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/fishhook/images/fishhook-mach-o.png -------------------------------------------------------------------------------- /contents/fishhook/images/fishhook-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/fishhook/images/fishhook-result.png -------------------------------------------------------------------------------- /contents/fishhook/images/fishhook-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/fishhook/images/fishhook-symbol.png -------------------------------------------------------------------------------- /contents/images/AFURLResponseSerialization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/AFURLResponseSerialization.png -------------------------------------------------------------------------------- /contents/images/PendingInitializeMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/PendingInitializeMap.png -------------------------------------------------------------------------------- /contents/images/afnetworking-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/afnetworking-arch.png -------------------------------------------------------------------------------- /contents/images/afnetworking-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/afnetworking-logo.png -------------------------------------------------------------------------------- /contents/images/afnetworking-plist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/afnetworking-plist.png -------------------------------------------------------------------------------- /contents/images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/banner.png -------------------------------------------------------------------------------- /contents/images/blockskit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/blockskit.png -------------------------------------------------------------------------------- /contents/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/logo.png -------------------------------------------------------------------------------- /contents/images/obj-method-struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/obj-method-struct.png -------------------------------------------------------------------------------- /contents/images/objc-ao-associateobjcect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-ao-associateobjcect.png -------------------------------------------------------------------------------- /contents/images/objc-ao-isa-struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-ao-isa-struct.png -------------------------------------------------------------------------------- /contents/images/objc-ao-warning-category-property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-ao-warning-category-property.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-AutoreleasePoolPage-linked-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-AutoreleasePoolPage-linked-list.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-AutoreleasePoolPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-AutoreleasePoolPage.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-after-insert-to-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-after-insert-to-page.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-breakpoint-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-breakpoint-main.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-main-cpp-struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-main-cpp-struct.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-main-cpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-main-cpp.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-main.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-page-in-memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-page-in-memory.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-pop-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-pop-stack.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-pop-string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-pop-string.png -------------------------------------------------------------------------------- /contents/images/objc-autorelease-print-pool-content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-autorelease-print-pool-content.png -------------------------------------------------------------------------------- /contents/images/objc-hashtable-copy-class-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-hashtable-copy-class-list.png -------------------------------------------------------------------------------- /contents/images/objc-hashtable-hash-state-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-hashtable-hash-state-init.png -------------------------------------------------------------------------------- /contents/images/objc-hashtable-hashstate-next.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-hashtable-hashstate-next.gif -------------------------------------------------------------------------------- /contents/images/objc-hashtable-insert-empty.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-hashtable-insert-empty.gif -------------------------------------------------------------------------------- /contents/images/objc-hashtable-insert-many.gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-hashtable-insert-many.gif.gif -------------------------------------------------------------------------------- /contents/images/objc-hashtable-insert-one.gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-hashtable-insert-one.gif.gif -------------------------------------------------------------------------------- /contents/images/objc-hashtable-instrument.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-hashtable-instrument.png -------------------------------------------------------------------------------- /contents/images/objc-hashtable-nsarray-instrument.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-hashtable-nsarray-instrument.png -------------------------------------------------------------------------------- /contents/images/objc-initialize-breakpoint-lookup-imp-or-forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-initialize-breakpoint-lookup-imp-or-forward.png -------------------------------------------------------------------------------- /contents/images/objc-initialize-breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-initialize-breakpoint.png -------------------------------------------------------------------------------- /contents/images/objc-initialize-class_rw_t_-bits-flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-initialize-class_rw_t_-bits-flag.png -------------------------------------------------------------------------------- /contents/images/objc-initialize-print-initialize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-initialize-print-initialize.png -------------------------------------------------------------------------------- /contents/images/objc-initialize-print-nothing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-initialize-print-nothing.png -------------------------------------------------------------------------------- /contents/images/objc-initialize-print-selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-initialize-print-selector.png -------------------------------------------------------------------------------- /contents/images/objc-isa-class-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-class-diagram.png -------------------------------------------------------------------------------- /contents/images/objc-isa-class-object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-class-object.png -------------------------------------------------------------------------------- /contents/images/objc-isa-class-pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-class-pointer.png -------------------------------------------------------------------------------- /contents/images/objc-isa-isat-bits-has-css-dtor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-isat-bits-has-css-dtor.png -------------------------------------------------------------------------------- /contents/images/objc-isa-isat-bits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-isat-bits.png -------------------------------------------------------------------------------- /contents/images/objc-isa-isat-class-highlight-bits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-isat-class-highlight-bits.png -------------------------------------------------------------------------------- /contents/images/objc-isa-isat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-isat.png -------------------------------------------------------------------------------- /contents/images/objc-isa-meta-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-meta-class.png -------------------------------------------------------------------------------- /contents/images/objc-isa-print-class-object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-print-class-object.png -------------------------------------------------------------------------------- /contents/images/objc-isa-print-cls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-print-cls.png -------------------------------------------------------------------------------- /contents/images/objc-isa-print-object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-isa-print-object.png -------------------------------------------------------------------------------- /contents/images/objc-load-break-after-add-breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-load-break-after-add-breakpoint.png -------------------------------------------------------------------------------- /contents/images/objc-load-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-load-diagram.png -------------------------------------------------------------------------------- /contents/images/objc-load-image-binary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-load-image-binary.png -------------------------------------------------------------------------------- /contents/images/objc-load-print-image-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-load-print-image-info.png -------------------------------------------------------------------------------- /contents/images/objc-load-print-load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-load-print-load.png -------------------------------------------------------------------------------- /contents/images/objc-load-producer-consumer-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-load-producer-consumer-diagram.png -------------------------------------------------------------------------------- /contents/images/objc-load-symbolic-breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-load-symbolic-breakpoint.png -------------------------------------------------------------------------------- /contents/images/objc-message-add-imp-to-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-add-imp-to-cache.png -------------------------------------------------------------------------------- /contents/images/objc-message-after-flush-cache-trap-in-lookup-again.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-after-flush-cache-trap-in-lookup-again.png -------------------------------------------------------------------------------- /contents/images/objc-message-after-flush-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-after-flush-cache.png -------------------------------------------------------------------------------- /contents/images/objc-message-before-flush-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-before-flush-cache.png -------------------------------------------------------------------------------- /contents/images/objc-message-cache-struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-cache-struct.png -------------------------------------------------------------------------------- /contents/images/objc-message-core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-core.png -------------------------------------------------------------------------------- /contents/images/objc-message-find-selector-before-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-find-selector-before-init.png -------------------------------------------------------------------------------- /contents/images/objc-message-first-call-hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-first-call-hello.png -------------------------------------------------------------------------------- /contents/images/objc-message-objc-msgSend-with-cache.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-objc-msgSend-with-cache.gif -------------------------------------------------------------------------------- /contents/images/objc-message-run-after-add-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-run-after-add-cache.png -------------------------------------------------------------------------------- /contents/images/objc-message-selector-undefined.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-selector-undefined.png -------------------------------------------------------------------------------- /contents/images/objc-message-selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-selector.png -------------------------------------------------------------------------------- /contents/images/objc-message-step-in-cache-getimp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-step-in-cache-getimp.png -------------------------------------------------------------------------------- /contents/images/objc-message-wrong-step-in.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-wrong-step-in.gif -------------------------------------------------------------------------------- /contents/images/objc-message-youtube-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-message-youtube-preview.jpg -------------------------------------------------------------------------------- /contents/images/objc-method-after-compile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-after-compile.png -------------------------------------------------------------------------------- /contents/images/objc-method-after-methodizeClass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-after-methodizeClass.png -------------------------------------------------------------------------------- /contents/images/objc-method-after-realize-breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-after-realize-breakpoint.png -------------------------------------------------------------------------------- /contents/images/objc-method-after-realize-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-after-realize-class.png -------------------------------------------------------------------------------- /contents/images/objc-method-before-realize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-before-realize.png -------------------------------------------------------------------------------- /contents/images/objc-method-breakpoint-before-set-rw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-breakpoint-before-set-rw.png -------------------------------------------------------------------------------- /contents/images/objc-method-class-data-bits-t.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-class-data-bits-t.png -------------------------------------------------------------------------------- /contents/images/objc-method-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-class.png -------------------------------------------------------------------------------- /contents/images/objc-method-class_data_bits_t.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-class_data_bits_t.png -------------------------------------------------------------------------------- /contents/images/objc-method-compile-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-compile-class.png -------------------------------------------------------------------------------- /contents/images/objc-method-lldb-breakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-lldb-breakpoint.png -------------------------------------------------------------------------------- /contents/images/objc-method-lldb-print-before-realize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-lldb-print-before-realize.png -------------------------------------------------------------------------------- /contents/images/objc-method-lldb-print-method-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-lldb-print-method-list.png -------------------------------------------------------------------------------- /contents/images/objc-method-print-class-struct-after-realize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-print-class-struct-after-realize.png -------------------------------------------------------------------------------- /contents/images/objc-method-target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-method-target.png -------------------------------------------------------------------------------- /contents/images/objc-rr-isa-struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bestswifter/iOS-Source-Code-Analyze/3c97afdf68314de7610c2c74a7dcd036f9a91ab2/contents/images/objc-rr-isa-struct.png -------------------------------------------------------------------------------- /contents/libextobjc/如何在 Objective-C 的环境下实现 defer.md: -------------------------------------------------------------------------------- 1 | # 如何在 Objective-C 的环境下实现 defer 2 | 3 | 这篇文章会对 [libextobjc](https://github.com/jspahrsummers/libextobjc) 中的一小部分代码进行分析,也是**如何扩展 Objective-C 语言**系列文章的第一篇,笔者会从 libextobjc 中选择一些黑魔法进行介绍。 4 | 5 | 对 Swift 稍有了解的人都知道,`defer` 在 Swift 语言中是一个关键字;在 `defer` 代码块中的代码,会**在作用域结束时执行**。在这里,我们会使用一些神奇的方法在 Objective-C 中实现 `defer`。 6 | 7 | > 如果你已经非常了解 `defer` 的作用,你可以跳过第一部分的内容,直接看 [Variable Attributes](#variable-attributes)。 8 | 9 | ## 关于 defer 10 | 11 | `defer` 是 Swift 在 2.0 时代加入的一个关键字,它提供了一种非常安全并且简单的方法声明一个在作用域结束时执行的代码块。 12 | 13 | 如果你在 Swift Playground 中输入以下代码: 14 | 15 | ```objectivec 16 | func hello() { 17 | defer { 18 | print("4") 19 | } 20 | if true { 21 | defer { 22 | print("2") 23 | } 24 | defer { 25 | print("1") 26 | } 27 | } 28 | print("3") 29 | } 30 | 31 | hello() 32 | ``` 33 | 34 | 控制台的输出会是这样的: 35 | 36 | ``` 37 | 1 38 | 2 39 | 3 40 | 4 41 | ``` 42 | 43 | 你可以仔细思考一下为什么会有这样的输出,并在 Playground 使用 `defer` 写一些简单的代码,相信你可以很快理解它是如何工作的。 44 | 45 | > 如果对 `defer` 的作用仍然不是非常了解,可以看 [guard & defer](http://nshipster.com/guard-and-defer/#defer) 这篇文章的后半部分。 46 | 47 | ## Variable Attributes 48 | 49 | libextobjc 实现的 `defer` 并没有基于 Objective-C 的动态特性,甚至也没有调用已有的任何方法,而是使用了 [Variable Attributes](https://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html) 这一特性。 50 | 51 | > 同样在 GCC 中也存在用于修饰函数的 [Function Attributes](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) 52 | 53 | Variable Attributes 其实是 GCC 中用于描述变量的一种修饰符。我们可以使用 `__attribute__` 来修饰一些变量来参与静态分析等编译过程;而在 Cocoa Touch 中很多的宏其实都是通过 `__attribute__` 来实现的,例如: 54 | 55 | ```objectivec 56 | #define NS_ROOT_CLASS __attribute__((objc_root_class)) 57 | ``` 58 | 59 | 而 [cleanup](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes#cleanup) 就是在这里会使用的变量属性: 60 | 61 | > The cleanup attribute runs a function when the variable goes out of scope. This attribute can only be applied to auto function scope variables; it may not be applied to parameters or variables with static storage duration. The function must take one parameter, a pointer to a type compatible with the variable. The return value of the function (if any) is ignored. 62 | 63 | GCC 文档中对 `cleanup` 属性的介绍告诉我们,在 `cleanup` 中必须传入**只有一个参数的函数并且这个参数需要与变量的类型兼容**。 64 | 65 | 如果上面这句比较绕口的话很难理解,可以通过一个简单的例子理解其使用方法: 66 | 67 | ```objectivec 68 | void cleanup_block(int *a) { 69 | printf("%d\n", *a); 70 | } 71 | 72 | int variable __attribute__((cleanup(cleanup_block))) = 2; 73 | ``` 74 | 75 | 在 `variable` 这个变量离开作用域之后,就会自动将这个变量的**指针**传入 `cleanup_block` 中,调用 `cleanup_block` 方法来进行『清理』工作。 76 | 77 | ## 实现 defer 78 | 79 | 到目前为止已经有了实现 `defer` 需要的全部知识,我们可以开始分析 libextobjc 是怎么做的。 80 | 81 | 在 libextobjc 中并没有使用 `defer` 这个名字,而是使用了 `onExit`(表示代码是在退出作用域时执行) 82 | 83 | > 为了使 `onExit` 在使用时更加明显,libextobjc 通过一些其它的手段使得我们在每次使用 `onExit` 时都需要添加一个 `@` 符号。 84 | 85 | ```objectivec 86 | { 87 | @onExit { 88 | NSLog("Log when out of scope."); 89 | }; 90 | NSLog("Log before out of scope."); 91 | } 92 | ``` 93 | 94 | `onExit` 其实只是一个精心设计的宏: 95 | 96 | ```objectivec 97 | #define onExit \ 98 | ext_keywordify \ 99 | __strong ext_cleanupBlock_t metamacro_concat(ext_exitBlock_, __LINE__) __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^ 100 | ``` 101 | 102 | 既然它只是一个宏,那么上面的代码其实是可以展开的: 103 | 104 | ```objectivec 105 | autoreleasepool {} 106 | __strong ext_cleanupBlock_t ext_exitBlock_19 __attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^ { 107 | NSLog("Log when out of scope."); 108 | }; 109 | ``` 110 | 111 | 这里,我们分几个部分来分析上面的代码片段是如何实现 `defer` 的功能的: 112 | 113 | 1. `ext_keywordify` 也是一个宏定义,它通过添加在宏之前添加 `autoreleasepool {}` 强迫 `onExit` 前必须加上 `@` 符号。 114 | 115 | ```objectivec 116 | #define ext_keywordify autoreleasepool {} 117 | ``` 118 | 119 | 2. `ext_cleanupBlock_t` 是一个类型: 120 | 121 | ```objectivec 122 | typedef void (^ext_cleanupBlock_t)(); 123 | ``` 124 | 125 | 3. `metamacro_concat(ext_exitBlock_, __LINE__)` 会将 `ext_exitBlock` 和当前行号拼接成一个临时的的变量名,例如:`ext_exitBlock_19`。 126 | 127 | 4. `__attribute__((cleanup(ext_executeCleanupBlock), unused))` 将 `cleanup` 函数设置为 `ext_executeCleanupBlock`;并将当前变量 `ext_exitBlock_19` 标记为 `unused` 来抑制 `Unused variable` 警告。 128 | 129 | 5. 变量 `ext_exitBlock_19` 的值为 `^{ NSLog("Log when out of scope."); }`,是一个类型为 `ext_cleanupBlock_t` 的 block。 130 | 131 | 6. 在这个变量离开作用域时,会把上面的 block 的指针传入 `cleanup` 函数,也就是 `ext_executeCleanupBlock`: 132 | 133 | ```objectivec 134 | void ext_executeCleanupBlock (__strong ext_cleanupBlock_t *block) { 135 | (*block)(); 136 | } 137 | ``` 138 | 139 | 这个函数的作用只是简单的执行传入的 block,它满足了 GCC 文档中对 `cleanup` 函数的几个要求: 140 | 141 | 1. 只能包含一个参数 142 | 2. 参数的类型是一个**指向变量类型的指针** 143 | 3. 函数的返回值是 `void` 144 | 145 | ## 总结 146 | 147 | > 这是分析 libextobjc 框架的第一篇文章,也是比较简短的一篇,因为我们在日常开发中基本上用不到这个框架提供的 API,但是它依然会为我们展示了很多编程上的黑魔法。 148 | 149 | libextobjc 将 `cleanup` 这一变量属性,很好地包装成了 `@onExit`,它的实现也是比较有意思的,也激起了笔者学习 GCC 编译命令并且阅读一些文档的想法。 150 | 151 | > Follow: [Draveness · Github](https://github.com/Draveness) 152 | 153 | 154 | -------------------------------------------------------------------------------- /contents/objc/README.md: -------------------------------------------------------------------------------- 1 | # objc 源代码阅读 2 | 3 | 当前文件夹下的 objc-runtime 工程,是我在阅读 objc 源代码时所使用的版本,你可以 clone 整个仓库来进行调试,原工程地址为 [objc-runtime](https://github.com/RetVal/objc-runtime)。 4 | 5 | 6 | + ObjC 源代码 7 | + [从 NSObject 的初始化了解 isa](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/从%20NSObject%20的初始化了解%20isa.md) 8 | + [深入解析 ObjC 中方法的结构](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/深入解析%20ObjC%20中方法的结构.md) 9 | + [从源代码看 ObjC 中消息的发送](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/从源代码看%20ObjC%20中消息的发送.md) 10 | + [你真的了解 load 方法么?](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/你真的了解%20load%20方法么?.md) 11 | + [懒惰的 initialize](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/懒惰的%20initialize%20方法.md) 12 | + [自动释放池的前世今生](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/自动释放池的前世今生.md) 13 | + [黑箱中的 retain 和 release](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/黑箱中的%20retain%20和%20release.md) 14 | 15 | 16 | -------------------------------------------------------------------------------- /contents/objc/从 NSObject 的初始化了解 isa.md: -------------------------------------------------------------------------------- 1 | # 从 NSObject 的初始化了解 isa 2 | 3 | > 因为 ObjC 的 runtime 只能在 Mac OS 下才能编译,所以文章中的代码都是在 Mac OS,也就是 `x86_64` 架构下运行的,对于在 arm64 中运行的代码会特别说明。 4 | 5 | 如果你曾经对 ObjC 底层的实现有一定的了解,你应该会知道 **Objective-C 对象都是 C 语言结构体**,所有的对象都包含一个类型为 `isa` 的指针,那么你可能确实对 ObjC 的底层有所知,不过现在的 ObjC 对象的结构已经不是这样了。代替 `isa` 指针的是结构体 `isa_t`, 这个结构体中"包含"了当前对象指向的类的信息,这篇文章中会介绍一些关于这个变化的知识。 6 | 7 | ```objectivec 8 | struct objc_object { 9 | isa_t isa; 10 | }; 11 | ``` 12 | 13 | 当 ObjC 为一个对象分配内存,初始化实例变量后,在这些对象的实例变量的结构体中的第一个就是 `isa`。 14 | 15 |

16 | ![objc-isa-class-object](../images/objc-isa-class-object.png) 17 | 18 | > 所有继承自 `NSObject` 的类实例化后的对象都会包含一个类型为 `isa_t` 的结构体。 19 | 20 | 从上图中可以看出,不只是**实例**会包含一个 `isa` 结构体,所有的**类**也有这么一个 `isa`。在 ObjC 中 Class 的定义也是一个名为 `objc_class` 的结构体,如下: 21 | 22 | ```objectivec 23 | struct objc_class : objc_object { 24 | isa_t isa; 25 | Class superclass; 26 | cache_t cache; 27 | class_data_bits_t bits; 28 | }; 29 | ``` 30 | 31 | > 由于 `objc_class` 结构体是继承自 `objc_object` 的,所以在这里显式地写出了 `isa_t isa` 这个成员变量。 32 | 33 | ## `isa` 指针的作用与元类 34 | 35 | 到这里,我们就明白了:**Objective-C 中类也是一个对象**。 36 | 37 | 这个 `isa` 包含了什么呢?回答这个问题之前,要引入了另一个概念 *元类(meta class)*,我们先了解一些关于元类的信息。 38 | 39 | 因为在 Objective-C 中,对象的方法并**没有存储于对象的结构体中**(如果每一个对象都保存了自己能执行的方法,那么对内存的占用有极大的影响)。 40 | 41 | 当**实例方法**被调用时,它要通过自己持有的 `isa` 来查找对应的类,然后在这里的 `class_data_bits_t` 结构体中查找对应方法的实现。同时,每一个 `objc_class` 也有一个**指向自己的父类的指针** `super_class` 用来查找继承的方法。 42 | 43 | > 关于如何在 `class_data_bits_t` 中查找对应方法会在之后的文章中讲到。这里只需要知道,它会在这个结构体中查找到对应方法的实现就可以了。[深入解析 ObjC 中方法的结构](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/深入解析%20ObjC%20中方法的结构.md) 44 | 45 |

46 | ![objc-isa-class-pointer](../images/objc-isa-class-pointer.png) 47 | 48 | 但是,这样就有一个问题,类方法的实现又是如何查找并且调用的呢?这时,就需要引入*元类*来保证无论是类还是对象都能**通过相同的机制查找方法的实现**。 49 | 50 |

51 | ![objc-isa-meta-class](../images/objc-isa-meta-class.png) 52 | 53 | 54 | 让每一个类的 `isa` 指向对应的元类,这样就达到了使类方法和实例方法的调用机制相同的目的: 55 | 56 | + 实例方法调用时,通过对象的 `isa` 在类中获取方法的实现 57 | + 类方法调用时,通过类的 `isa` 在元类中获取方法的实现 58 | 59 | 下面这张图介绍了对象,类与元类之间的关系,笔者认为已经觉得足够清晰了,所以不在赘述。 60 | 61 |

62 | ![](../images/objc-isa-class-diagram.png) 63 | 64 | > 图片来自 [objc_explain_Classes_and_metaclasses](http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html) 65 | 66 | 有关与介绍类与元类之间的关系的文章实在是太多了,因为这篇文章主要介绍 `isa`,在这一小节只是对其作用以及元类的概念进行介绍。如果想要了解更多关于类与元类的信息,可以看 [What is a meta-class in Objective-C?](http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html) 67 | 68 | ## 结构体 `isa_t` 69 | 70 | 其实 `isa_t` 是一个定义得非常"奇怪"的结构体,在 ObjC 源代码中可以看到这样的定义: 71 | 72 | ```objectivec 73 | #define ISA_MASK 0x00007ffffffffff8ULL 74 | #define ISA_MAGIC_MASK 0x001f800000000001ULL 75 | #define ISA_MAGIC_VALUE 0x001d800000000001ULL 76 | #define RC_ONE (1ULL<<56) 77 | #define RC_HALF (1ULL<<7) 78 | 79 | union isa_t { 80 | isa_t() { } 81 | isa_t(uintptr_t value) : bits(value) { } 82 | 83 | Class cls; 84 | uintptr_t bits; 85 | 86 | struct { 87 | uintptr_t indexed : 1; 88 | uintptr_t has_assoc : 1; 89 | uintptr_t has_cxx_dtor : 1; 90 | uintptr_t shiftcls : 44; 91 | uintptr_t magic : 6; 92 | uintptr_t weakly_referenced : 1; 93 | uintptr_t deallocating : 1; 94 | uintptr_t has_sidetable_rc : 1; 95 | uintptr_t extra_rc : 8; 96 | }; 97 | }; 98 | ``` 99 | 100 | > 这是在 `__x86_64__` 上的实现,对于 iPhone5s 等架构为 `__arm64__` 的设备上,具体结构体的实现和位数可能有些差别,不过这些字段都是存在的,可以看这里的 [arm64 上结构体的实现](#arm64) 101 | 102 | **在本篇文章中, 我们会以 `__x86_64__` 为例进行分析,而不会对两种架构下由于不同的内存布局方式导致的差异进行分析**。在我看来,这个细节不会影响对 `isa` 指针的理解,不过还是要知道的。 103 | 104 | 笔者对这个 `isa_t` 的实现声明顺序有一些更改,更方便分析和理解。 105 | 106 | ```objectivec 107 | union isa_t { 108 | ... 109 | }; 110 | ``` 111 | 112 | `isa_t` 是一个 `union` 类型的结构体,对 `union` 不熟悉的读者可以看这个 stackoverflow 上的[回答](http://stackoverflow.com/questions/252552/why-do-we-need-c-unions). 也就是说其中的 `isa_t`、`cls`、 `bits` 还有结构体共用同一块地址空间。而 `isa` 总共会占据 64 位的内存空间(决定于其中的结构体) 113 | 114 |

115 | ![objc-isa-isat](../images/objc-isa-isat.png) 116 | 117 | ```objectivec 118 | struct { 119 | uintptr_t indexed : 1; 120 | uintptr_t has_assoc : 1; 121 | uintptr_t has_cxx_dtor : 1; 122 | uintptr_t shiftcls : 44; 123 | uintptr_t magic : 6; 124 | uintptr_t weakly_referenced : 1; 125 | uintptr_t deallocating : 1; 126 | uintptr_t has_sidetable_rc : 1; 127 | uintptr_t extra_rc : 8; 128 | }; 129 | ``` 130 | 131 | ## `isa` 的初始化 132 | 133 | 我们可以通过 `isa` 初始化的方法 `initIsa` 来初步了解这 64 位的 bits 的作用: 134 | 135 | ```objectivec 136 | inline void 137 | objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) 138 | { 139 | initIsa(cls, true, hasCxxDtor); 140 | } 141 | 142 | inline void 143 | objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 144 | { 145 | if (!indexed) { 146 | isa.cls = cls; 147 | } else { 148 | isa.bits = ISA_MAGIC_VALUE; 149 | isa.has_cxx_dtor = hasCxxDtor; 150 | isa.shiftcls = (uintptr_t)cls >> 3; 151 | } 152 | } 153 | ``` 154 | 155 | ### `indexed` 和 `magic` 156 | 157 | 当我们对一个 ObjC 对象分配内存时,其方法调用栈中包含了上述的两个方法,这里关注的重点是 `initIsa` 方法,由于在 `initInstanceIsa` 方法中传入了 `indexed = true`,所以,我们简化一下这个方法的实现: 158 | 159 | ```objectivec 160 | inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 161 | { 162 | isa.bits = ISA_MAGIC_VALUE; 163 | isa.has_cxx_dtor = hasCxxDtor; 164 | isa.shiftcls = (uintptr_t)cls >> 3; 165 | } 166 | ``` 167 | 168 | 对整个 `isa` 的值 `bits` 进行设置,传入 `ISA_MAGIC_VALUE`: 169 | 170 | ```objectivec 171 | #define ISA_MAGIC_VALUE 0x001d800000000001ULL 172 | ``` 173 | 174 | 我们可以把它转换成二进制的数据,然后看一下哪些属性对应的位被这行代码初始化了(标记为红色): 175 | 176 |

177 | ![objc-isa-isat-bits](../images/objc-isa-isat-bits.png) 178 | 179 | 从图中了解到,在使用 `ISA_MAGIC_VALUE` 设置 `isa_t` 结构体之后,实际上只是设置了 `indexed` 以及 `magic` 这两部分的值。 180 | 181 | + 其中 `indexed` 表示 `isa_t` 的类型 182 | + 0 表示 `raw isa`,也就是没有结构体的部分,访问对象的 `isa` 会直接返回一个指向 `cls` 的指针,也就是在 iPhone 迁移到 64 位系统之前时 isa 的类型。 183 | 184 | ```objectivec 185 | union isa_t { 186 | isa_t() { } 187 | isa_t(uintptr_t value) : bits(value) { } 188 | 189 | Class cls; 190 | uintptr_t bits; 191 | }; 192 | ``` 193 | 194 | + 1 表示当前 `isa` 不是指针,但是其中也有 `cls` 的信息,只是其中**关于类的指针都是保存在 `shiftcls` 中**。 195 | 196 | ```objectivec 197 | union isa_t { 198 | isa_t() { } 199 | isa_t(uintptr_t value) : bits(value) { } 200 | 201 | Class cls; 202 | uintptr_t bits; 203 | 204 | struct { 205 | uintptr_t indexed : 1; 206 | uintptr_t has_assoc : 1; 207 | uintptr_t has_cxx_dtor : 1; 208 | uintptr_t shiftcls : 44; 209 | uintptr_t magic : 6; 210 | uintptr_t weakly_referenced : 1; 211 | uintptr_t deallocating : 1; 212 | uintptr_t has_sidetable_rc : 1; 213 | uintptr_t extra_rc : 8; 214 | }; 215 | }; 216 | ``` 217 | + `magic` 的值为 `0x3b` 用于调试器判断当前对象是真的对象还是没有初始化的空间 218 | 219 | ### `has_cxx_dtor` 220 | 221 | 在设置 `indexed` 和 `magic` 值之后,会设置 `isa` 的 `has_cxx_dtor`,这一位表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。 222 | 223 | ```objectivec 224 | isa.has_cxx_dtor = hasCxxDtor; 225 | ``` 226 | 227 |

228 | ![objc-isa-isat-bits-has-css-dto](../images/objc-isa-isat-bits-has-css-dtor.png) 229 | 230 | ### `shiftcls` 231 | 232 | 在为 `indexed`、 `magic` 和 `has_cxx_dtor` 设置之后,我们就要将当前对象对应的类指针存入 `isa` 结构体中了。 233 | 234 | ```objectivec 235 | isa.shiftcls = (uintptr_t)cls >> 3; 236 | ``` 237 | 238 | > **将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0**。 239 | > 240 | > 绝大多数机器的架构都是 [byte-addressable](https://en.wikipedia.org/wiki/Byte_addressing) 的,但是对象的内存地址必须对齐到字节的倍数,这样可以提高代码运行的性能,在 iPhone5s 中虚拟地址为 33 位,所以用于对齐的最后三位比特为 `000`,我们只会用其中的 30 位来表示对象的地址。 241 | 242 | 243 | 而 ObjC 中的类指针的地址后三位也为 0,在 `_class_createInstanceFromZone` 方法中打印了调用这个方法传入的类指针: 244 | 245 |

246 | ![objc-isa-print-cls](../images/objc-isa-print-cls.png) 247 | 248 | 可以看到,这里打印出来的**所有类指针十六进制地址的最后一位都为 8 或者 0**。也就是说,类指针的后三位都为 0,所以,我们在上面存储 `Class` 指针时右移三位是没有问题的。 249 | 250 | ```objectivec 251 | isa.shiftcls = (uintptr_t)cls >> 3; 252 | ``` 253 | 254 | 如果再尝试打印对象指针的话,会发现所有对象内存地址的**后四位**都是 0,说明 ObjC 在初始化内存时是以 16 个字节对齐的, 分配的内存地址后四位都是 0。 255 | 256 |

257 | ![objc-isa-print-object](../images/objc-isa-print-object.png) 258 | 259 | > 使用整个指针大小的内存来存储 `isa` 指针有些浪费,尤其在 64 位的 CPU 上。在 `ARM64` 运行的 iOS 只使用了 33 位作为指针(与结构体中的 33 位无关,Mac OS 上为 47 位),而剩下的 31 位用于其它目的。类的指针也同样根据字节对齐了,每一个类指针的地址都能够被 8 整除,也就是使最后 3 bits 为 0,为 `isa` 留下 34 位用于性能的优化。 260 | > 261 | > Using an entire pointer-sized piece of memory for the isa pointer is a bit wasteful, especially on 64-bit CPUs which don't use all 64 bits of a pointer. ARM64 running iOS currently uses only 33 bits of a pointer, leaving 31 bits for other purposes. Class pointers are also aligned, meaning that a class pointer is guaranteed to be divisible by 8, which frees up another three bits, leaving 34 bits of the isa available for other uses. Apple's ARM64 runtime takes advantage of this for some great performance improvements. 262 | > from [ARM64 and You](https://www.mikeash.com/pyblog/friday-qa-2013-09-27-arm64-and-you.html) 263 | 264 | 我尝试运行了下面的代码将 `NSObject` 的类指针和对象的 `isa` 打印出来,具体分析一下 265 | 266 |

267 | ![objc-isa-print-class-object](../images/objc-isa-print-class-object.png) 268 | 269 | ``` 270 | object_pointer: 0000000001011101100000000000000100000000001110101110000011111001 // 补全至 64 位 271 | class_pointer: 100000000001110101110000011111000 272 | ``` 273 | 274 | > 编译器对直接访问 `isa` 的操作会有警告,因为直接访问 `isa` 已经不会返回类指针了,这种行为已经被弃用了,取而代之的是使用 [ISA()](#ISA()) 方法来获取类指针。 275 | 276 | 代码中的 `object` 对象的 `isa` 结构体中的内容是这样的: 277 | 278 | ![objc-isa-isat-class-highlight-bits](../images/objc-isa-isat-class-highlight-bits.png) 279 | 280 | 281 | 其中红色的为**类指针**,与上面打印出的 `[NSObject class]` 指针右移三位的结果完全相同。这也就验证了我们之前对于初始化 `isa` 时对 `initIsa` 方法的分析是正确的。它设置了 `indexed`、`magic` 以及 `shiftcls`。 282 | 283 | ### ISA() 方法 284 | 285 | 因为我们使用结构体取代了原有的 isa 指针,所以要提供一个方法 `ISA()` 来返回类指针。 286 | 287 | 其中 `ISA_MASK` 是宏定义,这里通过掩码的方式获取类指针: 288 | 289 | ```objectivec 290 | #define ISA_MASK 0x00007ffffffffff8ULL 291 | inline Class 292 | objc_object::ISA() 293 | { 294 | return (Class)(isa.bits & ISA_MASK); 295 | } 296 | ``` 297 | 298 | ### 其它 bits 299 | 300 | 在 `isa_t` 中,我们还有一些没有介绍的其它 bits,在这个小结就简单介绍下这些 bits 的作用 301 | 302 | + `has_assoc` 303 | + 对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存 304 | + `weakly_referenced` 305 | + 对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放 306 | + `deallocating` 307 | + 对象正在释放内存 308 | + `has_sidetable_rc` 309 | + 对象的引用计数太大了,存不下 310 | + `extra_rc` 311 | + 对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,`extra_rc` 的值就为 9 312 | 313 | ```objectivec 314 | struct { 315 | uintptr_t indexed : 1; 316 | uintptr_t has_assoc : 1; 317 | uintptr_t has_cxx_dtor : 1; 318 | uintptr_t shiftcls : 44; 319 | uintptr_t magic : 6; 320 | uintptr_t weakly_referenced : 1; 321 | uintptr_t deallocating : 1; 322 | uintptr_t has_sidetable_rc : 1; 323 | uintptr_t extra_rc : 8; 324 | }; 325 | ``` 326 | 327 | ### arm64 架构中的 `isa_t` 结构体 328 | 329 | ```objectivec 330 | #define ISA_MASK 0x0000000ffffffff8ULL 331 | #define ISA_MAGIC_MASK 0x000003f000000001ULL 332 | #define ISA_MAGIC_VALUE 0x000001a000000001ULL 333 | #define RC_ONE (1ULL<<45) 334 | #define RC_HALF (1ULL<<18) 335 | union isa_t { 336 | isa_t() { } 337 | isa_t(uintptr_t value) : bits(value) { } 338 | 339 | Class cls; 340 | uintptr_t bits; 341 | 342 | struct { 343 | uintptr_t indexed : 1; 344 | uintptr_t has_assoc : 1; 345 | uintptr_t has_cxx_dtor : 1; 346 | uintptr_t shiftcls : 33; 347 | uintptr_t magic : 6; 348 | uintptr_t weakly_referenced : 1; 349 | uintptr_t deallocating : 1; 350 | uintptr_t has_sidetable_rc : 1; 351 | uintptr_t extra_rc : 19; 352 | }; 353 | }; 354 | ``` 355 | 356 | ## 参考资料 357 | 358 | + [Objective-C Runtime Programming Guide](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html) 359 | + [What is a meta-class in Objective-C?](http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html) 360 | + [objc_explain_Classes_and_metaclasses](http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html) 361 | + [Storing things in isa](http://stackoverflow.com/questions/18997362/storing-things-in-isa) 362 | + [Why do we need C Unions?](http://stackoverflow.com/questions/252552/why-do-we-need-c-unions) 363 | + [objc_explain_Non-pointer_isa](http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html) 364 | + [Tagged Pointer](https://en.wikipedia.org/wiki/Tagged_pointer) 365 | + [ARM64 and You](https://www.mikeash.com/pyblog/friday-qa-2013-09-27-arm64-and-you.html) 366 | + [64位与Tagged Pointer](http://blog.xcodev.com/posts/tagged-pointer-and-64-bit/) 367 | 368 | Follow: [@Draveness](https://github.com/Draveness) 369 | 370 | -------------------------------------------------------------------------------- /contents/objc/对象是如何初始化的(iOS).md: -------------------------------------------------------------------------------- 1 | # 对象是如何初始化的(iOS) 2 | 3 | 在之前,我们已经讨论了非常多的问题了,关于 objc 源代码系列的文章也快结束了,其实关于对象是如何初始化的这篇文章本来是我要写的第一篇文章,但是由于有很多前置内容不得不说,所以留到了这里。 4 | 5 | `+ alloc` 和 `- init` 这一对我们在 iOS 开发中每天都要用到的初始化方法一直困扰着我, 于是笔者仔细研究了一下 objc 源码中 `NSObject` 如何进行初始化。 6 | 7 | 在具体分析对象的初始化过程之前,我想先放出结论,以免文章中的细枝末节对读者的理解有所影响;整个对象的初始化过程其实只是**为一个分配内存空间,并且初始化 isa_t 结构体的过程**。 8 | 9 | ## alloc 方法分析 10 | 11 | 先来看一下 `+ alloc` 方法的调用栈(在调用栈中省略了很多不必要的方法的调用): 12 | 13 | ```objectivec 14 | id _objc_rootAlloc(Class cls) 15 | └── static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) 16 | └── id class_createInstance(Class cls, size_t extraBytes) 17 | └── id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct, size_t *outAllocatedSize) 18 | ├── size_t instanceSize(size_t extraBytes) 19 | ├── void *calloc(size_t, size_t) 20 | └── inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) 21 | ``` 22 | 23 | 这个调用栈中的方法涉及了多个文件中的代码,在下面的章节中会对调用的方法逐步进行分析,如果这个调用栈让你觉得很头疼,也不是什么问题。 24 | 25 | ### alloc 的实现 26 | 27 | ```objectivec 28 | + (id)alloc { 29 | return _objc_rootAlloc(self); 30 | } 31 | ``` 32 | 33 | `alloc` 方法的实现真的是非常的简单, 它直接调用了另一个私有方法 `id _objc_rootAlloc(Class cls)` 34 | 35 | ```objectivec 36 | id _objc_rootAlloc(Class cls) { 37 | return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); 38 | } 39 | ``` 40 | 41 | 这就是上帝类 `NSObject` 对 `callAlloc` 的实现,我们省略了非常多的代码,展示了最常见的执行路径: 42 | 43 | ```objectivec 44 | static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { 45 | id obj = class_createInstance(cls, 0); 46 | return obj; 47 | } 48 | 49 | id class_createInstance(Class cls, size_t extraBytes) { 50 | return _class_createInstanceFromZone(cls, extraBytes, nil); 51 | } 52 | ``` 53 | 54 | 对象初始化中最重要的操作都在 `_class_createInstanceFromZone` 方法中执行: 55 | 56 | ```objectivec 57 | static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { 58 | size_t size = cls->instanceSize(extraBytes); 59 | 60 | id obj = (id)calloc(1, size); 61 | if (!obj) return nil; 62 | obj->initInstanceIsa(cls, hasCxxDtor); 63 | 64 | return obj; 65 | } 66 | ``` 67 | 68 | ### 对象的大小 69 | 70 | 在使用 `calloc` 为对象分配一块内存空间之前,我们要先获取对象在内存的大小: 71 | 72 | ```objectivec 73 | size_t instanceSize(size_t extraBytes) { 74 | size_t size = alignedInstanceSize() + extraBytes; 75 | if (size < 16) size = 16; 76 | return size; 77 | } 78 | 79 | uint32_t alignedInstanceSize() { 80 | return word_align(unalignedInstanceSize()); 81 | } 82 | 83 | uint32_t unalignedInstanceSize() { 84 | assert(isRealized()); 85 | return data()->ro->instanceSize; 86 | } 87 | ``` 88 | 89 | 实例大小 `instanceSize` 会存储在类的 `isa_t` 结构体中,然后经过对齐最后返回。 90 | 91 | > Core Foundation 需要所有的对象的大小都必须大于或等于 16 字节。 92 | 93 | 在获取对象大小之后,直接调用 `calloc` 函数就可以为对象分配内存空间了。 94 | 95 | ### isa 的初始化 96 | 97 | 在对象的初始化过程中除了使用 `calloc` 来分配内存之外,还需要根据类初始化 `isa_t` 结构体: 98 | 99 | ```objectivec 100 | inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) { 101 | if (!indexed) { 102 | isa.cls = cls; 103 | } else { 104 | isa.bits = ISA_MAGIC_VALUE; 105 | isa.has_cxx_dtor = hasCxxDtor; 106 | isa.shiftcls = (uintptr_t)cls >> 3; 107 | } 108 | } 109 | ``` 110 | 111 | 上面的代码只是对 `isa_t` 结构体进行初始化而已: 112 | 113 | ```objectivec 114 | union isa_t { 115 | isa_t() { } 116 | isa_t(uintptr_t value) : bits(value) { } 117 | 118 | Class cls; 119 | uintptr_t bits; 120 | 121 | struct { 122 | uintptr_t indexed : 1; 123 | uintptr_t has_assoc : 1; 124 | uintptr_t has_cxx_dtor : 1; 125 | uintptr_t shiftcls : 44; 126 | uintptr_t magic : 6; 127 | uintptr_t weakly_referenced : 1; 128 | uintptr_t deallocating : 1; 129 | uintptr_t has_sidetable_rc : 1; 130 | uintptr_t extra_rc : 8; 131 | }; 132 | }; 133 | ``` 134 | 135 | > 在这里并不想过多介绍关于 `isa_t` 结构体的内容,你可以看[从 NSObject 的初始化了解 isa](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/从%20NSObject%20的初始化了解%20isa.md) 来了解你想知道的关于 `isa_t` 的全部内容。 136 | 137 | ## init 方法 138 | 139 | `NSObject` 的 `- init` 方法只是调用了 `_objc_rootInit` 并返回了当前对象: 140 | 141 | ```objectivec 142 | - (id)init { 143 | return _objc_rootInit(self); 144 | } 145 | 146 | id _objc_rootInit(id obj) { 147 | return obj; 148 | } 149 | ``` 150 | 151 | ## 总结 152 | 153 | 在 iOS 中一个对象的初始化过程很符合直觉,只是分配内存空间、然后初始化 `isa_t` 结构体,其实现也并不复杂,这篇文章也是这个系列文章中较为简单并且简短的一篇。 154 | 155 | > Follow: [Draveness · Github](https://github.com/Draveness) 156 | 157 | 158 | -------------------------------------------------------------------------------- /contents/objc/懒惰的 initialize 方法.md: -------------------------------------------------------------------------------- 1 | # 懒惰的 initialize 方法 2 | 3 | > 因为 ObjC 的 runtime 只能在 Mac OS 下才能编译,所以文章中的代码都是在 Mac OS,也就是 `x86_64` 架构下运行的,对于在 arm64 中运行的代码会特别说明。 4 | 5 | ## 写在前面 6 | 7 | 这篇文章可能是对 Objective-C 源代码解析系列文章中最短的一篇了,在 Objective-C 中,我们总是会同时想到 `load`、`initialize` 这两个类方法。而这两个方法也经常在一起比较: 8 | 9 | 在上一篇介绍 `load` 方法的[文章](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/你真的了解%20load%20方法么?.md)中,已经对 `load` 方法的调用时机、调用顺序进行了详细地分析,所以对于 `load` 方法,这里就不在赘述了。 10 | 11 | 这篇文章会~~假设你知道:~~假设你是 iOS 开发者。 12 | 13 | 本文会主要介绍: 14 | 15 | 1. `initialize` 方法的调用为什么是惰性的 16 | 2. 这货能干啥 17 | 18 | ## initialize 的调用栈 19 | 20 | 在分析其调用栈之前,首先来解释一下,什么是惰性的。 21 | 22 | 这是 `main.m` 文件中的代码: 23 | 24 | ```objectivec 25 | #import 26 | 27 | @interface XXObject : NSObject @end 28 | 29 | @implementation XXObject 30 | 31 | + (void)initialize { 32 | NSLog(@"XXObject initialize"); 33 | } 34 | 35 | @end 36 | 37 | int main(int argc, const char * argv[]) { 38 | @autoreleasepool { } 39 | return 0; 40 | } 41 | ``` 42 | 43 | 主函数中的代码为空,如果我们运行这个程序: 44 | 45 | ![objc-initialize-print-nothing](../images/objc-initialize-print-nothing.png) 46 | 47 | 你会发现与 `load` 方法不同的是,虽然我们在 `initialize` 方法中调用了 `NSLog`。但是程序运行之后没有任何输出。 48 | 49 | 如果,我们在自动释放池中加入以下代码: 50 | 51 | ```objectivec 52 | int main(int argc, const char * argv[]) { 53 | @autoreleasepool { 54 | __unused XXObject *object = [[XXObject alloc] init]; 55 | } 56 | return 0; 57 | } 58 | ``` 59 | 60 | 再运行程序: 61 | 62 | ![objc-initialize-print-initialize](../images/objc-initialize-print-initialize.png) 63 | 64 | 你会发现,虽然我们没有直接调用 `initialize` 方法。但是,这里也打印出了 `XXObject initialize` 字符串。 65 | 66 | > `initialize` **只会在对应类的方法第一次被调用时,才会调用**。 67 | 68 | 我们在 `initialize` 方法中打一个断点,来查看这个方法的调用栈: 69 | 70 | ![objc-initialize-breakpoint](../images/objc-initialize-breakpoint.png) 71 | 72 | 73 | ```objectivec 74 | 0 +[XXObject initialize] 75 | 1 _class_initialize 76 | 2 lookUpImpOrForward 77 | 3 _class_lookupMethodAndLoadCache3 78 | 4 objc_msgSend 79 | 5 main 80 | 6 start 81 | ``` 82 | 83 | 直接来看调用栈中的 `lookUpImpOrForward` 方法,`lookUpImpOrForward` 方法**只会在向对象发送消息,并且在类的缓存中没有找到消息的选择子时**才会调用,具体可以看这篇文章,[从源代码看 ObjC 中消息的发送](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/从源代码看%20ObjC%20中消息的发送.md)。 84 | 85 | 在这里,我们知道 `lookUpImpOrForward` 方法是 `objc_msgSend` 触发的就够了。 86 | 87 | ![objc-initialize-print-selecto](../images/objc-initialize-print-selector.png) 88 | 89 | 在 lldb 中输入 `p sel` 打印选择子,会发现当前调用的方法是 `alloc` 方法,也就是说,`initialize` 方法是在 `alloc` 方法之前调用的,`alloc` 的调用导致了前者的执行。 90 | 91 | 其中,使用 `if (initialize && !cls->isInitialized())` 来判断当前类是否初始化过: 92 | 93 | ```objectivec 94 | bool isInitialized() { 95 | return getMeta()->data()->flags & RW_INITIALIZED; 96 | } 97 | ``` 98 | 99 | > 当前类是否初始化过的信息就保存在[元类](http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html)的 `class_rw_t` 结构体中的 `flags` 中。 100 | 101 | 这是 `flags` 中保存的信息,它记录着跟当前类的元数据,其中第 16-31 位有如下的作用: 102 | 103 | ![objc-initialize-class_rw_t_-bits-flag](../images/objc-initialize-class_rw_t_-bits-flag.png) 104 | 105 | `flags` 的第 29 位 `RW_INITIALIZED` 就保存了当前类是否初始化过的信息。 106 | 107 | ## \_class_initialize 方法 108 | 109 | 在 `initialize` 的调用栈中,直接调用其方法的是下面的这个 C 语言函数: 110 | 111 | ```objectivec 112 | void _class_initialize(Class cls) 113 | { 114 | Class supercls; 115 | BOOL reallyInitialize = NO; 116 | 117 | // 1. 强制父类先调用 initialize 方法 118 | supercls = cls->superclass; 119 | if (supercls && !supercls->isInitialized()) { 120 | _class_initialize(supercls); 121 | } 122 | 123 | { 124 | // 2. 通过加锁来设置 RW_INITIALIZING 标志位 125 | monitor_locker_t lock(classInitLock); 126 | if (!cls->isInitialized() && !cls->isInitializing()) { 127 | cls->setInitializing(); 128 | reallyInitialize = YES; 129 | } 130 | } 131 | 132 | if (reallyInitialize) { 133 | // 3. 成功设置标志位,向当前类发送 +initialize 消息 134 | _setThisThreadIsInitializingClass(cls); 135 | 136 | ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); 137 | 138 | // 4. 完成初始化,如果父类已经初始化完成,设置 RW_INITIALIZED 标志位, 139 | // 否则,在父类初始化完成之后再设置标志位。 140 | monitor_locker_t lock(classInitLock); 141 | if (!supercls || supercls->isInitialized()) { 142 | _finishInitializing(cls, supercls); 143 | } else { 144 | _finishInitializingAfter(cls, supercls); 145 | } 146 | return; 147 | } else if (cls->isInitializing()) { 148 | // 5. 当前线程正在初始化当前类,直接返回,否则,会等待其它线程初始化结束后,再返回 149 | if (_thisThreadIsInitializingClass(cls)) { 150 | return; 151 | } else { 152 | monitor_locker_t lock(classInitLock); 153 | while (!cls->isInitialized()) { 154 | classInitLock.wait(); 155 | } 156 | return; 157 | } 158 | } else if (cls->isInitialized()) { 159 | // 6. 初始化成功后,直接返回 160 | return; 161 | } else { 162 | _objc_fatal("thread-safe class init in objc runtime is buggy!"); 163 | } 164 | } 165 | ``` 166 | 167 | 方法的主要作用自然是向未初始化的类发送 `+initialize` 消息,不过会强制父类先发送 `+initialize`。 168 | 169 | 1. 强制**未初始化过的**父类调用 `initialize` 方法 170 | 171 | ```objectivec 172 | if (supercls && !supercls->isInitialized()) { 173 | _class_initialize(supercls); 174 | } 175 | ``` 176 | 177 | 2. 通过加锁来设置 `RW_INITIALIZING` 标志位 178 | 179 | ```objectivec 180 | monitor_locker_t lock(classInitLock); 181 | if (!cls->isInitialized() && !cls->isInitializing()) { 182 | cls->setInitializing(); 183 | reallyInitialize = YES; 184 | } 185 | ``` 186 | 187 | 3. 成功设置标志位、向当前类发送 `+initialize` 消息 188 | 189 | ```objectivec 190 | ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); 191 | ``` 192 | 193 | 4. 完成初始化,如果父类已经初始化完成,设置 `RW_INITIALIZED` 标志位。否则,在父类初始化完成之后再设置标志位 194 | 195 | ```objectivec 196 | monitor_locker_t lock(classInitLock); 197 | if (!supercls || supercls->isInitialized()) { 198 | _finishInitializing(cls, supercls); 199 | } else { 200 | _finishInitializingAfter(cls, supercls); 201 | } 202 | ``` 203 | 204 | 5. 如果当前线程正在初始化当前类,直接返回,否则,会等待其它线程初始化结束后,再返回,**保证线程安全** 205 | 206 | ```objectivec 207 | if (_thisThreadIsInitializingClass(cls)) { 208 | return; 209 | } else { 210 | monitor_locker_t lock(classInitLock); 211 | while (!cls->isInitialized()) { 212 | classInitLock.wait(); 213 | } 214 | return; 215 | } 216 | ``` 217 | 218 | 6. 初始化成功后,直接返回 219 | 220 | ```objectivec 221 | return; 222 | ``` 223 | 224 | ## 管理初始化队列 225 | 226 | 因为我们始终要保证父类的初始化方法要在子类之前调用,所以我们需要维护一个 `PendingInitializeMap` 的数据结构来存储**当前的类初始化需要哪个父类先初始化完成**。 227 | 228 | ![PendingInitializeMap](../images/PendingInitializeMap.png) 229 | 230 | 这个数据结构中的信息会被两个方法改变: 231 | 232 | ```objectivec 233 | if (!supercls || supercls->isInitialized()) { 234 | _finishInitializing(cls, supercls); 235 | } else { 236 | _finishInitializingAfter(cls, supercls); 237 | } 238 | ``` 239 | 240 | 分别是 `_finishInitializing` 以及 `_finishInitializingAfter`,先来看一下后者是怎么实现的,也就是**在父类没有完成初始化的时候**调用的方法: 241 | 242 | ```objectivec 243 | static void _finishInitializingAfter(Class cls, Class supercls) 244 | { 245 | PendingInitialize *pending; 246 | pending = (PendingInitialize *)malloc(sizeof(*pending)); 247 | pending->subclass = cls; 248 | pending->next = (PendingInitialize *)NXMapGet(pendingInitializeMap, supercls); 249 | NXMapInsert(pendingInitializeMap, supercls, pending); 250 | } 251 | ``` 252 | 253 | 因为当前类的父类没有初始化,所以会将子类加入一个数据结构 `PendingInitialize` 中,这个数据结构其实就类似于一个保存子类的链表。这个链表会以父类为键存储到 `pendingInitializeMap` 中。 254 | 255 | ```objective 256 | NXMapInsert(pendingInitializeMap, supercls, pending); 257 | ``` 258 | 259 | 而在**父类已经调用了初始化方法**的情况下,对应方法 `_finishInitializing` 的实现就稍微有些复杂了: 260 | 261 | ```objectivec 262 | static void _finishInitializing(Class cls, Class supercls) 263 | { 264 | PendingInitialize *pending; 265 | 266 | cls->setInitialized(); 267 | 268 | if (!pendingInitializeMap) return; 269 | pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls); 270 | if (!pending) return; 271 | 272 | NXMapRemove(pendingInitializeMap, cls); 273 | 274 | while (pending) { 275 | PendingInitialize *next = pending->next; 276 | if (pending->subclass) _finishInitializing(pending->subclass, cls); 277 | free(pending); 278 | pending = next; 279 | } 280 | } 281 | ``` 282 | 283 | 首先,由于父类已经完成了初始化,在这里直接将当前类标记成已经初始化,然后**递归地将被当前类 block 的子类标记为已初始化**,再把这些当类移除 `pendingInitializeMap`。 284 | 285 | ## 小结 286 | 287 | 到这里,我们对 `initialize` 方法的研究基本上已经结束了,这里会总结一下关于其方法的特性: 288 | 289 | 1. `initialize` 的调用是惰性的,它会在第一次调用当前类的方法时被调用 290 | 2. 与 `load` 不同,`initialize` 方法调用时,所有的类都**已经加载**到了内存中 291 | 3. `initialize` 的运行是线程安全的 292 | 4. 子类会**继承**父类的 `initialize` 方法 293 | 294 | 而其作用也非常局限,一般我们只会在 `initialize` 方法中进行一些常量的初始化。 295 | 296 | ## 参考资料 297 | 298 | + [What is a meta-class in Objective-C?](http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html) 299 | + [NSObject +load and +initialize - What do they do?](http://stackoverflow.com/questions/13326435/nsobject-load-and-initialize-what-do-they-do) 300 | 301 | Follow: [@Draveness](https://github.com/Draveness) 302 | 303 | -------------------------------------------------------------------------------- /contents/objc/黑箱中的 retain 和 release.md: -------------------------------------------------------------------------------- 1 | # 黑箱中的 retain 和 release 2 | 3 | > 由于 Objective-C 中的内存管理是一个比较大的话题,所以会分为两篇文章来对内存管理中的一些机制进行剖析,一部分分析自动释放池以及 `autorelease` 方法,另一部分分析 `retain`、`release` 方法的实现以及自动引用计数。 4 | 5 | + [自动释放池的前世今生](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/自动释放池的前世今生.md) 6 | + [黑箱中的 retain 和 release](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/黑箱中的%20retain%20和%20release.md) 7 | 8 | ## 写在前面 9 | 10 | 在接口设计时,我们经常要考虑某些意义上的平衡。在内存管理中也是这样,Objective-C 同时为我们提供了增加引用计数的 `retain` 和减少引用计数的 `release` 方法。 11 | 12 | 这篇文章会在源代码层面介绍 Objective-C 中 `retain` 和 `release` 的实现,它们是如何达到平衡的。 13 | 14 | ## 从 retain 开始 15 | 16 | 如今我们已经进入了全面使用 ARC 的时代,几年前还经常使用的 `retain` 和 `release` 方法已经很难出现于我们的视野中了,绝大多数内存管理的实现细节都由编译器代劳。 17 | 18 | 在这里,我们还要从 `retain` 方法开始,对内存管理的实现细节一探究竟。 19 | 20 | 下面是 `retain` 方法的调用栈: 21 | 22 | ```objectivec 23 | - [NSObject retain] 24 | └── id objc_object::rootRetain() 25 | └── id objc_object::rootRetain(bool tryRetain, bool handleOverflow) 26 | ├── uintptr_t LoadExclusive(uintptr_t *src) 27 | ├── uintptr_t addc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout) 28 | ├── uintptr_t bits 29 | │ └── uintptr_t has_sidetable_rc 30 | ├── bool StoreExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) 31 | └── bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)    32 | └── uintptr_t addc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout) 33 | ``` 34 | 35 | 调用栈中的前两个方法的实现直接调用了下一个方法: 36 | 37 | ```objectivec 38 | - (id)retain { 39 | return ((id)self)->rootRetain(); 40 | } 41 | 42 | id objc_object::rootRetain() { 43 | return rootRetain(false, false); 44 | } 45 | ``` 46 | 47 | 而 `id objc_object::rootRetain(bool tryRetain, bool handleOverflow)` 方法是调用栈中最重要的方法,其原理就是将 `isa` 结构体中的 `extra_rc` 的值加一。 48 | 49 | `extra_rc` 就是用于保存自动引用计数的标志位,下面就是 `isa` 结构体中的结构: 50 | 51 | ![objc-rr-isa-struct](../images/objc-rr-isa-struct.png) 52 | 53 | 接下来我们会分三种情况对 `rootRetain` 进行分析。 54 | 55 | ### 正常的 rootRetain 56 | 57 | 这是简化后的 `rootRetain` 方法的实现,其中只有处理一般情况的代码: 58 | 59 | ```objectivec 60 | id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { 61 | isa_t oldisa; 62 | isa_t newisa; 63 | 64 | do { 65 | oldisa = LoadExclusive(&isa.bits); 66 | newisa = oldisa; 67 | 68 | uintptr_t carry; 69 | newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); 70 | } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)); 71 | 72 | return (id)this; 73 | } 74 | ``` 75 | 76 | > 在这里我们假设的条件是 `isa` 中的 `extra_rc` 的位数足以存储 `retainCount`。 77 | 78 | 1. 使用 `LoadExclusive` 加载 `isa` 的值 79 | 2. 调用 `addc(newisa.bits, RC_ONE, 0, &carry)` 方法将 `isa` 的值加一 80 | 3. 调用 `StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)` 更新 `isa` 的值 81 | 4. 返回当前对象 82 | 83 | ### 有进位版本的 rootRetain 84 | 85 | 在这里调用 `addc` 方法为 `extra_rc` 加一时,8 位的 `extra_rc` 可能不足以保存引用计数。 86 | 87 | ```objectivec 88 | id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { 89 | transcribeToSideTable = false; 90 | isa_t oldisa = LoadExclusive(&isa.bits); 91 | isa_t newisa = oldisa; 92 | 93 | uintptr_t carry; 94 | newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); 95 | 96 | if (carry && !handleOverflow) 97 | return rootRetain_overflow(tryRetain); 98 | } 99 | ``` 100 | 101 | > `extra_rc` 不足以保存引用计数,并且 `handleOverflow = false`。 102 | 103 | 当方法传入的 `handleOverflow = false` 时(这也是通常情况),我们会调用 `rootRetain_overflow` 方法: 104 | 105 | ```objectivec 106 | id objc_object::rootRetain_overflow(bool tryRetain) { 107 | return rootRetain(tryRetain, true); 108 | } 109 | ``` 110 | 111 | 这个方法其实就是重新执行 `rootRetain` 方法,并传入 `handleOverflow = true`。 112 | 113 | ### 有进位版本的 rootRetain(处理溢出) 114 | 115 | 当传入的 `handleOverflow = true` 时,我们就会在 `rootRetain` 方法中处理引用计数的溢出。 116 | 117 | ```objectivec 118 | id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { 119 | bool sideTableLocked = false; 120 | 121 | isa_t oldisa; 122 | isa_t newisa; 123 | 124 | do { 125 | oldisa = LoadExclusive(&isa.bits); 126 | newisa = oldisa; 127 | uintptr_t carry; 128 | newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); 129 | 130 | if (carry) { 131 | newisa.extra_rc = RC_HALF; 132 | newisa.has_sidetable_rc = true; 133 | } 134 | } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)); 135 | 136 | sidetable_addExtraRC_nolock(RC_HALF); 137 | 138 | return (id)this; 139 | } 140 | ``` 141 | 142 | 当调用这个方法,并且 `handleOverflow = true` 时,我们就可以确定 `carry` 一定是存在的了, 143 | 144 | 因为 `extra_rc` 已经溢出了,所以要更新它的值为 `RC_HALF`: 145 | 146 | ```c 147 | #define RC_HALF (1ULL<<7) 148 | ``` 149 | 150 | > `extra_rc` 总共为 8 位,`RC_HALF = 0b10000000`。 151 | 152 | 然后设置 `has_sidetable_rc` 为真,存储新的 `isa` 的值之后,调用 `sidetable_addExtraRC_nolock` 方法。 153 | 154 | ```objectivec 155 | bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc) { 156 | SideTable& table = SideTables()[this]; 157 | 158 | size_t& refcntStorage = table.refcnts[this]; 159 | size_t oldRefcnt = refcntStorage; 160 | 161 | if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true; 162 | 163 | uintptr_t carry; 164 | size_t newRefcnt = 165 | addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); 166 | if (carry) { 167 | refcntStorage = SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK); 168 | return true; 169 | } else { 170 | refcntStorage = newRefcnt; 171 | return false; 172 | } 173 | } 174 | ``` 175 | 176 | 这里我们将溢出的一位 `RC_HALF` 添加到 `oldRefcnt` 中,其中的各种 `SIDE_TABLE` 宏定义如下: 177 | 178 | ```objectivec 179 | #define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0) 180 | #define SIDE_TABLE_DEALLOCATING (1UL<<1) 181 | #define SIDE_TABLE_RC_ONE (1UL<<2) 182 | #define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1)) 183 | 184 | #define SIDE_TABLE_RC_SHIFT 2 185 | #define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1) 186 | ``` 187 | 188 | 因为 `refcnts` 中的 64 为的最低两位是有意义的标志位,所以在使用 `addc` 时要将 `delta_rc` 左移两位,获得一个新的引用计数 `newRefcnt`。 189 | 190 | 如果这时出现了溢出,那么就会撤销这次的行为。否则,会将新的引用计数存储到 `refcntStorage` 指针中。 191 | 192 | ---- 193 | 194 | 也就是说,在 iOS 的内存管理中,我们使用了 `isa` 结构体中的 `extra_rc` 和 `SideTable` 来存储某个对象的自动引用计数。 195 | 196 | 更重要的是,**如果自动引用计数为 1,`extra_rc` 实际上为 0**,因为它保存的是额外的引用计数,我们通过这个行为能够减少很多不必要的函数调用。 197 | 198 | 到目前为止,我们已经从头梳理了 `retain` 方法的调用栈及其实现。下面要介绍的是在内存管理中,我们是如何使用 `release` 方法平衡这个方法的。 199 | 200 | ## 以 release 结束 201 | 202 | 与 release 方法相似,我们看一下这个方法简化后的调用栈: 203 | 204 | ```objectivec 205 | - [NSObject release] 206 | └── id objc_object::rootRelease() 207 | └── id objc_object::rootRetain(bool performDealloc, bool handleUnderflow) 208 | ``` 209 | 210 | 前面的两个方法的实现和 `retain` 中的相差无几,这里就直接跳过了。 211 | 212 | 同样,在分析 `release` 方法时,我们也根据上下文的不同,将 `release` 方法的实现拆分为三部分,说明它到底是如何调用的。 213 | 214 | ### 正常的 release 215 | 216 | 这一个版本的方法调用可以说是最简版本的方法调用了: 217 | 218 | ```objectivec 219 | bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { 220 | isa_t oldisa; 221 | isa_t newisa; 222 | 223 | do { 224 | oldisa = LoadExclusive(&isa.bits); 225 | newisa = oldisa; 226 | 227 | uintptr_t carry; 228 | newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); 229 | } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)); 230 | 231 | return false; 232 | } 233 | ``` 234 | 235 | 1. 使用 `LoadExclusive` 获取 `isa` 内容 236 | 2. 将 `isa` 中的引用计数减一 237 | 3. 调用 `StoreReleaseExclusive` 方法保存新的 `isa` 238 | 239 | ### 从 SideTable 借位 240 | 241 | 接下来,我们就要看两种相对比较复杂的情况了,首先是从 `SideTable` 借位的版本: 242 | 243 | ```objectivec 244 | bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { 245 | isa_t oldisa; 246 | isa_t newisa; 247 | 248 | do { 249 | oldisa = LoadExclusive(&isa.bits); 250 | newisa = oldisa; 251 | 252 | uintptr_t carry; 253 | newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); 254 | if (carry) goto underflow; 255 | } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)); 256 | 257 | ... 258 | 259 | underflow: 260 | newisa = oldisa; 261 | 262 | if (newisa.has_sidetable_rc) { 263 | if (!handleUnderflow) { 264 | return rootRelease_underflow(performDealloc); 265 | } 266 | 267 | size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); 268 | 269 | if (borrowed > 0) { 270 | newisa.extra_rc = borrowed - 1; 271 | bool stored = StoreExclusive(&isa.bits, oldisa.bits, newisa.bits); 272 | 273 | return false; 274 | } 275 | } 276 | } 277 | ``` 278 | 279 | > 这里省去了使用锁来**防止竞争条件**以及**调用 `StoreExclusive` 失败后恢复现场**的代码。 280 | > 我们会默认这里存在 `SideTable`,也就是 `has_sidetable_rc = true`。 281 | 282 | 你可以看到,这里也有一个 `handleUnderflow`,与 retain 中的相同,如果发生了 `underflow`,会重新调用该 `rootRelease` 方法,并传入 `handleUnderflow = true`。 283 | 284 | 在调用 `sidetable_subExtraRC_nolock` 成功借位之后,我们会重新设置 `newisa` 的值 `newisa.extra_rc = borrowed - 1` 并更新 `isa`。 285 | 286 | ### release 中调用 dealloc 287 | 288 | 如果在 `SideTable` 中也没有获取到借位的话,就说明没有任何的变量引用了当前对象(即 `retainCount = 0`),就需要向它发送 `dealloc` 消息了。 289 | 290 | ```objectivec 291 | bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { 292 | isa_t oldisa; 293 | isa_t newisa; 294 | 295 | retry: 296 | do { 297 | oldisa = LoadExclusive(&isa.bits); 298 | newisa = oldisa; 299 | 300 | uintptr_t carry; 301 | newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); 302 | if (carry) goto underflow; 303 | } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)); 304 | 305 | ... 306 | 307 | underflow: 308 | newisa = oldisa; 309 | 310 | if (newisa.deallocating) { 311 | return overrelease_error(); 312 | } 313 | newisa.deallocating = true; 314 | StoreExclusive(&isa.bits, oldisa.bits, newisa.bits); 315 | 316 | if (performDealloc) { 317 | ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); 318 | } 319 | return true; 320 | } 321 | ``` 322 | 323 | 上述代码会直接调用 `objc_msgSend` 向当前对象发送 `dealloc` 消息。 324 | 325 | 不过为了确保消息只会发送一次,我们使用 `deallocating` 标记位。 326 | 327 | ## 获取自动引用计数 328 | 329 | 在文章的最结尾,笔者想要介绍一下 `retainCount` 的值是怎么计算的,我们直接来看 `retainCount` 方法的实现: 330 | 331 | ```objectivec 332 | - (NSUInteger)retainCount { 333 | return ((id)self)->rootRetainCount(); 334 | } 335 | 336 | inline uintptr_t objc_object::rootRetainCount() { 337 | isa_t bits = LoadExclusive(&isa.bits); 338 | uintptr_t rc = 1 + bits.extra_rc; 339 | if (bits.has_sidetable_rc) { 340 | rc += sidetable_getExtraRC_nolock(); 341 | } 342 | return rc; 343 | } 344 | ``` 345 | 346 | 根据方法的实现,retainCount 有三部分组成: 347 | 348 | + 1 349 | + `extra_rc` 中存储的值 350 | + `sidetable_getExtraRC_nolock` 返回的值 351 | 352 | 这也就证明了我们之前得到的结论。 353 | 354 | ## 小结 355 | 356 | 我们在这篇文章中已经介绍了 `retain` 和 `release` 这一对用于内存管理的方法是如何实现的,这里总结一下文章一下比较重要的问题。 357 | 358 | + `extra_rc` 只会保存额外的自动引用计数,对象实际的引用计数会在这个基础上 +1 359 | + Objective-C 使用 `isa` 中的 `extra_rc` 和 `SideTable` 来存储对象的引用计数 360 | + 在对象的引用计数归零时,会调用 `dealloc` 方法回收对象 361 | 362 | 有关于自动释放池实现的介绍,可以看[自动释放池的前世今生](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/自动释放池的前世今生.md)。 363 | 364 | > Follow: [Draveness · Github](https://github.com/Draveness) 365 | 366 | 367 | --------------------------------------------------------------------------------