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)
|
||||
|
||||
@@ -1,13 +1,31 @@
|
||||
# Electron:PC 端多端融合方案
|
||||
|
||||
> 每天都要写第二天的 todoList。有一天在写的时候突然想到,为了让自己清楚知道自己需要做啥、做了多少、还剩多少没做,想写一个电脑端程序,在技术选型的时候就选了 electron。
|
||||
> 每天都要写第二天的 todoList。有一天在写的时候突然想到,为了让自己清楚知道自己需要做啥、做了多少、还剩多少没做,想写一个电脑端程序,在技术选型的时候就选了 Electron。
|
||||
>
|
||||
> 本篇文章的目的不是讲解 API 如何使用,想知道这些可以直接看[官方文档](https://www.electronjs.org/docs)。本文目的旨在讲明如何技术如何选择、如何快速上手、如何调试、Electron 底层原理、工程体系方面的总结。
|
||||
> 本篇文章的目的不是讲解 API 如何使用,想知道这些可以直接看[官方文档](https://www.Electronjs.org/docs)。本文目的旨在讲明如何技术如何选择、如何快速上手、如何调试、Electron 底层原理、工程体系方面的总结。
|
||||
|
||||
|
||||
|
||||
## 一、浅谈 GUI 系统
|
||||
|
||||
```shell
|
||||
浏览器是如何将布局数据计算为像素数据的,你能实现出原理类似的渲染器吗?
|
||||
浏览器在各个平台上的文字排版渲染结果是否一致,你能解释原因吗?
|
||||
你所负责的前端应用,其渲染性能还有多大的提升空间,你能量化地证明吗?
|
||||
你能设计实现出类似 RN 和小程序那样的 Hybrid 方案吗?
|
||||
你能自己控制 GPU 渲染管线,实现渲染的硬件加速吗?
|
||||
```
|
||||
|
||||
GUI 起源:从 1979 年乔布斯造访施乐 PARC 算起
|
||||
|
||||
GUI 架构:过程化绘制(drawLine、drawRect)-> 面向对象抽象时代 -> 界面与样式分离时代 -> MVC、MVVM 时代 -> 声明式、组件式时代(Vue、React、RN、Weex、Flutter)
|
||||
|
||||
我们可以看到不变的是:随着计算机科学技术的发展,为了实现某个效果,一流程序员或者组织不断研发各种技术框架,来提高开发效率和效果。Electron 就是这条历史长河中诞生的 PC 端技术框架之一。
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、 方案选型
|
||||
## 二、 技术选型
|
||||
|
||||
3天时间写了个 PC 端应用程序。先看看结果吧
|
||||
|
||||
@@ -17,33 +35,36 @@
|
||||
|
||||

|
||||
|
||||
为什么要选 electron 作为 pc 端开发方案?
|
||||
|
||||
|
||||
**为什么要选 Electron 作为 pc 端开发方案?**
|
||||
|
||||
史前时代,以 MFC 为代表的技术栈,开发效率较低,维护成本高。
|
||||
后来使用 QT 技术,特点是使用 DirectUI + 面向对象 + XML 定义 UI,适用于小型软件、性能要求、包大小、UI 复杂度叫高的需求。
|
||||
再到后来,以 QT Quick 为代表的技术,特点是框架本身提供子控件,基于子控件组合来创建新的控件。类似于 ActionScript 的脚本化界面逻辑代码。
|
||||
新时代主要是以 [electron](https://electronjs.org) 和 [Cef](https://bitbucket.org/chromiumembedded/cef) 为 代表。特点是界面开发以 Web 技术为主,部分逻辑需要 Native 代码实现。大家都熟悉的 VS Code 就是使用 electron 开发的。适用于 UI 变化较多、体积限制不大、开发效率高的场景。
|
||||
新时代主要是以 [Electron](https://Electronjs.org) 和 [Cef](https://bitbucket.org/chromiumembedded/cef) 为 代表。特点是界面开发以 Web 技术为主,部分逻辑需要 Native 代码实现。大家都熟悉的 VS Code 就是使用 Electron 开发的。适用于 UI 变化较多、体积限制不大、开发效率高的场景。
|
||||
|
||||
拿 C 系列写应用程序的体验不好,累到奔溃。再加上有 Hybrid、React Native、iOS、Vue、React 等开发经验,electron 是不二选择。
|
||||
拿 C 系列写应用程序的体验不好,累到奔溃。再加上有 Hybrid、React Native、iOS、Vue、React 等开发经验,Electron 是不二选择。
|
||||
|
||||
|
||||
|
||||
|
||||
## 二、 Quick start
|
||||
## 三、 Quick start
|
||||
执行下面命令快速体验 Hello world,也是官方给的一个 Demo。
|
||||
```shell
|
||||
git clone https://github.com/electron/electron-quick-start
|
||||
cd electron-quick-start
|
||||
git clone https://github.com/Electron/Electron-quick-start
|
||||
cd Electron-quick-start
|
||||
npm install && npm start
|
||||
```
|
||||
|
||||
简单介绍下 Demo 工程,工程目录如下所示
|
||||

|
||||

|
||||
|
||||
在终端执行 `npm start` 执行的是 package.json 中的 `scripts` 节点下的 start 命令,也就是 `electron .`,`.` 代表执行 main.js 中的逻辑。
|
||||
在终端执行 `npm start` 执行的是 package.json 中的 `scripts` 节点下的 start 命令,也就是 `Electron .`,`.` 代表执行 main.js 中的逻辑。
|
||||
|
||||
```Javascript
|
||||
// Modules to control application life and create native browser window
|
||||
const {app, BrowserWindow} = require('electron')
|
||||
const {app, BrowserWindow} = require('Electron')
|
||||
const path = require('path')
|
||||
|
||||
function createWindow () {
|
||||
@@ -84,13 +105,13 @@ app.on('activate', function () {
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and require them here.
|
||||
```
|
||||
写过 Vue、React、Native 的人看代码很容易,因为应用程序的生命周期钩子函数是很重要的,开发者根据需求在钩子函数里面做相应的视图创建、初始化、销毁对象等等。比如 electron 中的 activate、window-all-closed 等。
|
||||
写过 Vue、React、Native 的人看代码很容易。比如应用程序的生命周期钩子函数对开发者很重要,也是一个标准的做法,根据需求在钩子函数里面做相应的视图创建、初始化、销毁对象等等。比如 Electron 中的 `activate`、`window-all-closed` 等。
|
||||
|
||||
app 对象在 `whenReady` 的时候执行 createWindow 方法。内部创建了一个 `BrowserWindow` 对象,指定了大小和功能设置。
|
||||
app 对象在 `whenReady` 的时候执行 `createWindow` 方法。内部创建了一个 `BrowserWindow` 对象,指定了大小和功能设置。
|
||||
|
||||
1. webPreferences Object (可选) - 网页功能的设置。
|
||||
1. webPreferences:Object (可选) - 网页功能的设置。
|
||||
|
||||
2. preload String (可选) - 在页面运行其他脚本之前预先加载指定的脚本 无论页面是否集成 Node, 此脚本都可以访问所有 Node API 脚本路径为文件的绝对路径。 当 node integration 关闭时, 预加载的脚本将从全局范围重新引入 node 的全局引用标志。
|
||||
2. preload: String (可选) - 在页面运行其他脚本之前预先加载指定的脚本。无论页面是否集成 Node, 此脚本都可以访问所有 Node API 脚本路径为文件的绝对路径。 当 node `integration` 关闭时, 预加载的脚本将从全局范围重新引入 node 的全局引用标志。
|
||||
|
||||
`mainWindow.loadFile('index.html')` 加载了同级目录下的 index.html 文件。也可以加载服务器资源(部署好的网页),比如 `win.loadURL('https://github.com/FantasticLBP')`
|
||||
|
||||
@@ -108,25 +129,25 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
console.table(process)
|
||||
console.info(process.versions)
|
||||
for (const type of ['chrome', 'node', 'electron']) {
|
||||
for (const type of ['chrome', 'node', 'Electron']) {
|
||||
replaceText(`${type}-version`, process.versions[type])
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
在页面运行其他脚本之前预先加载指定的脚本,无论页面是否集成 Node, 此脚本都可以访问所有 Node API 脚本路径为文件的绝对路径。Demo 中的逻辑很简单,就是读取 `process.versions` 对象中的 node、chrome、electron 的版本信息并展示出来。
|
||||
在页面运行其他脚本之前预先加载指定的脚本,无论页面是否集成 Node, 此脚本都可以访问所有 Node API 脚本路径为文件的绝对路径。Demo 中的逻辑很简单,就是读取 `process.versions` 对象中的 node、chrome、Electron 的版本信息并展示出来。
|
||||
|
||||
`index.html` 中的内容就是主页面显示的内容。一般不会直接写 html、css、js,都会根据技术背景选择前端框架,比如 Vue、React 等,或者模版引擎 ejs 等。
|
||||
`index.html` 中的内容就是主页面显示的内容。一般不会直接写 html、css、js,都会根据技术背景选择前端框架,比如 Vue、React 等,或者模版引擎 [ejs](https://ejs.co) 等。
|
||||
|
||||
|
||||
|
||||
|
||||
## 三、 实现原理
|
||||
## 四、 实现原理
|
||||
|
||||
electron 分为**渲染进程和主进程**。和 Native 中的概念不一样的是 electron 中主进程只有一个,渲染进程(也就是 UI 进程) 有多个。主进程在后台运行,每次打开一个界面,会新开一个新的渲染进程。
|
||||
Electron 分为**渲染进程和主进程**。和 Native 中的概念不一样的是 Electron 中主进程只有一个,渲染进程(也就是 UI 进程) 有多个。主进程在后台运行,每次打开一个界面,会新开一个新的渲染进程。
|
||||
|
||||
- 渲染进程: 用户看到的 web 界面就是由渲染进程绘制出来的,包括 html、css、js。
|
||||
- 主进程:electron 运行 package.json 中的 main.js 脚本的进程被称为主进程。在主进程中运行的脚本通过创建 web 页面来展示用户界面。一个 electron 应用程序总是只有一个主进程。
|
||||
- 主进程:Electron 运行 package.json 中的 main.js 脚本的进程被称为主进程。在主进程中运行的脚本通过创建 web 页面来展示用户界面。一个 Electron 应用程序总是只有一个主进程。
|
||||
|
||||
|
||||
|
||||
@@ -343,7 +364,11 @@ Electron 架构和 Chromium 架构类似,也是具有1个主进程和多个渲
|
||||
|
||||
|
||||
|
||||
## 四、 如何调试
|
||||
|
||||
|
||||
|
||||
|
||||
## 五、 如何调试
|
||||
|
||||
调试分为主进程调试和渲染进程调试。
|
||||
|
||||
@@ -370,12 +395,12 @@ Electron 架构和 Chromium 架构类似,也是具有1个主进程和多个渲
|
||||
```
|
||||
- 然后打开浏览器,在地址栏输入 `chrome://inspect`
|
||||
- 点击 `configure`,在弹出的面板中填写需要调试的端口信息
|
||||

|
||||

|
||||
- 重新开启服务 `npm start`,在 chrome inspect 面板的 `Target` 节点中选择需要调试的页面
|
||||
- 在面板中可以看到主进程执行的 `main.js`。可以加断点进行调试
|
||||

|
||||

|
||||
|
||||
方法二:利用 VS Code 调试 electron 主进程。
|
||||
方法二:利用 VS Code 调试 Electron 主进程。
|
||||
|
||||
- 在 VS Code 的左侧菜单栏,第四个功能模块就是调试,点击调试,弹出对话框让你添加调试配置文件 `launch.json`
|
||||
|
||||
@@ -390,9 +415,9 @@ Electron 架构和 Chromium 架构类似,也是具有1个主进程和多个渲
|
||||
"request": "launch",
|
||||
"name": "Debug main process",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/Electron",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/Electron.cmd"
|
||||
},
|
||||
"args": ["."],
|
||||
"outputCapture": "std"
|
||||
@@ -417,7 +442,7 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
|
||||
|
||||
|
||||
|
||||
## 五、 开发 tips及其优化手段
|
||||
## 六、 开发 tips及其优化手段
|
||||
|
||||
### 1. 开发 tips
|
||||
|
||||
@@ -431,14 +456,14 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
|
||||
Refused to execute inline event handler because it violates the following Content Security Policy directive:
|
||||
```
|
||||
|
||||
4. 利用 electron 进行开发的时候,可以看成是 NodeJS + chromium + Web 前端开发技术。NodeJS 拥有文件访问等后端能力,chromium 提供展示功能,以及网络能力(electron 网络能力不是 NodeJS 提供的,而是 chromium 的 net 模块提供的)。web 前端开发技术方案都可以应用在 electron 中,比如 Vue、React、Bootstrap、sass 等。
|
||||
4. 利用 Electron 进行开发的时候,可以看成是 NodeJS + chromium + Web 前端开发技术。NodeJS 拥有文件访问等后端能力,chromium 提供展示功能,以及网络能力(Electron 网络能力不是 NodeJS 提供的,而是 chromium 的 net 模块提供的)。web 前端开发技术方案都可以应用在 Electron 中,比如 Vue、React、Bootstrap、sass 等。
|
||||
|
||||
5. 在工程化角度看,使用 yarn 比 npm 好一些,因为 yarn 会缓存已经安装过的依赖,其他项目只要发现存在缓存,则读取本地的包依赖,会更加快速。
|
||||
|
||||
6. 在使用 Vue、React 开发 electron 应用时,可以使用 npm 或 yarn install 包,也可以使用 electron-vue 脚手架工具。
|
||||
6. 在使用 Vue、React 开发 Electron 应用时,可以使用 npm 或 yarn install 包,也可以使用 Electron-vue 脚手架工具。
|
||||
|
||||
```shell
|
||||
vue init simulatedgreg/electron-vue my-project
|
||||
vue init simulatedgreg/Electron-vue my-project
|
||||
cd my-project
|
||||
npm install
|
||||
npm run dev
|
||||
@@ -461,9 +486,9 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
|
||||
```
|
||||
解决办法是在 main/index.dev.js 修改代码
|
||||
```javascript
|
||||
- require('electron-debug')({ showDevTools: true });
|
||||
- require('Electron-debug')({ showDevTools: true });
|
||||
+ // NB: Don't open dev tools with this, it is causing the error
|
||||
+ require('electron-debug')();
|
||||
+ require('Electron-debug')();
|
||||
```
|
||||
在 In main/index.js in the createWindow() function:
|
||||
```javascript
|
||||
@@ -490,7 +515,7 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
|
||||
|
||||
12. Electron 和 Web 开发相比,各自有侧重点
|
||||
|
||||

|
||||

|
||||
|
||||
### 2. 优化手段
|
||||
|
||||
@@ -529,7 +554,7 @@ node --cpu-prof --heap-prof -e "require('request’)”“
|
||||
|
||||
|
||||
|
||||
## 六、 技术体系搭建
|
||||
## 七、 技术体系搭建:全景
|
||||
|
||||
其实一个技术本身的难易程度并不是能否在自己企业、公司、团队内顺利使用的唯一标尺,其配套的 CI/CD、APM、埋点系统、发布更新、灰度测试等能否与现有的系统以较小成本融合才是很大的决定要素。因为某个技术并不是非常难,要是大多数开发者觉得很难,那它设计上就是失败的。
|
||||
|
||||
@@ -551,10 +576,234 @@ Electron 提供的 crash 信息进行包装。
|
||||
|
||||

|
||||
|
||||
```js
|
||||
import { BrowserWindow, app, dialog} from 'Electron';
|
||||
|
||||
|
||||
const mainWindow = BrowserWindow.fromId(global.mainId);
|
||||
mainWindow.webContents.on('crashed',
|
||||
const options = {
|
||||
type: 'error',
|
||||
title: '进程崩溃了',
|
||||
message: '这个进程已经崩溃.',
|
||||
buttons: ['重载', '退出'],
|
||||
};
|
||||
recordCrash().then(() => {
|
||||
dialog.showMessageBox(options, (index) => {
|
||||
if (index === 0) reloadWindow(mainWindow);
|
||||
else app.quit();
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.log('err', e);
|
||||
});
|
||||
})
|
||||
|
||||
function recordCrash() {
|
||||
return new Promise(resolve => {
|
||||
// 崩溃日志请求成功....
|
||||
resolve();
|
||||
})
|
||||
}
|
||||
|
||||
function reloadWindow(mainWin) {
|
||||
if (mainWin.isDestroyed()) {
|
||||
app.relaunch();
|
||||
app.exit(0);
|
||||
} else {
|
||||
BrowserWindow.getAllWindows().forEach((w) => {
|
||||
if (w.id !== mainWin.id) w.destroy();
|
||||
});
|
||||
mainWin.reload();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
// index.js
|
||||
app.on('will-finish-launching', () => {
|
||||
if(!isDev) {
|
||||
require('./updater.js')
|
||||
}
|
||||
require('./crash-reporter').init()
|
||||
})
|
||||
|
||||
// crash-reporter.js
|
||||
const {crashReporter} = require('Electron')
|
||||
|
||||
function init() {
|
||||
crashReporter.start({
|
||||
productName: 'ZanPandora',
|
||||
companyName: 'youzan',
|
||||
submitURL: 'http://127.0.0.1:33855/crash',
|
||||
|
||||
})
|
||||
}
|
||||
module.exports = {init}
|
||||
|
||||
// server
|
||||
const Koa = require('koa')
|
||||
const app = new Koa()
|
||||
const Router = require('koa-router')
|
||||
const serve = require('koa-static-server')
|
||||
const router = new Router()
|
||||
const compareVersions = require('compare-versions')
|
||||
const multer = require('koa-multer')
|
||||
const uploadCrash = multer({dest: 'crash/'})
|
||||
router.post('/crash', uploadCrash.single('upload_file_minidump'), (ctx, next) => {
|
||||
console.log(ctx.req.body)
|
||||
// 存DB
|
||||
})
|
||||
```
|
||||
|
||||
### 4. 软件更新
|
||||
|
||||
```javascript
|
||||
// package.json
|
||||
"dependencies": {
|
||||
"Electron-is-dev": "^1.1.0",
|
||||
"Electron-squirrel-startup": "^1.0.0",
|
||||
// ...
|
||||
},
|
||||
|
||||
// index.js
|
||||
app.on('will-finish-launching', () => {
|
||||
if(!isDev) {
|
||||
require('./updater.js')
|
||||
}
|
||||
require('./crash-reporter').init()
|
||||
})
|
||||
|
||||
// updater.js
|
||||
const {autoUpdater, app, dialog} = require('Electron')
|
||||
if(process.platform == 'darwin') {
|
||||
autoUpdater.setFeedURL('http://127.0.0.1:33855/darwin?version=' + app.getVersion())
|
||||
} else {
|
||||
autoUpdater.setFeedURL('http://127.0.0.1:33855/win32?version=' + app.getVersion())
|
||||
}
|
||||
|
||||
autoUpdater.checkForUpdates() // 定时轮训、服务端推送
|
||||
autoUpdater.on('update-available', () => {
|
||||
console.log('update-available')
|
||||
})
|
||||
|
||||
autoUpdater.on('update-downloaded', (e, notes, version) => {
|
||||
// 提醒用户更新
|
||||
app.whenReady().then(() => {
|
||||
let clickId = dialog.showMessageBoxSync({
|
||||
type: 'info',
|
||||
title: '升级提示',
|
||||
message: '已为你升级到最新版,是否立即体验',
|
||||
buttons: ['马上升级', '手动重启'],
|
||||
cancelId: 1,
|
||||
})
|
||||
if(clickId === 0) {
|
||||
autoUpdater.quitAndInstall()
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
autoUpdater.on('error', (err) => {
|
||||
console.log('error', err)
|
||||
})
|
||||
|
||||
// server
|
||||
// index.js
|
||||
const Koa = require('koa')
|
||||
const app = new Koa()
|
||||
const Router = require('koa-router')
|
||||
const serve = require('koa-static-server')
|
||||
const router = new Router()
|
||||
const compareVersions = require('compare-versions')
|
||||
const multer = require('koa-multer')
|
||||
const uploadCrash = multer({dest: 'crash/'})
|
||||
router.post('/crash', uploadCrash.single('upload_file_minidump'), (ctx, next) => {
|
||||
console.log(ctx.req.body)
|
||||
// 存DB
|
||||
})
|
||||
function getNewVersion(version) {
|
||||
if(!version) return null
|
||||
let maxVersion = {
|
||||
name: '1.0.1',
|
||||
pub_date: '2020-02-01T12:26:53+1:00',
|
||||
notes: '新增功能AAA',
|
||||
url: `http://127.0.0.1:33855/public/ZanPandora-1.0.1-mac.zip`
|
||||
}
|
||||
if(compareVersions.compare(maxVersion.name , version, '>')) {
|
||||
return maxVersion
|
||||
}
|
||||
return null
|
||||
}
|
||||
router.get('/win32/RELEASES', (ctx, next) => {
|
||||
let newVersion = getNewVersion(ctx.query.version)
|
||||
if(newVersion) {
|
||||
ctx.body='BBC6F98A5CD32C675AAB6737A5F67176248B900C ZanPandora-1.0.1-full.nupkg 62177782'
|
||||
} else {
|
||||
ctx.status = 204
|
||||
}
|
||||
})
|
||||
router.get('/win32/*.nupkg', (ctx, next) => {
|
||||
// redirect s3 静态文件服务
|
||||
ctx.redirect(`/public/${ctx.params[0]}.nupkg`)
|
||||
})
|
||||
router.get('/darwin', (ctx, next) => {
|
||||
// 处理Mac更新, ?version=1.0.0&uid=123
|
||||
let {version} = ctx.query
|
||||
let newVersion = getNewVersion(version)
|
||||
if(newVersion) {
|
||||
ctx.body = newVersion
|
||||
} else {
|
||||
ctx.status = 204
|
||||
}
|
||||
})
|
||||
app.use(serve({rootDir: 'public', rootPath: '/public'}))
|
||||
app.use(router.routes())
|
||||
.use(router.allowedMethods())
|
||||
|
||||
app.listen(33855)
|
||||
```
|
||||
|
||||
|
||||
引申资料
|
||||
|
||||
- [electron-vue架构解析-开发环境启动流程分析](https://dushaofeng.github.io/2018/06/08/electron-vue架构解析-开发环境启动流程分析/)
|
||||
- [chromium 进程模型](https://www.chromium.org/developers/design-documents/process-models)
|
||||
|
||||
|
||||
## 八、 Electron 应用场景
|
||||
|
||||
### 1. 字节跳动
|
||||
|
||||
- Electron做了一个工具,能直接查看线上包的函数耗时,无任何侵入
|
||||
|
||||
- 调试工具的合集
|
||||
|
||||
- 研发需求管理
|
||||
|
||||
- 代码合并工具(MR)。区别于 gitlab 的特性,没有多仓合代码的能力。
|
||||
|
||||
比如同时在主工程系修改了7个 pod 、1个主工程的代码,需要分批次提交,不具备同时将8个仓库的代码原子性合入
|
||||
|
||||
pod 的 changeLog、版本号等需要设计自动发版的流程
|
||||
|
||||
- 把性能调试和效率工具都整合到一起了。
|
||||
|
||||
|
||||
|
||||
### 2. 阿里
|
||||
|
||||
- 沙盒的查看与操作,比如在 PC 端查看移动沙盒内文件内容、数据库文件的查看与 SQL 执行等
|
||||
- lint 功能、检测无用方法、在线日志解密查看、数据库文件的解密查看、jspatch 的 mock、
|
||||
- 针对网络请求和响应的操作,比如自定义请求、延迟、mock response、
|
||||
- Mock、查看
|
||||
- 性能测试:cpu、load、fps、启动耗时(机房有高速摄像机解帧,模拟点击 icon、启动页启动、App 首页出现、首页图片出现、App 可以滚动交互了。不需要 hook 去监控耗时)
|
||||
- pre-main、main、首页打点
|
||||
- 数据板:埋点体系的数据,看坑位的点击效果
|
||||
- 应用数据:沙盒、Cookie、数据
|
||||
- 开发工具:CPU、内存、OOM
|
||||
- 视觉:移动端元素的参考线
|
||||
- 网络、开关数据、卡顿、内存泄漏
|
||||
|
||||
|
||||
|
||||
一言以蔽之就是:通过 deviceID、ip 地址等与设备产生连接,将一切可以标准化的流程都抽象、自动化、比如性能调试和效率工具都整合到一起。
|
||||
|
||||
一个原则:该工具只做数据的查看、Mock 等工作,不做线上数据的干扰和生产
|
||||
|
||||
|
||||
24
Chapter2 - Web FrontEnd/2.42.md
Normal file
24
Chapter2 - Web FrontEnd/2.42.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# weex 优化和原理
|
||||
|
||||

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

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

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

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

|
||||
|
||||
|
||||
|
||||
3
Chapter8 - Finance/8.14.md
Normal file
3
Chapter8 - Finance/8.14.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# k 线图学习
|
||||
|
||||
## k 线的参数、构成、分类
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
第八部分主要记录在学习金融学中遇到的概念、生活中的经济学现象的解读
|
||||
|
||||
* [1、“账期”](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [2、亚洲金融危机两三谈](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [3、提高利率的副作用](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [4、白话贸易战](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [5、股票的本质是帮助企业高飞](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [6、需求是分层次的](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [7、炒期货比炒股风险高的本质原因](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [8、浅谈一带一路](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [9、隐形的投资你看得到吗](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [10、存款准备金率的衍生知识](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [11、降准的魔法效应](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [12、揭开″定向降准″的神秘面纱](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [13、股市飘来了降准的芳香](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [1、“账期”](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [2、亚洲金融危机两三谈](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.2.md)
|
||||
* [3、提高利率的副作用](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.3.md)
|
||||
* [4、白话贸易战](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.4.md)
|
||||
* [5、股票的本质是帮助企业高飞](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.5.md)
|
||||
* [6、需求是分层次的](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.6.md)
|
||||
* [7、炒期货比炒股风险高的本质原因](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.7.md)
|
||||
* [8、浅谈一带一路](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.8.md)
|
||||
* [9、隐形的投资你看得到吗](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.9.md)
|
||||
* [10、存款准备金率的衍生知识](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.10.md)
|
||||
* [11、降准的魔法效应](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.11.md)
|
||||
* [12、揭开″定向降准″的神秘面纱](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.12.md)
|
||||
* [13、股市飘来了降准的芳香](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.13.md)
|
||||
* [14、K线图学习](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.14.md)
|
||||
|
||||
30
SUMMARY.md
30
SUMMARY.md
@@ -91,6 +91,8 @@
|
||||
* [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)
|
||||
|
||||
* [Chapter2 - Web FrontEnd](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/chapter2.md)
|
||||
* [1、-last-child与-last-of-type你只是会用,有研究过区别吗?](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.1.md)
|
||||
@@ -134,7 +136,8 @@
|
||||
* [39、前端模块化演进之路](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.39.md)
|
||||
* [40、Electron:PC 端多端融合方案](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.40.md)
|
||||
* [41、sourceMap 闪亮登场](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.41.md)
|
||||
|
||||
* [42、weex 优化和原理](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.42.md)
|
||||
|
||||
* [Chapter3 - Server](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/chapter3.md)
|
||||
* [1、利用分页和模糊查询技术实现一个App接口](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/3.1.md)
|
||||
* [2、网页端扫码登录实现原理](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter3%20-%20Server/3.2.md)
|
||||
@@ -186,15 +189,16 @@
|
||||
|
||||
* [Chapter8 - Finance](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/chapter8.md)
|
||||
* [1、“账期”](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [2、亚洲金融危机两三谈](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [3、提高利率的副作用](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [4、白话贸易战](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [5、股票的本质是帮助企业高飞](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [6、需求是分层次的](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [7、炒期货比炒股风险高的本质原因](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [8、浅谈一带一路](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [9、隐形的投资你看得到吗](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [10、存款准备金率的衍生知识](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [11、降准的魔法效应](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [12、揭开″定向降准″的神秘面纱](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [13、股市飘来了降准的芳香](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.1.md)
|
||||
* [2、亚洲金融危机两三谈](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.2.md)
|
||||
* [3、提高利率的副作用](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.3.md)
|
||||
* [4、白话贸易战](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.4.md)
|
||||
* [5、股票的本质是帮助企业高飞](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.5.md)
|
||||
* [6、需求是分层次的](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.6.md)
|
||||
* [7、炒期货比炒股风险高的本质原因](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.7.md)
|
||||
* [8、浅谈一带一路](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.8.md)
|
||||
* [9、隐形的投资你看得到吗](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.9.md)
|
||||
* [10、存款准备金率的衍生知识](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.10.md)
|
||||
* [11、降准的魔法效应](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.11.md)
|
||||
* [12、揭开″定向降准″的神秘面纱](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.12.md)
|
||||
* [13、股市飘来了降准的芳香](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.13.md)
|
||||
* [14、K线图学习](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter8%20-%20Finance/8.14.md)
|
||||
BIN
assets/2020-08-12-RunLoopStructure.png
Normal file
BIN
assets/2020-08-12-RunLoopStructure.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
assets/2020-08-13-ImageRenderProcess.png
Normal file
BIN
assets/2020-08-13-ImageRenderProcess.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 934 KiB |
BIN
assets/2020-08-13-MachPort.png
Normal file
BIN
assets/2020-08-13-MachPort.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 774 KiB |
BIN
assets/2020-08-13-MachPortTest.png
Normal file
BIN
assets/2020-08-13-MachPortTest.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 446 KiB |
BIN
assets/2020-08-13-RunLoopLifecycle.png
Normal file
BIN
assets/2020-08-13-RunLoopLifecycle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/2020-08-13-YYImageClassLevel.png
Normal file
BIN
assets/2020-08-13-YYImageClassLevel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
Reference in New Issue
Block a user