docs: SSL/TLS

This commit is contained in:
LiuBinPeng
2022-05-24 13:00:23 +08:00
parent 7241220c8e
commit 4f11b95363
90 changed files with 4054 additions and 451 deletions

View File

@@ -1,4 +1,4 @@
# 内存问题研
# iOS 内存原理探
## 定时器内存泄漏
@@ -10,7 +10,7 @@ NSTimer、CADisplayLink 的 基础 API `[NSTimer scheduledTimersWithTimeInterval
栈、堆、BSS、数据段、代码段
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/iOS-MemoryLayout.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/iOS-MemoryLayout.png)
stack又称作堆栈用来存储程序的局部变量但不包括static声明的变量static修饰的数据存放于数据段中。除此之外在函数被调用时栈用来传递参数和返回值。栈内存地址越来越少
@@ -74,10 +74,12 @@ obj: 0x6000012780e0
height: 0x7ff7b83fdbb8
age: 0x7ff7b83fdbbc
```
```
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%p %p %@", obj, &obj, obj);
```
分别打印 obj指针指向的堆上的内存地址、obj 指针在栈上的地址、obj 内容
## Tagged Pointer
@@ -145,7 +147,7 @@ Demo1
运行该代码会 Crash报错信息如下
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TaggedPointerCrash.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/TaggedPointerCrash.png)
说明:一开始的报错信息只说坏内存访问,但是并没有显示具体的方法调用堆。想知道具体 Crash 原因还是需要看看堆栈比较方便。输入 bt 查看最后是由于 `objc_release` 方法造成 crash。
@@ -157,8 +159,9 @@ Demo1
-(void)setName:(NSString *)name
{
if (_name!=name) {
[name retain];
[_name release];
_name = [name retain];
_name = name;
}
}
```
@@ -495,9 +498,41 @@ NSLog(@"%@", p3);
当用 `__unsafe_unretained` 修饰后,虽然释放了,但是内存还没回收,这时候去使用很容易出错。
## dealloc
## dealloc 是如何工作的?
查看 objc4 源码
在 MRC 时代,写完代码都需要显示在 dealloc 方法中做一些内存回收之类的工作。对象析构时将内部对象先 release 掉,非 OC 对象(比如定时器、c 对象、CF 对象等) 也需要回收内存,最后调用 `[super dealloc]` 继续将父类对象做析构。
```objectivec
- (void)dealloc {
CFRelease(XX);
self.timer = nil;
[super dealloc];
}
```
但在 ARC 时代dealloc 中一般只需要写一些非 OC 对象的内存释放工作,比如 CFRelease()
带来2个问题
- 类中的实例变量在哪释放?
- 当前类中没有显示调用 `[super dealloc]` ,父类的析构如何触发?
### LLVM 文档对 dealloc 的描述
[LLVM ARC 文档对 dealloc 描述](https://clang.llvm.org/docs/AutomaticReferenceCounting.html#dealloc) 如下
> A class may provide a method definition for an instance method named `dealloc`. This method will be called after the final `release` of the object but before it is deallocated or any of its instance variables are destroyed. The superclasss implementation of `dealloc` will be called automatically when the method returns.
>
> The instance variables for an ARC-compiled class will be destroyed at some point after control enters the `dealloc` method for the root class of the class. The ordering of the destruction of instance variables is unspecified, both within a single class and between subclasses and superclasses.
根据描述可以看到 dealloc 方法在最后一次 release 方法调用后触发但实例变量ivars 还未释放,父类的 dealloc 方法将会在子类 dealloc 方法返回后自动调用。
ARC 模式下,对象的实例变量会在根类 [NSObject dealloc] 中释放,但是释放的顺序是不一定的。
也就是说会自动调用 `[super dealloc]`,那到底如何实现的,探究下。
### 查看 objc4 源码
```c
- (void)dealloc {
@@ -619,6 +654,299 @@ weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
}
```
可以清楚看到在 `objc_destructInstance` 方法中调用了3个核心方法
- object_cxxDestruct(obj) 清除成员变量
- object_remove_assocations(obj):去除该对象相关的关联属性(Category 添加的)
- obj->clearDeallocating():清空引用技术表和弱引用表,将 weak 引用设置为 nil
继续看看 object_cxxDestruct 方法内部细节。
### 神秘的 cxx_destruct
`object_cxxDestruct` 方法最终会调用到 `object_cxxDestructFromClass`
```c
void object_cxxDestruct(id obj) {
if (_objc_isTaggedPointerOrNil(obj)) return;
object_cxxDestructFromClass(obj, obj->ISA());
}
static void object_cxxDestructFromClass(id obj, Class cls) {
void (*dtor)(id);
// Call cls's dtor first, then superclasses's dtors.
for ( ; cls; cls = cls->getSuperclass()) {
if (!cls->hasCxxDtor()) return;
dtor = (void(*)(id))
lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
// 调用
if (dtor != (void(*)(id))_objc_msgForward_impcache) {
if (PrintCxxCtors) {
_objc_inform("CXX: calling C++ destructors for class %s",
cls->nameForLogging());
}
(*dtor)(obj);
}
}
}
```
做的事情就是遍历,不断寻找父类中 `SEL_cxx_destruct`这个 selector找到函数实现并调用。
```c
void sel_init(size_t selrefCount){
#if SUPPORT_PREOPT
if (PrintPreopt) {
_objc_inform("PREOPTIMIZATION: using dyld selector opt");
}
#endif
namedSelectors.init((unsigned)selrefCount);
// Register selectors used by libobjc
mutex_locker_t lock(selLock)
SEL_cxx_construct = sel_registerNameNoLock(".cxx_construct", NO);
SEL_cxx_destruct = sel_registerNameNoLock(".cxx_destruct", NO);
}
```
继续翻阅源码发现 `SEL_cxx_destruct` 其实就是 `.cxx_destruct`。在 《Effective Objective-C 2.0》中说明:
> When the compiler saw that an object contained C++ objects, it would generate a method called .cxx_destruct. ARC piggybacks on this method and emits the required cleanup code within it.
也就是说,当编译器看到 C++ 对象的时候,它将会生成 `.cxx_destruct` 析构方法,但是 ARC 借用这个方法,并在其中插入了代码以实现自动内存释放的功能。
### 探究啥时候生成 .cxx_destruct 方法
```objectivec
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end
//
- (void)viewDidLoad {
[super viewDidLoad];
{
NSLog(@"comes");
Person *p = [[Person alloc] init];
p.name = @"杭城小刘";
NSLog(@"gone");
}
}
```
在 gone 处加断点,利用 runtime 查看类中的方法信息
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/cxx_destructDemo1.png)
发现存在 `.cxx_destruct` 方法。
我们一开要研究的是 ivars 啥时候释放,所以控制变量,将属性改为成员对象
```objectivec
@interface Person : NSObject
{
@public
NSString *name;
}
@end
{
NSLog(@"comes");
Person *p = [[Person alloc] init];
p->name = @"杭城小刘";
NSLog(@"gone");
}
```
也有 `.cxx_destruct` 方法
将成员变量换为基本数据类型
```objectivec
@interface Person : NSObject
{
@public
int age;
}
@end
```
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/cxx_destructdemo3.png)
Tips@property 会自动生成成员变量,另外类后面加 `{}` 在内部也可以加成员变量,假如成员变量是对象类型,比如 NSString则叫实例变量。
得出结论:
- 只有 ARC 模式下才有 `.cxx_destruct` 方法
- 类拥有实例变量的时候(`{}` 或者 `@property`) 才有 `.cxx_destruct`,父类成员对象的实例变量不会让子类拥有该方法
使用 watchpoint 观察内存释放时机
在 gone 的地方加断点,输入 `watchpoint set variable p->_name`,则会将 `_name` 实例变量加入 watchpoint当变量被修改时会触发断点可以看出从某个值变为 0x0也就是 nil。此时边上调用堆栈显示在 `objc_storestrong` 方法中,被设置为 nil.
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/cxx_destructDemo2.png)
### 深入 .cxx_destruct
简单梳理下,在 ARC 模式下,类拥有实例变量的时候会在 `.cxx_destruct` 方法内调用 `objc_storeStrong` 去释放的内存。
我们也知道 `.cxx_destruct` 是编译器生成的代码。去查询资料 `.cxx_destruct site:clang.llvm.org`
在 clang 的 doxygen 文档中 [CodeGenModule 模块源码](https://clang.llvm.org/doxygen/CodeGenModule_8cpp_source.html)发现了相关逻辑。在 5907 行代码
```c
void CodeGenModule::EmitObjCIvarInitializations(ObjCImplementationDecl *D) {
// We might need a .cxx_destruct even if we don't have any ivar initializers.
if (needsDestructMethod(D)) {
IdentifierInfo *II = &getContext().Idents.get(".cxx_destruct");
Selector cxxSelector = getContext().Selectors.getSelector(0, &II);
ObjCMethodDecl *DTORMethod = ObjCMethodDecl::Create(
getContext(), D->getLocation(), D->getLocation(), cxxSelector,
getContext().VoidTy, nullptr, D,
/*isInstance=*/true, /*isVariadic=*/false,
/*isPropertyAccessor=*/true, /*isSynthesizedAccessorStub=*/false,
/*isImplicitlyDeclared=*/true,
/*isDefined=*/false, ObjCMethodDecl::Required);
D->addInstanceMethod(DTORMethod);
CodeGenFunction(*this).GenerateObjCCtorDtorMethod(D, DTORMethod, false);
D->setHasDestructors(true);
}
// If the implementation doesn't have any ivar initializers, we don't need
// a .cxx_construct.
if (D->getNumIvarInitializers() == 0 ||
AllTrivialInitializers(*this, D))
return;
IdentifierInfo *II = &getContext().Idents.get(".cxx_construct");
Selector cxxSelector = getContext().Selectors.getSelector(0, &II);
// The constructor returns 'self'.
ObjCMethodDecl *CTORMethod = ObjCMethodDecl::Create(
getContext(), D->getLocation(), D->getLocation(), cxxSelector,
getContext().getObjCIdType(), nullptr, D, /*isInstance=*/true,
/*isVariadic=*/false,
/*isPropertyAccessor=*/true, /*isSynthesizedAccessorStub=*/false,
/*isImplicitlyDeclared=*/true,
/*isDefined=*/false, ObjCMethodDecl::Required);
D->addInstanceMethod(CTORMethod);
CodeGenFunction(*this).GenerateObjCCtorDtorMethod(D, CTORMethod, true);
D->setHasNonZeroConstructors(true);
}
```
源码大概做的事情就是:获取 `.cxx_destructor` 的 selector创建 Method然后将新创建的 Method 插入到 class 方法列表中。调用 `GenerateObjCCtorDtorMethod` 方法,才创建这个方法的实现。查看 GenerateObjCCtorDtorMethod 的实现。在 https://clang.llvm.org/doxygen/CGObjC_8cpp_source.html 的1626行处。
```c
static void emitCXXDestructMethod(CodeGenFunction &CGF,
ObjCImplementationDecl *impl) {
CodeGenFunction::RunCleanupsScope scope(CGF);
llvm::Value *self = CGF.LoadObjCSelf();
const ObjCInterfaceDecl *iface = impl->getClassInterface();
for (const ObjCIvarDecl *ivar = iface->all_declared_ivar_begin();
ivar; ivar = ivar->getNextIvar()) {
QualType type = ivar->getType();
// Check whether the ivar is a destructible type.
QualType::DestructionKind dtorKind = type.isDestructedType();
if (!dtorKind) continue;
CodeGenFunction::Destroyer *destroyer = nullptr;
// Use a call to objc_storeStrong to destroy strong ivars, for the
// general benefit of the tools.
if (dtorKind == QualType::DK_objc_strong_lifetime) {
destroyer = destroyARCStrongWithStore;
// Otherwise use the default for the destruction kind.
} else {
destroyer = CGF.getDestroyer(dtorKind);
}
CleanupKind cleanupKind = CGF.getCleanupKind(dtorKind);
CGF.EHStack.pushCleanup<DestroyIvar>(cleanupKind, self, ivar, destroyer,
cleanupKind & EHCleanup);
}
assert(scope.requiresCleanups() && "nothing to do in .cxx_destruct?");
}
```
可以看到:遍历了当前对象的所有实例变量,调用 `objc_storeStrong`,从 clang 文档上可以看出
```c
id objc_storeStrong(id *object, id value) {
value = [value retain];
id oldValue = *object;
*object = value;
[oldValue release];
return value;
}
```
`.cxx_destruct` 方法内部会对所有的实例变量调用 `objc_storeStrong(&ivar, null)` ,实例变量就会 release 。
### 自动调用 [super dealloc] 的原理
同理CodeGen 也会做自动调用 `[super dealloc]` 的事情。https://clang.llvm.org/doxygen/CGObjC_8cpp_source.html第751行 `StartObjCMethod` 方法。
```c
751 void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
752 const ObjCContainerDecl *CD) {
// ...
789 // In ARC, certain methods get an extra cleanup.
790 if (CGM.getLangOpts().ObjCAutoRefCount &&
791 OMD->isInstanceMethod() &&
792 OMD->getSelector().isUnarySelector()) {
793 const IdentifierInfo *ident =
794 OMD->getSelector().getIdentifierInfoForSlot(0);
795 if (ident->isStr("dealloc"))
796 EHStack.pushCleanup<FinishARCDealloc>(getARCCleanupKind());
797 }
798 }
```
可以看到在调用到 dealloc 方法时,插入了代码,实现如下
```c
struct FinishARCDealloc : EHScopeStack::Cleanup {
void Emit(CodeGenFunction &CGF, Flags flags) override {
const ObjCMethodDecl *method = cast<ObjCMethodDecl>(CGF.CurCodeDecl);
const ObjCImplDecl *impl = cast<ObjCImplDecl>(method->getDeclContext());
const ObjCInterfaceDecl *iface = impl->getClassInterface();
if (!iface->getSuperClass()) return;
bool isCategory = isa<ObjCCategoryImplDecl>(impl);
// Call [super dealloc] if we have a superclass.
llvm::Value *self = CGF.LoadObjCSelf();
CallArgList args;
CGF.CGM.getObjCRuntime().GenerateMessageSendSuper(CGF, ReturnValueSlot(),
CGF.getContext().VoidTy,
method->getSelector(),
iface,
isCategory,
self,
/*is class msg*/ false,
args,
method);
}
};
```
代码大概就是向父类转发 dealloc 的调用实现,内部自动调用 [super dealloc] 方法。
总结下:
- ARC 模式下,实例变量由编译器插入 `.cxx_destruct` 方法自动释放
- ARC 模式下 `[super dealloc]` 由 llvm 编译器自动插入(CodeGen)
## 自动释放池底层原理探索
上 Demo
@@ -705,7 +1033,7 @@ class AutoreleasePoolPage {
- 每个 AutoreleasePoolPage 对象占用 4096 字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放 autorelease 对象的地址
- 所有的 AutoreleasePoolPage 对象通过**双向链表**的形式连接在一起。child 指向下一个对象parent 指向上一个对象
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/autoreleasepool.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/autoreleasepool.png)
```objectivec
id * begin() {
@@ -761,7 +1089,7 @@ int main(int argc, const char * argv[]) {
main 方法内部3个 autoreleasepool 底层怎么样工作的?
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/AutoreleasePoolMoreItem.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/AutoreleasePoolMoreItem.png)
3个@auto releasepool 系统遇到第一个的时候底层就是初始化一个结构体 `__AtAutoreleasePool`,结构体构造方法内部调用 `AutoreleasePoolPage::push` 方法,系统给 AutoreleasePoolPage 真正保存 autorelease 对象的地方存储进一个 `POOL_BOUNDARY` 对象,然后储存 P1、P2 对象地址,遇到第二个则继续初始化结构体,调用 push 方法,存储一个` POOL_BOUNDARY` 对象,继续保存 P3遇到第三个则继续初始化结构体调用 push 方法,存储一个 `POOL_BOUNDARY` 对象,继续保存 P4。
@@ -1535,7 +1863,7 @@ iOS 在主线程的 Runloop 中注册了2个 Observer
结合 RunLoop 运行图
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/RunLoop-SourceCode.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/assets/RunLoop-SourceCode.png)
- 01 通知 Observer 进入 Loop 会调用 `objc_autoreleasePoolPush`