Objective-C语法和底层知识的整理,方便个人复习。
Objective-C语法和底层知识的整理,方便个人复习。如有侵权请联系(qq:294161255)删除。
Objective-C的底层代码其实都是由C/C++来实现的,Objective-C中的对象就有C++中的结构体这一种数据结构来构造。
一个NSObject对象,系统分配了16个字节给NSObject对象(通过malloc_size函数获得),但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)。这里涉及到一个知识点:内存对齐。
object1和object2分别是两个不同的实例对象,分别占据两块不同的内存空间。
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
objectClass1 ~ objectClass5都是NSObject的class对象(类对象)。它们是同一个对象。每个类在内存中有且只有一个class对象。
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];
objectMetaClass是NSObject的meta-class对象(元类对象),每个类在内存中有且只有一个meta-class对象。
Class objectMetaClass = object_getClass(objectClass5); //Runtime API
// 注意以下方法获得的是类对象而不是元类对象
Class object = [[NSObject class] class];
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
### 未使用KVO监听的对象

可以看到当对象没有被监听的时候,对对象的属性赋值是直接调用了该对象属性的set方法。
### 使用了KVO监听的对象

当对象被监听之后,Objective-C通过Runtime机制,动态的生成了该对象的一个子类:`NSKVONotifying_类名`,并将原来的实例对象的isa指针指向`NSKVONotifying_类名`这个子类。<br>
当实例对象的属性值被修改后,实例对象通过isa指针找到`NSKVONotifying_类名`这个类对象,然后调用set方法,`NSKVONotifying_类名`重写set方法实现了属性值得监听。
这是Apple官方文档对于load方法的解释,意思是类和分类的load方法是在Objective-C运行时加载的时候调用的。
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
+initialize和+load的比较:
// 获得关联对象
id objc_getAssociatedObject(id object, const void * key)
// 移除所有的关联对象
void objc_removeAssociatedObjects(id object)
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))
## 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 |
## 关联对象的原理
实现关联对象技术的核心对象有:
* AssociationsManager
* AssociationsHashMap
* ObjectAssociationMap
* ObjcAssociation
关联对象并不是存储在被关联对象本身内存中,关联对象存储在全局的统一的一个AssociationsManager中
# block
## block本质
block本质上也是一个OC对象,它内部也有个isa指针。<br>block是封装了函数调用以及函数调用环境的OC对象。
block的底层结构如图所示

## block的声明
作为局部变量:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {…};
作为属性:
@property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
作为函数参数:
作为方法调用的参数:
[someObject someMethodThatTakesABlock:^returnType (parameters) {…}];
作为C函数的参数:
void SomeFunctionThatTakesABlock(returnType (^blockName)(parameterTypes));
typedef:
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {…};
## block变量的捕获
block内部为了保证能够访问外部的变量,block有一个变量捕获机制,如下图所示:

### auto变量的捕获
......
## block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
* \__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
* \__NSStackBlock__ ( _NSConcreteStackBlock )
* \__NSMallocBlock__ ( _NSConcreteMallocBlock )
| block的类型 | 环境 |
| ------------- | :----- |
| NSGlobalBlock | 没有访问auto变量 |
| NSStackBlock | 访问了auto变量 |
| NSMallocBlock | NSStackBlock调用了copy |
block在内存中的存储位置:

每种类型的block调用了copy后的结果如下
| block的类型 | 副本源的配置存储域 | 复制效果 |
| ----------- | :----- | :------- |
| NSGlobalBlock | 栈 | 从栈复制到堆 |
| NSStackBlock | 程序的数据区域 | 什么也不做 |
| NSMallocBlock | 堆 | 引用技术加1 |
## block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况<br>
* 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);
## 访问对象类型的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)
| 函数 | 调用时机 |
| --- | :----- |
| copy | 栈上的block复制到堆 |
| dispose | 堆上的block被废弃 |
## \__block修饰符
\__block可以用于解决block内部无法修改auto变量值的问题,\__block不能修饰全局变量、静态变量(static),编译器会将\__block变量包装成一个对象。
## \__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修饰的对象类型
* 当__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)
## 循环引用问题
循环引用即:A对象持有block,block内部持有A。

### ARC下解决循环引用问题
* 用__weak、__unsafe_unretained解决
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"age is %d", myself->_age);
};
__unsafe_unretained typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"age is %d", weakSelf.age);
};

* 用__block解决(必须要调用block)
__block id weakSelf = self;
self.block = ^{
weakSelf = nil;
}
self.block();

### MRC下解决循环引用问题
__unsafe_unretained typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"age is %d", weakSelf.age);
};
__block id weakSelf = self;
self.block = ^{
weakSelf = nil;
};
```
注意MRC下是不用像ARC那样必须调用block才能解决循环引用,因为MRC下__block修饰的对象类型,并不会强引用。