博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS底层代码探索003-类的底层探索后续
阅读量:3960 次
发布时间:2019-05-24

本文共 11638 字,大约阅读时间需要 38 分钟。

1.概念普及

在阅读本篇博客前,需要了解:

建议阅读博客:

1.1 isa走位与类继承关系图

 1.2 Clean/Dirty Memory

Dirty memory:在进程运行时会发生更改的内存。

Clean memory:加载后不会发生更改的内存。

  • ro属于clean memory,在编辑时及确定的内存空间,只读,加载后不会发生改变的内存空间,包括类名称、方法、协议和实例变量的信息;
  • rw的数据空间属于dirty memory,rw是运行时的结构,可读可写,由于其动态性,可以往类中添加属性、方法、协议。在运行时会发生变更的内存。
  • dirty memory要比clean memory昂贵的多,只要进行运行它就必须一直存在,通过分离出那些不会被改变的数据,可以把大部分的类数据存储为clean memory。

1.3 类整体结构

1.3.1 当类第一次从磁盘加载到内存时的结构

1.3.2 当类第一次被使用时的结构(runtime)

1.3.3 将需要动态更新的部分提取出来,存入class_rw_ext_t

 1.4 SEL和IMP

SEL : 类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。

IMP:一个函数指针,保存了方法的地址。

2.类底层探索的一些后续问题

2.1 类的懒加载与存储形式

一开始的TWPerson的firstSubclass为nill的原因是因为TWTeacher一开始只有class_ro的clean内存,在第一次使用类的时后候,才会载入class_rw_t的dirty内存,此时有了firstSubclass指针和nextSiblingClass指针。类实际上是树状结构存储遍历。

2.2 成员变量与属性区别+getter方法

使用Clang将源文件转换为C++文件。

属性有getter和setter方法。

Setter的本质是内存赋值封装。

static void _I_TWPerson_setNickname_(TWPerson * self, SEL _cmd, NSString *nickname) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct TWPerson, _nickname), (id)nickname, 0, 1);}static void _I_TWPerson_setName_(TWPerson * self, SEL _cmd, NSString *name) {(*(NSString **)((char *)self + OBJC_IVAR_$_TWPerson$_name)) = name; }

但是setter方法为什么有的是内存平移赋值 有的使用objc_setProperty呢?

查看可执行文件函数表,发现getter和setter方法在编译期函数地址就已经确定,应该是LLVM编译时就确定了。

在getSetPropertyFn()函数中创建了objc_setProperty,那我们继续查找getSetPropertyFn。

GetPropertySetFunction()调用了 getSetPropertyFn()函数。

找它的父类。 

下面有个函数: 

/// Pick an implementation strategy for the given property synthesis.PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,                                     const ObjCPropertyImplDecl *propImpl) {  const ObjCPropertyDecl *prop = propImpl->getPropertyDecl();  ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind();  IsCopy = (setterKind == ObjCPropertyDecl::Copy);  IsAtomic = prop->isAtomic();  HasStrong = false; // doesn't matter here.  // Evaluate the ivar's size and alignment.  ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();  QualType ivarType = ivar->getType();  auto TInfo = CGM.getContext().getTypeInfoInChars(ivarType);  IvarSize = TInfo.Width;  IvarAlignment = TInfo.Align;  // If we have a copy property, we always have to use getProperty/setProperty.  if (IsCopy) {    Kind = GetSetProperty;    return;  }  if (setterKind == ObjCPropertyDecl::Retain) {    if (CGM.getLangOpts().getGC() == LangOptions::GCOnly) {    } else if (CGM.getLangOpts().ObjCAutoRefCount && !IsAtomic) {      if (ivarType.getObjCLifetime() == Qualifiers::OCL_Strong)        Kind = Expression;      else        Kind = SetPropertyAndExpressionGet;      return;    } else if (!IsAtomic) {      Kind = SetPropertyAndExpressionGet;      return;    } else {      Kind = GetSetProperty;      return;    }  }  if (!IsAtomic) {    Kind = Expression;    return;  }  if (ivar->isBitField()) {    Kind = Expression;    return;  }  if (ivarType.hasNonTrivialObjCLifetime() ||      (CGM.getLangOpts().getGC() &&       CGM.getContext().getObjCGCAttrKind(ivarType))) {    Kind = Expression;    return;  }  if (CGM.getLangOpts().getGC())    if (const RecordType *recordType = ivarType->getAs
()) HasStrong = recordType->getDecl()->hasObjectMember(); if (HasStrong) { Kind = CopyStruct; return; } if (!IvarSize.isPowerOfTwo()) { Kind = CopyStruct; return; } llvm::Triple::ArchType arch = CGM.getTarget().getTriple().getArch(); if (IvarAlignment < IvarSize && !hasUnalignedAtomics(arch)) { Kind = CopyStruct; return; } if (IvarSize > getMaxAtomicAccessSize(CGM, arch)) { Kind = CopyStruct; return; } Kind = Native;}

发现使用copy修饰的属性使用objc_setProperty方法实现。

在可编译源代码中查看objc_setProperty

void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) {    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);    bool mutableCopy = (shouldCopy == MUTABLE_COPY);    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy){    if (offset == 0) {        object_setClass(self, newValue);        return;    }    id oldValue;    id *slot = (id*) ((char*)self + offset);    if (copy) {        newValue = [newValue copyWithZone:nil];    } else if (mutableCopy) {        newValue = [newValue mutableCopyWithZone:nil];    } else {        if (*slot == newValue) return;        newValue = objc_retain(newValue);    }    if (!atomic) {        oldValue = *slot;        *slot = newValue;    } else {        spinlock_t& slotlock = PropertyLocks[slot];        slotlock.lock();        oldValue = *slot;        *slot = newValue;                slotlock.unlock();    }    objc_release(oldValue);}

发现这就是一个深拷贝,重新开辟个内存,新值retain,旧值release。

2.3 ivar Type编码

Table 6-1  Objective-C type encodings

Code

Meaning

c

char

i

An int

s

short

l

long

l is treated as a 32-bit quantity on 64-bit programs.

q

long long

C

An unsigned char

I

An unsigned int

S

An unsigned short

L

An unsigned long

Q

An unsigned long long

f

float

d

double

B

A C++ bool or a C99 _Bool

v

void

*

A character string (char *)

@

An object (whether statically typed or typed id)

#

A class object (Class)

:

A method selector (SEL)

[array type]

An array

{

name=type...}

A structure

(name=type...)

A union

bnum

A bit field of num bits

^type

A pointer to type

?

An unknown type (among other things, this code is used for function pointers)

Table 6-2  Objective-C method encodings

Code

Meaning

r

n

in

N

inout

o

out

O

bycopy

R

byref

V

oneway

 eg.

 types表示:

 2.4 使用runtime API探索与验证

sayhi是TWPerson对象方法,saybye是TWPerson类方法。

2.4.1 类方法列表中的方法

void TWObjc_copyMethodList(Class pClass){    unsigned int count = 0;    Method *methods = class_copyMethodList(pClass, &count);    for (unsigned int i=0; i < count; i++) {        Method const method = methods[i];        //获取方法名        NSString *key = NSStringFromSelector(method_getName(method));        NSLog(@"Method, name: %@", key);    }    free(methods);}

2.4.2 类与元类的实例方法 

void TWInstanceMethod_classToMetaclass(Class pClass){        const char *className = class_getName(pClass);    Class metaClass = objc_getMetaClass(className);        Method method1 = class_getInstanceMethod(pClass, @selector(sayhi));    Method method2 = class_getInstanceMethod(metaClass, @selector(sayhi));    Method method3 = class_getInstanceMethod(pClass, @selector(saybye));    Method method4 = class_getInstanceMethod(metaClass, @selector(saybye));        NSLog(@"---%s---\n ---对象方法sayhi---\nTWPerson类实例方法:%p\nTWPerson元类类实例方法:%p\n---类方法saybye---\nTWPerson类实例方法:%p\nTWPerson元类类实例方法:%p\n",__func__,method1,method2,method3,method4);}

2.4.3 类与元类的类方法 

void TWClassMethod_classToMetaclass(Class pClass){        const char *className = class_getName(pClass);    Class metaClass = objc_getMetaClass(className);        Method method1 = class_getClassMethod(pClass, @selector(sayhi));    Method method2 = class_getClassMethod(metaClass, @selector(sayhi));    Method method3 = class_getClassMethod(pClass, @selector(saybye));    Method method4 = class_getClassMethod(metaClass, @selector(saybye));        NSLog(@"%s\n---对象方法sayhi---\nTWPerson类方法:%p\nTWPerson元类类方法:%p\n---类方法saybye---\nTWPerson类方法:%p\nTWPerson元类类方法:%p\n",__func__,method1,method2,method3,method4);}

为什么类中的类方法在元类中还是有类方法,不应该时以对象方法存储么。

/************************************************************************ class_getClassMethod.  Return the class method for the specified* class and selector.**********************************************************************/Method class_getClassMethod(Class cls, SEL sel){    if (!cls  ||  !sel) return nil;    return class_getInstanceMethod(cls->getMeta(), sel);}
// NOT identical to this->ISA when this is a metaclass    Class getMeta() {		//如果是元类就不会再找了		//故本质也是取元类的方法。        if (isMetaClassMaybeUnrealized()) return (Class)this;        else return this->ISA();    }

万物皆对象,类也是对象拿的都是对象实例方法。在底层没有类方法,只有对象方法。 

 2.4.4 类的IMP

void TWIMP_classToMetaclass(Class pClass){    //有了IMP就有函数实现    const char *className = class_getName(pClass);    Class metaClass = objc_getMetaClass(className);    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayhi));    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayhi));// 应该是nil    // sel -> imp 方法的查找流程 imp_Forward    IMP imp3 = class_getMethodImplementation(pClass, @selector(saybye)); // 应该是nil    IMP imp4 = class_getMethodImplementation(metaClass, @selector(saybye));    NSLog(@"%s\n---对象方法sayhi---\nTWPerson的IMP:%p\nTWPerson元类的IMP:%p\n---类方法saybye---\nTWPerson类的IMP:%p\nTWPerson元类的IMP:%p\n",__func__,imp1,imp2,imp3,imp4);}

为什么对象方法在元类中也找到了实现

__attribute__((flatten))IMP class_getMethodImplementation(Class cls, SEL sel){    IMP imp;    if (!cls  ||  !sel) return nil;    lockdebug_assert_no_locks_locked_except({ &loadMethodLock });    imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);    // Translate forwarding function to C-callable external version    if (!imp) {		//imp不存在 返回objc_msgForward        return _objc_msgForward;    }    return imp;}

当imp=nil时,会返回_objc_msgForward,所以imp2和ipm3才有相同的函数指针地址。

3.补充" is Kind/Member of Class "面试题 

判断八个结果的真假。 

void TWisKindofDemo(void){    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];           BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];        BOOL re3 = [(id)[TWPerson class] isKindOfClass:[TWPerson class]];           BOOL re4 = [(id)[TWPerson class] isMemberOfClass:[TWPerson class]];         NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];           BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];         BOOL re7 = [(id)[TWPerson alloc] isKindOfClass:[TWPerson class]];           BOOL re8 = [(id)[TWPerson alloc] isMemberOfClass:[TWPerson class]];         NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);}

 本质是理解isa走位与继承链图。

首先先看源码,先查看类方法的isKindOfClass和isMemberOfClass

//tcls=根元类 再一直找父类循环 一直到tcls为null+ (BOOL)isKindOfClass:(Class)cls {    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {        if (tcls == cls) return YES;    }    return NO;}+ (BOOL)isMemberOfClass:(Class)cls {    return self->ISA() == cls;}

类方法的isKindOfClass是一个for循环,从当前类的isa开始找,顺着继承链,判断当前指向的类是不是调用类方法的类。 

类方法的isMemberOfClass判断当前类的isa是不是调用类方法的类。 

根据源码与继承链分析

NSObject的元类的父类还是NSObject,NSObject的isa指向NSObject指向元类。

TWPerson的isa和它的继承链都不会指向TWPerson。

故1-4应该是1-0-0-0 

- (BOOL)isKindOfClass:(Class)cls {    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {        if (tcls == cls) return YES;    }    return NO;}- (BOOL)isMemberOfClass:(Class)cls {    return [self class] == cls;}

对象方法的isKindOfClass也是一个for循环,从当前对象的类开始,顺着继承链,判断当前指向的类是不是调用对象的类。

对方方法的isMemberOfClass判断当前对象的类是不是调用对象的类。

故5-8应该是1-1-1-1。

断点调试发现不会进入iskindOfClass里面。

但凡事不能只看源码,任何东西拿过来都要跑一跑,汇编看一看。

不能只看源码 ,因为LLVM会重定向,就像之前分析alloc做了什么一样,其实我第一篇文章没找到类加载,是因为一开始并不是就简单的先执行了alloc,这里的问题我以后在补,先看当前问题。遇到这里 我们打开编译器的汇编功能。

​​​​​​​

发现它走的是objc_opt_isKindOfClass

而 isMemberOfClass走的还是isMemberOfClass。那我们就再看看objc_opt_isKindOfClass

// Calls [obj isKindOfClass]BOOLobjc_opt_isKindOfClass(id obj, Class otherClass){#if __OBJC2__    if (slowpath(!obj)) return NO;//如果obj是对象,就获取类,如果obj是类,就获取元类    Class cls = obj->getIsa();    if (fastpath(!cls->hasCustomCore())) {        for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {            if (tcls == otherClass) return YES;        }        return NO;    }#endif//不是_OBJC2_就直接走之前看的源码    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);}

 如果obj是对象,就获取对象的类,如果obj是类,就获取类的元类。

然后还是一个for循环走继承链判断。

4.结尾

如有问题,欢迎大家留言,与我交流。

转载地址:http://bbqzi.baihongyu.com/

你可能感兴趣的文章