iOS-OC底层12:类的加载
前沿
我们已经了解了dyly和objc关联,接下来我们重点研究一下read_images下和我们息息相关的类的处理
read_images下的if (!doneOnce)当第一次进入这个方法时
1.DisableNonpointerIsa = true;当swift代码过旧,app过旧还有app包含__DATA,__objc_rawisa部分时
2.gdb_objc_realized_classes:初始化实现class的表
下面我们着重关注对class的处理
我们先做准备类
@interface MyPerson : NSObject
@property (nonatomic, copy) NSString *my_name;
@property (nonatomic, assign) int my_age;
-(void)myInstanceMethod2;
-(void)myInstanceMethod1;
-(void)myInstanceMethod3;
+(void)myClassMethod;
@end
@implementation MyPerson
+(void)load {
}
-(void)myInstanceMethod2 {
NSLog(@"%s",__func__);
}
-(void)myInstanceMethod1{
NSLog(@"%s",__func__);
}
-(void)myInstanceMethod3{
NSLog(@"%s",__func__);
}
+(void)myClassMethod{
NSLog(@"%s",__func__);
}
@end
readClass
我们在readClass做一些出来,来帮助我们针对性的研究我们自定义的类,方便我们打断点进行调试
const char *mangledName = cls->mangledName();
const char *PersonName = "MyPerson";
// printf("诶唷: %s \n ",mangledName);
if (strcmp(mangledName, PersonName) == 0) {
auto kc_ro = (const class_ro_t *)cls->data();
printf("readClass: 这个是我要研究的 %s \n",PersonName);
}
在我们的if判断内打断点,防止系统的类干扰我们研究,我们通过单步调试来到了
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
addNamedClass
在跳用addNamedClass之前我们p cls的出(Class) $3 = 0x0000000100008268
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
}
addNamedClass主要是把类名加到read_images中第一次进入初始化的表中,经过
addNamedClass之后我们p cls 的结果是(Class) $4 = MyPerson,我们可以得出addNamedClass是让cls的地址和类名关联起来。
addClassTableEntry
通过打断可知addClassTableEntry对MyPerson类没做任何处理
解惑
我们在read_images方法中的关于non-lazy进行代码处理
Class cls = remapClass(classlist[i]);
const char *mangledName = cls->mangledName();
const char *PersonName = "MyPerson";
if (strcmp(mangledName, PersonName) == 0) {
auto kc_ro = (const class_ro_t *)cls->data();
printf("_getObjc2NonlazyClassList: 这个是我要研究的 %s \n",PersonName);
}
通过在if打断点,程序没有走到if判断中。我们貌似对整个read_images中关于类的都研究完毕,但是我们发现这个注释关于load的方法,所以我们大胆尝试对MyPerson增加load方法,奇迹出现if中走到了
懒加载类与非懒加载类
官方在对类进行处理的时候, 为了提高对类处理的效率以及性能, 就对类进行了识别, 当类需要使用的时候, 系统才会对类进行实现. 如果没有使用就不会实现. 当需要实现才进行加载的类就被称为懒加载类. 反之无论是否使用到这个类, 都对这个类进行加载的类就被称为非懒加载类.
懒加载类和非懒加载类的区别就是是否实现load方法,实现load的方法是非懒加载类,没有实现load方法是懒加载类,另一个原因可能是在load_image中有调用load方法,如果类没有被加载完备,调用load方法可能会有问题。赖加载类在lookupImp是有realizeClass
image.png
realizeClassWithoutSwift
在此函数中我们也进行针对性的处理
const char *mangledName = cls->mangledName();
const char *PersonName = "MyPerson";
if (strcmp(mangledName, PersonName) == 0) {
auto kc_ro = (const class_ro_t *)cls->data();
auto kc_isMeta = kc_ro->flags & RO_META;
if (!kc_isMeta) {
printf("%s: 这个是我要研究的 %s \n",__func__,PersonName);
}
}
1.从cls中读ro数据 ,(ro数据是编译的时候写入到镜像文件,从镜像文件中读到的ro)。然后把ro设置到rw中,然后rw设置到cls内
2.写入此类的索引chooseClassArrayIndex
- realizeClassWithoutSwift父类和元类,递归
4.设置其他的信息如isa,NonpointerIsa等等
5.设置父类和元类 cls->superclass = supercls; cls->initClassIsa(metacls);
6.methodizeClass方法化现在的类
在对rw设值之前和之后打印cls
x/4gx cls
0x100008280: 0x0000000100008258 0x000000010034c140
0x100008290: 0x0000000100346440 0x0000000000000000
cls->setData(rw);之后
x/4gx cls
0x100008280: 0x0000000100008258 0x000000010034c140
0x100008290: 0x0000000100346440 0x0000000000000000
两次中的bit值为什么都是空的呢 0x0000000000000000,在后面我们再解答这个问题
methodizeClass
1.prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle)中的addedLists为二维数组。fixupMethodList是对方法的排序,fixupMethodList中的排序是按照selector 的地址进行排序的
2.if (rwe) rwe->methods.attachLists(&list, 1); rwe为null
rwe值什么情况才有会有,rwe的初始化是cls->data()->extAllocIfNeeded(),全局搜索可知关联分类(attachCategories),类设置版本号(class_setVersion),增加方法(addMethod),增加协议(class_addProtocol),增加属性(_class_addProperty)
添加category后的流程
在main文件中添加MyPerson的分类
@interface MyPerson (LG)
@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;
@end
@implementation MyPerson (LG)
+ (void)load {
}
- (void)cate_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
NSLog(@"%s",__func__);
}
@end
探索分类的本质,通过clang -rewrite-objc main.m -o main2.cpp
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
static struct _category_t _OBJC_$_CATEGORY_MyPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MyPerson", //name
0, // &OBJC_CLASS_$_MyPerson, //cls
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyPerson_$_LG, //instance_methods
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MyPerson_$_LG, //class_methods
0, //protocols
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyPerson_$_LG, //properties
};
分类中添加的属性不会有set和get方法,我们可以通过runtime的关联实现.
methodizeClass有对分类的处理,objc::unattachedCategories.attachToClass
attachToClass内有一个方法attachCategories,这个貌似是对分类的处理。但是在attachToClass中没有捕捉到对attachCategories的调用。但是我们可以在attachCategories做针对性分析
if (strcmp(mangledName, LGPersonName) == 0) {
bool kc_isMeta = cls->isMetaClass();
auto kc_rw = cls->data();
auto kc_ro = kc_rw->ro();
if (!kc_isMeta) {
printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);
}
}
在写入的代码里打断点,看堆栈信息,
image.png
attachCategories
1.对rwe进行初始化auto rwe = cls->data()->extAllocIfNeeded();