iOS底层-17:类的加载

这篇文章主要分析_objc_init里的流程。

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
  • environ_init环境变量初始化
    主要是读取当前的环境变量,这些环境变量可以在Xcode中设置,Edit Scheme -> Arguments -> Environment Variables

借助终端指令查看所有的环境变量:

export OBJC_HELP=1
/Applications/Safari.app/Contents/MacOS/Safari

环境变量表:

变量名 介绍 备注
OBJC_PRINT_OPTIONS list which options are set 输出OBJC已设置的选项
OBJC_PRINT_IMAGES log image and library names as they are loaded 输出已load的image信息
OBJC_PRINT_IMAGE_TIMES measure duration of image loading steps image加载的持续时间
OBJC_PRINT_LOAD_METHODS log calls to class and category +load methods 打印class 以及 category中+load方法的调用信息
OBJC_PRINT_INITIALIZE_METHODS log calls to class +initialize methods 打印class中+initialize方法的调用信息
OBJC_PRINT_RESOLVED_METHODS log methods created by +resolveClassMethod: and +resolveInstanceMethod: 打印通过resolveInstanceMethod和resolveClassMethod生成的方法
OBJC_PRINT_CLASS_SETUP log progress of class and category setup 打印class和category的设置过程
OBJC_PRINT_PROTOCOL_SETUP log progress of protocol setup 打印协议protocol的设置过程
OBJC_PRINT_IVAR_SETUP log processing of non-fragile ivars 打印ivars的设置过程
OBJC_PRINT_VTABLE_SETUP log processing of class vtables 打印class vtables设置过程
OBJC_PRINT_VTABLE_IMAGES print vtable images showing overridden methods 打印vtable images 被覆盖的方法
OBJC_PRINT_CACHE_SETUP log processing of method caches 打印cache设置过程
OBJC_PRINT_FUTURE_CLASSES log use of future classes for toll-free bridging 打印从 CFType 无缝转换到 NSObject 将要使用的类(如 CFArrayRef 到 NSArray * )
OBJC_PRINT_PREOPTIMIZATION log preoptimization courtesy of dyld shared cache 打印共享缓存预优化礼貌用语
OBJC_PRINT_CXX_CTORS log calls to C++ ctors and dtors for instance variables 打印实例对象Cxx 构造方法和析构方法
OBJC_PRINT_EXCEPTIONS log exception handling 打印异常处理
OBJC_PRINT_EXCEPTION_THROW log backtrace of every objc_exception_throw() 打印异常抛出时的堆栈
OBJC_PRINT_ALT_HANDLERS log processing of exception alt handlers 打印alt操作异常处理
OBJC_PRINT_REPLACED_METHODS log methods replaced by category implementations 打印被category替换的方法
OBJC_PRINT_DEPRECATION_WARNINGS warn about calls to deprecated runtime functions 打印所有过时的方法调用
OBJC_PRINT_POOL_HIGHWATER og high-water marks for autorelease pools 打印autorelease pools高水位警告
OBJC_PRINT_CUSTOM_CORE log classes with custom core methods 打印拥有自定义核心方法的类
OBJC_PRINT_CUSTOM_RR log classes with custom retain/release methods 打印拥有自定义retain/release方法的类
OBJC_PRINT_CUSTOM_AWZ log classes with custom allocWithZone methods 打印有自定义allocWithZone方法的类
OBJC_PRINT_RAW_ISA log classes that require raw pointer isa fields 打印需要访问原始isa指针的类
OBJC_DEBUG_UNLOAD warn about poorly-behaving bundles when unloaded 卸载不良行为的bundles时打印警告
OBJC_DEBUG_FRAGILE_SUPERCLASSES warn about subclasses that may have been broken by subsequent changes to superclasses 子类可能对父类修改破坏打印警告
OBJC_DEBUG_NIL_SYNC warn about @synchronized(nil), which does no synchronization 警告@synchronized(nil)
OBJC_DEBUG_NONFRAGILE_IVARS capriciously rearrange non-fragile ivars 打印突发地重新布置 non-fragile ivars 的行为
OBJC_DEBUG_ALT_HANDLERS record more info about bad alt handler use 记录更多的alt错误操作信息
OBJC_DEBUG_MISSING_POOLS warn about autorelease with no pool in place, which may be a leak 警告没有pool的情况下使用autorelease,可能存在内存泄漏
OBJC_DEBUG_POOL_ALLOCATION halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools 当自动释放池无序弹出时停止,并允许堆调试器跟踪自动释放池
OBJC_DEBUG_DUPLICATE_CLASSES halt when multiple classes with the same name are present 出现重复的类时停止
OBJC_DEBUG_DONT_CRASH halt the process by exiting instead of crashing 进程停止时通过退出代替崩溃
OBJC_DISABLE_VTABLES disable vtable dispatch 关闭vtable分发
OBJC_DISABLE_PREOPTIMIZATION disable preoptimization courtesy of dyld shared cache 关闭共享缓存预优化礼貌用语
OBJC_DISABLE_TAGGED_POINTERS disable tagged pointer optimization of NSNumber et al. 关闭tagged pointer指针优化
OBJC_DISABLE_TAG_OBFUSCATION disable obfuscation of tagged pointers 关闭 tagged pointers 的混淆
OBJC_DISABLE_NONPOINTER_ISA disable non-pointer isa fields 关闭non-pointer isa指针优化
OBJC_DISABLE_INITIALIZE_FORK_SAFETY disable safety checks for +initialize after fork fork后禁用+initialize 的安全检查
  • tls_init() 线程key的绑定
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
  • static_init() 调用C++静态构造函数

调用C++静态构造函数,libc 调用_objc_init 是在dyld加载静态构造函数之前,所以这里我们必须自己做。

static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
}
  • runtime_init()
    初始化unattachedCategoriesallocatedClasses
void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}
  • exception_init()初始化objc的异常回调
    初始化old_terminate,发生异常时调用old_terminate里的方法
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
        // No current exception.
        (*old_terminate)();
    }
    else {
        // There is a current exception. Check if it's an objc exception.
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any.
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}
  • cache_init() 缓存初始化
void cache_init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
    mach_msg_type_number_t count = 0;
    kern_return_t kr;

    while (objc_restartableRanges[count].location) {
        count++;
    }

    kr = task_restartable_ranges_register(mach_task_self(),
                                          objc_restartableRanges, count);
    if (kr == KERN_SUCCESS) return;
    _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
                kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}
  • _imp_implementationWithBlock_init()
    Mac_OS_OSX环境下,初始化Trampolines。此函数主要是启动回调函数,通常不做什么,因为所有的初始化都是惰性的,但是对于某些进程,要迫不及待的加载libobjc-trampolines.dylib
void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    //
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
        Trampolines.Initialize();
    }
#endif
}
  • _dyld_objc_notify_register
_dyld_objc_notify_register(&map_images, load_images, unmap_image);

主要向dyld注册这三个函数,map_images、 load_images、 unmap_image

1.map_images:管理文件和动态库中所有的符号
2.load_images:加载执行load方法
3.unmap_imagedyld取消映射的image进程
下面我们会着重分析map_imagesload_images

map_images
  • map_images
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
  • map_images_nolock
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ....已省略部分代码.....

    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    //初始化之前的一些操作
    if (firstTime) {
        preopt_init();
    }
    
    //类的计数,根据数目调整各种表格大小
   // Find all images with Objective-C metadata.
    hCount = 0;
    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[i];

            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) { ... }
            
            if (mhdr->filetype == MH_EXECUTE) { ... }
         
            hList[hCount++] = hi;
            
            if (PrintImages) {  ...  }
        }
    }

    if (firstTime) {
        //初始化一些方法名,注册cxx_construct和cxx_destruct
        sel_init(selrefCount);
        // AutoreleasePoolPage::init();
        //SideTablesMap.init();
        //_objc_associations_init();
        arr_init();
    }

    //读所有的镜像文件
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // Call image load funcs after everything is set up.
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}

我们主要分析_read_images方法。

  • _read_images
    _read_images的代码比较多,我们首先讲一下它主要做了什么事情,等下再分段探索。

1.条件控制进行一次的加载
2.修复预编译阶段@selector的混乱问题
3.错误混乱的类处理
4.修复重映射一些没有被镜像文件加载进来的类
5.修复一些消息
6.当我们的类里有协议的时候 readProtocol
7.修复没有被加载的协议
8.分类处理
9.类的加载处理
10.没有被处理的类 优化那些被侵犯的类

1. 条件控制进行一次的加载

    if (!doneOnce) {
        doneOnce = YES;
        launchTime = YES;

//主要给DisableNonpointerIsa赋值
#if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa under some conditions.

# if TARGET_OS_OSX
        // Disable non-pointer isa if the app is too old
        // (linked before OS X 10.11)
        if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {
            DisableNonpointerIsa = true;
            if (PrintRawIsa) {
                _objc_inform("");
            }
        }

        // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
        // New apps that load old extensions may need this.
        for (EACH_HEADER) {
            if (hi->mhdr()->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
                DisableNonpointerIsa = true;
                if (PrintRawIsa) {
                    _objc_inform("");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
# endif

#endif
        //当DisableTaggedPointers为true时,给一些mask和shift赋值
        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        //给objc_debug_taggedpointer_obfuscator赋值 针对小对象处理的
        initializeTaggedPointerObfuscator();
  
        //创建了一张表,用来放类
        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
    }
  • DisableNonpointerIsa赋值
  • DisableTaggedPointerstrue时,给一些maskshift赋值
  • objc_debug_taggedpointer_obfuscator赋值 针对小对象处理的
  • 创建一张表,用来放类

2. 修复预编译阶段@selector的混乱问题

 static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel;// 将相同的函数,使他们`sel`的地址相等。
                }
            }
        }
    }

遍历Mach-o的方法,注册到内存,相同的sel地址赋值相同。
3. 错误混乱的类处理

bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }

        classref_t const *classlist = _getObjc2ClassList(hi, &count);

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[i];
            //前面获取的cls只是一个地址,在`readClass`之后才有相关信息
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }

类被移动但是并没有删除才会走到这里,一般情况不会做任何处理,这里不做研究。这里有一个值得关注的函数,就是readClass。cls的指针经过readClass之后就会关联上一些信息,比如类名。

3.1 readClass

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();
    //当前类没有superclass才会走,防错处理
    if (missingWeakSuperclass(cls)) {
   
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        return nil;
    }
    //swift类处理
    cls->fixupBackwardDeployingStableSwift();

    //以后可能会被用到的类,保留rw数据
    Class replacing = nil;
    if (Class newCls = popFutureNamedClass(mangledName)) {
        // This name was previously allocated as a future class.
        // Copy objc_class to future class's struct.
        // Preserve future's rw data block.
        
        if (newCls->isAnySwift()) {
            _objc_fatal("Can't complete future class request for '%s' "
                        "because the real class is too big.", 
                        cls->nameForLogging());
        }
        
        class_rw_t *rw = newCls->data();
        const class_ro_t *old_ro = rw->ro();
        memcpy(newCls, cls, sizeof(objc_class));
        rw->set_ro((class_ro_t *)newCls->data());
        newCls->setData(rw);
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);
        
        addRemappedClass(cls, newCls);
        
        replacing = cls;
        cls = newCls;
    }
    //主要走addNamedClass和addClassTableEntry这两个方法
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(getClassExceptSomeSwift(mangledName));
    } else {
        addNamedClass(cls, mangledName, replacing);
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}

我们自定义类主要走addNamedClassaddClassTableEntry这两个方法。

  • addNamedClass
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked();
    Class old;
    //用类名去查找类,找到了说明他是元类,通过NXMapInsert把元类插入表中
    //没有找到,说明表中没这个类,就把name 和 cls 添加到映射中,在这之后就可以查找到这个类
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);
        addNonMetaClass(cls);
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    ASSERT(!(cls->data()->flags & RO_META));
}

这个方法实际上是吧 namecls 添加到非元类的映射中。

  • addClassTableEntry
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

把一个类加到所有类的列表中,如果addMeta为真也会自动添加元类。

4. 修复重映射一些没有被镜像文件加载进来的类

 if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
            // fixme why doesn't test future1 catch the absence of this?
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }

修复重映射的类,类列表和非懒加载的类保持未重新隐射。类引用和超级引用被重新映射用于消息调度。正常情况也不会走。

5. 修复一些消息

    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

objc才会进来。

6. 当我们的类里有协议的时候 readProtocol

bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();

    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->hasPreoptimizedProtocols();

        // Skip reading protocols if this is an image from the shared cache
        // and we support roots
        // Note, after launch we do need to walk the protocol as the protocol
        // in the shared cache is marked with isCanonical() and that may not
        // be true if some non-shared cache binary was chosen as the canonical
        // definition
        if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
            if (PrintProtocols) {
                _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                             hi->fname());
            }
            continue;
        }

        bool isBundle = hi->isBundle();

        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

7. 修复没有被加载的协议

for (EACH_HEADER) {
        // At launch time, we know preoptimized image refs are pointing at the
        // shared cache definition of a protocol.  We can skip the check on
        // launch, but have to visit @protocol refs for shared cache images
        // loaded later.
        if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
            continue;
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }

8. 分类处理
发现类别,仅在初始化类别之后进行操作。对于启动时发现的类别,这一步操作被推迟到_dyld_objc_notify_register调用完成后的第一个 load_images调用。

 if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

9. 类的加载处理

for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }

10.没有被处理的类 优化那些被侵犯的类

if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

map_images的调用流程

上篇文章我们讲了load_images是在镜像库初始化时调用的,这里我们探索一下map_images在哪调用?

  • 打开dyld源码,全局搜索sNotifyObjCMapped,在上篇文章我们了解到sNotifyObjCMapped = map_images
    notifyBatchPartial函数中找到了sNotifyObjCMapped执行的地方。

    image.png

  • 全局搜索notifyBatchPartial


    我们发现在registerObjCNotifiers函数中,赋值之后已经开始调用notifyBatchPartial。也就是说map_images其实调用在load_images之前。

深入研究

先准备好代码:

@interface LRPerson : NSObject

@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *icon;

- (void)sayHello;
- (void)sayHappy;
- (void)instanceMethod;
+ (void)classMethod;

@end

@implementation LRPerson

//- (void)sayHello {
//    NSLog(@"%@",_cmd);
//}
- (void)sayHappy {
    NSLog(@"%@",_cmd);
}
- (void)instanceMethod {
    NSLog(@"%@",_cmd);
}
+ (void)classMethod {
    NSLog(@"%@",_cmd);
}
@end

添加一个categary

@protocol LRPersonDelegate 

- (void)delegateMethod;

@end


NS_ASSUME_NONNULL_BEGIN

@interface LRPerson (LR)

- (void)instanceMethod;
+ (void)classMethod;

@end
@implementation LRPerson (LR)


- (void)instanceMethod {
    NSLog(@"category----%s",__func__);
}
+ (void)classMethod {
    NSLog(@"category----%s",__func__);
}

@end

在我们要研究的方法里添加一下代码,方便研究我们创建的类。

    const char *mangledName = cls->mangledName();
    const char *className = "LRPerson";
    if (strcmp(mangledName, className) == 0){
       printf("来了 \n");
    }
类的加载

这里我们主要研究类的加载部分,在函数中加上以上代码,并打上断点,运行程序。


然而事情超出了预料,这个断点并没有走。那到底是为什么呢?

阅读上方的注释,发现有一个非懒加载类,我们猜测只有非懒加载类才会走这个方法。


  • 懒加载类和非懒加载类
    这个很好区分,实现了+load 方法就是非懒加载类。

除此之外还有其他两种情况:
1.子类实现了+load
2.category实现了+load

懒加载类: 没有实现+load方法,在第一次对其发送消息之前才初始化。
好处:降低启动时间,减少启动工作量。项目中有成千上万的类,没有必要启动时全部加载。

意识到了问题之后,我们给LRPerson类实现+load方法,再次运行代码。断点果然进来了

addClassTableEntry在前面讲过,主要是把类加入所有类的表里,元类也会添加。
下面重点分析realizeClassWithoutSwift

  • realizeClassWithoutSwift
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    ASSERT(cls == remapClass(cls));

    //设置类的rw 和 ro
    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        //正常类走这里
        //设置rw的数据ro flags
        //cls 设置 rw
        rw = objc::zalloc();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

#if FAST_CACHE_META
    //是元类时 设置cache 的 bit
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    //为这个类选择一个索引,如果没有可用的索引则设置它的cls->instancesRequireRawIsa
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    //递归处理父类和元类
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        //进来的是元类 对cache->bit 赋值
        cls->setInstancesRequireRawIsa();
    } else {
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->name, "OS_object"))
        {
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  &&
                 supercls->instancesRequireRawIsa())
        {
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // 更新它的元类和父类
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // 协调实例变量偏移/布局。
    // 这可能会重新分配 class_ro_t,更新我们的 ro 变量。
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    //设置setInstanceSize
    cls->setInstanceSize(ro->instanceSize);

    //设置有C++的构造方法和析构方法
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    //将此类连接到他的父类的子类列表
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    //处理类的方法列表 协议列表 属性列表
    methodizeClass(cls, previously);

    return cls;
}

在这里会从Mach-o文件中取出类的ro,复制出一个rw,再把rw设置给当前类。如果当前类有父类或者元类,会递归处理父类和元类。还会做一下工作:

  • 进来的类是元类时,设置cache ->setBit
  • 为这个类选择一个索引,如果没有可用的索引则设置它的cls->instancesRequireRawIsa
  • 设置setInstanceSize、 setHasCxxDtor() 、setHasCxxCtor()
  • 链接完成父类和子类的列表
  • 处理类的方法列表 协议列表 属性列表

我们接下来研究他的主要方法methodizeClass

  • methodizeClass
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

   //获取方法列表 按照sel的值排序 增序
  //如果存在rwe 则在rwe添加这些方法
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
    //如果存在rwe 则在rwe添加property_list
    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
    //如果存在rwe 则在rwe添加protocol_list
    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    //当进来的是根元类时,添加initialize方法
    if (cls->isRootMetaclass()) {
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods()) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name));
        }
        ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name); 
    }
#endif
}

我们看一下prepareMethodLists排序方法。

  • prepareMethodLists
static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle)
{
    runtimeLock.assertLocked();

    if (addedCount == 0) return;

    if (baseMethods) {
        ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
    }

   
    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[i];
        ASSERT(mlist);

        //方法在这里排序
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }

    if (cls->isInitialized()) {
        objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
    }
}
  • fixupMethodList
static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());

  
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            meth.name = sel_registerNameNoLock(name, bundleCopy);
        }
    }

    // 根据sel的地址排序
    if (sort) {
        //排序
        method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);
    }
    
    // 设置mlist->setFixedUp() 已经排序了
    mlist->setFixedUp();
}
  • SortBySELAddress
struct SortBySELAddress :
        public std::binary_function
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };

在以下地方打上断点,分别打印mlist,对比排序前后的顺序。


排序前
排序后

在打印排序后的sel对应的二进制和16进制大小
sel的二进制

对比发现,是根据sel增序排列的。
非懒加载类的加载到这里就差不多完成了。

懒加载类

懒加载类是在第一次发送消息时才加载的。我们修改一下代码去掉+ load方法,在main函数里发送一次消息。

realizeClassWithoutSwift加入以下代码,打上断点。

 auto lr_ro = (const class_ro_t *)cls->data();
    bool lr_isMeta = lr_ro->flags & RO_META;
    const char *mangledName = cls->mangledName();
    const char *className = "LRPerson";
    if (strcmp(mangledName, className) == 0 && !lr_isMeta ){

    method_list_t *list = lr_ro->baseMethods();
    printf("来了 \n");
    }

进了断点之后,bt打印当前堆栈信息

进入realizeClassWithoutSwift流程是:
1.dyld start
2.进入main
3.objc_alloc(cls=LRPerson)
4.callAlloc
5._objc_msgSend_uncached
6.lookUpImpOrForward
7.initializeAndLeaveLocked
8.initializeAndMaybeRelock
9.realizeClassMaybeSwiftAndUnlock
10.realizeClassMaybeSwiftMaybeRelock
11.realizeClassWithoutSwift
看到这个不知道大家有没有觉得熟悉,这正是之前消息查找流程之中的一个分支。

  • lookUpImpOrForward

这两个方法正是lookUpImpOrForward里的slowpath方法

总结

你可能感兴趣的:(iOS底层-17:类的加载)