mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
feature: App 逆向防护
This commit is contained in:
@@ -219,12 +219,65 @@ NSLog(@"5");
|
||||
|
||||
|
||||
|
||||
Demo8
|
||||
|
||||
```objective-c
|
||||
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
dispatch_sync(queue, ^{
|
||||
NSLog(@"1");
|
||||
});
|
||||
dispatch_async(queue, ^{
|
||||
NSLog(@"2");
|
||||
dispatch_sync(queue, ^{
|
||||
// 这里有没有具体的逻辑都不影响,本质是一个任务 block,区别是空 block 和非空 block。
|
||||
});
|
||||
NSLog(@"4");
|
||||
});
|
||||
dispatch_sync(queue, ^{
|
||||
NSLog(@"5");
|
||||
});
|
||||
// console
|
||||
1
|
||||
2
|
||||
死锁
|
||||
```
|
||||
|
||||
Demo9
|
||||
|
||||
```objective-c
|
||||
dispatch_sync(dispathc_get_main_queue(), ^{
|
||||
NSLog(@"主队列同步");
|
||||
});
|
||||
// 死锁
|
||||
dispatch_sync(dispathc_get_main_queue(), ^{});
|
||||
// 死锁
|
||||
dispatch_sync(dispatch_get_main_queue(), nil);
|
||||
// 死锁
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 总结
|
||||
|
||||
只要是同步提交任务 `dispatch_sync()` 不管是提交到串行队列还是并发队列,都是在当前线程执行。
|
||||
|
||||
|
||||
|
||||
## 一些经典 Demo
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/BlockAndQueue.png" style="zoom:30%" />
|
||||
|
||||
会输出什么?
|
||||
|
||||
打印结果,电脑速度快的话,会有很多次打印出5.慢的话,打印出大于5的几次。
|
||||
|
||||
分析:因为在循环内部,是全局并发队列。多线程的情况下,执行异步任务,任务的先后顺序没办法保证。可能线程1,拿到a=0,然后内部加了1.线程2一开始拿到a=0,但是代码还没执行到a++,在线程1里面,a就已经变为2,因为是 __block 修饰的。所以线程2里面拿到的a变成了a,然后内部a++后,a就是3.其他线程执行情况类似。
|
||||
|
||||
NSLog 属于 IO 流,比普通运算耗时。所以当能执行 NSLog 的时候,a 一定是大于等于5的。某条线程 a 大于等于5之后,就立马结束 while 循环,开始执行最后的 NSLog。
|
||||
|
||||
所以电脑越快,打印5的次数更多。电脑慢的情况下,可能会存在几次输出大于5的情况。
|
||||
|
||||
|
||||
|
||||
## performSelector...withObject 研究
|
||||
@@ -263,7 +316,7 @@ NSLog(@"5");
|
||||
|
||||
Demo1
|
||||
|
||||
<img src="./../assets/RunloopPerformSelector.png" style="zoom:25%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunloopPerformSelector.png" style="zoom:25%" />
|
||||
|
||||
QA:为什么先打印1、3再打印2?因为 `performSelector...withObject...afterDelay` 相当于给 RunLoop 添加了一个 Timer,Timer 运行需要 RunLoop 配合。RunLoop 在被唤醒的时候会处理定时器。
|
||||
|
||||
@@ -271,7 +324,7 @@ Demo2:
|
||||
|
||||
|
||||
|
||||
<img src="./../assets/RunloopPerformSelectorAfterDelay.png" style="zoom:25%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunloopPerformSelectorAfterDelay.png" style="zoom:25%" />
|
||||
|
||||
|
||||
|
||||
@@ -327,7 +380,7 @@ QA:为什么 showLog 里的2没有打印?
|
||||
|
||||
Demo3:
|
||||
|
||||
<img src="./../assets/GCDThreadWillTerminateWhenBlockFinished.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/GCDThreadWillTerminateWhenBlockFinished.png" style="zoom:30%" />
|
||||
|
||||
同理,GCD 虽然开启了子线程,但是 Block 结束后,线程也就结束了。所以线程任务中的1秒后的任务肯定也结束了。
|
||||
|
||||
@@ -335,13 +388,13 @@ Demo3:
|
||||
|
||||
Demo4:
|
||||
|
||||
<img src="./../assets/NSThreadWillTerminateSoCannotUse.png" style="zoom:25%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSThreadWillTerminateSoCannotUse.png" style="zoom:25%" />
|
||||
|
||||
可以看到 NSThread 里的 block 执行结束后,thread 结束了。后面的 performSelector 想在线程里执行任务,就会 crash。
|
||||
|
||||
解决办法也是在线程的 block 里面加 RunLoop,让它保活
|
||||
|
||||
<img src="./../assets/NSThreadWillTerminateSoCanUseViaRunLoopPort.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSThreadWillTerminateSoCanUseViaRunLoopPort.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -550,21 +603,21 @@ si:step instruction,简写为 stepi,si。当你在 Xcode 汇编面板看
|
||||
|
||||
第一步:当第二次调用 saveMoney 方法,开启汇编调试
|
||||
|
||||
<img src="./../assets/OSSpinLock-Assemble2.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OSSpinLock-Assemble2.png" style="zoom:30%" />
|
||||
|
||||
看到可疑方法 `OSSpinLockLock`,给它加断点,看到第10行高亮了。lldb 模式输入 c,敲回车。次数输入 si 即可进入 `OSSpinLockLock` 方法内部调试
|
||||
|
||||
第二步:继续输入 si,敲回车
|
||||
|
||||
<img src="./../assets/OSSpinLock-Assemble3.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OSSpinLock-Assemble3.png" style="zoom:30%" />
|
||||
|
||||
第三步:看到可疑方法 `_OSSpinLockLockSlow`,给它加断点,lldb 输入 C。此时断点到这一行了,继续输入 si。
|
||||
|
||||
<img src="./../assets/OSSpinLock-Assemble4.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OSSpinLock-Assemble4.png" style="zoom:30%" />
|
||||
|
||||
第四步:在 `OSSpinLockLockSlow` 方法内部调试,不断输入 si。
|
||||
|
||||
<img src="./../assets/OSSpinLockAssemble1.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OSSpinLockAssemble1.png" style="zoom:30%" />
|
||||
|
||||
发现不断 si 最终一直会在第6行到第19行之间执行。懂汇编的会发现这其实是一个 while 循环。便可以证明自旋锁 OSSpinLock 在等锁的时候,底层实现是执行 while 循环,忙等,“太浪费性能了”(如果使用锁资源的线程任务很简单,那自旋也是高效的,可以快速获取锁。)
|
||||
|
||||
@@ -643,26 +696,26 @@ int cursorr = 1;
|
||||
|
||||
假如对存钱过程,忘记解锁怎么办?产生死锁,如下
|
||||
|
||||
<img src="./../assets/Thread-deadlock-unfaillock.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Thread-deadlock-unfaillock.png" style="zoom:30%" />
|
||||
|
||||
添加 cursor 标记死锁是发生在 `saveMoney` 方法执行的第几次。发现是第二次。因为第一次锁没有任何使用方,所以加锁成功,当第二次加锁的时候发现锁没有释放,所以产生死锁。
|
||||
|
||||
这时候使用尝试加锁 API `os_unfair_lock_trylock` 即可成功如下
|
||||
|
||||
<img src="./../assets/Thread-deadlock-unfairTrylock.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Thread-deadlock-unfairTrylock.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
#### 汇编窥探原理
|
||||
|
||||
同样方式看看 ,按照上述调试汇编代码的步骤,我将关键步骤截图如下
|
||||
<img src="./../assets/osunfairlock-assemble1.png" style="zoom:30%" />
|
||||
<img src="./../assets/osunfairlock-assemble2.png" style="zoom:30%" />
|
||||
<img src="./../assets/osunfairlock-assemble3.png" style="zoom:30%" />
|
||||
<img src="./../assets/osunfairlock-assemble4.png" style="zoom:30%" />
|
||||
<img src="./../assets/osunfairlock-assemble5.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble1.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble2.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble3.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble4.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble5.png" style="zoom:30%" />
|
||||
|
||||
<img src="./../assets/osunfairlock-assemble6.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/osunfairlock-assemble6.png" style="zoom:30%" />
|
||||
|
||||
结论:可以看到 `os_unfair_lock` 在锁等待的时候,底层调用的是 `sysCall`,当这一步执行后会发现后续代码都不执行了,也就是调用系统底层能力,线程真正休眠了,而不是一个循环忙等,性能也好。
|
||||
|
||||
@@ -711,7 +764,7 @@ pthread_mutex_destroy(&_moneyLock);
|
||||
|
||||
使用如下
|
||||
|
||||
<img src="./../assets/PThreadMutextLock.png" style="zoom:25%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutextLock.png" style="zoom:25%" />
|
||||
|
||||
#### 递归锁
|
||||
|
||||
@@ -757,7 +810,7 @@ pthread_mutex_destroy(&_moneyLock);
|
||||
|
||||
改进后的效果如下
|
||||
|
||||
<img src="./../assets/PThreadMutextRecursiveLock.png" style="zoom:25%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutextRecursiveLock.png" style="zoom:25%" />
|
||||
|
||||
|
||||
|
||||
@@ -769,33 +822,33 @@ QA:互斥递归锁,可以在不同线程中加锁吗?
|
||||
|
||||
#### 汇编窥探原理
|
||||
|
||||
<img src="./../assets/PThreadMutexLock1.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock1.png" style="zoom:30%" />
|
||||
|
||||
输入 si 继续跟进,可以看到还是在执行我们自己的代码,LockExplore image 的 `pthread_mutex_lock` 方法
|
||||
|
||||
<img src="./../assets/PThreadMutexLock2.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock2.png" style="zoom:30%" />
|
||||
|
||||
继续输入 si 跟进
|
||||
|
||||
<img src="./../assets/PThreadMutexLock3.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock3.png" style="zoom:30%" />
|
||||
|
||||
可以看到此时调用到系统 `libsystem_pthread.dylib` 库的 `pthread_mutex_lock` 方法了。
|
||||
|
||||
第41行看到关键函数,继续输入 si 进去看看
|
||||
|
||||
<img src="./../assets/PThreadMutexLock4.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock4.png" style="zoom:30%" />
|
||||
|
||||
可以看到内部第62行关键函数调用了 `_pthread_mutex_firstfit_lock_wait` 方法。此时继续输入 si 跟踪看看
|
||||
|
||||
<img src="./../assets/PThreadMutexLock5.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock5.png" style="zoom:30%" />
|
||||
|
||||
可以看到内部第25行调用了关键函数 `__psynch_mutexwait`,继续输入 si 看看
|
||||
|
||||
<img src="./../assets/PThreadMutexLock6.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock6.png" style="zoom:30%" />
|
||||
|
||||
可以看到内部继续调用了系统 `libsystem_pthread.dylib` 库的 `__psynch_mutexwait` 方法。继续输入 si
|
||||
|
||||
<img src="./../assets/PThreadMutexLock7.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadMutexLock7.png" style="zoom:30%" />
|
||||
|
||||
可以看到内部第4行发生了系统调用 `sysCall`,执行完第四句指令,线程立马就结束了。
|
||||
|
||||
@@ -815,7 +868,7 @@ QA:互斥递归锁,可以在不同线程中加锁吗?
|
||||
|
||||
激活所有等待该条件的线程 `pthread_cond_broadcast(&_condition)`
|
||||
|
||||
<img src="./../assets/PThreadConditionLock.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadConditionLock.png" style="zoom:30%" />
|
||||
|
||||
可以看到同时调用 remove、add 方法
|
||||
|
||||
@@ -917,13 +970,13 @@ NSRecursiveLock 不能在多线程下递归调用。@synchronized 可以在多
|
||||
|
||||
Demo
|
||||
|
||||
<img src="./../assets/NSLockDemo.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSLockDemo.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
NSLock 死锁
|
||||
|
||||
<img src="./../assets/NSLockDeadLock.png" style="zoom:40%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSLockDeadLock.png" style="zoom:40%" />
|
||||
|
||||
会发生死锁,后续代码无法执行,App 表现就是 ANR。重复对 NSLock 进行加锁可能导致死锁问题,同时也可能引发数据竞争和性能下降等并发相关隐患
|
||||
|
||||
@@ -973,7 +1026,7 @@ API
|
||||
|
||||
Demo:
|
||||
|
||||
<img src="./../assets/NSConditionLockDemo.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSConditionLockDemo.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -981,13 +1034,13 @@ Demo:
|
||||
|
||||
使用 NSCondition 的时候 unlock 和 signal 的顺序可能会对结果造成影响。举个例子
|
||||
|
||||
<img src="./../assets/NSConditionOrder1.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSConditionOrder1.png" style="zoom:30%" />
|
||||
|
||||
可以看到在这种情况下,由于 NSCondition 另一个地方 wait,wait 也需要释放锁,但是另一个发 signal 的地方,还没释放锁。所以会等待2s。
|
||||
|
||||
针对这个情况,可以将 unlock 和 signal 的顺序进行调整。
|
||||
|
||||
<img src="./../assets/NSConditionOrder2.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSConditionOrder2.png" style="zoom:30%" />
|
||||
|
||||
先解锁,然后发送 singal,后续其他的业务逻辑也不影响。当然这个需要针对实际代码进行设计。
|
||||
|
||||
@@ -1030,7 +1083,7 @@ API 如下:
|
||||
|
||||
Demo
|
||||
|
||||
<img src="./../assets/NSConditionLockDemo1.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSConditionLockDemo1.png" style="zoom:30%" />
|
||||
|
||||
分析:虽然通过3个线程,设置了线程的先后顺序,但是多线程任务执行的时候到底谁先执行,是没办法控制的。但是通过 `NSConditionLock lockWhenCondition:*` 的能力,可以控制线程的执行顺序。
|
||||
|
||||
@@ -1046,7 +1099,7 @@ Demo
|
||||
|
||||
线程同步的本质就是多线程的任务是顺序执行
|
||||
|
||||
<img src="./../assets/SerialQueueToSolveThreadSyn.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/SerialQueueToSolveThreadSyn.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -1060,11 +1113,11 @@ semaphore 叫做”信号量”
|
||||
|
||||
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
|
||||
|
||||
<img src="./../assets/DispatchSemaphoreControlThreadCount1.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchSemaphoreControlThreadCount1.png" style="zoom:30%" />
|
||||
|
||||
可以看到打印了20个线程,但是我们控制线程最大数量怎么办呢?可以用信号量实现。效果如下:
|
||||
|
||||
<img src="./../assets/DispatchSemaphoreControlThreadCount2.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchSemaphoreControlThreadCount2.png" style="zoom:30%" />
|
||||
|
||||
`dispatch_semaphore_wait` 函数的本质
|
||||
|
||||
@@ -1076,7 +1129,7 @@ semaphore 叫做”信号量”
|
||||
|
||||
所以如何让线程同步?设置信号量的值=1即可。保证同一时间只有一个线程任务在执行。代码如下
|
||||
|
||||
<img src="./../assets/SemaphoreMethodToControlThreadSync.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/SemaphoreMethodToControlThreadSync.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -1136,15 +1189,15 @@ dispatch_semaphore_signal(semaphore);
|
||||
|
||||
`@synchronized` 使用很方便,它是对 `pthread_mutex_t` 递归锁的封装。Demo 如下
|
||||
|
||||
<img src="./../assets/SynchronizedControlThreadSync.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/SynchronizedControlThreadSync.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
为了探究下实现,开启汇编调试
|
||||
|
||||
<img src="./../assets/synchronized-asemble1.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/synchronized-asemble1.png" style="zoom:30%" />
|
||||
|
||||
<img src="./../assets/synchronized-asemble2.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/synchronized-asemble2.png" style="zoom:30%" />
|
||||
|
||||
通过汇编可以看到 `@synchronized` 底层调用了 `objc_sync_enter` 方法,其中又调用了 `id2data` 和 `os_unfair_recursive_lock_lock_with_options` 方法。 可以查看 objc4 的源码(笔者的 objc 版本为 objc4-objc4-912.3),查找 `objc_sync_enter`
|
||||
|
||||
@@ -1620,7 +1673,7 @@ pthread_rwlock_init(&_lock, NULL)
|
||||
|
||||
Demo
|
||||
|
||||
<img src="./../assets/PThreadRWLockDemo.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PThreadRWLockDemo.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -1648,7 +1701,7 @@ dispatch_barrier_async(self.queue, ^{
|
||||
|
||||
上 Demo
|
||||
|
||||
<img src="./../assets/DispatchBarrierReadWriteDemo.png" style="zoom:30%" />
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchBarrierReadWriteDemo.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user