类方法及成员变量存储

补充说明结构体嵌套

typedef struct person{
    char a;
    int b;
    short c;
}myPerson;
struct p{
    int d;
    double e;
    char f;
    myPerson  g;
    short h;
}per;

结构体嵌套所需开辟的内存空间是结构体内最大长度数据成员(非结构体)所占大小的整数倍

截屏2020-09-15 上午10.05.42.png

如图所示:

  • myPerson本身作为结构体,遵循内存对齐原则,故而所占12个字节
  • per作为嵌套结构体,加入myPerson结构体时应该是myPerson内最大成员变量的整数倍开始,由代码可知,myPerson中最大为int类型,占4个字节,而myPerson的起始位置为17,根据min(17,4)得出起始位置应该为20,并且其自身占12个字节故而所占位数为(20-31)
  • 根据内存规则,所占内存必须为最大成员所占的整数倍,故而为8的整数倍,最小即为min(33,8)= 40
  • 总结:结构体嵌套时,本身嵌套的结构体都要满足内存对齐规则,不足的自动补齐

类的结构中class_rw_tclass_ro_t的区别

class_ro_t

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    ...
  };
  • class_ro_t存储了当前类在编译期就已经确定的属性方法以及遵循的协议,里面是没有分类的方法的。那些运行时添加的方法将会存储在运行时生成的class_rw_t中。
  • ro即表示read only,是无法进行修改的。

class_rw_t

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
    ...
    }
  • ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t中:

class_rw_t生成时机

class_rw_t生成在运行时,在编译期间,class_ro_t结构体就已经确定,objc_class中的bitsdata部分存放着该结构体的地址。在runtime运行之后,具体说来是在运行runtimerealizeClass 方法时,会生成class_rw_t结构体,该结构体包含了class_ro_t,并且更新data部分,换成class_rw_t结构体的地址。
类的realizeClass运行之前如下图所示:

20200427214831995.png

类的realizeClass运行之后如下图所示:
20200427215000605.png

由此可见,class_rw_tclass_ro_t中的成员变量有一些是相同的,区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_tclass_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容

引自class_rw_t与class_ro_t的区别

之前我们在打印类的结构时,只能读取到其中的属性以及实例方法,但是其中的成员变量类方法均没有读取到,接下来我们就分析一下:

@interface LGPerson : NSObject
{
    NSString *hobby;
    NSObject *objc;
    id        age;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
@end

成员变量、实例变量、属性之间的关系

  • 如上面代码所示,{}中的都是成员变量,而其中的objc就是实例变量(id是OC特有的类,本质上等于(void *)),所以age也是实例变量
    • 实例变量就是Class类通过实例化出来的对象,是一种特殊的成员变量实例变量+基本数据类型变量=成员变量
    • 成员变量一般用于类内部,不会生成setter、getter方法,外界无法获取到
  • 属性变量就是自动生成setter、getter方法,其他对象可以进行访问

copy与strong的实现

static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }

static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }

首先通过clang生成的main.cpp文件中可以看出通过copy修饰的属性会含有objc_setProperty,而通过strong修饰的就没有
copy是在编译时就已经进行处理调用objc_setProperty方法

//通过调用GetOptimizedPropertySetFunction()方法去判断对应的属性,是否为atomic及是否为copy
llvm::FunctionType *FTy =
Types.GetFunctionType( Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
    const char *name;
    if (atomic && copy)
      name = "objc_setProperty_atomic_copy";
    else if (atomic && !copy)
      name = "objc_setProperty_atomic";
    else if (!atomic && copy)
      name = "objc_setProperty_nonatomic_copy";
    else
      name = "objc_setProperty_nonatomic";
//根据对应的属性修饰查找
//通过runtime返回对应的sel进行value赋值
return CGM.CreateRuntimeFunction(FTy, name);

strong方法通过引用计数retain进行查看

void
objc_storeStrong(id *location, id obj)
{
    id prev = *location;
    if (obj == prev) {
        return;
    }
    // 新值赋值
    objc_retain(obj);
    *location = obj;
    // 旧值释放
    objc_release(prev);
}

通过llvm中进行查找

case BlockCaptureEntityKind::ARCStrong: {
      llvm::Value *srcValue = Builder.CreateLoad(srcField, "blockcopy.src");
      // At -O0, store null into the destination field (so that the
      // storeStrong doesn't over-release) and then call storeStrong.
      // This is a workaround to not having an initStrong call.
      if (CGM.getCodeGenOpts().OptimizationLevel == 0) {
        auto *ty = cast<llvm::PointerType>(srcValue->getType());
        llvm::Value *null = llvm::ConstantPointerNull::get(ty);
        Builder.CreateStore(null, dstField);
        EmitARCStoreStrongCall(dstField, srcValue, true);
      } else {
        EmitARCRetainNonBlock(srcValue);
        if (!needsEHCleanup(captureType.isDestructedType()))
          cast<llvm::Instruction>(dstField.getPointer())->eraseFromParent();
      }
      break;
    }
    llvm::Value *CodeGenFunction::EmitARCStoreStrongCall(Address addr,
                                                     llvm::Value *value,
                                                     bool ignored) {
  assert(addr.getElementType() == value->getType());

  llvm::Function *&fn = CGM.getObjCEntrypoints().objc_storeStrong;
  if (!fn) {
    fn = CGM.getIntrinsic(llvm::Intrinsic::objc_storeStrong);
    setARCRuntimeFunctionLinkage(CGM, fn);
  }

  llvm::Value *args[] = {
    Builder.CreateBitCast(addr.getPointer(), Int8PtrPtrTy),
    Builder.CreateBitCast(value, Int8PtrTy)
  };
  EmitNounwindRuntimeCall(fn, args);

  if (ignored) return nullptr;
  return value;
}

case BlockCaptureEntityKind:为ARCStrong时,->EmitARCStoreStrongCall()->objc_stroeStrong方法,进而可以找到上述底层编码

获取成员变量及类方法所在位置

截屏2020-09-15 下午3.26.18.png

首先读取读取LGperson类去获取对应的bits
截屏2020-09-15 下午3.27.43.png

根据上面class_rw_tclass_ro_t的区别得出,class_rw_t本身是包含ro的,所以我们通过上图去读取对应的ro,得出其中除了基础类型baseProperties外还有ivars,通过读取ivars可以看出成员变量是存在中的,因此得出成员变量本身是不能被外界所读取
截屏2020-09-15 下午3.33.52.png

我们通过查看LGPersonisa去查看对应的元类信息,看出其中是包含LGPerson中的类方法 :say666,故而我们得出类方法是存在于其对应的元类中。

方法签名

截屏2020-09-15 上午11.56.33.png
  • 每一个方法都会包含sel(方法编号)imp(函数指针,指向函数方法的实现)
  • 第一个参数:返回值 v(void) @(id)
  • 第二个参数:开辟的内存总字节数
  • 第三个参数:传入的参数,位置从0开始,占用(0-7)
  • 第四个参数::代表sel(方法编号),位置从8开始占用(8-15)
    下面我们打印了一部分,具体的对应关系可以去苹果官网Type Encoding 查看
    截屏2020-09-15 下午1.15.08.png

面试题解析

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];   
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; 
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];   
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; 
        NSLog(@" 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)[LGPerson alloc] isKindOfClass:[LGPerson class]];   
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; 
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

首先我们通过方法查找到对应的类方法实例方法

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

解析

  • 首先[NSObject class]为类,需要调用对应的类方法,根元类的superclass是指向NSObject,故而两者相等
    • [LGPerson class]LGPerson,里面循环查找superclass,没有与LGPerson相等的类,故而返回NO
  • 根据isMemberOfClass方法源码可以得出,self->ISA()指向的是根元类,而NSObject并不等于根元类,所以返回NO
  • 根据isKindOfClass实例方法可以得出,tcls == [self class]第一次循环时直接相等,返回YES
  • 根据isMemberOfClass实例方法得出,根据[self class]返回的是self,故而传入的与返回的相等
  • 注:(如果元类、父类、isa之间的走位不懂可以参考下面月月大神的流程图)
    2251862-9c4ff5faa937e5ef.png

面试题类方法与实例方法存在位置

void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    // - (void)sayHello;
    // + (void)sayHappy;
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
    <!--lgInstanceMethod_classToMetaclass - 0x10000-->31b0-0x0-0x0-0x100003148
}

void lgClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void lgIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    // - (void)sayHello;
    // + (void)sayHappy;
    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
}

解析
* 1.class_getInstanceMethod是否存在对应的实例方法,根据打印结果可以看出,method1method4有值,而method2、method3没有值,故而得出,实例方法存在对应的类中,而类方法是存在对应的元类中。
* 2.class_getClassMethod是否存在对应的类方法,根据打印结果看出,method1、method2没有值,method3、method4有值,故而得出类方法可以在自身类与对应的元类中找到
* 3.class_getMethodImplementation查看实例方法与类方法对应的imp,根据结果可以看出imp2,imp3是一致的,根据下面源码得出,如果没有对应的imp即sel方法则会固定返回_objc_msgForward,否则返回imp

IMP class_getMethodImplementation(Class cls, SEL sel)
{
   IMP imp;

   if (!cls  ||  !sel) return nil;

   imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

   // Translate forwarding function to C-callable external version
   if (!imp) {
       return _objc_msgForward;
   }

   return imp;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容