1、Objective-C的本质
Objective-C代码,底层实现其实都是C\C++代码。
Objective-C的对象、类主要是基于C\C++的结构体实现的。
将Objective-C代码转换为C\C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m -o ViewController.cpp
如果需要链接其他框架,使用-framework参数。比如-framework UIKit
2、OC对象的本质
OC对象占用字节数
NSObject *obj = [[NSObject alloc] init];
// 获得NSObject实例对象的成员变量所占用的大小 >> 8个字节
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
// 获得obj指针所指向内存的大小 >> 16个字节
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
NSObject底层实现
NSObject其实是用结构体实现,有个isa属性。
@interface NSObject {
Class isa;
}
转化为结构体
struct NSObject_IMPL {
Class isa; // 8个字节
};
//typedef struct objc_class *Class;//指针
获取分配字节的函数
- 创建一个实例对象,至少需要多少内存
#import
class_getInstanceSize([NSObject class]);
- 创建一个实例对象,实际上分配了多少内存
#import
malloc_size((__bridge const void *)obj);
继承的类占用字节数
@interface Student : NSObject
{
Class isa;//8
int _no;//4
int _age;//4
int _age1;//4
int _age2;//4
}
@end
NSLog(@"%zd", class_getInstanceSize([Student class]));//24 成员变量占用24个字节
NSLog(@"%zd", malloc_size((__bridge const void *)stu));//32 系统分配了32个字节
2、OC对象的分类
- instance对象(实例对象)
- class对象(类对象)
- meta-class对象(元类对象)
2.1 instance
instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象。
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
- object1、object2是NSObject的instance对象(实例对象)
- 它们是不同的两个对象,分别占据着两块不同的内存
- instance对象在内存中存储的信息包括isa指针、其他成员变量。
例
@interface Person : NSObject {
@public
int _age;
}
@end
@implementation Person
@end
Person *p1 = [[Person alloc] init];
person1->_age = 3;
Person *p2 = [[Person alloc] init];
person2->_age = 4;
2.2 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];
Class objectClass6 = [[NSObject class] class];
- objectClass1 ~ objectClass6都是NSObject的class对象(类对象)
- 它们是同一个对象。每个类在内存中有且只有一个class对象
- class对象在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的属性信息(@property)、类的对象方法信息(instance method)
- 类的协议信息(protocol)、类的成员变量信息(ivar)
2.3 meta-class
Class objectMetaClass = object_getClass([NSObject class]);
- objectMetaClass是NSObject的meta-class对象(元类对象)
- 每个类在内存中有且只有一个meta-class对象
- meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的类方法信息
- 以下代码获取的objectClass是class对象,并不是meta-class对象
Class objectClass6 = [[NSObject class] class];
- 查看Class是否为meta-class
#import
BOOL result = class_isMetaClass([NSObject class]);
2.4 isa指针
- instance的isa指向class
当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用 - class的isa指向meta-class
当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用
2.5 class对象的superclass指针
- 当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用
2.6 meta-class对象的superclass指针
- 当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用
2.7 isa、superclass总结
- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基类的meta-class
- class的superclass指向父类的class, 如果没有父类,superclass指针为nil
- meta-class的superclass指向父类的meta-class, 基类的meta-class的superclass指向基类的class
- instance调用对象方法的轨迹: isa找到class,方法不存在,就通过superclass找父类
- class调用类方法的轨迹:isa找meta-class,方法不存在,就通过superclass找父类
注意第5点
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
- (void)test {
NSLog(@"-[NSObject test] - %p", self);
}
@end
@interface Person : NSObject
+ (void)test;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"[Person class] - %p", [Person class]);
NSLog(@"[NSObject class] - %p", [NSObject class]);
[Person test];
// objc_msgSend([Person class], @selector(test))
// isa -> superclass -> suerpclass -> superclass -> .... superclass == nil
[NSObject test];
//objc_msgSend([NSObject class], @selector(test))
}
return 0;
}
3、ISA_MASK
- 从64bit开始,isa需要进行一次位运算,才能计算出真实地址
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
4、objc_class结构
objc4源码: https://opensource.apple.com/tarballs/objc4/
struct objc_object {
private:
isa_t isa;
};
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // 方法缓存
class_data_bits_t bits; // 用于获取类的信息
class_rw_t *data() {
return bits.data();
}
};
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; //方法列表
property_array_t properties; //属性列表
protocol_array_t protocols; //协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //instance对象占用的内存空间
#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;
};
5、KVO
KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变
例如下类
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end
@implementation Person
@end
5.1 未使用KVO监听的对象
5.2 使用KVO监听的对象
- _NSSet*ValueAndNotify的内部实现
- (void)setAge:(int)age {
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
调用步骤
- 调用
willChangeValueForKey:
- 调用原来的
setter
实现 - 调用
didChangeValueForKey:
-
didChangeValueForKey:
内部会调用observer的observeValueForKeyPath:ofObject:change:context
5.3 Observer存在哪里
可通过GNUstep-Foundation源码查看源码观察实现原理
5.4 KVO代码例子
#import "ViewController.h"
#import
@interface ViewController ()
@property (strong, nonatomic) Person *person1;
@property (strong, nonatomic) Person *person2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
NSLog(@"person1.class == %@", object_getClass(self.person1)) ;
NSLog(@"person2.class == %@", object_getClass(self.person2)) ;
[self printMethodNamesOfClass:object_getClass(self.person1)];
[self printMethodNamesOfClass:object_getClass(self.person2)];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.person1.age = 20;
self.person2.age = 20;
}
- (void)dealloc {
[self.person1 removeObserver:self forKeyPath:@"age"];
[self.person1 removeObserver:self forKeyPath:@"height"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}
- (void)printMethodNamesOfClass:(Class)cls {
unsigned int count;
Method *methodList = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [NSMutableString string];
for (int i = 0; i < count; i++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
free(methodList);
NSLog(@"%@ %@", cls, methodNames);
}
@end
6、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;
- setValue:forKey:的原理
accessInstanceVariablesDirectly方法的默认返回值是YES
-
valueForKey:的原理
7、Category
7.1 Category底层结构
定义在objc-runtime-new.h中
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);
};
7.1 Category的加载处理过程
- 通过Runtime加载某个类的所有Category数据
- 把所有Category的方法、属性、协议数据,合并到一个大数组中, 后面参与编译的Category数据,会在数组的前面
- 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
源码解读顺序
//objc-os.mm
_objc_init
map_images
map_images_nolock
//objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、memcpy
7.2 +load方法
- +load方法会在runtime加载类、分类时调用
- 每个类、分类的+load,在程序运行过程中只调用一次.
- 先调用类的+load
- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的+load之前会先调用父类的+load
- 再调用分类的+load
- 按照编译先后顺序调用(先编译,先调用)
objc4源码解读过程
//objc-os.mm
_objc_init
load_images
prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list
call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_load)
+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
7.3 +initialize方法
+initialize方法会在类第一次接收到消息时调用
先调用父类的+initialize,再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)+initialize是通过objc_msgSend进行调用的,所以有以下特点
- 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
- 如果分类实现了+initialize,就覆盖类本身的+initialize调用
objc4源码解读过程
//objc-msg-arm64.s
objc_msgSend
//objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)
7.4 +load方法、+initialize方法区别
- 调用方式
- load是根据函数地址直接调用
- initialize是通过objc_msgSend调用
- 调用时刻
- load是runtime加载类、分类的时候调用(只会调用1次)
- initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次
- load调用顺序
- 先调用类的load, 先编译的类, 优先调用load,调用子类的load之前, 会先调用父类的load
- 再调用分类的load, 先编译的分类,优先调用load
- initialize调用顺序
- 先初始化父类
- 再初始化子类(可能最终调用的是父类的initialize方法)
7.5 给分类"添加成员变量"
默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现
7.5.1 关联对象API
//添加关联对象
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
//获得关联对象
id objc_getAssociatedObject(id object, const void * key)
//移除所有的关联对象
void objc_removeAssociatedObjects(id object)
7.5.2 key的常见用法
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
//使用属性名作为key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");
//使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))
7.5.3 objc_AssociationPolicy
objc_AssociationPolicy | 对应的修饰符 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | strong, nonatomic |
OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
OBJC_ASSOCIATION_RETAIN | strong, atomic |
OBJC_ASSOCIATION_COPY | copy, atomic |
7.5.4 关联对象的原理
实现关联对象技术的核心对象有
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
objc4源码解读:objc-references.mm
class AssociationsManager {
static AssociationsHashMap *_map;
};
class AssociationsHashMap : public unordered_map
class ObjectAssociationMap : public std::map
class ObjcAssociation {
uintptr_t _policy;
id _value;
};
- 关联对象并不是存储在被关联对象本身内存中
- 关联对象存储在全局的统一的一个
AssociationsManager
中 - 设置关联对象为nil,就相当于是移除关联对象
8、Block
8.1 block的本质
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
-
block的底层结构
8.2 block的变量捕获
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
int global_var = 10;//全局变量
static int static_global_var = 10;//静态全局变量
void (^block)(void);
void test() {
auto int local_var = 10;//局部变量
static int static_local_var = 10;//静态局部变量
block = ^{
NSLog(@"%d, %d, %d, %d", global_var, static_global_var, local_var, static_local_var);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
}
return 0;
}
转换为C++代码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
}
int global_var = 10; //全局变量
static int static_global_var = 10; //静态全局变量
void (*block)(void);
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int local_var; //捕获的局部变量
int *static_local_var; //捕获的静态局部变量
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _local_var, int *_static_local_var, int flags=0) : local_var(_local_var), static_local_var(_static_local_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
8.3 block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
- __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
- __NSStackBlock__ ( _NSConcreteStackBlock )
- __NSMallocBlock__ ( _NSConcreteMallocBlock )
- 每一种类型的block调用copy后的结果如下所示
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
- block作为函数返回值时
- 将block赋值给
__strong
指针时 - block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
8.4 对象类型的auto变量
当block内部访问了对象类型的auto变量时, 如果block是在栈上,将不会对auto变量产生强引用
如果block被拷贝到堆上,会调用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
)。
例子
//MRC环境下测试
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end
@implementation Person
- (void)dealloc {
NSLog(@"Person - dealloc");
[super dealloc];
}
@end
1、如果block是在栈上,将不会对auto变量产生强引用
//MRC环境下测试
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"---------%d", person.age);
};//栈block
[person release];
}
NSLog(@"---------%@", block);
NSLog(@"---end---");
}
return 0;
}
转换为C++代码如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;//栈Block
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
2 如果block是在堆上, 根据auto变量的__strong
、__weak
、__unsafe_unretained
形成强引用或者弱引用
strong
//ARC环境下测试
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"---------%d", person.age);
};
block();
}
NSLog(@"---------%@", block);
NSLog(@"---end---");
}
return 0;
}
转化为C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;//堆block
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__weak
//ARC环境下测试
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *weakPerson = person;
block = ^{
NSLog(@"---------%d", weakPerson.age);
};
block();
}
NSLog(@"---------%@", block);
NSLog(@"---end---");
}
return 0;
}
转化为C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak weakPerson; //weak指针
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
8.5 __block修饰符
8.5.1 __block原理
-
__block
可以用于解决block内部无法修改auto变量值的问题 -
__block
不能修饰全局变量、静态变量(static) - 编译器会将
__block
变量包装成一个对象
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
};
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
8.5.2 __block的内存管理
1、当block在栈上时,并不会对__block变量产生强引用。
2、当block被copy到堆时
- 会调用block内部的copy函数
-
copy
函数内部会调用_Block_object_assign
函数 -
_Block_object_assign
函数会对__block
变量形成强引用(retain
)
3、当block从堆中移除时
- 会调用block内部的
dispose
函数 -
dispose
函数内部会调用_Block_object_dispose
函数 -
_Block_object_dispose
函数会自动释放引用的__block
变量(release
)
4、__block
的__forwarding
指针
5、对象类型的auto变量、__block变量
当block在栈上时,对它们都不会产生强引用
当block拷贝到堆上时,都会通过copy函数来处理它们
__block
变量(假设变量名叫做a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
当block从堆上移除时,都会通过dispose函数来释放它们
__block
变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
8.6 Block的循环引用问题
ARC下
使用__weak
、__unsafe_unretained
解决循环引用问题
__weak
、__unsafe_unretained
区别在于__weak
在对象释放时会自动置nil,而__unsafe_unretained
不会置nil。
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
};
使用__block
解决循环引用问题,必须调用Block
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
weakSelf = nil;
};
self.block();
MRC下
用__unsafe_unretained
解决循环引用问题
__unsafe_unretained id weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
};
用__block
解决循环引用问题,可以不用调用Block,
__block id weakSelf = self;
self.block = ^{
NSLog(@"%p", weakSelf);
};