Objective-C的本质

Objective-C的本质

OC是C语言的超集,我们平时写得OC,底层的实现都是C\C++代码,OC的对象、类是基于C\C++的结构体实现的。

  • 将Objective-C代码转换为C\C++代码
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -o <输出的.cpp文件>
    如果需要链接其他框架,使用-framework参数。比如-framework UIKit
OC对象的本质
  • NSObject的底层实现
struct NSObject_IMPL{
  Class *isa
}
//Class是objc_class结构体指针
typedef objc_class *Class
//-----------obj指针的地址值就是isa的地址值------------------
NSObject  *obj = [[NSObject alloc] init];
//obj == obj结构体->isa
Objective-C的本质_第1张图片
OC对象本质.png

从上面可以看得到,NSObject对象的本质就是NSObject_IMPL结构体,OC中的类本质就是结构体,NSObject类里只有Class isa指针,obj对象的地址值就是isa指针的地址值。表面上看NSObect类只占用了8个字节(指针变量占8个字节),不过实际上是使用了16个字节,追踪alloc方法可以直到instanceSize函数可以知道,内存分配至少是16个字节。

//一个实例对象至少需要多少字节
class_getInstanceSize([NSObject class]);
//一个实例对象实际占用多少字节
malloc_size((__brige const void *)obj);

一个继承NSObject的类的结构体

//例如
@interface Person : NSObject
@property (nonatomic, assign) int age;
@end
struct Person_IMPL{
  stuct NSObject_IMPL NSObject_IVARS;
  int _age;
}
//NSObject_IMPL的成员变量只有isa指针一个,所以实际结构体等价如下,person实例对象的地址值还是isa指针的地址值,再继承也同理
struct Person_IMPL{
  Class *isa;
  int _age;
}
OC对象的分类
Objective-C中的对象,简称OC对象,主要分3种
  • instance对象(实例对象)

instance对象就是通过类alloc出来的对象,每次alloc都会创建新的对象

NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
//obj1、obj2都是instance对象,它们是不同的两个对象,分别占据着不同的内存

instance对象在内存中存储的信息有isa指针和其他成员变量。

Person *p1 = [[Person alloc] init];
p1->_age = 3;
Person *p2 = [[Person alloc] init];
p1->_age = 4;
Objective-C的本质_第2张图片
p1、p2对象内存布局
  • class对象(类对象)
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];

objectClass1 ~ objectClass5都是NSObject的class对象(类对象);
它们都是同一个对象,每一个类在内存中有且只有一个class对象;
class对象在内存中存储的信息主要包括:
isa指针
superclass指针
类的属性信息(@property)、类的对象方法信息(instance method)
类的协议信息(protocol)、类的成员变量信息(ivar)

  • meta-class(元类对象)
//获取元类对象的方法
Class objMetaClass = object_getClass([NSObject class]);
/*
  注意:这个方法获取到的还是类对象
  Class objMetaClass = [[NSObject class] class];
*/

objectMetaClass是NSObject的meta-class对象(元类对象)
每个类在内存中有且只有一个meta-class对象
meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:
isa指针
superClass指针
类的类方法信息

isa指针和supclass指针
Objective-C的本质_第3张图片
isa指针指向
  • instance的isa指针指向class
    调用对象方法时,通过isa指针找到class,找出对应的对象方法进行调用
  • class的isa指针指向meta-class
    调用类方法时,通过class的isa找到meta-class,找出对应的类方法进行调用

从Arm64开始,优化了isa指针,isa需要进行位运算(&ISA_MASK)才是真正的地址值

Objective-C的本质_第4张图片
class对象的superclass指针指向
  • class对象的superclass指针指向父类class对象,当student调用父类Person对象方法时,会先通过isa找到student的class对象,再通过class的superclass指针知道到父类Person的class对象,然后找出对应的对象方法进行调用。


    Objective-C的本质_第5张图片
    meta-class对象的superclass指针指向
  • meta-class对象的superclass指针指向父类的meta-class对象,当student调用父类的类方法时,通过class对象的isa找到meta-class对象,再通过meta-class对象的superclass指针找到父类的meta-class对象,找出对应的类方法进行调用。


    Objective-C的本质_第6张图片
    isa、superclass总结
  • instance的isa指向class
  • class的isa指向meta-class
  • meta-class的isa指向基类的meta-class
  • class的superclass指向父类的class
    如果没有父类,则superclass置nil
  • meta-class的superclass指向父类的meta-class
    如果没有父类,则指向基类的class
  • instance调用对象方法的轨迹
    isa找到class,方法不存在,通过superclass找父类
  • class调用类方法的轨迹
    isa找到meta-class,方法不存在,通过superclass找父类
Class的结构

class和meta-class的本质结构都是struct objc_class,也可以理解为meta-class是特殊的class对象,只是用途不一样。

//struct objc_class 结构
struct objc_class{
  Class isa;
  Class superclass;
  Cache_t cache;//方法缓存
  class_data_bit_t bit;//用于获取类的具体信息
}
// bit & FAST_DATA_MASK 得到类的具体信息
struct class_rw_t{
  unit32_t flags;
  unit32_t version;
  const class_ro_t *ro;
  method_list_t * methods;//方法列表,是一个二维数组
  property_list_t * properties;//属性列表
  const protocol_list_t * protocols;//协议列表
  Class firtSubslass;
  Class nextSiblingClass;
  char *demanglegName;
}
//struct class_ro_t 的结构
struct class_ro_t{
  unit32_t flags;
  unit32_t instanceStart;
  unit32_t instanceSize;//instance对象占用的内存空间
  unit32_t flag;
#ifdef __LP64__
  unit32_t reserved;
#endif
  const unit8_t * ivarLayout;
  const char * name;//类名
  method_list_t * baseMethodList;
  protocol_list_t * baseProtocols;
  const ivar_list_t * ivars;//成员变量列表
  const unit8_t * weakIvarLayout;
  property_list_t * baseProperties;
}
KVO
  • KVO 全称Key-Value Observing,俗称"键值监听",可以用于某个对象属性值得改变


    Objective-C的本质_第7张图片
    未使用KVO监听的对象

    Objective-C的本质_第8张图片
    使用了KVO监听的对象
  • KVO的原理解释
    利用Rumtime动态生成一个子类,并让instance对象的isa指向这个全新的类,当instance对象的属性修改时,会调用Foundation的_NSSetXXXValueAndNotify函数,该函数内部的调用流程是,先调用willChangeForKey:,父类原来的setter,didChangeForKey。然后内部会触发监听器(Observer)的监听方法(observeValueForKeyPath:ofObject:change:context:)
KVC
  • KVC全称是Key-Value Coding,键值编码,可以通过一个key来访问访问某个属性
    常见API
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key; 
Objective-C的本质_第9张图片
setValue:forKey:的原理

accessInstanceVariablesDirectly的默认返回值是YES


Objective-C的本质_第10张图片
valueForKey:的原理
Category
//category的底层结构
struct category_t {
    const char *name;//类名
    classref_t cls;
    struct method_list_t *instanceMethods;//对象方法列表
    struct method_list_t *classMethods;//类方法列表
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
  • category的加载处理过程
    1.通过runtime加载某个类的category数据
    2.把所有的category的方法、属性、协议数据合并到一个大数组中
    后面参与编译的category数据,会在数组的前面
    3.将合并后的分类数据(方法、属性、协议),插到类原来数据的前面
+load方法
  • load方法会在runtime加载类、分类时调用
  • 每个类、分类在程序运行过程中只调用 一次load方法
  • 调用顺序
    1.先调用类的+load
    按照编译顺序调用,先编译先调用
    调用子类的+load之前会先调用父类的+load
    2.再调用分类的+load
    按照编译顺序调用,先编译先调用
  • load方法是根据方法地址(*load_method)(cls, SEL_load)
    直接调用的,并不是经过objc_msgSend函数调用
+initialize方法
  • +initialize方法会在类第一次接收到消息时调用
  • 调用顺序
    先调用父类的+initialize,再调用子类的+initialize
    (相处初始化父类,再初始化子类,每个类只会初始化一次)
+initialize和+load的区别

+initialize是通过objc_msgSend调用的,所以有以下特点:如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+intialize方法可以会被调用多次),如果分类实现的+initialize,就会覆盖类的+initialize调用。而+load是通过方法地址直接调用的,所以不会有这些特点。

关联对象
Block
block的本质
  • block本质也是个OC对象,它的内部有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象


    Objective-C的本质_第11张图片
    block的底层结构
//OC代码
int age = 20;
void (^block)(void) = ^{
    NSLog(@"age is %d",age);
};
//block的本质源码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int age;//变量捕获
}
//__block_impl结构
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
//__test_block_desc_0结构
struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
block的变量捕获

为了能够保证block内部访问到外部变量,block有个变量捕获机制


Objective-C的本质_第12张图片
block的变量捕获机制
block的类型

block有3种类型,可以通过class方法或者isa指针查看,它们都继承自NSBlock

__NSGlobalBlock__(全局block)
__NSStackBlock__(栈block)
__NSMallocBlock__(堆block)
Objective-C的本质_第13张图片
block类型的区分

Objective-C的本质_第14张图片
每一种block调用了copy后的结果
block的copy

在ARC环境下,编译器会根据情况将栈block copy为堆block,比如以下情况

  • block作为函数返回值时
  • 将block赋值给__Strong指针时
  • block作为Cocoa API中方法名含有usingBlock的方法参数时
  • block作为GCD API的参数时
//MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);

//ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
block访问对象类型的auto变量
  • 如果block是在栈上,将不会对auto变量产生强引用
  • 如果block被copy到堆上
    block会调用内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
  • 如果block从堆上被移除
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的auto变量(release)


    Objective-C的本质_第15张图片
    copy和dispose函数的调用时机
__block修饰符
  • __block可以用于解决block内部无法修改外部auto变量值的问题
  • __block不能用于修改全局变量,静态变量(static变量)
  • 编译器会将__block修饰的变量包装成一个对象
__block int age = 10;
^{
  NSLog(@"age is %d",age);
};
//age包装成对象的结构体代码
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;//该指针指向自身
 int __flags;
 int __size;
 int age;
};
//block捕获age变量,实际捕获ae对象
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *p;
  __Block_byref_age_0 *age; // 变量捕获
}

__block的内存管理

  • 当block在栈时,不会对__block变量产生强引用
  • 当block被copy到堆时
    会调用block的copy函数
    copy函数会调用_Block_object_assign函数
    _Block_object_assign函数会对__block变量形成强引用(retain)
  • 当block从堆中被移除时
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的__block变量(release)

__block的_forwarding指针


Objective-C的本质_第16张图片
block的_forwarding指针
  • 当__block变量在栈上时,不会对指向的对象产生强引用
  • 当__block变量被copy到堆时
    会调用__block变量内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
  • 如果__block变量从堆上移除
    会调用__block变量内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放指向的对象(release)
block循环引用问题
Objective-C的本质_第17张图片
block循环引用问题

解决方法

//用__weak
__weak typeof(self) weakSelf = self;
self.block = ^{
  print("%p",weakSelf);
};
//用__unsafe_unretained
__unsafe_unretained id weakSelf = self;
self.block = ^{
  print("%p",weakSelf);
}

你可能感兴趣的:(Objective-C的本质)