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 | 
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 | 
47 |
48 | 但是,这样就有一个问题,类方法的实现又是如何查找并且调用的呢?这时,就需要引入*元类*来保证无论是类还是对象都能**通过相同的机制查找方法的实现**。
49 |
50 |
51 | 
52 |
53 |
54 | 让每一个类的 `isa` 指向对应的元类,这样就达到了使类方法和实例方法的调用机制相同的目的:
55 |
56 | + 实例方法调用时,通过对象的 `isa` 在类中获取方法的实现
57 | + 类方法调用时,通过类的 `isa` 在元类中获取方法的实现
58 |
59 | 下面这张图介绍了对象,类与元类之间的关系,笔者认为已经觉得足够清晰了,所以不在赘述。
60 |
61 |
62 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
268 |
269 | ```
270 | object_pointer: 0000000001011101100000000000000100000000001110101110000011111001 // 补全至 64 位
271 | class_pointer: 100000000001110101110000011111000
272 | ```
273 |
274 | > 编译器对直接访问 `isa` 的操作会有警告,因为直接访问 `isa` 已经不会返回类指针了,这种行为已经被弃用了,取而代之的是使用 [ISA()](#ISA()) 方法来获取类指针。
275 |
276 | 代码中的 `object` 对象的 `isa` 结构体中的内容是这样的:
277 |
278 | 
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 | 
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 | 
63 |
64 | 你会发现,虽然我们没有直接调用 `initialize` 方法。但是,这里也打印出了 `XXObject initialize` 字符串。
65 |
66 | > `initialize` **只会在对应类的方法第一次被调用时,才会调用**。
67 |
68 | 我们在 `initialize` 方法中打一个断点,来查看这个方法的调用栈:
69 |
70 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------