## 重构报告
#### 一、背景
在 iOS 15.0 Beta5 中,我们遇到了一个崩溃问题。当使用 GCDAsyncSocket 的 `cfstreamThread` 方法时,程序会在该方法内部触发一个崩溃。具体崩溃信息如下:
```objectivec
NSThread[_NSThreadPerformInfo dealloc]
```
崩溃发生在 `GCDAsyncSocket-[_NSThreadPerformInfo dealloc]` 这个方法中。
根据堆栈信息,我们发现问题出现在 CocoaAsyncSocket 项目的 GitHub 仓库中,相关 issue 为 #775。项目地址:robbiehanson/CocoaAsyncSocket · GitHub
为了解决这个问题,我们需要对代码进行重构。
Thread 32 name: GCDAsyncSocket-CFStream Thread 32 Crashed: 0 libobjc.A.dylib 0x19b483c50 objc_release + 16
Crash occurred in the following stack trace:
1. Foundation 0x184161344 -[_NSThreadPerformInfo dealloc] + 56
2. Foundation 0x1842d08dc __NSThreadPerform + 160
3. CoreFoundation 0x1829b069c CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + 28
4. CoreFoundation 0x1829c12f0 __CFRunLoopDoSource0 + 208
5. CoreFoundation 0x1828fabf8 __CFRunLoopDoSources0 + 268
6. CoreFoundation 0x182900404 __CFRunLoopRun + 820
7. CoreFoundation 0x182913fc8 CFRunLoopRunSpecific + 600
8. Foundation 0x184134104 -[NSRunLoop+ 102660 (NSRunLoop) runMode:beforeDate:] + 236
9. MyApp 0x104990290 0x1026b0000 + 36569744
10. Foundation 0x184183950 NSThread__start + 764
11. libsystem_pthread.dylib 0x1f2745a60 _pthread_start + 148
12. libsystem_pthread.dylib 0x1f2744f5c thread_start + 8
The crash happened in a system library, causing many apps to have difficulty resolving this issue. Specifically, it was caused by the use of GCDAsyncSocket. The stack trace shows that the crash occurred during the initialization of a socket object with the GCDAsyncSocket library:socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
在这段代码中,我们首先尝试连接到指定的主机和端口。如果发生错误,可能是由于 "already connected" 或 "no delegate set" 等原因。接下来,我们使用 getaddrinfo 函数获取 IP 地址信息,并创建一个 socket 文件描述符。然后,我们绑定 socket 到指定的接口地址,并尝试连接到目标地址。最后,我们使用 GCDAsyncSocket 取消与 CFReadStream 相关的调度。
重构后的代码如下:
```objc
// 连接到指定的主机和端口
NSError *err = nil;
if (![socket connectToHost:@"deusty.com" onPort:80 error:&err]) {
// 如果发生错误,可能是由于 "already connected" 或 "no delegate set" 等原因
NSLog(@"I goofed: %@", err);
} else {
// 当 socket 创建后,会创建对应 stream
CFReadStreamRef readStream = NULL;
// 获取 IP 地址信息
intgai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
ip = [gai_addresses firstObject];
// 创建一个 socket 文件描述符
int socketFD = socket(family, SOCK_STREAM, 0);
// 将 socket 绑定到指定的接口地址
socklen_t interfaceLength = [connectInterface length];
int result = bind(socketFD, interfaceAddr, interfaceLength);
if (result == 0) {
// 如果绑定成功,尝试连接到目标地址
socklen_t addressLength = [address length];
int result = connect(socketFD, (const struct sockaddr *)address.bytes, addressLength);
if (result == 0) {
// 如果连接成功,将 GCDAsyncSocket 与 CFReadStream 结合使用
readStream = CFReadStreamCreateWithSocket(kCFAllocatorDefault, socketFD);
// 将 GCDAsyncSocket 与 CFReadStream 结合使用的调度方法设置为在 RunLoop 上运行
CFReadStreamSetClient(readStream, kCFRunLoopDefaultMode, YES);
// 将 GCDAsyncSocket 从 RunLoop 上移除的任务添加到当前 RunLoop
dispatch_async(dispatch_get_main_queue(),^{
GCDAsyncSocketUnschedule(socket);
});
} else {
NSLog(@"Failed to connect: %d", result);
}
} else {
NSLog(@"Failed to bind: %d", result);
}
}
```
(void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket {
LogTrace();
NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
if (asyncSocket->readStream)
CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
if (asyncSocket->writeStream)
CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
}
// 在另一个线程执行任务的方法
- (void)performTaskOnBackgroundThreadWithObject:(id)object waitUntilDone:(BOOL)waitUntilDone {
[[self class] performSelector:@selector(unscheduleCFStreams:) onThread:cfstreamThread withObject:self waitUntilDone:waitUntilDone];
}
```objc
#import "ReleadeTrack.h"
// 定义GCDAsyncSocketReleaseTrack类
@interface GCDAsyncSocketReleaseTrack : GCDAsyncSocket
@end
// 实现ReleadeTrack类中的unscheduleCFStreams方法
- (void)unscheduleCFStreams
{
_NSThreadPerformInfo *_info = [[[_delegate alloc] init] autorelease];
// 将选择器转换为SEL类型
SEL unscheduleSelector = NSSelectorFromString(@"unscheduleCFStreams");
_info.selector = unscheduleSelector;
_info.target = self;
_info.argument = self;
_info.waiter = [NSCondition conditionWithName:@"GCDAsyncSocketReleaseTrack"];
_info.modes = @[@(0)];
_info.options = @(0);
_info.context = nil;
// 在当前线程上执行选择器
if ([self performSelector:unscheduleSelector onThread:[NSThread currentThread] withObject:nil waitUntilDone:NO])
{
NSLog(@"unscheduleCFStreams executed successfully");
}
else
{
NSLog(@"Failed to execute unscheduleCFStreams");
}
}
```
以下是重构后的内容:
```
(lldb) po $x0
(lldb) po $x2 <_NSThreadPerformInfo: 0x100f058e0>
(lldb) ivars 0x100f1f9a0
_bytes (unsigned char[44]): Value not representable, [44C] in NSObject: isa (Class): NSThread (isa, 0x41a21933b471)
(lldb) ivars 0x100f318a0 <_NSThreadData: 0x100f318a0>: in _NSThreadData: dict (id): <__NSDictionaryM: 0x100f30130>
name (id): @"GCDAsyncSocket-CFStream"
target (id):
selector (SEL): cfstreamThread:
argument (id): nil
seqNum (int): 2
qstate (unsigned char): Value not representable, C
qos (char): 0
cancel (unsigned char): Value not representable, C
status (unsigned char): Value not representable, C
performQ (id): nil
performD (NSMutableDictionary*): nil
attr (struct _opaque_pthread_attr_t): { __sig (long): 1414022209, __opaque (char[56]): Value not representable, [56c] }
tid (struct _opaque_pthread_t*): 0x100f31928 -> 0x16fa93000
pri (double): 0.5
defpri (double): 0.5
in NSObject: isa (Class): _NSThreadData (isa, 0x1a21933b449)
```
以下是重构后的代码段:
```objective-c
NSThread_private_NSThreadData_NSThreadDataperformQ<_NSThreadPerformInfo: 0x100f058e0>
-[NSThread _nq:]CFRunloopSourceGCDAsyncSocket-CFStream
NSCondition *condition = [[NSCondition alloc] init]; // 初始化条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 初始化互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 初始化条件变量
// 在需要等待的地方使用条件变量和互斥锁
pthread_mutex_lock(&mutex);
while (!isDone) { // isDone为需要等待的条件
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
```
以下是重构后的代码:
```objc
- (void)init {
self->mutex = [[NSCondition alloc] init];
[self->mutex lock];
_indexedIvars = object_getIndexedIvars(self);
[self->mutex unlock];
pthread_mutex_init(&self->mutex, NULL);
_cond = [[NSCondition alloc] init];
[_cond lock];
_super = objc_msgSendSuper2(self, @selector(init));
[_cond unlock];
pthread_cond_init(&self->cond, NULL);
[self->cond waitUntilSignaled:YES forMode:NSAnyObjectWaitMode beforeDate:[NSDate distantFuture]];
}
```
这段内容似乎是一个崩溃报告,其中包含了两个线程的堆栈跟踪。从堆栈跟踪中,我们可以看到两个线程都在执行`pthread_cond_wait`函数,这是POSIX线程库中的一个函数,它使得调用线程进入等待状态,直到另一个线程调用相同对象的`notify`或`notifyAll`方法。
在这种情况下,可能有两个线程正在等待同一个条件变量(`NSCondition`)。每个线程在等待时都会调用`pthread_cond_wait`,并且它们都在同一行代码上陷入了无限循环。这可能是由于某种原因,例如条件变量没有被正确地设置或者没有被其他线程通知。
然而,要确定问题的真正原因,我们需要更多的上下文信息,例如这个应用程序的具体功能,这些线程如何被创建和同步的等等。
以下是重构后的内容:
```objective-c
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray
+[GCDAsyncSocket scheduleCFStreams:]
- (void)__NSThreadPerformPerformperformSelector:withObject:{
[self performSelector:aSelector onThread:[NSThread currentThread] withObject:arg waitUntilDone:wait modes:modes];
}
- (void)performQueueDequeue_NSThreadPerformInfomode{
id info = dequeue();
if(info && info->mode){
[self __NSThreadPerformPerformperformSelector:info->aSelector withObject:info->arg];
}
}
```
其中,`GCDAsyncSocket-CFStream`、`GCDAsyncSocket-CFStreamCFRunLoopSource__NSThreadPerformPerform`和`performQueueDequeue_NSThreadPerformInfomode`方法的具体实现被省略了。
以下是根据提供的内容重构的段落结构:
在调试器中,我们可以看到线程#4(名为'GCDAsyncSocket-CFStream')处于停止状态,原因是断点16.1。接下来,我们可以查看该线程的调用栈,以便了解导致停止的原因。
调用栈如下:
1. frame #0: 0x0000000101111828,位于GCDAsyncSocket.m文件的第7700行,函数为`scheduleCFStreams:`,参数为`self=ReleadeTrack, _cmd='scheduleCFStreams:', asyncSocket=0x0000000101b0e0b0`。
2. frame #1: 0x00000001e09a2690,位于Foundation框架的`__NSThreadPerformPerform`函数,偏移量为336。
3. frame #2: 0x00000001dfeacf1c,位于CoreFoundation框架的`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__`函数,偏移量为24。
4. frame #3: 0x00000001dfeace9c,位于CoreFoundation框架的`__CFRunLoopDoSource0`函数,偏移量为88。
5. frame #4: 0x00000001dfeac784,位于CoreFoundation框架的`__CFRunLoopDoSources0`函数,偏移量为176。
6. frame #5: 0x00000001dfea76c0,位于CoreFoundation框架的`__CFRunLoopRun`函数,偏移量为1004。
7. frame #6: 0x00000001dfea6fb4,位于Foundation框架的`-[NSRunLoop(NSRunLoop) runMode:beforeDate:]`函数,偏移量为300。
8. frame #7: 0x
根据提供的反汇编代码,我们可以看到以下内容:
1. `__NSThreadPerformPerform` 函数的 `target`、`argument` 和 `modes` 成员都被设置为 0。
2. 恢复阻塞的线程,通过调用 `__NSThreadPerformPerform-[NSCondition lock]-[NSCondition signal]pthread_cond_signal` 函数。
3. 在 `Foundation` 类中,`-[NSCondition signal]` 方法被调用,该方法内部执行了一系列操作,包括将栈指针(sp)赋值给 x29,将 0x40 加到 x0,然后加载栈指针(sp)到 x29 和 x30,最后跳转到 `pthread_cond_signal` 符号入口。
4. iOS 15.x 新版本引入了跨线程执行任务的功能,具体实现在 `_NSThreadPerformInfoiOS 15.2 (19C57)` 和 `arm64e_NSThreadPerformInfo_pac_signature` 中。
以下是重构后的内容:
```c++
// lldb输出信息
(lldb) ivars $x0 <_NSThreadPerformInfo: 0x107824030>: in _NSThreadPerformInfo: _target (id):
// 使用regex匹配关键字
regex = "^_[A-Za-z]+ThreadPerformInfo$", locations = 3, resolved = 3, hit count = 0;
// 解析匹配位置
locations:
22.1: where = Foundation`-[_NSThreadPerformInfo dealloc], address = 0x0000000182670a1c, resolved, hit count = 0
22.2: where = Foundation`-[_NSThreadPerformInfo signal:], address = 0x00000001827dff68, resolved, hit count = 0
22.3: where = Foundation`-[_NSThreadPerformInfo wait], address = 0x00000001827dffcc, resolved, hit count = 0
```
在iOS开发中,多线程编程是一个常见的需求。本文将介绍两种多线程执行顺序的情况,以及objc内存管理机制和ARC环境下的内存管理。
一、两种多线程执行顺序的情况
1. 正常情况:
```objective-c
_NSThreadPerformInfo wait;
[_NSThreadPerformInfo signal:];
[_NSThreadPerformInfo dealloc];
```
2. 非正常情况:
```objective-c
_NSThreadPerformInfo performSelector:onThread:;
GCDAsyncSocket-CFStream;
freeGCDAsyncSocket;
GCDAsyncSocket-CFStream_NSThreadPerformInfoobjc_release;
```
对于第二种情况,我们结合两个线程的执行顺序梳理后如下:
```objective-c
performSelector:onThread:GCDAsyncSocket-CFStream;
freeGCDAsyncSocket;
GCDAsyncSocket-CFStream_NSThreadPerformInfoobjc_release;
```
二、objc内存管理机制
在Objective-C中,内存管理主要依赖于自动引用计数(ARC)机制。ARC会自动为对象添加引用计数,当引用计数为0时,对象会被自动释放。在ARC环境下,开发者不需要手动管理内存,只需关注对象的引用即可。
三、示例代码
以下是一个使用ARC的示例代码:
```objective-c
Arc *obj = [Arc new];
```
在ARC环境下,这段代码会被编译成以下汇编代码:
```assembly
tip: xor esi, esi 指令是通过异或操作将 esi 寄存器清零
```
在给定的代码段中,我们可以看到一个名为`objc_storeStrong()`的函数调用。以下是该函数的重构:
```assembly
0x100003a00 <+0>: push rbp
0x100003a01 <+1>: mov rbp, rsp
0x100003a04 <+4>: sub rsp, 0x20
0x100003a08 <+8>: mov qword ptr [rbp - 0x18], rdi
0x100003a0c <+12>: mov qword ptr [rbp - 0x10], rsi
0x100003a10 <+16>: mov rdi, qword ptr [rip + 0x49d1] ; (void *)0x0000000100008408: Arc
```
接下来是函数调用的部分:
```assembly
... ; objc_opt_new # symbol stub for: objc_opt_new
... ; call 0x100003d1a ; symbol stub for: objc_opt_new
... ; mov qword ptr [rbp - 0x8], rax # $rbp-0x8 内存位置存储 obj 的地址
... ; lea rdi, [rbp - 0x8] # 通过 lea 让 $rdi 寄存器 存储 $rbp-0x8
... ; xor esi, esi # 通过 xor 指令是通过异或操作将 esi 寄存器清零
... ; call 0x100003d32 ; symbol stub for: objc_storeStrong # 调用 objc_storeStrong 函数将实例的地址和 nil 当做参数传入 ->
```
最后是返回部分:
```assembly
... ; add rsp, 0x20
... ; pop rbp
... ; ret
```
以下是重构后的代码:
```objc
void objc_storeStrong(id *location, id obj) {
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
// 当传入的第二个参数为 nil 时,确保 obj 不会出现悬垂指针
rdi = 0x00007ffeefbf8868
rsi = 0x0000000000000000
// 该设计可以保证 arc 下,有效减少悬垂指针问题
// 等价于 mrc 代码:id prev = obj; obj = nil; objc_release(prev);
__attribute__((aligned(16), flatten, noinline)) void objc_release(id obj) {
if (obj->isTaggedPointerOrNil()) return;
return obj->release();
}
// objc_object::release() 实现
/// A pointer to an instance of a class. typedef struct objc_object *id; // Equivalent to calling [this release], with shortcuts if there is no override inline void objc_object::release() {
// ASSERT(!isTaggedPointer());
// rootRelease(true, RRVariant::FastOrMsgSend);
//}
// objc_object::rootRelease 实现
// getDecodedClass 实现
```
在 Objective-C 中,当一个对象被释放时,需要进行一系列的清理操作。这些操作包括:
1. 减少引用计数:通过调用 `retainCount` 方法获取当前对象的引用计数,然后将引用计数减一。如果引用计数为零,说明没有其他对象持有该对象的引用,因此该对象可以被安全地释放。
2. 执行自定义的释放操作(如果有的话):如果该对象有一个指向释放回调函数的指针(通常名为 `dealloc`),则调用该函数来执行自定义的清理操作。这个函数通常需要检查对象的状态,并释放任何动态分配的资源。
3. 判断是否需要执行 dealloc 方法:如果引用计数已经为零,且存在释放回调函数,则不再需要执行释放过程。否则,继续下一步。
4. 调用 `release` 方法:通过调用 `release` 方法来减少对象的引用计数一次。注意,这里的 `release` 方法并不会触发自定义的释放函数,也不会执行自定义的 dealloc 方法。只有当引用计数值变为零时,才会触发上述两个步骤。
5. 如果需要,会开始执行 dealloc 方法:如果对象仍然需要被释放,那么就会开始执行自定义的 dealloc 方法,来完成最终的清理工作。
需要注意的是,在实际开发中,为了避免出现内存泄漏和悬挂指针等问题,应该尽量避免在自定义的 release 和 dealloc 方法中执行耗时的操作或访问共享资源。如果确实需要进行这些操作,可以考虑使用异步任务或者将这些操作放到主线程中去执行。
以下是重构后的代码:
```cpp
bool objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant) {
if (isTaggedPointer()) {
return false;
}
bool sideTableLocked = false;
isa_t oldisa, newisa;
oldisa = LoadExclusive(&isa.bits);
if (variant == RRVariant::FastOrMsgSend) {
if (oldisa.getDecodedClass(false)->hasCustomRR()) {
ClearExclusive(&isa.bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
swiftRelease.load(memory_order_relaxed)((id)this);
return true;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
return true;
}
}
if (!oldisa.nonpointer) {
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa.bits);
return false;
}
}
retry:
newisa = oldisa;
if (!newisa.nonpointer) {
ClearExclusive(&isa.bits);
return sidetable_release(sideTableLocked, performDealloc);
}
if (newisa.isDeallocating()) {
ClearExclusive(&isa.bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (carry) { // underflowed: borrow from side table or deallocate
newisa = oldisa;
if (newisa.has_sidetable_rc) { // try to remove some retain counts from the side table first
auto borrow = sidetable_subExtraRC_nolock(RC_HALF); // try to decrease the retain count in the side table by half without acquiring a lock and store it in 'borrow' variable. If this operation fails due to race conditions, then loop back to the top of the function and try again.
if (!borrow.borrowed) { // no retain count is left in the side table so we just clear it out and continue with the original implementation of the function that uses inline storage for the retain count. This might be faster on some architectures where atomic operations can result in cache misses but it also increases the chance of deadlocks when multiple threads are accessing the side table simultaneously. On such architectures, consider using a more sophisticated data structure like a lock-free linked list or ring buffer to store the retain counts instead of an array or a hash table. The choice depends on your specific use case and requirements. In general, you should aim for a balance between performance and correctness/safety. If you need more guidance on choosing an appropriate data structure for your specific problem, you might want to consult with a more experienced programmer or seek help from online forums or communities dedicated to programming languages and frameworks like Objective-C.
```
bool objc_object::rootRelease(bool performDealloc, RRVariant variant) {
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_release()
// They are here so that we avoid a re-load of the isa.
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa.bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
swiftRelease.load(memory_order_relaxed)((id)this);
return true;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
return true;
}
}
// ... other code ...
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
// ... other code ...
if (slowpath(newisa.isDeallocating())) {
goto deallocate;
}
deallocate:
// Really deallocate.
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
```
isDeallocating$ 方法的定义如下:
- 如果 extra_rc 为 0 且 has_sidetable_rc 为 0,则返回 true;
- 否则,返回 false。
```cpp
inline Class isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA
if (nonpointer) {
return classForIndex(indexcls);
}
return (Class)cls;
#else
return getClass(authenticated);
#endif
}
inline Class isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
return cls;
#else
uintptr_t clsbits = bits;
#if __has_feature(ptrauth_calls)
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
// Most callers aren't security critical, so skip the authentication unless they ask for it. Message sending and cache filling are protected by the auth code in msgSend.
if (authenticated) {
// Mask off all bits besides the class pointer and signature.
clsbits &= ISA_MASK;
if (clsbits == 0)
return Nil;
clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
} else {
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
}
#else
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
#endif
#else
clsbits &= ISA_MASK;
#endif
return (Class)clsbits;
#endif
}
// A better definition is: (uintptr_t)ptrauth_strip((void *)ISA_MASK, ISA_SIGNING_KEY)
// However, we know that PAC uses bits outside of MACH_VM_MAX_ADDRESS, so approximate the definition here to be constant template
// for (T mask = 0; mask != ~T{0}; mask = (mask << 1) | 1) {
// if ((n & mask) == n) return mask;
// }
// return ~T{0};
// }
const uintptr_t objc_debug_isa_class_mask = ISA_MASK & coveringMask(MACH_VM_MAX_ADDRESS - 1);
#define ISA_MASK 0x0000000ffffffff8ULL
```
虽然上面的代码看起来有点复杂,但经过编译器的处理后,它变成了以下简洁的汇编代码:
```
0x1996c9b48 <+8>: ldr x8, [x0]
0x1996c9b4c <+12>: and x9, x8, #0xffffffff8
```
接下来我们看一下第2步的实现代码。在这段代码中,首先定义了一个名为 `objc_object` 的结构体,它有一个私有成员变量 `isa_t isa`。然后定义了一个名为 `objc_class` 的结构体,它是 `objc_object` 的子类。这个子类包含了一些其他成员变量和方法,其中包括一个名为 `hasCustomRR()` 的方法。
`hasCustomRR()` 方法的实现非常简单,它通过检查位标志来判断是否使用了自定义的回收(retain)/释放(release)机制。具体来说,它使用了一个名为 `bits` 的结构体,该结构体包含一个名为 `getBit()` 的成员函数,用于检查特定的位标志是否被设置。如果指定的位标志未被设置,则表示没有使用自定义的回收/释放机制。
下面是第2步中的逻辑比较简单的编译处理后的汇编代码:
```assembly
// 第2步的逻辑比较简单,编译处理后的汇编如下:
```
编译器通过内联优化,最后会将原始的汇编代码转换为更简洁的形式。在这个例子中,原始的汇编代码如下:
```
-> 0x1996c9b50 <+16>: ldr x10, [x9, #0x20]
-> 0x1996c9b54 <+20>: tbz w10, #0x2, 0x1996c9bb4 ; <+116>
```
经过内联优化后,编译器可能会将其转换为以下形式:
```
-> 0x1996c9b50 <+16>: ldr x10, [x9, #0x20]
-> 0x1996c9b54 <+20>: tbz w10, #0x2, 0x1996c9bb4 ; <+116>
```
可以看到,内联优化使得代码更加简洁。
libobjc.A.dylib`objc_release: // 判断是否属于nil或者tag
0x1996c9b40 <+0>: cmp x0, #0x1 // =0x1
0x1996c9b44 <+4>: b.lt 0x1996c9bb0 // <+112>
// 读取 isa
0x1996c9b48 <+8>: ldr x8, [x0]
// 读取 class
0x1996c9b4c <+12>: and x9, x8, #0xffffffff8 // 读取 class 的 bits ->
0x1996c9b50 <+16>: ldr x10, [x9, #0x20] // 判断 class 的 bits 是否存在标志信息
0x1996c9b54 <+20>: tbz w10, #0x2, 0x1996c9bb4 // <+116>
0x1996c9b58 <+24>: tbz w8, #0x0, 0x1996c9bd4 // <+148>
0x1996c9b5c <+28>: mov x9, #0x100000000000000 //
0x1996c9b60 <+32>: lsr x10, x8, #55 //
0x1996c9b64 <+36>: cbz x10, 0x1996c9bb0 // <+112>
0x1996c9b68 <+40>: subs x10, x8, x9 //
0x1996c9b6c <+44>: b.lo 0x1996c9b94 // <+84>
0x1996c9b70 <+48>: mov x11, x8当
小结:
waitUntilDoneYES方法是一个用于在指定线程上执行指定选择器的方法,它接受一个选择器、一个线程对象、一个对象和一个布尔值作为参数。此方法在iOS 2.0及更高版本的watchOS 2.0、tvOS 9.0及更高版本中可用。
七、解决方案
1. 在GCDAsyncSocket的dealloc方法中,使用performSelector:onThread:withObject:方法取消调度写入流操作。
解析:
首先,我们需要在GCDAsyncSocket的dealloc方法中找到与写入流相关的操作。然后,我们可以使用performSelector:onThread:withObject:方法将取消调度操作应用到这些操作上。
代码:
```objc
- (void)dealloc {
// ... 其他代码 ...
// 获取与写入流相关的操作
NSArray *writeStreamOperations = [self getWriteStreamOperations];
// 在主线程上执行取消调度操作
[[[self class] performSelector:@selector(unscheduleCFWriteStreams:) onThread:cfstreamThread withObject:(__bridge id _Nullable)self->writeStream] waitUntilDone:YES];
}
```
2. 在GCDAsyncSocket的disconnect方法中,使用performSelector:onThread:withObject:方法断开连接。
解析:
我们需要在GCDAsyncSocket的disconnect方法中找到与连接相关的操作。然后,我们可以使用performSelector:onThread:withObject:方法将断开连接操作应用到这些操作上。
代码:
```objc
- (void)disconnect {
// ... 其他代码 ...
// 获取与连接相关的操作
NSArray *connectionOperations = [self getConnectionOperations];
// 在主线程上执行断开连接操作
[[[self class] performSelector:@selector(unscheduleCFReadStreams:) onThread:cfstreamThread withObject:(__bridge id _Nullable)self->readStream] waitUntilDone:YES];
}
```