iOS底层原理篇(一)----类的本质和底层实现

1.在iOS中,类的结构是什么样的呢?

main.m中首先有这样一段代码(objc源码中):main.m代码
我们cd到当前路径后输入命令行:clang -rewrite-objc main.m -o main.cpp获得c++文件,打开文件,我们拉到最下面的代码,就是oc经过编译后的代码:
oc编译后的代码

  • 在上图中,pClass使用Class类型接收,说明pClass是Class类型的!我们找到Class的定义:

      typedef struct objc_class *Class;
      struct objc_class {
      	Class _Nonnull isa __attribute__((deprecated));
      } __attribute__((unavailable));
    
  • oh my god! 废弃了,多么痛的领悟!没关系,我们去找找有没有objc_class的定义!
    objc_class定义

  • 我们现在点进去看看源码:

      struct objc_object {
      	Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
      };
    
      struct objc_class : objc_object {
      	// Class ISA; 隐藏的isa,来源于objc_object,是继承来的一个东西.
      	Class superclass; //父类
      	cache_t cache; 
      	class_data_bits_t bits;
    
      	class_rw_t *data() { 
      		return bits.data();
      	}
      	/**
      	objc_class居然是继承了objc_object的一个结构体(万物皆对象).
      	说到万物皆对象,我们的大部分类不是继承自NSObject吗?
      	我们也去看看NSObject的定义:
      	@interface NSObject <NSObject> {
      		Class isa  OBJC_ISA_AVAILABILITY;
      	}
      	你看,是不是和 struct objc_object {
      		Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
      	}
      	一样呢?
      	以上就是这个结构体的定义内容,下面的方法以及省略的很多方法都是一些相关的设置
      	*/
      	void setData(class_rw_t *newData) {
      		bits.setData(newData);
      	}
      	......
      }
    

2.在类中,属性以及方法是怎么存储的?

我们知道了类的结构,就是继承了objc_object结构体的objc_class的结构体!那么我们在类里面生命的属性,方法等内容又是怎么存储的呢?

  • 首先看几个结构截图:

    • 1.objc_class : objc_object {}
      objc_class结构体

    • 2.cache_t结构
      cache_t结构

    • 3.class_rw_t结构
      class_rw_t结构体

    • 4.我们在WMPerson类里面作如下操作

        @interface WMPerson : NSObject {
        	NSString *myStyle;//实例变量
        }
        //属性
        @property (nonatomic, copy) NSString *nickName;
      
        - (void)instanceMethodTest;
        + (void)ClassMethodTest;
      
        @end
        
        #import "WMPerson.h"
        @implementation WMPerson
        //对象方法
        - (void)instanceMethodTest {
        	NSLog(@"WMPerson instanceMethodTest!");
        }
        //类方法
        + (void)ClassMethodTest {
        	NSLog(@"WMPerson ClassMethodTest!");
        }
        @end
      
    • 5.我们运行项目并将断点断到如图处,使用lldb指令打印内存结构得到如图的打印结果!0x10000458(因为多次运行项目,我们此篇文章中,x/4gx pClass指令打印出来的这两个指针是会变的,提前说一下,因为类的内存地址分配是会变的)指针指向cache_t的首地址!(下面的截图是未设置属性及方法时截取的,所以bits内存地址指针为0)
      类的内存结构

    • 根据上面的一些内容,我们可以知道下图:
      在这里插入图片描述

  • 我们经过内存偏移得到bits指针,我们就是要通过指针来调用data方法获取里面的内容,看看里面存了什么内容!我们对WMPerson作如下处理,运行并通过lldb打印我们想要看到的内容:

      //对WMPerson作如下处理:
      @interface WMPerson : NSObject{
      	NSString *myStyle;
      }
      @property (nonatomic, copy) NSString *nickName;
      @end
    
      (lldb) x/4gx pClass
      0x100001478: 0x001d800100001451 0x0000000100b36140
      0x100001488: 0x0000000100fcc5c0 0x0000000100000003
      (lldb) p (class_data_bits_t *)0x100001498
      (class_data_bits_t *) $8 = 0x0000000100001498
      (lldb) p $8->data()
      (class_rw_t *) $9 = 0x0000000100fcbef0
      (lldb) p *$9
      (class_rw_t) $10 = {
      flags = 2148139008
      version = 0
      ro = 0x0000000100001260
      methods = {
      	list_array_tt<method_t, method_list_t> = {
     		= {
      		list = 0x0000000100001198
      		arrayAndFlag = 4294971800
    		}
      	}
      }
      properties = {
      	list_array_tt<property_t, property_list_t> = {
     		= {
      		list = 0x0000000100001248
      		arrayAndFlag = 4294971976
    		}
      	}
      }
      protocols = {
      	list_array_tt<unsigned long, protocol_list_t> = {
     		= {
      		list = 0x0000000000000000
      		arrayAndFlag = 0
    		}
      	}
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
      demangledName = 0x0000000000000000
      }
      (lldb) p $9.properties
      (property_array_t) $11 = {
      	list_array_tt<property_t, property_list_t> = {
      		= {
    			list = 0x0000000100001248
    			arrayAndFlag = 4294971976
      		}
      	}
      }
      Fix-it applied, fixed expression was: $9->properties
      (lldb) p $11.list
      (property_list_t *) $12 = 0x0000000100001248
      (lldb) p $12.first
      (property_t) $13 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
      Fix-it applied, fixed expression was: $12->first
      /**
      此时我们在通过p $12.second或者p $12.get[1]等其他方法,
      但是就是读不出我们的myStyle属性.
      这里解释一下,其实我们的属性是不存在这里面的,我们在(lldb) p *$9打印后的结果中我们可以看到一个 ro = 0x0000000100001260 的值,我们去打印这个东西看看里面是什么
      */
    
      //这里我们重新运行打印一下(下面接着打印到class_rw_t处开始):
      (lldb) p $3.ro
      (const class_ro_t *) $4 = 0x0000000100001260
      (lldb) p *$4
      (const class_ro_t) $5 = {
      	flags = 388
      	instanceStart = 8
      	instanceSize = 24
      	reserved = 0
      	ivarLayout = 0x0000000100000f09 "\x02"
      	name = 0x0000000100000f00 "WMPerson"
      	baseMethodList = 0x0000000100001198
      	baseProtocols = 0x0000000000000000
      	ivars = 0x0000000100001200
      	weakIvarLayout = 0x0000000000000000
      	baseProperties = 0x0000000100001248
      }
      (lldb) p $5.baseProperties
      (property_list_t *const) $6 = 0x0000000100001248
      (lldb) p *$6
      (property_list_t) $7 = {
      	entsize_list_tt<property_t, property_list_t, 0> = {
      		entsizeAndFlags = 16
      		count = 1
      		first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
      	}
      }
      /**
      我们在此处看到first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
      那么,我们的myStyle哪去了呢?
      在指令(lldb) p *$4打印得到的(const class_ro_t) $5中,我们可以看到 
      ivars = 0x0000000100001200 的值
      我们再打印看一下这个里面存的是不是我们想要的东西
      */
      (lldb) p $5.ivars
      (const ivar_list_t *const) $8 = 0x0000000100001200
      (lldb) p *$8
      (const ivar_list_t) $9 = {
      	entsize_list_tt<ivar_t, ivar_list_t, 0> = {
      		entsizeAndFlags = 32
      		count = 2 //此处count  = 2;说明什么呢?
      		first = {
    			offset = 0x0000000100001430
    			name = 0x0000000100000f5b "myStyle"
    			type = 0x0000000100000fa0 "@\"NSString\""
    			alignment_raw = 3
    			size = 8
      		}
      	}
      }
      /**
      我们看到了myStyle成员变量,
      并且在entsize_list_tt二维数组里面count = 2,是不是有两个呢?
      我们下面再打印:
      */
      (lldb) p $8.get(1)
      (ivar_t) $10 = {
      offset = 0x0000000100001438
      name = 0x0000000100000f63 "_nickName"
      type = 0x0000000100000fa0 "@\"NSString\""
      alignment_raw = 3
      size = 8
      }
      /**
      哇哇,带下划线的成员变量_nickName,这是不是也说明,我们使用property声明属性,会自动生成一个带下划线的成员变量呢!!!
      */
    
  • 自此我们看到了属性在类底层的存储,那么方法有事怎么存储的呢???我们重新运行,再次lldb打印:按照上面的流程重新走一遍,当我们获取到 const class_ro_t 时,在里面的键值对中我们可以找到 aseMethodList = 0x0000000100001198 ,是不是存在这里面呢?我们去看!

      //我们获取到的 const class_ro_t:
      (const class_ro_t) $12 = {
      	flags = 388
      	instanceStart = 8
      	instanceSize = 24
      	reserved = 0
      	ivarLayout = 0x0000000100000f09 "\x02"
      	name = 0x0000000100000f00 "WMPerson"
      	baseMethodList = 0x0000000100001198
      	baseProtocols = 0x0000000000000000
      	ivars = 0x0000000100001200
      	weakIvarLayout = 0x0000000000000000
      	baseProperties = 0x0000000100001248
      }
      
      (lldb) p $12.baseMethodList
      (method_list_t *const) $13 = 0x0000000100001198
      (lldb) p *$13
      (method_list_t) $14 = {
      	entsize_list_tt<method_t, method_list_t, 3> = {
      		entsizeAndFlags = 26
      		count = 4 //count为4,说明这里面存了四个方法
      		first = {
    				name = "instanceMethodTest"  //对象方法
    				types = 0x0000000100000f85 "v16@0:8"  //参数及返回值类型
    				imp = 0x0000000100000ae0 (LGTest -[WMPerson instanceMethodTest] at WMPerson.m:12)  //imp指针
      		}
      	}
      }
      /**
      count = 4,说明这里面存了四个方法,我们明明只写了两个方法,
      一个是对象方法instanceMethodTest,
      一个是类方法ClassMethodTest!
      我们接着把四个值都打印出来看个究竟!
      */
      (lldb) p $14.get(0)
      (method_t) $15 = {
      	name = "instanceMethodTest"
      	types = 0x0000000100000f85 "v16@0:8"
      	imp = 0x0000000100000ae0 (LGTest -[WMPerson instanceMethodTest] at WMPerson.m:12)
      }
      (lldb) p $14.get(1)
      (method_t) $16 = {
      	name = ".cxx_destruct"
      	types = 0x0000000100000f85 "v16@0:8"
      	imp = 0x0000000100000bb0 (LGTest -[WMPerson .cxx_destruct] at WMPerson.m:10)
      }
      (lldb) p $14.get(2)
      (method_t) $17 = {
      	name = "nickName"
      	types = 0x0000000100000f8d "@16@0:8"
      	imp = 0x0000000100000b40 (LGTest -[WMPerson nickName] at WMPerson.h:16)
      }
      (lldb) p $14.get(3)
      (method_t) $18 = {
      	name = "setNickName:"
      	types = 0x0000000100000f95 "v24@0:8@16"
      	imp = 0x0000000100000b70 (LGTest -[WMPerson setNickName:] at WMPerson.h:16)
      }
      /**
      $14.get(0)是我们的对象方法
      $14.get(1)是系统的c++方法
      $14.get(2)是属性nickName的getter
      $14.get(3)是属性nickName的setter
      从这里我们是不是看出来,property属性,底层会为我们自动生成setter和getter
      而成员变量myStyle则没有setter和getter
      还有一个,我们没有看到类方法ClassMethodTest?类方法没有存在类里面吗?
      */
    
  • 经过上面一顿操作,我们看到了对象方法以及属性的getter和setter存储,但是我们并未看到类方法存储在哪里?先面我们换一种方法去看:

      /**
      实现以下两个方法:
      因为如果我们的类如果拥有这个方法,我们就可以通过API获取当前方法实现的指针;
      */
      void testInstanceMethod_classToMetaclass(Class pClass){
    
      	const char *className = class_getName(pClass);
      	Class metaClass = objc_getMetaClass(className);//元类
    
      	//调用API:class_getInstanceMethod获取类和元类中的对象方法,看能否找到该对象方法
      	Method method1 = class_getInstanceMethod(pClass, @selector(instanceMethodTest));
      	Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethodTest));
    
      	//调用API:class_getInstanceMethod获取类和元类中的对象方法,看能否找到该类方法
      	Method method3 = class_getInstanceMethod(pClass, @selector(ClassMethodTest));
      	Method method4 = class_getInstanceMethod(metaClass, @selector(ClassMethodTest));
      	NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
      	NSLog(@"%s",__func__);
      }
    
      void testClassMethod_classToMetaclass(Class pClass){
    
      	const char *className = class_getName(pClass);
      	Class metaClass = objc_getMetaClass(className);//元类
    
      	//调用API:class_getClassMethod获取类和元类中的类方法,看能否找到该对象方法
      	Method method1 = class_getClassMethod(pClass, @selector(instanceMethodTest));
      	Method method2 = class_getClassMethod(metaClass, @selector(instanceMethodTest));
    
      	//调用API:class_getClassMethod获取类和元类中的类方法,看能否找到该类方法
      	Method method3 = class_getClassMethod(pClass, @selector(ClassMethodTest));
      	Method method4 = class_getClassMethod(metaClass, @selector(ClassMethodTest));
    
      	NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
      	NSLog(@"%s",__func__);
      }
      
      //我们来看打印结果:
      2019-12-24 10:36:20.004775+0800 WMTest[1615:59975] 0x100002200-0x0-0x0-0x100002198
      2019-12-24 10:36:20.005300+0800 WMTest[1615:59975] testInstanceMethod_classToMetaclass
      2019-12-24 10:36:20.005422+0800 WMTest[1615:59975] 0x0-0x0-0x100002198-0x100002198
      2019-12-24 10:36:20.005473+0800 WMTest[1615:59975] testClassMethod_classToMetaclass
    
      /**
      解析:
      先看该打印:WMTest[1615:59975] 0x100002200-0x0-0x0-0x100002198
      前两个结果是在类和元类中查找其对象方法instanceMethodTest,
      在类中,我们得到了对象方法0x100002200,但是在元类中则是0x0,
      
      后两个结果,在类和元类中使用获取对象方法的API获取类方法ClassMethodTest
      我们得到的结果是,在类中,没有获取到,
      在元类中,我们则通过获取对象方法的API获取到了ClassMethodTest类方法
      这里说明:我们类的类方法在元类中是以对象方法存在的!
    
      再看另一个打印结果:WMTest[1615:59975] 0x0-0x0-0x100002198-0x100002198
    
      首先:在类和元类中,通过获取类方法的API去获取对象方法,结果都是0
    
      然后,类和元类中,通过获取类方法的API去获取对类方法,都存在!
      但是这里又有了问题,我们上面的结果说明,我们的类方法在元类中是以对象方法存在的,但是这里我们又发现,类方法在元类中,通过获取类方法的API也能得到结果,那这不是矛盾了吗?
      我们去看一下获取类方法的API:
      */
      Method class_getClassMethod(Class cls, SEL sel)
      {
      	if (!cls  ||  !sel) return nil;//先判断cls和sel是否nil
      	//cls->getMeta()获取元类,将元类传入到class_getInstanceMethod中查找对象方法
      	//原来如此
      	return class_getInstanceMethod(cls->getMeta(), sel);
      }
      Class getMeta() {
      	//是元类,就返回当前元类,不是元类就返回指向元类的ISA.
      	if (isMetaClass()) return (Class)this;
      	else return this->ISA();
      }
    

经过上面的过程,我们看到了类的结构以及属性、成员变量、对象方法、类方法等在类中的存储.再类结构中,我们还看到了一个结构, cache_t cache 缓存,这里面又存了什么呢,下一篇我们去探究!

谢谢观赏!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值