iOS底层(九)-懒加载类以及分类的加载

2,653 阅读5分钟

一、懒加载类

1、懒加载类与非懒加载类

官方在对类进行处理的时候, 为了提高对类处理的效率以及性能, 就对类进行了识别, 当类需要使用的时候, 系统才会对类进行实现. 如果没有使用就不会实现. 当需要实现才进行加载的类就被称为懒加载类. 反之无论是否使用到这个类, 都对这个类进行加载的类就被称为非懒加载类.

在_read_images()中, 当对类进行处理的时候, 通过_getObjc2NonlazyClassList()获取到的类中并没有我们自己创建的类. 这就说明我们平常通过XCode创建的类默认为是懒加载类. 我们知道+(void)load方法会在main()之前调用. 我们在自己的类中实现+(void)load方法:

+ (void)load{
    NSLog(@"load");
}

再来_getObjc2NonlazyClassList()中就可以找到自己的类. 这样的话意味着当一个类实现了+(void)load方法后, 它就会由懒加载类变成非懒加载类.

2、懒加载类的加载

我们已经知道了自由当一个类被调用了才会去加载这个类. 当我们对一个类进行调用, 那么肯定就会通过objc_msgSend()来发送消息了. 来到我们熟悉的lookUpImpOrForward(), 其中会有对类的一个判断:

if (!cls->isRealized()) {
    cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    // runtimeLock may have been dropped but is now locked again
}

紧跟着进入之后最终会来到realizeClassWithoutSwift()方法. 这不正是在_read_images()中对类处理后存放进内存的方法.

二、分类的加载

新建一个Person类以及它的分类:

@implementation Person
@end

@implementation Person (Test)
- (void)test;
@end

来到read_images:


// Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            const class_ro_t *ro = (const class_ro_t *)cls->data();

            const char *cname = ro->name;
            const char *oname = "LGTeacher";
            if (cname && (strcmp(cname, oname) == 0)) {
                printf("_getObjc2NonlazyClassList :%s \n",cname);
            }
            
            if (!cls) continue;

            // hack for class __ARCLite__, which didn't get this above
#if TARGET_OS_SIMULATOR
            if (cls->cache._buckets == (void*)&_objc_empty_cache  &&  
                (cls->cache._mask  ||  cls->cache._occupied)) 
            {
                cls->cache._mask = 0;
                cls->cache._occupied = 0;
            }
            if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache  &&  
                (cls->ISA()->cache._mask  ||  cls->ISA()->cache._occupied)) 
            {
                cls->ISA()->cache._mask = 0;
                cls->ISA()->cache._occupied = 0;
            }
#endif
            
            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);
        }
    }

...//省略


// Discover categories.
// 处理所有分类Category
for (EACH_HEADER) {
    // 从头文件中找到分类的列表
    category_t **catlist = 
        _getObjc2CategoryList(hi, &count);
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    for (i = 0; i < count; i++) {
        // 遍历当前类的所有分类
        category_t *cat = catlist[i];
        Class cls = remapClass(cat->cls);
        
        // 如果这个类已经被实现方法协议属性
        bool classExists = NO;
        if (cat->instanceMethods ||  cat->protocols  
            ||  cat->instanceProperties) 
        {
            // 将分类添加到对应的类中
            addUnattachedCategoryForClass(cat, cls, hi);
            // 将分类的方法、协议、属性添加到类中
            if (cls->isRealized()) {
                remethodizeClass(cls);
                classExists = YES;
            }
            if (PrintConnecting) {
                _objc_inform("CLASS: found category -%s(%s) %s", 
                             cls->nameForLogging(), cat->name, 
                             classExists ? "on existing class" : "");
            }
        }

        // 与类处理一致。这里主要是对元类的处理
        if (cat->classMethods  ||  cat->protocols  
            ||  (hasClassProperties && cat->_classProperties)) 
        {
            addUnattachedCategoryForClass(cat, cls->ISA(), hi);
            if (cls->ISA()->isRealized()) {
                remethodizeClass(cls->ISA());
            }
            if (PrintConnecting) {
                _objc_inform("CLASS: found category +%s(%s)", 
                             cls->nameForLogging(), cat->name);
            }
        }
    }
}

static void methodizeClass(Class cls)
{
    ...//省略
    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);
    ...//省略
}

一个类的加载,最后都会走到methodizeClass()方法里面去, 那么我们就可以在methodizeClass()里面断点来进行相应的调试。

2.1、懒加载类与懒加载分类

从上面源码处,当我们通过 Class cls = remapClass(cat->cls); 得到了这个类,我们需要精准的对 Person 进行调试,在methodizeClass里面加上一段:

在main中实现对象前加上断点,运行后等待断点观察堆栈信息:
可以发现:

  • 当懒加载类发送消息时,才会断点成功。在lookupimporforward里面就做了一次判断是否为懒加载类
  • realizeClassMaybeSwiftMaybeRelock中区分是否为Swift
  • realizeClassWithoutSwift跳转到methodizeClass讲类加载到内存中
  • 当分类没有实现load方法的时候,都是编译时处理, 直接存放进ro中

整体流程为:msgSend - lookupimporforward - realizeClassWithoutSwift - methodlizeClass

2.2、非懒加载类与懒加载分类

类实现load方法,分类不实现load方法

同样断点调试,运行:

依旧是再读取镜像文件的时候就将类加载到内存中,此时加载的是类不是分类,我们可以去rw看一下此时分类的test方法是否加载到了内存中
可以看到此时分类的方法也是保存在了ro中, 这也就说明了:当非懒加载类与懒加载分类,此时非懒加载类也会在程序启动时加载进内存。

整体流程为:read_iamges - realizeClassWithoutSwift - methodlizeClass - ro中

2.3、 懒加载类与非懒加载分类

在Person(Test)中加上load方法,Person中不实现load方法。 继续断点调试。

这一次发现,在main函数还没来到的时候就已经断点成功,这证明加载的是分类, 此时类应该并没有被加载。在对堆栈中还有一个prepare_load_methods()方法:

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;
    runtimeLock.assertLocked();
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    // map_images 完毕
    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        
        const class_ro_t *ro = (const class_ro_t *)cls->data();
        const char *cname = ro->name;
        const char *oname = "Person";
        if (strcmp(cname, oname) == 0) {
           printf("prepare_load_methods :%s \n",cname);
        }
        
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls);//分类已加载,补充对类的加载
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

它在这里做了一个操作,先判断是否在这个非懒加载分类里面,但是由于分类提前加载的原因,所以此时会补充一个对类的加载

2.4、非懒加载类与非懒加载分类

类与分类均实现load方法 当我们启动的时候肯定会加载分类,在分类里断点:

发现它来到了remethodizeClass()方法,通过attachCategories()来加载分类数据

三、总结

类的懒加载:要不要在程序启动时加载

懒加载类:给类发送第一条消息时确定

非懒加载:运行时确定

分类的懒加载:要不要运行时去处理

懒加载分类:编译时就确定

非懒加载:运行时确定