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

6582
1.74.md

File diff suppressed because it is too large Load Diff

1153
1.75.md

File diff suppressed because it is too large Load Diff

2512
1.80.md

File diff suppressed because it is too large Load Diff

View File

@@ -10,40 +10,45 @@
访问控制器的View就相当于调用控制器中的view get方法 访问控制器的View就相当于调用控制器中的view get方法
``` ```objective-c
-(UIView *)view
-(UIView *)view{ {
if(_view == nil){ if(_view == nil){
[self loadView]; [self loadView];
[self viewDidload]; [self viewDidload];
} }
return _view; return _view;
} }
``` ```
# 控制器加载view的流程 # 控制器加载view的流程
![控制器加载view的流程](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2287777-b6128646373dfffb.png) ![控制器加载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的处理 ## MyViewController加载view的处理
@@ -57,7 +62,7 @@ MyViewController *vc = [[MyViewController alloc] init];
例子 例子
``` ```objective-c
//在Appdelegate中 //在Appdelegate中
ViewController *vc = [[ViewController alloc] init]; ViewController *vc = [[ViewController alloc] init];
vc.view.backgroundColkor = [UIColor redColor]; vc.view.backgroundColkor = [UIColor redColor];
@@ -75,22 +80,21 @@ self.window.rootViewController = vc;
-(void)loadView{ -(void)loadView{
UIView*view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIView*view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
view.backgroundColor = [UIColor greenColor]; self.view = view; view.backgroundColor = [UIColor greenColor]; self.view = view;
} }
-(void)viewDidload{ -(void)viewDidload{
[super 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** **__CFRunLoopModeIsEmpty**
此函数的作用就是判断这个 Mode 下面有没有 source0、source1、timer只要存在就说明当前 Mode 不是空的,同时看看这个 Mode 是不是属于当前的 RunLoop 此函数的作用就是判断这个 Mode 下面有没有 source0、source1、timer只要存在就说明当前 Mode 不是空的,同时看看这个 Mode 是不是属于当前的 RunLoop
```objective-c ```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 是没有效果的。 注意:添加 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 - 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 不走消息转发 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: 代码编译后的可执行文件,被加载到内存中,就叫做镜像文件。 1. image: 代码编译后的可执行文件,被加载到内存中,就叫做镜像文件。
2. MachO 结构: 2. MachO 可执行文件被 dyld 加载到内存中,加载时并不是所有的符号都可以确定地址,有些是通过 lazy bind 在真正调用的时候绑定的。
3. iOS 代码在编译时没有办法确定方法的实现地址。**动态库共享缓存**里面有动态库。NSLog 属于 Foundation 框架,每个手机内部中的地址不一定。 3. iOS 代码在编译时没有办法确定方法的实现地址。**动态库共享缓存**里面有动态库。NSLog 属于 Foundation 框架,每个手机内部中的地址不一定。
@@ -30,11 +30,42 @@
| NSLog | 0xaabbcc | | | NSLog | 0xaabbcc | |
| ... | ... | | | ... | ... | |
![image-20200810201822593](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/mage-20200810201822593.png)
![image-20200810190329801](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/image-20200810190329801.png)
9. fishhook 做的事情就是将系统的符号表,将符号表中的特定符号对应的地址,修改为自定义的函数地址。起到了 hook 作用。也就是说外部的 c 函数,在 iOS 中的调用属于**动态调用**。 9. fishhook 做的事情就是将系统的符号表,将符号表中的特定符号对应的地址,修改为自定义的函数地址。起到了 hook 作用。也就是说外部的 c 函数,在 iOS 中的调用属于**动态调用**。
https://www.bilibili.com/video/BV1UZ4y1u7Ba?from=search&seid=14997461811427810898 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) * [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) * [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) * [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)

View File

@@ -1,13 +1,31 @@
# ElectronPC 端多端融合方案 # ElectronPC 端多端融合方案
> 每天都要写第二天的 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 端应用程序。先看看结果吧 3天时间写了个 PC 端应用程序。先看看结果吧
@@ -17,33 +35,36 @@
![Todo1](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-05-04-TodoApp3.png) ![Todo1](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-05-04-TodoApp3.png)
为什么要选 electron 作为 pc 端开发方案?
**为什么要选 Electron 作为 pc 端开发方案?**
史前时代,以 MFC 为代表的技术栈,开发效率较低,维护成本高。 史前时代,以 MFC 为代表的技术栈,开发效率较低,维护成本高。
后来使用 QT 技术,特点是使用 DirectUI + 面向对象 + XML 定义 UI适用于小型软件、性能要求、包大小、UI 复杂度叫高的需求。 后来使用 QT 技术,特点是使用 DirectUI + 面向对象 + XML 定义 UI适用于小型软件、性能要求、包大小、UI 复杂度叫高的需求。
再到后来,以 QT Quick 为代表的技术,特点是框架本身提供子控件,基于子控件组合来创建新的控件。类似于 ActionScript 的脚本化界面逻辑代码。 再到后来,以 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。 执行下面命令快速体验 Hello world也是官方给的一个 Demo。
```shell ```shell
git clone https://github.com/electron/electron-quick-start git clone https://github.com/Electron/Electron-quick-start
cd electron-quick-start cd Electron-quick-start
npm install && npm start npm install && npm start
``` ```
简单介绍下 Demo 工程,工程目录如下所示 简单介绍下 Demo 工程,工程目录如下所示
![工程目录](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-electron-packagejson.png) ![工程目录](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-Electron-packagejson.png)
在终端执行 `npm start` 执行的是 package.json 中的 `scripts` 节点下的 start 命令,也就是 `electron .``.` 代表执行 main.js 中的逻辑。 在终端执行 `npm start` 执行的是 package.json 中的 `scripts` 节点下的 start 命令,也就是 `Electron .``.` 代表执行 main.js 中的逻辑。
```Javascript ```Javascript
// Modules to control application life and create native browser window // Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron') const {app, BrowserWindow} = require('Electron')
const path = require('path') const path = require('path')
function createWindow () { 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 // 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. // code. You can also put them in separate files and require them here.
``` ```
写过 Vue、React、Native 的人看代码很容易,因为应用程序的生命周期钩子函数是很重要的,开发者根据需求在钩子函数里面做相应的视图创建、初始化、销毁对象等等。比如 electron 中的 activatewindow-all-closed 等。 写过 Vue、React、Native 的人看代码很容易。比如应用程序的生命周期钩子函数对开发者很重要,也是一个标准的做法,根据需求在钩子函数里面做相应的视图创建、初始化、销毁对象等等。比如 Electron 中的 `activate``window-all-closed` 等。
app 对象在 `whenReady` 的时候执行 createWindow 方法。内部创建了一个 `BrowserWindow` 对象,指定了大小和功能设置。 app 对象在 `whenReady` 的时候执行 `createWindow` 方法。内部创建了一个 `BrowserWindow` 对象,指定了大小和功能设置。
1. webPreferences Object (可选) - 网页功能的设置。 1. webPreferencesObject (可选) - 网页功能的设置。
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')` `mainWindow.loadFile('index.html')` 加载了同级目录下的 index.html 文件。也可以加载服务器资源(部署好的网页),比如 `win.loadURL('https://github.com/FantasticLBP')`
@@ -108,25 +129,25 @@ window.addEventListener('DOMContentLoaded', () => {
} }
console.table(process) console.table(process)
console.info(process.versions) console.info(process.versions)
for (const type of ['chrome', 'node', 'electron']) { for (const type of ['chrome', 'node', 'Electron']) {
replaceText(`${type}-version`, process.versions[type]) 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。 - 渲染进程: 用户看到的 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` - 然后打开浏览器,在地址栏输入 `chrome://inspect`
- 点击 `configure`,在弹出的面板中填写需要调试的端口信息 - 点击 `configure`,在弹出的面板中填写需要调试的端口信息
![chrome inspect](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-electronChromeInspect.png) ![chrome inspect](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-ElectronChromeInspect.png)
- 重新开启服务 `npm start`,在 chrome inspect 面板的 `Target` 节点中选择需要调试的页面 - 重新开启服务 `npm start`,在 chrome inspect 面板的 `Target` 节点中选择需要调试的页面
- 在面板中可以看到主进程执行的 `main.js`。可以加断点进行调试 - 在面板中可以看到主进程执行的 `main.js`。可以加断点进行调试
![chrome inspect](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-electron-MainProcessInspect.png) ![chrome inspect](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-04-21-Electron-MainProcessInspect.png)
方法二:利用 VS Code 调试 electron 主进程。 方法二:利用 VS Code 调试 Electron 主进程。
- 在 VS Code 的左侧菜单栏,第四个功能模块就是调试,点击调试,弹出对话框让你添加调试配置文件 `launch.json` - 在 VS Code 的左侧菜单栏,第四个功能模块就是调试,点击调试,弹出对话框让你添加调试配置文件 `launch.json`
@@ -390,9 +415,9 @@ Electron 架构和 Chromium 架构类似也是具有1个主进程和多个渲
"request": "launch", "request": "launch",
"name": "Debug main process", "name": "Debug main process",
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/Electron",
"windows": { "windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/Electron.cmd"
}, },
"args": ["."], "args": ["."],
"outputCapture": "std" "outputCapture": "std"
@@ -417,7 +442,7 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
## 、 开发 tips及其优化手段 ## 、 开发 tips及其优化手段
### 1. 开发 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: 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 会缓存已经安装过的依赖,其他项目只要发现存在缓存,则读取本地的包依赖,会更加快速。 5. 在工程化角度看,使用 yarn 比 npm 好一些,因为 yarn 会缓存已经安装过的依赖,其他项目只要发现存在缓存,则读取本地的包依赖,会更加快速。
6. 在使用 Vue、React 开发 electron 应用时,可以使用 npm 或 yarn install 包,也可以使用 electron-vue 脚手架工具。 6. 在使用 Vue、React 开发 Electron 应用时,可以使用 npm 或 yarn install 包,也可以使用 Electron-vue 脚手架工具。
```shell ```shell
vue init simulatedgreg/electron-vue my-project vue init simulatedgreg/Electron-vue my-project
cd my-project cd my-project
npm install npm install
npm run dev npm run dev
@@ -461,9 +486,9 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
``` ```
解决办法是在 main/index.dev.js 修改代码 解决办法是在 main/index.dev.js 修改代码
```javascript ```javascript
- require('electron-debug')({ showDevTools: true }); - require('Electron-debug')({ showDevTools: true });
+ // NB: Don't open dev tools with this, it is causing the error + // 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: 在 In main/index.js in the createWindow() function:
```javascript ```javascript
@@ -490,7 +515,7 @@ Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化
12. Electron 和 Web 开发相比,各自有侧重点 12. Electron 和 Web 开发相比,各自有侧重点
![electronAndWeb](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-05-04-ElectronAndWeb.png) ![ElectronAndWeb](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-05-04-ElectronAndWeb.png)
### 2. 优化手段 ### 2. 优化手段
@@ -529,7 +554,7 @@ node --cpu-prof --heap-prof -e "require('request)”“
## 、 技术体系搭建 ## 、 技术体系搭建:全景
其实一个技术本身的难易程度并不是能否在自己企业、公司、团队内顺利使用的唯一标尺,其配套的 CI/CD、APM、埋点系统、发布更新、灰度测试等能否与现有的系统以较小成本融合才是很大的决定要素。因为某个技术并不是非常难要是大多数开发者觉得很难那它设计上就是失败的。 其实一个技术本身的难易程度并不是能否在自己企业、公司、团队内顺利使用的唯一标尺,其配套的 CI/CD、APM、埋点系统、发布更新、灰度测试等能否与现有的系统以较小成本融合才是很大的决定要素。因为某个技术并不是非常难要是大多数开发者觉得很难那它设计上就是失败的。
@@ -551,10 +576,234 @@ Electron 提供的 crash 信息进行包装。
![crash 分析](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-05-04-ElectronCrash.png) ![crash 分析](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-05-04-ElectronCrash.png)
```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 等工作,不做线上数据的干扰和生产

View File

@@ -0,0 +1,24 @@
# weex 优化和原理
![image-20200815004043104](/Users/lbp/Library/Application Support/typora-user-images/image-20200815004043104.png)
![image-20200815004612271](/Users/lbp/Library/Application Support/typora-user-images/image-20200815004612271.png)
![image-20200815004625829](/Users/lbp/Library/Application Support/typora-user-images/image-20200815004625829.png)
![image-20200815004636381](/Users/lbp/Library/Application Support/typora-user-images/image-20200815004636381.png)
![image-20200815004803634](/Users/lbp/Library/Application Support/typora-user-images/image-20200815004803634.png)

View File

@@ -0,0 +1,3 @@
# k 线图学习
## k 线的参数、构成、分类

View File

@@ -2,17 +2,18 @@
第八部分主要记录在学习金融学中遇到的概念、生活中的经济学现象的解读 第八部分主要记录在学习金融学中遇到的概念、生活中的经济学现象的解读
* [1、“账期”](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.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.1.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.1.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.1.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.1.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.1.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.1.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.1.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.1.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.1.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.1.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.1.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)

View File

@@ -91,6 +91,8 @@
* [86、GCD 源码探究](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.86.md) * [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) * [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) * [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) * [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) * [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) * [39、前端模块化演进之路](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.39.md)
* [40、ElectronPC 端多端融合方案](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.40.md) * [40、ElectronPC 端多端融合方案](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) * [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) * [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) * [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) * [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) * [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) * [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) * [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.1.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.1.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.1.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.1.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.1.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.1.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.1.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.1.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.1.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.1.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.1.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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB