mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
docs: SSL/TLS
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# 内存问题研究
|
||||
# iOS 内存原理探究
|
||||
|
||||
## 定时器内存泄漏
|
||||
|
||||
@@ -10,7 +10,7 @@ NSTimer、CADisplayLink 的 基础 API `[NSTimer scheduledTimersWithTimeInterval
|
||||
|
||||
栈、堆、BSS、数据段、代码段
|
||||
|
||||

|
||||

|
||||
|
||||
栈(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,报错信息如下
|
||||
|
||||

|
||||

|
||||
|
||||
说明:一开始的报错信息只说坏内存访问,但是并没有显示具体的方法调用堆。想知道具体 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 superclass’s 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 查看类中的方法信息
|
||||
|
||||

|
||||
|
||||
发现存在 `.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
|
||||
```
|
||||
|
||||

|
||||
|
||||
Tips:@property 会自动生成成员变量,另外类后面加 `{}` 在内部也可以加成员变量,假如成员变量是对象类型,比如 NSString,则叫实例变量。
|
||||
|
||||
得出结论:
|
||||
|
||||
- 只有 ARC 模式下才有 `.cxx_destruct` 方法
|
||||
|
||||
- 类拥有实例变量的时候(`{}` 或者 `@property`) 才有 `.cxx_destruct`,父类成员对象的实例变量不会让子类拥有该方法
|
||||
|
||||
使用 watchpoint 观察内存释放时机
|
||||
|
||||
在 gone 的地方加断点,输入 `watchpoint set variable p->_name`,则会将 `_name` 实例变量加入 watchpoint,当变量被修改时会触发断点,可以看出从某个值变为 0x0,也就是 nil。此时边上调用堆栈显示在 `objc_storestrong` 方法中,被设置为 nil.
|
||||
|
||||

|
||||
|
||||
### 深入 .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 指向上一个对象
|
||||
|
||||

|
||||

|
||||
|
||||
```objectivec
|
||||
id * begin() {
|
||||
@@ -761,7 +1089,7 @@ int main(int argc, const char * argv[]) {
|
||||
|
||||
main 方法内部3个 autoreleasepool 底层怎么样工作的?
|
||||
|
||||

|
||||

|
||||
|
||||
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 运行图
|
||||
|
||||

|
||||

|
||||
|
||||
- 01 通知 Observer 进入 Loop 会调用 `objc_autoreleasePoolPush`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user