iOS开发底层之类的底层探究下-06

Posted iOS_developer_zhong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之类的底层探究下-06相关的知识,希望对你有一定的参考价值。

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


类底层探究

一、解决上篇博客遗留的问题?

1. 类方法藏在何处?

答案在本章节的第二部分的第4小点详细说明。 (往下翻)

二、成员变量(实例变量)与属性

1.把文件编译成cpp类型,看c++源码。

// 成员变量 vs 属性 VS 实例变量
@interface LGPerson : NSObject
{
    // STRING   int  double  float char bool
    NSString *hobby; // 字符串
    int a;
    NSObject *objc;  // 结构体
}

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;

@end
  1. 通过观看底层代码,两者的区别如下:
  • 属性:会自动生成带下划线的名称
  • 属性还会自动生成get,set方法

看cpp代码留下了疑问? 为什么属性set方法有的是内存偏移,有的是objc_setProperty,后续在回到这个问题。
在这里插入图片描述

2.类型编码

#pragma mark - 各种类型编码 代码获取
void lgTypes(void){
    NSLog(@"char --> %s",@encode(char));
    NSLog(@"int --> %s",@encode(int));
    NSLog(@"short --> %s",@encode(short));
    NSLog(@"long --> %s",@encode(long));
    NSLog(@"long long --> %s",@encode(long long));
    NSLog(@"unsigned char --> %s",@encode(unsigned char));
    NSLog(@"unsigned int --> %s",@encode(unsigned int));
    NSLog(@"unsigned short --> %s",@encode(unsigned short));
    NSLog(@"unsigned long --> %s",@encode(unsigned long long));
    NSLog(@"float --> %s",@encode(float));
    NSLog(@"bool --> %s",@encode(bool));
    NSLog(@"void --> %s",@encode(void));
    NSLog(@"char * --> %s",@encode(char *));
    NSLog(@"id --> %s",@encode(id));
    NSLog(@"Class --> %s",@encode(Class));
    NSLog(@"SEL --> %s",@encode(SEL));
    int array[] = {1,2,3};
    NSLog(@"int[] --> %s",@encode(typeof(array)));
    typedef struct person{
        char *name;
        int age;
    }Person;
    NSLog(@"struct --> %s",@encode(Person));
    
    typedef union union_type{
        char *name;
        int a;
    }Union;
    NSLog(@"union --> %s",@encode(Union));

    int a = 2;
    int *b = {&a};
    NSLog(@"int[] --> %s",@encode(typeof(b)));
}

学习苹果的简写方法:
如: @16@0:8
1| @ 代表 id
2| 16 代表 占用16个字节内存
3| @ 代表 id
4| 0 代表 从0号位置开始
5| : 代表 SEL
6| 8 代表从8号位置开始

苹果官方编码对照表
在这里插入图片描述
📢:类型编码图的获取途径:
打开xcode–> command+shift+0–> 搜索ivar_getTypeEncoding–> 点击Type Encodings

实用方法: 快速打印一个类的属性与实例变量? 来自逻辑教育 6的飞起老师

void lgObjc_copyIvar_copyProperies(Class pClass){
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Ivar const ivar = ivars[i];
        //获取实例变量名
        const char*cName = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:cName];
        LGLog(@"class_copyIvarList:%@",ivarName);
    }
    free(ivars);

    unsigned int pCount = 0;
    objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
    for (unsigned int i=0; i < pCount; i++) {
        objc_property_t const property = properties[i];
        //获取属性名
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        //获取属性值
        LGLog(@"class_copyProperiesList:%@",propertyName);
    }
    free(properties);
}

3.setter方法的底层原理

  1. setter方法的底层原理简要描述
    llvm源码下载
setName 和 setAge等属性的setter方法,如果一个属性对应一个setter方法,那就太低效了,所以,底层会封装一个通用的方法 objc_setProperty的方法来进行统一处理, 并进行绑定。 

通过llvm底层有个重大发现,也是接上了这篇博客上面提出的问题?,为什么成员属性的setter方法有的是objc_setProperty,有的又是通过内存平移来实现setter方法的。 原来是和修饰的关键字有关系。

// CGObjC.cpp   858行
 if (IsCopy) {   
 	kind = GetSetProperty;
 	return;
}

  1. setter小结:
    1. nonatomic, copy修饰的属性使用objc_getProperty方式实现 。atomic, copy修饰的属性也是使用objc_getProperty方式实现。retain一般在MRC模式下使用,现在使用的基本是在ARC模式,retain这种修饰暂时忽略。
    2. 其他关键字修饰的属性, 如单 nonatomicatomicnonatomic , Strong 等,都是走的内存平移方式。
    3. 苹果针对setter方法写了个适配器的中间层,因为如果底层需要维护,修改起来特别麻烦。中间层的作用是供上层的setter调用,中间层对属性的修饰符进行判断走不同的流程,调用底层的方法实现(为底层提供策略。)
中间层的优点:底层变化上层不受影响,上层变化底层也不会受影响

4.类方法的底层原理(为了解决本篇文章头部的问题 lldb)

  1. 类方法到底隐藏在何处?
    类方法是存放在元类的方法中。

  2. 继续实战操作

    2.1. 获取当前类的元类操作
    在这里插入图片描述
    2.2. lldb 获取类方法的操作步骤,注意看注释
    可以完美的知道类方法是藏在何处的。

(lldb) x/4gx LGPerson.class
0x100008408: 0x0000000100008430 0x000000010036a140
0x100008418: 0x00000001015065f0 0x0002802800000003
(lldb) p/x 0x0000000100008430 + 0x20   // 1. 此处是针对元类的偏移
(long) $1 = 0x0000000100008450
(lldb) p/x (class_data_bits_t *) 0x0000000100008450   // 2. 这个和前面的操作获取属性一模一样 ,转换成class_data_bits_t 对象
(class_data_bits_t *) $2 = 0x0000000100008450
(lldb) p/x $2->data(); // 3. 调用data()方法,获取class_rw_t对象
(class_rw_t *) $3 = 0x00000001015065b0
(lldb) p *$3 
(class_rw_t) $4 = { // 4. 查看class_rw_t内部结构
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000824
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff800bdeb0
}
(lldb) p/x $4->methods();   //5. 获得method_array_t对象
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008340
      }
      arrayAndFlag = 0x0000000100008340
    }
  }
}
  Fix-it applied, fixed expression was: 
    $4.methods();
(lldb) p/x $5.list  //6. 获得方法的列表 method_list_t
(const method_list_t_authed_ptr<method_list_t>) $6 = {
  ptr = 0x0000000100008340
}
(lldb) p/x $6.ptr;  // 7 得到内存指针
(method_list_t *const) $7 = 0x0000000100008340 method_t
(lldb) p *$7        // 8. 打印对象内部结构,得到、method_t对象
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $8.get(0).big(); //9.  打印方法的具体内容
(method_t::big) $9 = {
  name = "sayNB"
  types = 0x0000000100003f6a "v16@0:8"
  imp = 0x0000000100003d40 (KCObjcBuild`+[LGPerson sayNB])
}
(lldb) 

5. 类方法与实例方法代码分析

  1. 概念:在底层没有所谓的类方法与实例方法,只有对象方法。

  2. 获取类的方法列表代码段

void lgObjc_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));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}
  1. 下面通过一个案例来玩下实例方法与类方法。
@interface LGPerson : NSObject
{
    NSObject *objc; 
    NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSObject *obj;
- (void)sayHello;
+ (void)sayHappy;
@end

3.1 我们通过实例方法来对比下类和元类中的类方法与实例方法
在这里插入图片描述
结论: sayHello为实例方法这个我们清楚, 但sayHappy明明是个类方法, 为何打印元类中的实例方法能打印出值, 这就是为什么强调在底层是没有实例与类方法之分的, 其实sayHappy方法就是一个实例方法,放在了元类而已。

3.2 我们通过类方法来对比下类和元类中的类方法与实例方法

在这里插入图片描述
结论: sayHello为实例方法,所以打印不出来, sayHappy是类方法,类与元类都可以查询到。

3.3 我们通过类方法SEL 获取下 IMP 来实现下类和元类中的IM区别。
在这里插入图片描述
结论: 这里很奇怪,为啥 sayHello在元类是没有实现的,还返回了地址,同理 sayHappy方法也没有在类中实现,也返回了地址,还巧合的两个地址相同,这是为什么呢? 看源码一目了然。 在OBJC源码中,搜索 class_getMethodImplementation

在这里插入图片描述
原来底层处理为找不到对应== IMP ==,就指定返回一个 _objc_msgForward的函数,所以上面返回了相同的函数指针地址。


补充

1. objc_object 和 objc_class,person 和 NSObject 的联系?

解释: person是oc层面的一个对象, 底层就是一个结构体对象为 objc_object, 同理, NSObject为OC层面的一个对象, 底层对应的结构体对象为 objc_class 。

2. id 是什么?

解释: id是 objc_object的一个指针

3. class 是什么?

解释: class就是objc_class的一个指针

以上是关于iOS开发底层之类的底层探究下-06的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发底层之类的底层Cache_t 探究 - 07

iOS开发底层之类的底层Cache_t 探究 - 07

iOS开发底层之类的底层探究-05

iOS开发底层之RuntimeObjc_msgSend探究下 - 09

iOS开发底层之RuntimeObjc_msgSend探究下 - 09

iOS开发底层之类加载下 (关联对象) - 14