iOS底层代码探索003-类的底层探索后续

1.概念普及

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

建议阅读博客:iOS底层代码探索003-类的底层探索

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<RecordType>())
      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

const

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]
BOOL
objc_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.结尾

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值