mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
docs: 内容
This commit is contained in:
@@ -10,40 +10,45 @@
|
||||
|
||||
访问控制器的View就相当于调用控制器中的view get方法
|
||||
|
||||
```
|
||||
|
||||
-(UIView *)view{
|
||||
```objective-c
|
||||
-(UIView *)view
|
||||
{
|
||||
if(_view == nil){
|
||||
[self loadView];
|
||||
[self viewDidload];
|
||||
|
||||
}
|
||||
return _view;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
# 控制器加载view的流程
|
||||
|
||||

|
||||
|
||||
|
||||
* 控制器的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方法;存在则直接返回,所以界面先是绿色,再是棕色最后是红色
|
||||
|
||||
#### 来一个官方解释
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
**__CFRunLoopModeIsEmpty**
|
||||
|
||||
此函数的作用就是判断这个 Mode 下面有没有 source0、source1、timer,只要存在就说明当前 Mode 不是空的,同时看看这个 Mode 是不是属于当前的 RunLoop
|
||||
|
||||
|
||||
|
||||
|
||||
```objective-c
|
||||
@@ -768,3 +768,341 @@
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
- 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?意义是什么?应用场景
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
```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,线程继续向下运行
|
||||
|
||||

|
||||
|
||||
4. Mach port 如何进行跨线程通信?
|
||||
|
||||
线程开启一个 port,然后给 port 申请接收、发送的权限。mach_msg 是通信函数,在等待消息的时候不加 timeout 则会一直阻塞,等到消息到来
|
||||
|
||||

|
||||
|
||||
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)
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
可以看到 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 被 mach_msg 阻塞,等到消息到来,可以是手动给 RunLoop 的 wakeUpPort 发消息 mach_msg,或者是 timer、Source1。然后继续走到 RunLoop run,不断循环
|
||||
|
||||
Timer 不准确的原因:1. RunLoop 底层对 Timer 实现的精度不高,所以需要指定 Timer 的精度;2. RunLoop 在 DoSource0、DoSource1 的时候耗时较多,则导致 Timer 下次执行会不准确
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 源代码里面一直循环的条件,得来的停止条件
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 自动释放池
|
||||
|
||||
自动释放池什么时候创建和释放
|
||||
|
||||
@@ -32,4 +32,95 @@
|
||||
- https://blog.csdn.net/blog_jihq/article/details/80669616
|
||||
|
||||
|
||||
- RN iOS 插件化开发
|
||||
- RN iOS 插件化开发
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
- 各个组件彼此独立,互不影响。
|
||||
- 组件通过组件管理器(也被叫做 ComponentManager、Router、MediumBus)通信。通过中介者进行通信。
|
||||
- 公共库基础服务基本不变,所以需要下沉到公共组。所以这部分工作可以交给底层架构组到同学去做。
|
||||
- 业务开发专心去做业务开发、业务流程的相关
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
- 左侧最基础的 MVC 架构
|
||||
- 右侧是经过组件化之后的工程目录。 LianJiaClient 里面就是最基础的 AppDelegate,也就是 App 的启动入口。LJComponent 目录下按照彼此独立的功能拆分为多个组件。每个组件按照真实的物理文件夹,划分为多个工程文件夹,每个组件内部按照 MVC 组织,比如 UI、Model、Service、Logic、Connector
|
||||
- 工程文件和物理文件最好一一对应。好理解、好找
|
||||
|
||||
|
||||
|
||||
## 如何实施组件化
|
||||
|
||||
1. 制定代码规范基础服务独立成库
|
||||
|
||||
什么叫公共基础组件?和业务无关的技术功能
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
2. 单个组件内部可以按照合适的架构组织,比如 MVC 和一些分层,比如 service
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
3. 组件之间通信包括2部分:组件之间页面跳转、组件之间服务的调用
|
||||
|
||||
- 页面跳转
|
||||
|
||||
url 导航去中心化。如果集中放到 Router 的导航方法内,则该方法可能会很长(n个组件,每个组件内m个页面,则需要 n*m 个组合)。每个业务组件,内部某个地方集中处理该组件内可能需要用到的注册 url 并返回对应的 vc,把 VC 返回给 ComponentManager,然后决定跳转方式(push、present)
|
||||
|
||||

|
||||
|
||||
- 服务调用
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
4. 进一步优化。动态性
|
||||
|
||||

|
||||
|
||||
ComponentManager 在根据 lianjia://ModuleOverSeaHouseList 去匹配,然后发现有 url 则不跳转本地,直接打开 H5
|
||||
|
||||
5. 服务调用传递参数不方便。NSDictionary 组装很麻烦,可以将公共 Model 下沉,作为一个 Pod
|
||||
|
||||

|
||||
|
||||
6. 组件化架构
|
||||
|
||||

|
||||
|
||||
7. 工程组织方式
|
||||
|
||||

|
||||
|
||||
8. 遇到的问题
|
||||
|
||||

|
||||
|
||||
重复资源问题:图标可以用 iconfont,并且可以控制颜色;或者在打包编译阶段,使用 shell、ruby 脚本去删除重复图片(局限性:只能图片名,对比像素比较麻烦)
|
||||
|
||||
9. 建议
|
||||
|
||||

|
||||
|
||||
10. 总结
|
||||
|
||||

|
||||
|
||||
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
@@ -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 遍历查找,一直到根部 NSObject;3. 动态特性:
|
||||
- 动态方法解析,动态的添加一个方法,在方法列表中新建一个 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: 消息,程序也就崩溃了。
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
1. image: 代码编译后的可执行文件,被加载到内存中,就叫做镜像文件。
|
||||
|
||||
2. MachO 结构:
|
||||
2. MachO 可执行文件被 dyld 加载到内存中,加载时并不是所有的符号都可以确定地址,有些是通过 lazy bind 在真正调用的时候绑定的。
|
||||
|
||||
3. iOS 代码在编译时没有办法确定方法的实现地址。**动态库共享缓存**,里面有动态库。NSLog 属于 Foundation 框架,每个手机内部中的地址不一定。
|
||||
|
||||
@@ -30,11 +30,42 @@
|
||||
| NSLog | 0xaabbcc | |
|
||||
| ... | ... | |
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
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
21
Chapter1 - iOS/1.89.md
Normal 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();
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
字节对齐:k值:8的倍数。
|
||||
|
||||
字节对齐的原因:
|
||||
32
Chapter1 - iOS/1.90.md
Normal file
32
Chapter1 - iOS/1.90.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# YYImage 框架原理,探索图片高效加载原理
|
||||
|
||||
## 图片显示流程
|
||||
|
||||

|
||||
|
||||
```objective-c
|
||||
UIImage *image = [UIImage imageNamed:@"test"];
|
||||
_imageView.image = image;
|
||||
```
|
||||
|
||||
上述的代码叫做“隐形解码”。 代码测试一张图片从磁盘读取到内存中,通过 Instrucments 中的 Time Profiler 分析得到,从磁盘调用 ImageIO 中方法加载到内存中,这个过程比较耗时。
|
||||
|
||||
图片大,则需要更大的空间去将 Data Buffer 计算得到 Image Buffer
|
||||
|
||||
所以将图片解码过程,放到异步线程中去。
|
||||
|
||||
|
||||
|
||||
## YYImage 源码
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
很多框架使用锁都是 pthread_mutex_lock,分析原因
|
||||
|
||||
pthread_mutex_lock
|
||||
|
||||
pthread_mutex_unlock
|
||||
115
Chapter1 - iOS/1.91.md
Normal file
115
Chapter1 - iOS/1.91.md
Normal 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 time:Swift 这部分占优势
|
||||
|
||||
initializer time:load 方法耗时。
|
||||
|
||||
slowest intializers:
|
||||
|
||||
libS
|
||||
|
||||
libMain
|
||||
|
||||
|
||||
|
||||
查看 LinkMap。发现方法展示顺序是按照,写代码的顺序展示的。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 有没有办法将 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
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user