OC 底层探索 13、类的加载1 - dyld和objc的关联

本文开始探索类的加载。调试源码 objc 源码工程

_objc_init 函数:

 1 /***********************************************************************
 2 * _objc_init
 3 * Bootstrap initialization. Registers our image notifier with dyld.
 4 * Called by libSystem BEFORE library initialization time
 5 **********************************************************************/
 6 
 7 void _objc_init(void)
 8 {
 9     static bool initialized = false;
10     if (initialized) return;
11     initialized = true;
12     
13     // fixme defer initialization until an objc-using image is found?
14     environ_init();
15     tls_init();
16     static_init();
17     runtime_init();
18     exception_init();
19     cache_init();
20     _imp_implementationWithBlock_init();
21 
22     _dyld_objc_notify_register(&map_images, load_images, unmap_image);
23 
24 #if __OBJC2__
25     didCallDyldNotifyRegister = true;
26 #endif
27 }

一、各 init 初始化的简单了解

1)环境变量的初始化与简单使用

我们从 14 行 environ_init() 环境变量初始化开始进行探究。

environ_init() 函数:

读取影响运行时的环境变量

 1 /***********************************************************************
 2 * environ_init
 3 * Read environment variables that affect the runtime.
 4 * Also print environment variable help, if requested.
 5 **********************************************************************/
 6 void environ_init(void) 
 7 {
 8     if (issetugid()) {
 9         // All environment variables are silently ignored when setuid or setgid
10         // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
11         return;
12     } 
13 
14     bool PrintHelp = false;
15     bool PrintOptions = false;
16     bool maybeMallocDebugging = false;
17 
18     // Scan environ[] directly instead of calling getenv() a lot.
19     // This optimizes the case where none are set.
20     for (char **p = *_NSGetEnviron(); *p != nil; p++) {
21             ...... // 代码不全部展示了 
22     }
23 
24     // Special case: enable some autorelease pool debugging 
25     // when some malloc debugging is enabled 
26     // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
27     if (maybeMallocDebugging) {
28         ...... // 代码不全部展示了
29     }
30 
31     // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
32     if (PrintHelp  ||  PrintOptions) {
33         if (PrintHelp) {
34             _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
35             _objc_inform("OBJC_HELP: describe available environment variables");
36             if (PrintOptions) {
37                 _objc_inform("OBJC_HELP is set");
38             }
39             _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
40         }
41         if (PrintOptions) {
42             _objc_inform("OBJC_PRINT_OPTIONS is set");
43         }
44 
45         for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
46             const option_t *opt = &Settings[i];            
47             if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
48             if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
49         }
50     }
51     
52     // 我加的调试代码
53     for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
54         const option_t *opt = &Settings[i];
55         _objc_inform("%s: %s", opt->env, opt->help);
56         _objc_inform("%s is set", opt->env);
57     }
58 }

我们通过运行工程(objc源码工程),发现上述代码都没有走进去,把打印输出提取出来(52行)再次运行,可看到输出信息如下(信息比较长截取部分):

环境变量的使用

上图输出结果倒数4行,我们看到是 OBJC_DISABLE_NONPOINTER_ISA 是 isa 的设置,我们在工程中 Edit Scheme 中添加此属性,如下图:

 

main.m 中创建一个 MYPerson,不同场景运行工程结果如下:

我们可通过环境换量的设置,更便捷的对工程有整体的了解。

更直观的,我们添加 OBJC_PRINT_LOAD_METHODS : YES

注释掉上述调试代码(52~57),再次运行,所有的 load 方法都输出了,如下:

2)tls_init() -

关于线程 key 的绑定 - 例如现成的析构函数。(runloop/自动释放池等都依赖于线程)

1 void tls_init(void)
2 {
3 #if SUPPORT_DIRECT_THREAD_KEYS
4     pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
5 #else
6     _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
7 #endif
8 }

static_init() - objc 中的静态构造函数

运行C++静态构造函数,它们的调用会比 dyld 还早。

 1 /***********************************************************************
 2 * static_init
 3 * Run C++ static constructor functions.
 4 * libc calls _objc_init() before dyld would call our static constructors, 
 5 * so we have to do it ourselves.
 6 **********************************************************************/
 7 static void static_init()
 8 {
 9     size_t count;
10     auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
11     for (size_t i = 0; i < count; i++) {
12         inits[i]();
13     }
14 }

runtime_init() 

runtime 运行时环境初始化

1 void runtime_init(void)
2 {
3     objc::unattachedCategories.init(32);// 分类处理的初始化
4     objc::allocatedClasses.init();// 表 用来储存加载的类
5 }

exception_init() - 异常

初始化libobjc 的异常处理系统。

crash 系统抛出异常,程序中断

1 /***********************************************************************
2 * exception_init
3 * Initialize libobjc's exception handling system.
4 * Called by map_images().
5 **********************************************************************/
6 void exception_init(void)
7 {
8     old_terminate = std::set_terminate(&_objc_terminate);
9 }
 1 /***********************************************************************
 2 * _objc_terminate
 3 * Custom std::terminate handler.
 4 *
 5 * The uncaught exception callback is implemented as a std::terminate handler. 
 6 * 1. Check if there's an active exception
 7 * 2. If so, check if it's an Objective-C exception
 8 * 3. If so, call our registered callback with the object.
 9 * 4. Finally, call the previous terminate handler.
10 **********************************************************************/
11 static void (*old_terminate)(void) = nil;
12 static void _objc_terminate(void)
13 {
14     if (PrintExceptions) {
15         _objc_inform("EXCEPTIONS: terminating");
16     }
17 
18     if (! __cxa_current_exception_type()) {
19         // No current exception.
20         (*old_terminate)();
21     }
22     else {
23         // There is a current exception. Check if it's an objc exception.
24         @try {
25             __cxa_rethrow();
26         } @catch (id e) {
27             // It's an objc object. Call Foundation's handler, if any. 句柄 函数回调 - 截取异常
28             (*uncaught_handler)((id)e);
29             (*old_terminate)();
30         } @catch (...) {
31             // It's not an objc object. Continue to C++ terminate.
32             (*old_terminate)();
33         }
34     }
35 }

cache_init()

缓存条件初始化

 1 void cache_init()
 2 {
 3 #if HAVE_TASK_RESTARTABLE_RANGES
 4     mach_msg_type_number_t count = 0;
 5     kern_return_t kr;
 6 
 7     while (objc_restartableRanges[count].location) {
 8         count++;
 9     }
10 
11     kr = task_restartable_ranges_register(mach_task_self(),
12                                           objc_restartableRanges, count);
13     if (kr == KERN_SUCCESS) return;
14     _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
15                 kr, mach_error_string(kr));
16 #endif // HAVE_TASK_RESTARTABLE_RANGES
17 }

_imp_implementationWithBlock_init()

启用回调机制。通常情况下不做什么操作,因为所有的初始化都是懒加载的,但对于某些进程,迫切的需要加载 trampolines dylib.

 1 /// Initialize the trampoline machinery. Normally this does nothing, as
 2 /// everything is initialized lazily, but for certain processes we eagerly load
 3 /// the trampolines dylib.
 4 void
 5 _imp_implementationWithBlock_init(void)
 6 {
 7 #if TARGET_OS_OSX
 8     // Eagerly load libobjc-trampolines.dylib in certain processes. Some
 9     // programs (most notably QtWebEngineProcess used by older versions of
10     // embedded Chromium) enable a highly restrictive sandbox profile which
11     // blocks access to that dylib. If anything calls
12     // imp_implementationWithBlock (as AppKit has started doing) then we'll
13     // crash trying to load it. Loading it here sets it up before the sandbox
14     // profile is enabled and blocks it.
15     //
16     // This fixes EA Origin (rdar://problem/50813789)
17     // and Steam (rdar://problem/55286131)
18     if (__progname &&
19         (strcmp(__progname, "QtWebEngineProcess") == 0 ||
20          strcmp(__progname, "Steam Helper") == 0)) {
21         Trampolines.Initialize();
22     }
23 #endif
24 }

二、_dyld_objc_notify_register() - 

 

代码 --> 编译 --> machO --> 内存

把 macho 各式各样数据加载到内存中,如何加载?--> map_images 镜像文件装载到内存 

_dyld_objc_notify_register() 是 dyld 库 的调用(跨库,实现在dyld库中)。

执行流程:dyld 中 从 dyld_start 开始 --> 一系列操作 --> 跳 objc 中 处理完成 --> 回到 dyld 中继续执行 --> main 入口。 具体流程可见 OC 底层探索 12.

 1 /**
 2     mapped -- &map_images
 3     init -- load_images
 4 */
 5 void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
 6                                 _dyld_objc_notify_init      init,
 7                                 _dyld_objc_notify_unmapped  unmapped)
 8 {
 9     if ( gUseDyld3 )
10         return dyld3::_dyld_objc_notify_register(mapped, init, unmapped);
11 
12     DYLD_LOCK_THIS_BLOCK;
13     static bool (*p)(_dyld_objc_notify_mapped, _dyld_objc_notify_init, _dyld_objc_notify_unmapped) = NULL;
14 
15     if(p == NULL)
16         _dyld_func_lookup("__dyld_objc_notify_register", (void**)&p);
17     p(mapped, init, unmapped);
18 }

map_images / load_images / unmap_image 何时调:

dyld: 

 1 void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
 2 {
 3     // record functions to call
 4     sNotifyObjCMapped    = mapped;
 5     sNotifyObjCInit        = init;
 6     sNotifyObjCUnmapped = unmapped;
 7 
 8     // call 'mapped' function with all images mapped so far
 9     try {
10         notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
11     }
12     catch (const char* msg) {
13         // ignore request to abort during registration
14     }
15 
16     // <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
17     for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
18         ImageLoader* image = *it;
19         if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
20             dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
21             (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
22         }
23     }
24 }

sNotifyObjCMapped --> notifyBatchPartial() : (*sNotifyObjCMapped)(objcImageCount, paths, mhs);

sNotifyObjCInit --> notifySingle() : (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());

sNotifyObjCUnmapped --> removeImage() : (*sNotifyObjCUnmapped)(image->getRealPath(), image->machHeader());

1)map_images 

 1 /***********************************************************************
 2 * map_images
 3 * Process the given images which are being mapped in by dyld.
 4 * Calls ABI-agnostic code after taking ABI-specific locks.
 5 *
 6 * Locking: write-locks runtimeLock
 7 **********************************************************************/
 8 void
 9 map_images(unsigned count, const char * const paths[],
10            const struct mach_header * const mhdrs[])
11 {
12     mutex_locker_t lock(runtimeLock);
13     return map_images_nolock(count, paths, mhdrs);
14 }

跳到 map_images_nolock() 实现: 

_read_images():代码比较长

  1 /***********************************************************************
  2 * _read_images
  3 * Perform initial processing of the headers in the linked 
  4 * list beginning with headerList. 
  5 *
  6 * Called by: map_images_nolock
  7 *
  8 * Locking: runtimeLock acquired by map_images
  9 **********************************************************************/
 10 void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
 11 {
 12     header_info *hi;
 13     uint32_t hIndex;
 14     size_t count;
 15     size_t i;
 16     Class *resolvedFutureClasses = nil;
 17     size_t resolvedFutureClassCount = 0;
 18     static bool doneOnce;
 19     bool launchTime = NO;
 20     TimeLogger ts(PrintImageTimes);
 21 
 22     runtimeLock.assertLocked();
 23 
 24 #define EACH_HEADER \
 25     hIndex = 0;         \
 26     hIndex < hCount && (hi = hList[hIndex]); \
 27     hIndex++
 28 
 29     if (!doneOnce) {
 30         doneOnce = YES;
 31         launchTime = YES;
 32 
 33 #if SUPPORT_NONPOINTER_ISA
 34         // Disable non-pointer isa under some conditions.
 35 
 36 # if SUPPORT_INDEXED_ISA
 37         // Disable nonpointer isa if any image contains old Swift code
 38         for (EACH_HEADER) {
 39             if (hi->info()->containsSwift()  &&
 40                 hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
 41             {
 42                 DisableNonpointerIsa = true;
 43                 if (PrintRawIsa) {
 44                     _objc_inform("RAW ISA: disabling non-pointer isa because "
 45                                  "the app or a framework contains Swift code "
 46                                  "older than Swift 3.0");
 47                 }
 48                 break;
 49             }
 50         }
 51 # endif
 52 
 53 # if TARGET_OS_OSX
 54         // Disable non-pointer isa if the app is too old
 55         // (linked before OS X 10.11)
 56         if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {
 57             DisableNonpointerIsa = true;
 58             if (PrintRawIsa) {
 59                 _objc_inform("RAW ISA: disabling non-pointer isa because "
 60                              "the app is too old (SDK version " SDK_FORMAT ")",
 61                              FORMAT_SDK(dyld_get_program_sdk_version()));
 62             }
 63         }
 64 
 65         // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
 66         // New apps that load old extensions may need this.
 67         for (EACH_HEADER) {
 68             if (hi->mhdr()->filetype != MH_EXECUTE) continue;
 69             unsigned long size;
 70             if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
 71                 DisableNonpointerIsa = true;
 72                 if (PrintRawIsa) {
 73                     _objc_inform("RAW ISA: disabling non-pointer isa because "
 74                                  "the app has a __DATA,__objc_rawisa section");
 75                 }
 76             }
 77             break;  // assume only one MH_EXECUTE image
 78         }
 79 # endif
 80 
 81 #endif
 82 
 83         if (DisableTaggedPointers) {
 84             disableTaggedPointers();
 85         }
 86         
 87         initializeTaggedPointerObfuscator();
 88 
 89         if (PrintConnecting) {
 90             _objc_inform("CLASS: found %d classes during launch", totalClasses);
 91         }
 92 
 93         // namedClasses
 94         // Preoptimized classes don't go in this table.
 95         // 4/3 is NXMapTable's load factor
 96         int namedClassesSize = 
 97             (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
 98         gdb_objc_realized_classes =
 99             NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
100 
101         ts.log("IMAGE TIMES: first time tasks");
102     }
103 
104     // Fix up @selector references
105     static size_t UnfixedSelectors;
106     {
107         mutex_locker_t lock(selLock);
108         for (EACH_HEADER) {
109             if (hi->hasPreoptimizedSelectors()) continue;
110 
111             bool isBundle = hi->isBundle();
112             SEL *sels = _getObjc2SelectorRefs(hi, &count);
113             UnfixedSelectors += count;
114             for (i = 0; i < count; i++) {
115                 const char *name = sel_cname(sels[i]);
116                 SEL sel = sel_registerNameNoLock(name, isBundle);
117                 if (sels[i] != sel) {
118                     sels[i] = sel;
119                 }
120             }
121         }
122     }
123 
124     ts.log("IMAGE TIMES: fix up selector references");
125 
126     // Discover classes. Fix up unresolved future classes. Mark bundle classes.
127     bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
128 
129     for (EACH_HEADER) {
130         if (! mustReadClasses(hi, hasDyldRoots)) {
131             // Image is sufficiently optimized that we need not call readClass()
132             continue;
133         }
134 
135         classref_t const *classlist = _getObjc2ClassList(hi, &count);
136 
137         bool headerIsBundle = hi->isBundle();
138         bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
139 
140         for (i = 0; i < count; i++) {
141             Class cls = (Class)classlist[i];
142             Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
143 
144             if (newCls != cls  &&  newCls) {
145                 // Class was moved but not deleted. Currently this occurs 
146                 // only when the new class resolved a future class.
147                 // Non-lazily realize the class below.
148                 resolvedFutureClasses = (Class *)
149                     realloc(resolvedFutureClasses, 
150                             (resolvedFutureClassCount+1) * sizeof(Class));
151                 resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
152             }
153         }
154     }
155 
156     ts.log("IMAGE TIMES: discover classes");
157 
158     // Fix up remapped classes
159     // Class list and nonlazy class list remain unremapped.
160     // Class refs and super refs are remapped for message dispatching.
161     
162     if (!noClassesRemapped()) {
163         for (EACH_HEADER) {
164             Class *classrefs = _getObjc2ClassRefs(hi, &count);
165             for (i = 0; i < count; i++) {
166                 remapClassRef(&classrefs[i]);
167             }
168             // fixme why doesn't test future1 catch the absence of this?
169             classrefs = _getObjc2SuperRefs(hi, &count);
170             for (i = 0; i < count; i++) {
171                 remapClassRef(&classrefs[i]);
172             }
173         }
174     }
175 
176     ts.log("IMAGE TIMES: remap classes");
177 
178 #if SUPPORT_FIXUP
179     // Fix up old objc_msgSend_fixup call sites
180     for (EACH_HEADER) {
181         message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
182         if (count == 0) continue;
183 
184         if (PrintVtables) {
185             _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
186                          "call sites in %s", count, hi->fname());
187         }
188         for (i = 0; i < count; i++) {
189             fixupMessageRef(refs+i);
190         }
191     }
192 
193     ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
194 #endif
195 
196     bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
197 
198     // Discover protocols. Fix up protocol refs.
199     for (EACH_HEADER) {
200         extern objc_class OBJC_CLASS_$_Protocol;
201         Class cls = (Class)&OBJC_CLASS_$_Protocol;
202         ASSERT(cls);
203         NXMapTable *protocol_map = protocols();
204         bool isPreoptimized = hi->hasPreoptimizedProtocols();
205 
206         // Skip reading protocols if this is an image from the shared cache
207         // and we support roots
208         // Note, after launch we do need to walk the protocol as the protocol
209         // in the shared cache is marked with isCanonical() and that may not
210         // be true if some non-shared cache binary was chosen as the canonical
211         // definition
212         if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
213             if (PrintProtocols) {
214                 _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
215                              hi->fname());
216             }
217             continue;
218         }
219 
220         bool isBundle = hi->isBundle();
221 
222         protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
223         for (i = 0; i < count; i++) {
224             readProtocol(protolist[i], cls, protocol_map, 
225                          isPreoptimized, isBundle);
226         }
227     }
228 
229     ts.log("IMAGE TIMES: discover protocols");
230 
231     // Fix up @protocol references
232     // Preoptimized images may have the right 
233     // answer already but we don't know for sure.
234     for (EACH_HEADER) {
235         // At launch time, we know preoptimized image refs are pointing at the
236         // shared cache definition of a protocol.  We can skip the check on
237         // launch, but have to visit @protocol refs for shared cache images
238         // loaded later.
239         if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
240             continue;
241         protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
242         for (i = 0; i < count; i++) {
243             remapProtocolRef(&protolist[i]);
244         }
245     }
246 
247     ts.log("IMAGE TIMES: fix up @protocol references");
248 
249     // Discover categories. Only do this after the initial category
250     // attachment has been done. For categories present at startup,
251     // discovery is deferred until the first load_images call after
252     // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
253     if (didInitialAttachCategories) {
254         for (EACH_HEADER) {
255             load_categories_nolock(hi);
256         }
257     }
258 
259     ts.log("IMAGE TIMES: discover categories");
260 
261     // Category discovery MUST BE Late to avoid potential races
262     // when other threads call the new category code before
263     // this thread finishes its fixups.
264 
265     // +load handled by prepare_load_methods()
266 
267     // Realize non-lazy classes (for +load methods and static instances)
268     for (EACH_HEADER) {
269         classref_t const *classlist = 
270             _getObjc2NonlazyClassList(hi, &count);
271         for (i = 0; i < count; i++) {
272             Class cls = remapClass(classlist[i]);
273             if (!cls) continue;
274 
275             addClassTableEntry(cls);
276 
277             if (cls->isSwiftStable()) {
278                 if (cls->swiftMetadataInitializer()) {
279                     _objc_fatal("Swift class %s with a metadata initializer "
280                                 "is not allowed to be non-lazy",
281                                 cls->nameForLogging());
282                 }
283                 // fixme also disallow relocatable classes
284                 // We can't disallow all Swift classes because of
285                 // classes like Swift.__EmptyArrayStorage
286             }
287             realizeClassWithoutSwift(cls, nil);
288         }
289     }
290 
291     ts.log("IMAGE TIMES: realize non-lazy classes");
292 
293     // Realize newly-resolved future classes, in case CF manipulates them
294     if (resolvedFutureClasses) {
295         for (i = 0; i < resolvedFutureClassCount; i++) {
296             Class cls = resolvedFutureClasses[i];
297             if (cls->isSwiftStable()) {
298                 _objc_fatal("Swift class is not allowed to be future");
299             }
300             realizeClassWithoutSwift(cls, nil);
301             cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
302         }
303         free(resolvedFutureClasses);
304     }
305 
306     ts.log("IMAGE TIMES: realize future classes");
307 
308     if (DebugNonFragileIvars) {
309         realizeAllClasses();
310     }
311 
312 
313     // Print preoptimization statistics
314     if (PrintPreopt) {
315         static unsigned int PreoptTotalMethodLists;
316         static unsigned int PreoptOptimizedMethodLists;
317         static unsigned int PreoptTotalClasses;
318         static unsigned int PreoptOptimizedClasses;
319 
320         for (EACH_HEADER) {
321             if (hi->hasPreoptimizedSelectors()) {
322                 _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
323                              "in %s", hi->fname());
324             }
325             else if (hi->info()->optimizedByDyld()) {
326                 _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
327                              "in %s", hi->fname());
328             }
329 
330             classref_t const *classlist = _getObjc2ClassList(hi, &count);
331             for (i = 0; i < count; i++) {
332                 Class cls = remapClass(classlist[i]);
333                 if (!cls) continue;
334 
335                 PreoptTotalClasses++;
336                 if (hi->hasPreoptimizedClasses()) {
337                     PreoptOptimizedClasses++;
338                 }
339                 
340                 const method_list_t *mlist;
341                 if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
342                     PreoptTotalMethodLists++;
343                     if (mlist->isFixedUp()) {
344                         PreoptOptimizedMethodLists++;
345                     }
346                 }
347                 if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
348                     PreoptTotalMethodLists++;
349                     if (mlist->isFixedUp()) {
350                         PreoptOptimizedMethodLists++;
351                     }
352                 }
353             }
354         }
355 
356         _objc_inform("PREOPTIMIZATION: %zu selector references not "
357                      "pre-optimized", UnfixedSelectors);
358         _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
359                      PreoptOptimizedMethodLists, PreoptTotalMethodLists, 
360                      PreoptTotalMethodLists
361                      ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists 
362                      : 0.0);
363         _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
364                      PreoptOptimizedClasses, PreoptTotalClasses, 
365                      PreoptTotalClasses 
366                      ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
367                      : 0.0);
368         _objc_inform("PREOPTIMIZATION: %zu protocol references not "
369                      "pre-optimized", UnfixedProtocolReferences);
370     }
371 
372 #undef EACH_HEADER
373 }

read_images 做了什么,概括:

  1. 条件控制进行一次性的加载 - once
  2. 修复预编译阶段的 ‘@selector’ 的混乱问题
  3. 错误混乱的 类 的处理
  4. 修复 重映射 一些没有被镜像文件加载进来的类
  5. 修复一些消息
  6. 当类里面有协议时,readProtocol
  7. 修复没被加载的协议
  8. 分类的处理
  9. 非懒加载的类的加载处理
  10. 实现新解析的未来类,以防CF操作它们

下面对 read_images 详细解析

1、fix up @selector(上面源码的104行): 

selector 并非简单的字符串,而是一串 带地址的字符串.

2、Discover classes 类的处理(上面代码的126行开始)

144行:Class was moved but not deleted - 出现类的混乱的场景比较少,代码暂时执行不进去。

对于类,一般加载内存后不会移动,相对移动来说更倾向于删除重建,移动对性能能消耗太大。

142行代码 readClass():

readClass() 做了什么?.

readClass() 的实现源码:

 1 /***********************************************************************
 2 * readClass
 3 * Read a class and metaclass as written by a compiler.
 4 * Returns the new class pointer. This could be: 
 5 * - cls
 6 * - nil  (cls has a missing weak-linked superclass)
 7 * - something else (space for this class was reserved by a future class)
 8 *
 9 * Note that all work performed by this function is preflighted by 
10 * mustReadClasses(). Do not change this function without updating that one.
11 *
12 * Locking: runtimeLock acquired by map_images or objc_readClassPair
13 **********************************************************************/
14 Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
15 {
16     const char *mangledName = cls->mangledName();
17     
18     if (missingWeakSuperclass(cls)) {
19         // No superclass (probably weak-linked). 
20         // Disavow any knowledge of this subclass.
21         if (PrintConnecting) {
22             _objc_inform("CLASS: IGNORING class '%s' with "
23                          "missing weak-linked superclass", 
24                          cls->nameForLogging());
25         }
26         addRemappedClass(cls, nil);
27         cls->superclass = nil;
28         return nil;
29     }
30     
31     cls->fixupBackwardDeployingStableSwift();
32 
33     Class replacing = nil;
34     if (Class newCls = popFutureNamedClass(mangledName)) {
35         // This name was previously allocated as a future class.
36         // Copy objc_class to future class's struct.
37         // Preserve future's rw data block.
38         
39         if (newCls->isAnySwift()) {
40             _objc_fatal("Can't complete future class request for '%s' "
41                         "because the real class is too big.", 
42                         cls->nameForLogging());
43         }
44         
45         class_rw_t *rw = newCls->data();
46         const class_ro_t *old_ro = rw->ro();
47         memcpy(newCls, cls, sizeof(objc_class));
48         rw->set_ro((class_ro_t *)newCls->data());
49         newCls->setData(rw);
50         freeIfMutable((char *)old_ro->name);
51         free((void *)old_ro);
52         
53         addRemappedClass(cls, newCls);
54         
55         replacing = cls;
56         cls = newCls;
57     }
58     
59     if (headerIsPreoptimized  &&  !replacing) {
60         // class list built in shared cache
61         // fixme strict assert doesn't work because of duplicates
62         // ASSERT(cls == getClass(name));
63         ASSERT(getClassExceptSomeSwift(mangledName));
64     } else {
65         addNamedClass(cls, mangledName, replacing);
66         addClassTableEntry(cls);
67     }
68 
69     // for future reference: shared cache never contains MH_BUNDLEs
70     if (headerIsBundle) {
71         cls->data()->flags |= RO_FROM_BUNDLE;
72         cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
73     }
74     
75     return cls;
76 }

这里因系统类较多,不好调试,我们添加自己的类进行探究:

 

 继续断点调试:上面代码34行的并未进去!

走进了 65 行,即下图 3247 行:

2.1)addNamedClass():--> name 加到 cls 里面

 1 /***********************************************************************
 2 * addNamedClass
 3 * Adds name => cls to the named non-meta class map.// 将 name=>cls 添加到费元类的映射
 4 * Warns about duplicate class names and keeps the old mapping.// 重复的类名将保留旧的映射
 5 * Locking: runtimeLock must be held by the caller
 6 **********************************************************************/
 7 static void addNamedClass(Class cls, const char *name, Class replacing = nil)
 8 {
 9     runtimeLock.assertLocked();
10     Class old;
11     if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
12         inform_duplicate(name, old, cls);
13 
14         // getMaybeUnrealizedNonMetaClass uses name lookups.
15         // Classes not found by name lookup must be in the
16         // secondary meta->nonmeta table.
17         addNonMetaClass(cls);
18     } else {
19         NXMapInsert(gdb_objc_realized_classes, name, cls);// 插入
20     }
21     ASSERT(!(cls->data()->flags & RO_META));
22 
23     // wrong: constructed classes are already realized when they get here
24     // ASSERT(!cls->isRealized());
25 }

类名来自于 mangleName:--> 已经初始化or未来类则从 ro 里面读,否则从 machodata 里面读:

2.2)addClassTableEntry(cls) 

 1 /***********************************************************************
 2 * addClassTableEntry
 3 * Add a class to the table of all classes. If addMeta is true,
 4 * automatically adds the metaclass of the class as well.
 5 * Locking: runtimeLock must be held by the caller.
 6 **********************************************************************/
 7 static void
 8 addClassTableEntry(Class cls, bool addMeta = true)
 9 {
10     runtimeLock.assertLocked();
11 
12     // This class is allowed to be a known class via the shared cache or via
13     // data segments, but it is not allowed to be in the dynamic table already.
14     auto &set = objc::allocatedClasses.get();// 初始化位置在 runtime_init()
15 
16     ASSERT(set.find(cls) == set.end());
17    
18     if (!isKnownClass(cls))
19         set.insert(cls);// 添加 insert
20     if (addMeta)
21         addClassTableEntry(cls->ISA(), false);// 元类就添加到元类
22 }

readClass 做了:将 cls 从 macho 的格式里面 读取添加到内存里面

篇幅较长,接下篇:OC 底层探索 14、类的加载2

 

posted @ 2020-10-12 23:07  张张_z  阅读(391)  评论(0编辑  收藏  举报