docs: 内容

This commit is contained in:
杭城小刘
2020-11-08 15:51:47 +08:00
parent a744b950b0
commit e89fe0ca1c
27 changed files with 11290 additions and 10341 deletions

View File

@@ -10,40 +10,45 @@
访问控制器的View就相当于调用控制器中的view get方法
```
-(UIView *)view{
```objective-c
-(UIView *)view
{
if(_view == nil){
[self loadView];
[self viewDidload];
}
return _view;
}
```
# 控制器加载view的流程
![控制器加载view的流程](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2287777-b6128646373dfffb.png)
* 控制器的init方法底层会调用initWithNibName方法
* 控制器的 `init` 方法底层会调用 `initWithNibName` 方法
MyViewController *vc = [[MyViewController alloc] init];
`MyViewController *vc = [[MyViewController alloc] init];`
注意点:
* 系统做判断的前提提条件没有指定nibName没有自定义loadView方法控制器以...Controller命名
系统做判断的前提提条件没有指定nibName没有自定义loadView方法控制器以...Controller命名
* 判断原则:
* 1、判断下有没有指定nibName如果指定了就去加载nib
* 2、判断有没有跟控制器同名的xib但是xib的名称不带Controller的xib如果有就去加载
判断原则:
* 判断下有没有指定nibName如果指定了就去加载nib
* 判断有没有跟控制器同名的xib但是xib的名称不带Controller的xib如果有就去加载
* 如果第二步没有指定就判断有没有跟控制器类名同名的xib如果有就去加载
* 如果没有任何xib描述控制器的view就不加载xib
* 3、如果第二步没有指定就判断有没有跟控制器类名同名的xib如果有就去加载
* 4、如果没有任何xib描述控制器的view就不加载xib
## MyViewController加载view的处理
@@ -57,7 +62,7 @@ MyViewController *vc = [[MyViewController alloc] init];
例子
```
```objective-c
//在Appdelegate中
ViewController *vc = [[ViewController alloc] init];
vc.view.backgroundColkor = [UIColor redColor];
@@ -75,22 +80,21 @@ self.window.rootViewController = vc;
-(void)loadView{
UIView*view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
view.backgroundColor = [UIColor greenColor]; self.view = view;
}
-(void)viewDidload{
[super viewDidload];
self.view.backgroundColor = [UIColor brownColor];
self.view.backgroundColor = [UIColor brownColor];
}
```
### 请问此时界面颜色是什么?
可能很多人会回到绿色。其实答案是 红色
why在AppDelegate中vc.view.backgroundColor就是调用vc的view的getter方法在getter方法内部判断_view是否存在不存在则新建一个UIView新建view是通过[self loadView]方法创建创建成功直接调用viewdidload方法存在则直接返回所以界面先是绿色再是棕色最后是红色
why在AppDelegate中vc.view.backgroundColor就是调用vc的view的getter方法在getter方法内部判断_view是否存在不存在则新建一个UIView新建view是通过 `[self loadView]` 方法创建创建成功直接调用viewdidload方法存在则直接返回所以界面先是绿色再是棕色最后是红色
#### 来一个官方解释

View File

@@ -200,7 +200,7 @@
**__CFRunLoopModeIsEmpty**
此函数的作用就是判断这个 Mode 下面有没有 source0、source1、timer只要存在就说明当前 Mode 不是空的,同时看看这个 Mode 是不是属于当前的 RunLoop
```objective-c
@@ -768,3 +768,341 @@
}
```
![image-20200812221231993](/Users/lbp/Desktop/resume/2020-08-12-RunLoopStructure1.png)
- performSelector 是在 source0 上实现的
- RunLoopTimer 外部接口设置的精度精度大于0则使用 dispatch_source_set_timer精度小于0则使用 mk_timer_arm
- timer_source 使用 dispatch_timer_STRICT 创建,则系统会尽最大努力遵守设置的 leeway 值
- NSTimer 不准的原因:底层 RunLoop Timer 底层使用的 timer 的精度不高mk_timer与 RunLoop 底层的调用机制有关系
- 那么为什么存在 RunLoopTimer意义是什么应用场景
![image-20200812125534225](/Users/lbp/Desktop/Github/knowledge-kit/assets/2020-08-12-RunLoopStructure.png)
```c++
// Data structure to hold TSD data, cleanup functions for each
typedef struct __CFTSDTable {
uint32_t destructorCount;
uintptr_t data[CF_TSD_MAX_SLOTS];
tsdDestructor destructors[CF_TSD_MAX_SLOTS];
} __CFTSDTable;
```
```c++
// 主线程 RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
// 局部静态变量
static CFRunLoopRef __main = NULL; // no retain needed
// 创建主线程对应的 RunLoop
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
// 子线程 RunLoop
// 先从 __CFTSDTable 获取 RunLoop如果有则 return没有则调用 _CFRunLoopGet0_CFRunLoopGet0 内部调用 __CFRunLoopCreate 创建 CFRunLoop然后写入 __CFTSDTable
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
// __CFTSDTable
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
// 没有则创建
return _CFRunLoopGet0(pthread_self());
}
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
}
CFRunLoopRef newLoop = NULL;
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
newLoop = __CFRunLoopCreate(t);
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
__CFUnlock(&loopsLock);
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
if (newLoop) { CFRelease(newLoop); }
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
#if _POSIX_THREADS
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
#else
_CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop);
#endif
}
}
return loop;
}
```
- 为什么只有当 RunLoop 中存在 Timer Sourcrs、Input Sources 时,才能保证 RunLoop 不退出?
RunLoop 本质就是一个有条件的 do...while 循环。__CFRunLoopModeIsEmpty 里面去判断 source0、source1、timers 不存在则 while 循环条件不满足RunLoop 退出
```c++
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// expects rl and rlm locked
static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) {
// ...
if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false;
if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false;
if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false;
// ...
return true;
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// ...
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
// ...
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// ...
return result;
}
```
## 线程保活技术:
```objective-c
- (void)viewDidLoad {
[super viewDidLoad];
[self testKeepAliveThread];
}
- (void)testKeepAliveThread
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self.thread = (ThreadKeepLive *)[[NSThread alloc] initWithTarget:self selector:@selector(threadTask) object:nil];
[self.thread setName:@"LongLiveThread"];
[self.thread setQualityOfService:[NSThread mainThread].qualityOfService];
[self.thread start];
});
}
- (void)threadTask
{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"come");
}];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (true) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
```
- come 会打印几次1次因为 RunLoop 跑起来之后,后面的代码不会执行,内部不断的 do...while
- NSRunLoop 常驻线程可以运行在 NSRunLoopCommonModes 下吗?
不可以。因为在跑的时候如果 modeName 等于 kCFRunLoopCommonModes 则直接 kCFRunLoopRunFinished则 RunLoop 的 while 循环条件失败
```objective-c
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (modeName == NULL || modeName == kCFRunLoopCommonModes || CFEqual(modeName, kCFRunLoopCommonModes)) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFLog(kCFLogLevelError, CFSTR("invalid mode '%@' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution."), modeName);
_CFRunLoopError_RunCalledWithInvalidMode();
});
return kCFRunLoopRunFinished;
}
// ...
return result;
}
```
### Mach Port 跨线程通信
1. Mach IPC 基于 Mach 内核实现进程间通讯。
2. Mach IPC 被抽象为3种操作messages、ports、and port sets
3. Mach port 跨线程通信
线程 B 有个 port 在等待消息,具有消息接收权限,等待消息到来的时候会阻塞当前线程
线程 A 有个 port 在发送消息具有发送消息权限要把发送的消息包装成消息通过消息队列传递message 包括header目的地 port、size、data。
线程 B 收到消息后,解除 block线程继续向下运行
![image-20200813024843611](/Users/lbp/Library/Application Support/typora-user-images/image-20200813024843611.png)
4. Mach port 如何进行跨线程通信?
线程开启一个 port然后给 port 申请接收、发送的权限。mach_msg 是通信函数,在等待消息的时候不加 timeout 则会一直阻塞,等到消息到来
![image-20200813093820911](/Users/lbp/Desktop/Github/knowledge-kit/assets/2020-08-13-MachPort.png)
5. 在测试工作中 main.m 文件中打印当前的 RunLoop
```objective-c
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
NSLog(@"%@", NSRunLoop.currentRunLoop);
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
//
2020-08-13 09:49:41.326621+0800 ***[52423:2383402] <CFRunLoop 0x6000015f0000 [0x7fff8062d750]>{wakeup port = 0x1103, stopped = false, ignoreWakeUps = true,
current mode = (none),
common modes = <CFBasicHash 0x6000027f1320 [0x7fff8062d750]>{type = mutable set, count = 1,
entries =>
2 : <CFString 0x7fff80640a20 [0x7fff8062d750]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = (null),
modes = <CFBasicHash 0x6000027f1290 [0x7fff8062d750]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopMode 0x6000012fc0d0 [0x7fff8062d750]>{name = kCFRunLoopDefaultMode, port set = 0x1003, queue = 0x6000007f0100, source = 0x6000007f0280 (not fired), timer port = 0xe03,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
currently 618976181 (257342842892563) / soft deadline in: 1.84464867e+10 sec (@ -1) / hard deadline in: 1.84464867e+10 sec (@ -1)
},
}
}
```
![Mach Port Test Demo](./../assets/2020-08-13-MachPortTest.png)
可以看到 main.m 中,还没有 return 的时候当前 RunLoop 的内部结构中存在一个 **wakeup port** 的端口。查看 RunLoop 源代码 **wakeup port** 就是 mach port 的一种。
```c++
typedef mach_port_t __CFPort;
struct __CFRunLoop {
CFRuntimeBase _base;
_CFRecursiveMutex _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
_CFThreadRef _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
_Atomic(uint8_t) _fromTSD;
CFLock_t _timerTSRLock;
};
```
```c++
void CFRunLoopWakeUp(CFRunLoopRef rl) {
// ...
kern_return_t ret;
/* We unconditionally try to send the message, since we don't want
* to lose a wakeup, but the send may fail if there is already a
* wakeup pending, since the queue length is 1. */
ret = __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0);
// ...
int ret;
do {
ret = eventfd_write(rl->_wakeUpPort, 1);
} while (ret == -1 && errno == EINTR);
// ...
SetEvent(rl->_wakeUpPort);
// ...
}
```
`CFRunLoopWakeUp(CFRunLoopGetCurrent());` 可以唤醒 RunLoop函数的底层实现如上。核心实现就是 `__CFSendTrivialMachMessage` 函数。在iOS 中,除了 source1 可以自己唤醒 RunLoop 之外,其他的事件都需要用户手动唤醒 RunLoop 才可以。RunLoop 提供了专门的方法来实现这个功能。其核心部分就是调用 mach_msg 来向指定的 **_wakeUpPort** 端口发送消息,从而唤醒线程继续工作。
为什么 Source1 可以唤醒 RunLoop因为 Source1 本质上就是针对 Mach Port 的封装
6. 做个实验检测 _wakeUpPort 端口
```objective-c
- (void)viewDidLoad {
[super viewDidLoad];
[self listenWakeUpPort];
}
- (void)listenWakeUpPort
{
NSArray<NSString *> *array = [NSRunLoop.currentRunLoop.description componentsSeparatedByString:@"wakeup port = "];
NSString *wakeupPort = [array.lastObject substringToIndex:[array.lastObject rangeOfString:@","].location];
dispatch_queue_t queue = dispatch_queue_create("com.test.wake_up_port_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, [self numberWithHexString:wakeupPort], 0, queue);
dispatch_source_set_event_handler(source, ^{
mach_port_t port = (mach_port_t)dispatch_source_get_handle(source);
NSLog(@"%u--wakeUp", port);
});
dispatch_activate(source);
}
- (NSInteger)numberWithHexString:(NSString *)hexString
{
const char *hexChar = [hexString cStringUsingEncoding:NSUTF8StringEncoding];
int hexNumber;
sscanf(hexChar, "%x", &hexNumber);
return (NSInteger)hexNumber;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CFRunLoopWakeUp(CFRunLoopGetCurrent());
}
```
可以看到每次在点击屏幕时调用 `CFRunLoopWakeUp` 尝试唤醒 RunLoop然后监听 RunLoop 的 _wakeUpPort都可以在回调中获取到消息。
7. RunLoop lifecycle
![RunLoop lifecycle](./../assets/2020-08-13-RunLoopLifeCycle.png)
休眠时 RunLoop 被 mach_msg 阻塞,等到消息到来,可以是手动给 RunLoop 的 wakeUpPort 发消息 mach_msg或者是 timer、Source1。然后继续走到 RunLoop run不断循环
Timer 不准确的原因1. RunLoop 底层对 Timer 实现的精度不高,所以需要指定 Timer 的精度2. RunLoop 在 DoSource0、DoSource1 的时候耗时较多,则导致 Timer 下次执行会不准确

View File

@@ -105,6 +105,26 @@ UITableView 在滚动的时候一个优化点之一就是 UIImageView 的显示
注意:添加 Observer 是没有效果的。
注意:直接添加的 Mach Port 后线程确实是常驻线程,但是如果需要让线程停止,`[runLoop run]` 方法不能满足。
`[runLoop runMode:NSDefualtRunLoopMode beforeDate:[NSDate distantFuture]];`
设计可以停止的常驻线程
```Objective-C
@property (assign, nonmatioc) BOOL shouldStopRun;
self.shouldStopRun = NO;
- (void) start {
while(!shouldStopRun && [runLoop runMode:NSDefualtRunLoopMode beforeDate:[NSDate distantFuture]]);
}
- (void)removeSourceOrTimer {
self.shouldStopRun = YES;
CFRunLoopStop(CFRunLoopGetCurrent()); // RunLoop 源代码里面一直循环的条件,得来的停止条件
}
```
## 自动释放池
自动释放池什么时候创建和释放

View File

@@ -32,4 +32,95 @@
- https://blog.csdn.net/blog_jihq/article/details/80669616
- RN iOS 插件化开发
- RN iOS 插件化开发
![image-20200816000651547](/Users/lbp/Library/Application Support/typora-user-images/image-20200816000651547.png)
![image-20200816000711707](/Users/lbp/Library/Application Support/typora-user-images/image-20200816000711707.png)
- 各个组件彼此独立,互不影响。
- 组件通过组件管理器(也被叫做 ComponentManager、Router、MediumBus通信。通过中介者进行通信。
- 公共库基础服务基本不变,所以需要下沉到公共组。所以这部分工作可以交给底层架构组到同学去做。
- 业务开发专心去做业务开发、业务流程的相关
![image-20200816004925859](/Users/lbp/Library/Application Support/typora-user-images/image-20200816004925859.png)
- 左侧最基础的 MVC 架构
- 右侧是经过组件化之后的工程目录。 LianJiaClient 里面就是最基础的 AppDelegate也就是 App 的启动入口。LJComponent 目录下按照彼此独立的功能拆分为多个组件。每个组件按照真实的物理文件夹,划分为多个工程文件夹,每个组件内部按照 MVC 组织,比如 UI、Model、Service、Logic、Connector
- 工程文件和物理文件最好一一对应。好理解、好找
## 如何实施组件化
1. 制定代码规范基础服务独立成库
什么叫公共基础组件?和业务无关的技术功能
![image-20200816010644959](/Users/lbp/Library/Application Support/typora-user-images/image-20200816010644959.png)
2. 单个组件内部可以按照合适的架构组织,比如 MVC 和一些分层,比如 service
![image-20200816120154120](/Users/lbp/Library/Application Support/typora-user-images/image-20200816120154120.png)
3. 组件之间通信包括2部分组件之间页面跳转、组件之间服务的调用
- 页面跳转
url 导航去中心化。如果集中放到 Router 的导航方法内则该方法可能会很长n个组件每个组件内m个页面则需要 n*m 个组合)。每个业务组件,内部某个地方集中处理该组件内可能需要用到的注册 url 并返回对应的 vc把 VC 返回给 ComponentManager然后决定跳转方式push、present
![image-20200816120108178](/Users/lbp/Library/Application Support/typora-user-images/image-20200816120108178.png)
- 服务调用
![image-20200816123924669](/Users/lbp/Library/Application Support/typora-user-images/image-20200816123924669.png)
![image-20200816120957323](/Users/lbp/Library/Application Support/typora-user-images/image-20200816120957323.png)
4. 进一步优化。动态性
![image-20200816124854310](/Users/lbp/Library/Application Support/typora-user-images/image-20200816124854310.png)
ComponentManager 在根据 lianjia://ModuleOverSeaHouseList 去匹配,然后发现有 url 则不跳转本地,直接打开 H5
5. 服务调用传递参数不方便。NSDictionary 组装很麻烦,可以将公共 Model 下沉,作为一个 Pod
![image-20200816125008767](/Users/lbp/Library/Application Support/typora-user-images/image-20200816125008767.png)
6. 组件化架构
![image-20200816130556184](/Users/lbp/Library/Application Support/typora-user-images/image-20200816130556184.png)
7. 工程组织方式
![image-20200816130738633](/Users/lbp/Library/Application Support/typora-user-images/image-20200816130738633.png)
8. 遇到的问题
![image-20200816131719018](/Users/lbp/Library/Application Support/typora-user-images/image-20200816131719018.png)
重复资源问题:图标可以用 iconfont并且可以控制颜色或者在打包编译阶段使用 shell、ruby 脚本去删除重复图片(局限性:只能图片名,对比像素比较麻烦)
9. 建议
![image-20200816132840319](/Users/lbp/Library/Application Support/typora-user-images/image-20200816132840319.png)
10. 总结
![image-20200816133001605](/Users/lbp/Library/Application Support/typora-user-images/image-20200816133001605.png)
Protocol我遵循你这个组织的协议则我就可以加入你这个组织比如某个组件遵循协议然后就可以统一调度管理。

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -90,4 +90,28 @@ BClass initialize
TagPointerString 不走消息转发
CFString 走消息转发
CFString 走消息转发
Objective-C 方法调用则先通过对象的 isa 找到类对象,然后根据类对象的 cache_t 查找方法缓存列表,根据 sel mask 去计算 index这个 index 代表当前方法缓存在哈希表中的下标索引。 sel 比较,如果没命中,则继续走 objc_msgSend_uncached 流程。
1ookUpImpOrForward : 1. 当前类对象中的方法列表中遍历方法列表2. 继承链中 superClass 遍历查找,一直到根部 NSObject3. 动态特性:
- 动态方法解析,动态的添加一个方法,在方法列表中新建一个 SEL 和对应的 IMP resolveInstanceMethod、resolveClassMethod
- 重定向 ` - (id)forwardingTargetForSelector:(SEL)aSelector`
```
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
```
如果此方法返回 nil 或者 self则会进入下一步
- 消息重定向:`methodSignatureForSelector` 获取函数的参数和返回值类型
如果 methodSignatureForSelector: 返回了一个 NSMethodSignature 对象函数签名Runtime 系统就会创建一个 NSInvocation 对象,并通过 forwardInvocation: 消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。
如果 methodSignatureForSelector: 返回 nil。则 Runtime 系统会发出 doesNotRecognizeSelector: 消息,程序也就崩溃了。

View File

@@ -2,7 +2,7 @@
1. image: 代码编译后的可执行文件,被加载到内存中,就叫做镜像文件。
2. MachO 结构:
2. MachO 可执行文件被 dyld 加载到内存中,加载时并不是所有的符号都可以确定地址,有些是通过 lazy bind 在真正调用的时候绑定的。
3. iOS 代码在编译时没有办法确定方法的实现地址。**动态库共享缓存**里面有动态库。NSLog 属于 Foundation 框架,每个手机内部中的地址不一定。
@@ -30,11 +30,42 @@
| NSLog | 0xaabbcc | |
| ... | ... | |
![image-20200810190329801](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/image-20200810190329801.png)
![image-20200810201822593](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/mage-20200810201822593.png)
9. fishhook 做的事情就是将系统的符号表,将符号表中的特定符号对应的地址,修改为自定义的函数地址。起到了 hook 作用。也就是说外部的 c 函数,在 iOS 中的调用属于**动态调用**。
https://www.bilibili.com/video/BV1UZ4y1u7Ba?from=search&seid=14997461811427810898
fishhook去 hook c 函数的原理。
假如我们的代码中调用了 NSLog 函数,因为 NSLog 的实现是在 Foundation 库中,动态库在内存中的地址是不固定的, ASLR 机制下,
所以在编译阶段是没办法确定
## fishHook 不能 hook 自定义函数
可执行文件、动态链接库,加载到内存中的时候,会存在多种文件格式,系统为了统一标准,让加载到内存中的文件必须是 Mach-O 文件格式。
Hopper Disassembler v4
- Mach-O 的定义?结构组成
-
fishhook 可以 hook c 函数的原因?
1. 函数符号 数据段 被修改
2. 函数符号为何位于数据段?
3. 动态库每次被加载到内存中,地址都是随机不确定的。所以需要符号地址的修正。符号重定位、重绑定
4. Lazy Symbol Pointers 懒汉模式Non-Lazy Symbol Pointers 启动就去绑定
1. 可以 hook c++吗?为什么
2. linux 平台下能否 hook c/c++
3.

21
Chapter1 - iOS/1.89.md Normal file
View File

@@ -0,0 +1,21 @@
# block 原理
1. 解决循环引用不应该使用 weakself而是使用 strong-weak
```Objective-c
__weak typeof(self) Weakself = self;
self.block = ^ {
__strong typeof(Weakself) Strongself = Weakself;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ {
NSLog(@"%@", Strongself.name);
});
};
self.block();
```
![image-20200810214301251](/Users/lbp/Library/Application Support/typora-user-images/image-20200810214301251.png)
字节对齐k值8的倍数。
字节对齐的原因:

32
Chapter1 - iOS/1.90.md Normal file
View File

@@ -0,0 +1,32 @@
# YYImage 框架原理,探索图片高效加载原理
## 图片显示流程
![image-20200813130942777](/Users/lbp/Desktop/Github/knowledge-kit/assets/2020-08-13-ImageRenderProcess.png)
```objective-c
UIImage *image = [UIImage imageNamed:@"test"];
_imageView.image = image;
```
上述的代码叫做“隐形解码”。 代码测试一张图片从磁盘读取到内存中,通过 Instrucments 中的 Time Profiler 分析得到,从磁盘调用 ImageIO 中方法加载到内存中,这个过程比较耗时。
图片大,则需要更大的空间去将 Data Buffer 计算得到 Image Buffer
所以将图片解码过程,放到异步线程中去。
## YYImage 源码
![image-20200813131944130](/Users/lbp/Desktop/Github/knowledge-kit/assets/2020-08-13-YYImageClassLevel.png)
很多框架使用锁都是 pthread_mutex_lock分析原因
pthread_mutex_lock
pthread_mutex_unlock

115
Chapter1 - iOS/1.91.md Normal file
View File

@@ -0,0 +1,115 @@
# 二进制重排
1. 清吟街社保
2. 社保清单、户口本、身份证带上去做工作信息变更。信息变更为街道
3. 月底入住
身份证、社保清单、失业证明,去办理
## 启动检测
- App 动态库不要超过6个。
- 静态库:影响 Mach-O 文件。
- 动态库:影响 App 启动时间。
## 虚拟内存、物理内存、内存分页
早期计算机内部都是物理内存。物理内存一个问题就是安全问题,通过地址访问到别的应用程序的数据。进程如果能直接访问物理内存无疑是很不安全的,(诞生了解决方案:虚拟内存)所以操作系统在物理内存的上又建立了一层虚拟内存。
一个应用程序在使用的时候并不是全部使用的,往往是使用了一个应用程序的某个或者某几个功能。
所以早期工程师的第一个解决方案是将一个应用程序分块,按需加载功能模块的内存地址数据。
虚拟内存是间接访问了内存条。
内存分页iOS 一页就是16KB。
物理内存如果满了,则将最活跃的内存数据,覆盖掉,最不活跃的内存数据;
ASLR为了安全问题诞生。
自己的代码中有 NSLog 代码,可是 NSLog 函数的代码实现在 Foundation 动态库中。
App 启动则用 dyld 去加载库,共享缓存库。
虚拟地址:偏移是编译后就能确定的。
内存缺页异常:在使用中,访问虚拟内存的一个 page 而对应的物理内存缺不存在(没有被加载到物理内存中),则发生缺页异常。影响耗时,在几毫秒之内。
什么时候发生大量的缺页异常?一个应用程序刚启动的时候。
启动时所需要的代码分布在第一页、第二页、第三页...第200页。这样的情况下启动时间会影响较大所以解决思路就是将应用程序启动刻所需要的代码二进制优化一下统一放到某几页这样就可以避免内存缺页异常则优化了 App 启动时间。
dylib loading time
rebase/binding time 修正符号是由于 ASLR 导致。binding time 是动态库去 bind lazy table、non-lazy table 所占用的时间。rebase 地址偏移ASLR。 rebase 的时间如何缩小Mach-O 文件大小变小。 binding time 变小则需要动态库变小。2者优化手段冲突
Objc setup timeSwift 这部分占优势
initializer timeload 方法耗时。
slowest intializers
libS
libMain
查看 LinkMap。发现方法展示顺序是按照写代码的顺序展示的。
![image-20200814211215976](/Users/lbp/Library/Application Support/typora-user-images/image-20200814211215976.png)
## 有没有办法将 App 启动需要的方法集中收拢?
1. 在 Xcode 的 Build Settings 中设置 **Order File**Write Link Map Files 设置为 YES进行观察
2. 如果你给 Xcode 工程根目录下指定一个 order 文件,比如 `refine.order`,则 Xcode 会按照指定的文件顺序进行二进制数据重排。分析 App 启动阶段,将优先需要加载的函数、方法,集中合并,利用 Order File减小缺页异常从而减小启动时间。
## 如何拿到启动时刻所调用的所有方法名称
clang 插桩,才可以 hook OC、C、block、Swift 全部。LLVM 官方文档。
二进制重排提升 App 启动速度是通过「解决内存缺页异常」(内存缺页会有几毫秒的耗时)来提速的。
一个 App 发生大量「内存缺页」的时机就是 App 刚启动的时候。所以优化手段就是「将影响 App 启动的方法集中处理放到某一页或者某几页」虚拟内存中的页。Xcode 工程允许开发者指定 「Order File」可以「按照文件中的方法顺序去加载」可以查看 linkMap 文件(需要在 Xcode 中的 「Buiild Settings」中设置 Order File、Write Link Map Files 参数)。
其实难点是如何拿到启动时刻所调用的所用方法?代码可能是 Swift、block、c、OC所以hook 肯定不行、fishhook 也不行,用 clang 插桩可行
- https://mp.weixin.qq.com/s/SUHaGD1T2Vce4Ag-qgxtgg

View File

@@ -92,3 +92,5 @@
* [86、GCD 源码探究](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.86.md)
* [87、Objective-C 底层探究](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.87.md)
* [88、fishhook 原理](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.88.md)
* [89、block 原理](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.89.md)
* [90、YYImage 框架原理,探索图片高效加载原理](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.90.md)