docs: Electron
@@ -1,10 +1,12 @@
|
|||||||
# KVC && KVO
|
# KVC && KVO
|
||||||
|
|
||||||
## 基本用法
|
|
||||||
|
|
||||||
### 字典快速赋值
|
|
||||||
|
|
||||||
KVC 可以将字典里面和 model 同名的 property 进行快速赋值 **setValuesForKeysWithDictionary**
|
|
||||||
|
|
||||||
|
## 一、基本用法-字典快速赋值
|
||||||
|
|
||||||
|
KVC 可以将字典里面和 model 同名的 property 进行快速赋值 `setValuesForKeysWithDictionary`。
|
||||||
|
|
||||||
```objective-c
|
```objective-c
|
||||||
//前提:model 中的各个 property 必须和 NSDictionary 中的属性一致
|
//前提:model 中的各个 property 必须和 NSDictionary 中的属性一致
|
||||||
@@ -22,7 +24,8 @@ KVC 可以将字典里面和 model 同名的 property 进行快速赋值 **setVa
|
|||||||
运行上面的代码,代码不崩溃,只不过在输出值的时候输出了 null
|
运行上面的代码,代码不崩溃,只不过在输出值的时候输出了 null
|
||||||
|
|
||||||
- 情况二:在 NSDictionary 中存在某个值,但是在 model 里面没有值
|
- 情况二:在 NSDictionary 中存在某个值,但是在 model 里面没有值
|
||||||
|
|
||||||
|
|
||||||
运行后编译成功,但是代码奔溃掉。原因是 KVC 。所以我们只需要实现这么一个方法。甚至不需要写函数体部分
|
运行后编译成功,但是代码奔溃掉。原因是 KVC 。所以我们只需要实现这么一个方法。甚至不需要写函数体部分
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -62,7 +65,8 @@ NSMutableArray *hobbies = [_person mutableArrayValueForKeyPath:@"hobbies"];
|
|||||||
```
|
```
|
||||||
|
|
||||||
- 情况五: 注册依赖键.
|
- 情况五: 注册依赖键.
|
||||||
|
|
||||||
|
|
||||||
KVO 可以观察属性的二级属性对象的所有属性变化。说人话就是“假如 Person 类有个 Dog 类,Dog 类有 name、fur、weight 等属性,我们给 Person 的 Dog 属性观察,假如 Dog 的任何属性变化是,Person 的观察者对象都可以拿到当前的变化值。我们只需要在 Person 中写下面的方法即可”
|
KVO 可以观察属性的二级属性对象的所有属性变化。说人话就是“假如 Person 类有个 Dog 类,Dog 类有 name、fur、weight 等属性,我们给 Person 的 Dog 属性观察,假如 Dog 的任何属性变化是,Person 的观察者对象都可以拿到当前的变化值。我们只需要在 Person 中写下面的方法即可”
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -88,7 +92,9 @@ self.person.dog.weight = 50;
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## KVO 的本质
|
|
||||||
|
|
||||||
|
## 二、 KVO 的本质
|
||||||
|
|
||||||
kVO 是Objective-C 对观察者模式的实现。也是 Cocoa Binding 的基础。
|
kVO 是Objective-C 对观察者模式的实现。也是 Cocoa Binding 的基础。
|
||||||
|
|
||||||
@@ -96,16 +102,14 @@ kVO 是Objective-C 对观察者模式的实现。也是 Cocoa Binding 的基础
|
|||||||
|
|
||||||
1. KVO 观察者和属性被观察的对象之间不是强引用的关系
|
1. KVO 观察者和属性被观察的对象之间不是强引用的关系
|
||||||
|
|
||||||
2. KVO 的触发分为`自动触发模式`和`手动触发模式`2种。通常我们使用的都是自动通知,注册观察者之后,当条件触发的时候会自动调用`-(void)observeValueForKeyPath...`
|
2. KVO 的触发分为`自动触发模式`和`手动触发模式`2种。通常我们使用的都是自动通知,注册观察者之后,当条件触发的时候会自动调用 `-(void)observeValueForKeyPath`。如果需要实现手动通知,我们需要使用下面的方法
|
||||||
|
|
||||||
如果需要实现手动通知,我们需要使用下面的方法
|
|
||||||
|
|
||||||
```
|
```Objective-c
|
||||||
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
|
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
3. 若类有实例变量 NSString *_foo, 调用 setValue:forKey: 是以 foo 还是 _foo 作为 key ?
|
3. 若类有实例变量 NSString *_foo, 调用 setValue:forKey: 是以 foo 还是 _foo 作为 key ?
|
||||||
|
|
||||||
都可以
|
都可以
|
||||||
|
|
||||||
@@ -120,8 +124,7 @@ kVO 是Objective-C 对观察者模式的实现。也是 Cocoa Binding 的基础
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 三、 实现机制
|
||||||
### 实现机制
|
|
||||||
|
|
||||||
> Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...
|
> Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...
|
||||||
|
|
||||||
@@ -149,7 +152,8 @@ Apple 文档告诉我们:被观察对象的 `isa指针` 会指向一个中间
|
|||||||
关于分类与子类的关系可以看看我之前的 [文章](1.50.md).
|
关于分类与子类的关系可以看看我之前的 [文章](1.50.md).
|
||||||
|
|
||||||
|
|
||||||
### 模拟实现系统的 KVO
|
|
||||||
|
## 四、 模拟实现系统的 KVO
|
||||||
|
|
||||||
1. 创建被观察对象的子类
|
1. 创建被观察对象的子类
|
||||||
2. 重写观察对象属性的 set 方法,同时调用 `willChangeValueForKey、didChangeValueForKey`
|
2. 重写观察对象属性的 set 方法,同时调用 `willChangeValueForKey、didChangeValueForKey`
|
||||||
@@ -246,11 +250,12 @@ void setName (id self, SEL _cmd, NSString *name) {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### KVO 的缺陷
|
|
||||||
|
|
||||||
KVO 虽然很强大,你只能重写 `-observeValueForKeyPath:ofObject:change:context: ` 来获得通知,想要提供自定义的 selector ,不行;想要传入一个 block,没门儿。感觉如果加入 block 就更棒了。
|
KVO 的缺陷:
|
||||||
|
|
||||||
### KVO 的改装
|
KVO 虽然很强大,你只能重写 `-observeValueForKeyPath:ofObject:change:context: ` 来获得通知,想要提供自定义的 selector ,不行;想要传入一个 block 没门儿。感觉如果加入 block 就更棒了。
|
||||||
|
|
||||||
|
KVO 的改装:
|
||||||
|
|
||||||
看到官方的做法并不是很方便使用,我们看到无数的优秀框架都支持 block 特性,比如 AFNetworking ,所以我们可以将系统的 KVO 改装成支持 block。
|
看到官方的做法并不是很方便使用,我们看到无数的优秀框架都支持 block 特性,比如 AFNetworking ,所以我们可以将系统的 KVO 改装成支持 block。
|
||||||
|
|
||||||
|
|||||||
@@ -644,7 +644,7 @@ RISC 架构要求软件来指定各个操作步骤。比如上面的乘法,指
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 2. 获取线程信息
|
#### 2. 获取线程信息<a name="threadInfo"></a>
|
||||||
|
|
||||||
讲完了区别来讲下如何做 CPU 使用率的监控
|
讲完了区别来讲下如何做 CPU 使用率的监控
|
||||||
- 开启定时器,按照设定的周期不断执行下面的逻辑
|
- 开启定时器,按照设定的周期不断执行下面的逻辑
|
||||||
@@ -1857,11 +1857,11 @@ iOS 网络框架层级关系如下:
|
|||||||
|
|
||||||
iOS 网络现状是由4层组成的:最底层的 BSD Sockets、SecureTransport;次级底层是 CFNetwork、NSURLSession、NSURLConnection、WebView 是用 Objective-C 实现的,且调用 CFNetwork;应用层框架 AFNetworking 基于 NSURLSession、NSURLConnection 实现。
|
iOS 网络现状是由4层组成的:最底层的 BSD Sockets、SecureTransport;次级底层是 CFNetwork、NSURLSession、NSURLConnection、WebView 是用 Objective-C 实现的,且调用 CFNetwork;应用层框架 AFNetworking 基于 NSURLSession、NSURLConnection 实现。
|
||||||
|
|
||||||
目前业界对于网络监控主要有2种:一种是通过 NSURLProtocol 监控、一种是通过 Hook 来监控。
|
目前业界对于网络监控主要有2种:一种是通过 NSURLProtocol 监控、一种是通过 Hook 来监控。下面介绍几种办法来监控网络请求,各有优缺点。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 2.1 NSURLProtocol 监控 App 网络请求<a name="network-2.1"></a>
|
#### 2.1 方案一:NSURLProtocol 监控 App 网络请求<a name="network-2.1"></a>
|
||||||
|
|
||||||
NSURLProtocol 作为上层接口,使用较为简单,但 NSURLProtocol 属于 URL Loading System 体系中。应用协议的支持程度有限,支持 FTP、HTTP、HTTPS 等几个应用层协议,对于其他的协议则无法监控,存在一定的局限性。如果监控底层网络库 CFNetwork 则没有这个限制。
|
NSURLProtocol 作为上层接口,使用较为简单,但 NSURLProtocol 属于 URL Loading System 体系中。应用协议的支持程度有限,支持 FTP、HTTP、HTTPS 等几个应用层协议,对于其他的协议则无法监控,存在一定的局限性。如果监控底层网络库 CFNetwork 则没有这个限制。
|
||||||
|
|
||||||
@@ -2341,7 +2341,7 @@ API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0))
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 2.2 骚操作篇 <a name="network-2.2"></a>
|
#### 2.2 方案二:NSURLProtocol 监控 App 网络请求之黑魔法篇 <a name="network-2.2"></a>
|
||||||
|
|
||||||
文章上面 [2.1 ](#network-2.1)分析到了 NSURLSessionTaskMetrics 由于兼容性问题,对于网络监控来说似乎不太完美,但是自后在搜资料的时候看到了一篇[文章](https://www.jianshu.com/p/1c34147030d1)。文章在分析 WebView 的网络监控的时候分析 Webkit 源码的时候发现了下面代码
|
文章上面 [2.1 ](#network-2.1)分析到了 NSURLSessionTaskMetrics 由于兼容性问题,对于网络监控来说似乎不太完美,但是自后在搜资料的时候看到了一篇[文章](https://www.jianshu.com/p/1c34147030d1)。文章在分析 WebView 的网络监控的时候分析 Webkit 源码的时候发现了下面代码
|
||||||
|
|
||||||
@@ -2433,7 +2433,11 @@ NSURLSession 在 iOS9 之前使用 `_setCollectsTimingData:` 就可以使用 Tim
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 2.3 Hook
|
#### 2.3 方案三:Hook
|
||||||
|
|
||||||
|
iOS 中 hook 技术有2类,一种是 NSProxy,一种是 method swizzling(isa swizzling)
|
||||||
|
|
||||||
|
##### 2.3.1 方法一
|
||||||
|
|
||||||
写 SDK 肯定不可能手动侵入业务代码(你没那个权限提交到线上代码 😂),所以不管是 APM 还是无痕埋点都是通过 Hook 的方式。
|
写 SDK 肯定不可能手动侵入业务代码(你没那个权限提交到线上代码 😂),所以不管是 APM 还是无痕埋点都是通过 Hook 的方式。
|
||||||
|
|
||||||
@@ -2548,6 +2552,42 @@ void printResponseData (CFDataRef responseData) {
|
|||||||
|
|
||||||
我们知道 NSURLSession、NSURLConnection、CFNetwork 的使用都需要调用一堆方法进行设置然后需要设置代理对象,实现代理方法。所以针对这种情况进行监控首先想到的是使用 runtime hook 掉方法层级。但是针对设置的代理对象的代理方法没办法 hook,因为不知道代理对象是哪个类。所以想办法可以 hook 设置代理对象这个步骤,将代理对象替换成我们设计好的某个类,然后让这个类去实现 NSURLConnection、NSURLSession、CFNetwork 相关的代理方法。然后在这些方法的内部都去调用一下原代理对象的方法实现。所以我们的需求得以满足,我们在相应的方法里面可以拿到监控数据,比如请求开始时间、结束时间、状态码、内容大小等。
|
我们知道 NSURLSession、NSURLConnection、CFNetwork 的使用都需要调用一堆方法进行设置然后需要设置代理对象,实现代理方法。所以针对这种情况进行监控首先想到的是使用 runtime hook 掉方法层级。但是针对设置的代理对象的代理方法没办法 hook,因为不知道代理对象是哪个类。所以想办法可以 hook 设置代理对象这个步骤,将代理对象替换成我们设计好的某个类,然后让这个类去实现 NSURLConnection、NSURLSession、CFNetwork 相关的代理方法。然后在这些方法的内部都去调用一下原代理对象的方法实现。所以我们的需求得以满足,我们在相应的方法里面可以拿到监控数据,比如请求开始时间、结束时间、状态码、内容大小等。
|
||||||
|
|
||||||
|
NSURLSession、NSURLConnection hook 如下。
|
||||||
|
|
||||||
|

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

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
业界有 APM 针对 CFNetwork 的方案,整理描述下:
|
||||||
|
|
||||||
|
CFNetwork 是 c 语言实现的,要对 c 代码进行 hook 需要使用 Dynamic Loader Hook 库 - [fishhook](https://github.com/facebook/fishhook)。
|
||||||
|
|
||||||
|
> **Dynamic Loader**(dyld)通过更新 **Mach-O** 文件中保存的指针的方法来绑定符号。借用它可以在 **Runtime** 修改 **C** 函数调用的函数指针。**fishhook** 的实现原理:遍历 `__DATA segment` 里面 `__nl_symbol_ptr` 、`__la_symbol_ptr` 两个 section 里面的符号,通过 Indirect Symbol Table、Symbol Table 和 String Table 的配合,找到自己要替换的函数,达到 hook 的目的。
|
||||||
|
|
||||||
|
> /* Returns the number of bytes read, or -1 if an error occurs preventing any
|
||||||
|
>
|
||||||
|
> bytes from being read, or 0 if the stream's end was encountered.
|
||||||
|
>
|
||||||
|
> It is an error to try and read from a stream that hasn't been opened first.
|
||||||
|
>
|
||||||
|
> This call will block until at least one byte is available; it will NOT block
|
||||||
|
>
|
||||||
|
> until the entire buffer can be filled. To avoid blocking, either poll using
|
||||||
|
>
|
||||||
|
> CFReadStreamHasBytesAvailable() or use the run loop and listen for the
|
||||||
|
>
|
||||||
|
> kCFStreamEventHasBytesAvailable event for notification of data available. */
|
||||||
|
>
|
||||||
|
> CF_EXPORT
|
||||||
|
>
|
||||||
|
> CFIndex CFReadStreamRead(CFReadStreamRef **_Null_unspecified** stream, UInt8 * **_Null_unspecified** buffer, CFIndex bufferLength);
|
||||||
|
|
||||||
|
CFNetwork 使用 CFReadStreamRef 来传递数据,使用回调函数的形式来接受服务器的响应。当回调函数受到
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
具体步骤及其关键代码如下,以 NSURLConnection 举例
|
具体步骤及其关键代码如下,以 NSURLConnection 举例
|
||||||
|
|
||||||
- 因为要 Hook 挺多地方,所以写一个 method swizzling 的工具类
|
- 因为要 Hook 挺多地方,所以写一个 method swizzling 的工具类
|
||||||
@@ -2791,6 +2831,10 @@ void printResponseData (CFDataRef responseData) {
|
|||||||
|
|
||||||
这样下来就是可以监控到网络信息了,然后将数据交给数据上报 SDK,按照下发的数据上报策略去上报数据。
|
这样下来就是可以监控到网络信息了,然后将数据交给数据上报 SDK,按照下发的数据上报策略去上报数据。
|
||||||
|
|
||||||
|
##### 2.3.2 方法二
|
||||||
|
|
||||||
|
其实,针对上述的需求还有另一种方法一样可以达到目的,那就是 **isa swizzling**。
|
||||||
|
|
||||||
顺道说一句,上面针对 NSURLConnection、NSURLSession、NSInputStream 代理对象的 hook 之后,利用 NSProxy 实现代理对象方法的转发,有另一种方法可以实现,那就是 **isa swizzling**。
|
顺道说一句,上面针对 NSURLConnection、NSURLSession、NSInputStream 代理对象的 hook 之后,利用 NSProxy 实现代理对象方法的转发,有另一种方法可以实现,那就是 **isa swizzling**。
|
||||||
|
|
||||||
- Method swizzling 原理
|
- Method swizzling 原理
|
||||||
@@ -2835,13 +2879,93 @@ void printResponseData (CFDataRef responseData) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
我们来分析一下为什么修改 `isa` 可以实现目的呢?
|
||||||
|
|
||||||
|
1. 写 APM 监控的人没办法确定业务代码
|
||||||
|
2. 不可能为了方便监控 APM,写某些类,让业务线开发者别使用系统 NSURLSession、NSURLConnection 类
|
||||||
|
|
||||||
|
想想 KVO 的实现原理?结合上面的图
|
||||||
|
|
||||||
|
- 创建监控对象子类
|
||||||
|
- 重写子类中属性的 getter、seeter
|
||||||
|
- 将监控对象的 isa 指针指向新创建的子类
|
||||||
|
- 在子类的 getter、setter 中拦截值的变化,通知监控对象值的变化
|
||||||
|
- 监控完之后将监控对象的 isa 还原回去
|
||||||
|
|
||||||
|
按照这个思路,我们也可以对 NSURLConnection、NSURLSession 的 load 方法中动态创建子类,在子类中重写方法,比如 `- (**nullable** **instancetype**)initWithRequest:(NSURLRequest *)request delegate:(**nullable** **id**)delegate startImmediately:(**BOOL**)startImmediately;` ,然后将 NSURLSession、NSURLConnection 的 isa 指向动态创建的子类。在这些方法处理完之后还原本身的 isa 指针。
|
||||||
|
|
||||||
|
不过 isa swizzling 针对的还是 method swizzling,代理对象不确定,还是需要 NSProxy 进行动态处理。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### 2.4 监控 App 常见网络请求<a name="categoryNameRules"></a>
|
至于如何修改 isa,我写一个简单的 Demo 来模拟 KVO
|
||||||
|
|
||||||
|
```objective-c
|
||||||
|
- (void)lbpKVO_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
|
||||||
|
//生成自定义的名称
|
||||||
|
NSString *className = NSStringFromClass(self.class);
|
||||||
|
NSString *currentClassName = [@"LBPKVONotifying_" stringByAppendingString:className];
|
||||||
|
//1. runtime 生成类
|
||||||
|
Class myclass = objc_allocateClassPair(self.class, [currentClassName UTF8String], 0);
|
||||||
|
// 生成后不能马上使用,必须先注册
|
||||||
|
objc_registerClassPair(myclass);
|
||||||
|
|
||||||
|
//2. 重写 setter 方法
|
||||||
|
class_addMethod(myclass,@selector(say) , (IMP)say, "v@:@");
|
||||||
|
|
||||||
|
// class_addMethod(myclass,@selector(setName:) , (IMP)setName, "v@:@");
|
||||||
|
//3. 修改 isa
|
||||||
|
object_setClass(self, myclass);
|
||||||
|
|
||||||
|
//4. 将观察者保存到当前对象里面
|
||||||
|
objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_ASSIGN);
|
||||||
|
|
||||||
|
//5. 将传递的上下文绑定到当前对象里面
|
||||||
|
objc_setAssociatedObject(self, "context", (__bridge id _Nullable)(context), OBJC_ASSOCIATION_RETAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void say(id self, SEL _cmd)
|
||||||
|
{
|
||||||
|
// 调用父类方法一
|
||||||
|
struct objc_super superclass = {self, [self superclass]};
|
||||||
|
((void(*)(struct objc_super *,SEL))objc_msgSendSuper)(&superclass,@selector(say));
|
||||||
|
NSLog(@"%s", __func__);
|
||||||
|
// 调用父类方法二
|
||||||
|
// Class class = [self class];
|
||||||
|
// object_setClass(self, class_getSuperclass(class));
|
||||||
|
// objc_msgSend(self, @selector(say));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setName (id self, SEL _cmd, NSString *name) {
|
||||||
|
NSLog(@"come here");
|
||||||
|
//先切换到当前类的父类,然后发送消息 setName,然后切换当前子类
|
||||||
|
//1. 切换到父类
|
||||||
|
Class class = [self class];
|
||||||
|
object_setClass(self, class_getSuperclass(class));
|
||||||
|
//2. 调用父类的 setName 方法
|
||||||
|
objc_msgSend(self, @selector(setName:), name);
|
||||||
|
|
||||||
|
//3. 调用观察
|
||||||
|
id observer = objc_getAssociatedObject(self, "observer");
|
||||||
|
id context = objc_getAssociatedObject(self, "context");
|
||||||
|
if (observer) {
|
||||||
|
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), @"name", self, @{@"new": name, @"kind": @1 } , context);
|
||||||
|
}
|
||||||
|
//4. 改回子类
|
||||||
|
object_setClass(self, class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 2.4 方案四:监控 App 常见网络请求
|
||||||
|
|
||||||
本着成本的原因,由于现在大多数的项目的网络能力都是通过 [AFNetworking](https://github.com/AFNetworking/AFNetworking) 完成的,所以本文的网络监控可以快速完成。
|
本着成本的原因,由于现在大多数的项目的网络能力都是通过 [AFNetworking](https://github.com/AFNetworking/AFNetworking) 完成的,所以本文的网络监控可以快速完成。
|
||||||
|
|
||||||
@@ -2913,16 +3037,130 @@ self.didCompleteObserver = [[NSNotificationCenter defaultCenter] addObserverForN
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 2.5 iOS 流量监控
|
||||||
|
|
||||||
## 六、 Crash 监控
|
简易版本。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 六、 电量消耗
|
||||||
|
|
||||||
|
移动设备上电量一直是比较敏感的问题,如果用户在某款 App 的时候发现耗电量严重、手机发热严重,那么用户很大可能会马上卸载这款 App。所以需要在开发阶段关心耗电量问题。
|
||||||
|
|
||||||
|
一般来说遇到耗电量较大,我们立马会想到是不是使用了定位、是不是使用了频繁网络请求、是不是不断循环做某件事情?
|
||||||
|
|
||||||
|
开发阶段基本没啥问题,我们可以结合 `Instrucments` 里的 `Energy Log` 工具来定位问题。但是线上问题就需要代码去监控耗电量,可以作为 APM 的能力之一。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1. 如何获取电量
|
||||||
|
|
||||||
|
在 iOS 中,`IOKit` 是一个私有框架,用来获取硬件和设备的详细信息,也是硬件和内核服务通信的底层框架。所以我们可以通过 `IOKit `来获取硬件信息,从而获取到电量信息。步骤如下:
|
||||||
|
|
||||||
|
- 首先在苹果开放源代码 opensource 中找到 [IOPowerSources.h](https://opensource.apple.com/source/IOKitUser/IOKitUser-647.6/ps.subproj/IOPowerSources.h.auto.html)、[IOPSKeys.h](https://opensource.apple.com/source/IOKitUser/IOKitUser-647.6/ps.subproj/IOPSKeys.h)。在 Xcode 的 `Package Contents` 里面找到 `IOKit.framework`。 路径为 `/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/IOKit.framework`
|
||||||
|
- 然后将 IOPowerSources.h、IOPSKeys.h、IOKit.framework 导入项目工程
|
||||||
|
- 设置 UIDevice 的 batteryMonitoringEnabled 为 true
|
||||||
|
- 获取到的耗电量精确度为 1%
|
||||||
|
|
||||||
|
```objective-c
|
||||||
|
- (double)fetchBatteryCostUsage
|
||||||
|
{
|
||||||
|
// returns a blob of power source information in an opaque CFTypeRef
|
||||||
|
CFTypeRef blob = IOPSCopyPowerSourcesInfo();
|
||||||
|
// returns a CFArray of power source handles, each of type CFTypeRef
|
||||||
|
CFArrayRef sources = IOPSCopyPowerSourcesList(blob);
|
||||||
|
CFDictionaryRef pSource = NULL;
|
||||||
|
const void *psValue;
|
||||||
|
// returns the number of values currently in an array
|
||||||
|
int numOfSources = CFArrayGetCount(sources);
|
||||||
|
// error in CFArrayGetCount
|
||||||
|
if (numOfSources == 0) {
|
||||||
|
NSLog(@"Error in CFArrayGetCount");
|
||||||
|
return -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculating the remaining energy
|
||||||
|
for (int i=0; i<numOfSources; i++) {
|
||||||
|
// returns a CFDictionary with readable information about the specific power source
|
||||||
|
pSource = IOPSGetPowerSourceDescription(blob, CFArrayGetValueAtIndex(sources, i));
|
||||||
|
if (!pSource) {
|
||||||
|
NSLog(@"Error in IOPSGetPowerSourceDescription");
|
||||||
|
return -1.0f;
|
||||||
|
}
|
||||||
|
psValue = (CFStringRef) CFDictionaryGetValue(pSource, CFSTR(kIOPSNameKey));
|
||||||
|
|
||||||
|
int curCapacity = 0;
|
||||||
|
int maxCapacity = 0;
|
||||||
|
double percentage;
|
||||||
|
|
||||||
|
psValue = CFDictionaryGetValue(pSource, CFSTR(kIOPSCurrentCapacityKey));
|
||||||
|
CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &curCapacity);
|
||||||
|
|
||||||
|
psValue = CFDictionaryGetValue(pSource, CFSTR(kIOPSMaxCapacityKey));
|
||||||
|
CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &maxCapacity);
|
||||||
|
|
||||||
|
percentage = ((double) curCapacity / (double) maxCapacity * 100.0f);
|
||||||
|
NSLog(@"curCapacity : %d / maxCapacity: %d , percentage: %.1f ", curCapacity, maxCapacity, percentage);
|
||||||
|
return percentage;
|
||||||
|
}
|
||||||
|
return -1.0f;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2. 定位问题
|
||||||
|
|
||||||
|
通常我们通过 Instrucments 里的 Energy Log 解决了很多问题后,App 上线了,线上的耗电量解决就需要使用 APM 来解决了。耗电地方可能是二方库、三方库,也可能是某个同事的代码。
|
||||||
|
|
||||||
|
思路是:在检测到耗电后,先找到有问题的线程,然后堆栈 dump,还原案发现场。
|
||||||
|
|
||||||
|
在上面部分我们知道了线程信息的结构, `thread_basic_info` 中有个记录 CPU 使用率百分比的字段 `cpu_usage`。所以我们可以通过遍历当前线程,判断哪个线程的 CPU 使用率较高,从而找出有问题的线程。然后再 dump 堆栈,从而定位到发生耗电量的代码。详细请看 [3.2](#threadInfo) 部分。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3. 开发阶段针对电量消耗我们能做什么
|
||||||
|
|
||||||
|
CPU 密集运算是耗电量主要原因。所以我们对 CPU 的使用需要精打细算。尽量避免让 CPU 做无用功。对于大量数据的复杂运算,可以借助服务器的能力、GPU 的能力。如果方案设计必须是在 CPU 上完成数据的运算,则可以利用 GCD 技术,使用 `dispatch_block_create_with_qos_class(<#dispatch_block_flags_t flags#>, dispatch_qos_class_t qos_class, <#int relative_priority#>, <#^(void)block#>)()` 并指定 队列的 qos 为 `QOS_CLASS_UTILITY`。将任务提交到这个队列的 block 中,在 QOS_CLASS_UTILITY 模式下,系统针对大量数据的计算,做了电量优化
|
||||||
|
|
||||||
|
除了 CPU 大量运算,I/O 操作也是耗电主要原因。业界常见方案都是将「碎片化的数据写入磁盘存储」这个操作延后,先在内存中聚合吗,然后再进行磁盘存储。碎片化数据先聚合,在内存中进行存储的机制,iOS 提供 `NSCache` 这个对象。
|
||||||
|
|
||||||
|
NSCache 是线程安全的,NSCache 会在达到达预设的缓存空间的条件时清理缓存,此时会触发 `- (**void**)cache:(NSCache *)cache willEvictObject:(**id**)obj;` 方法回调,在该方法内部对数据进行 I/O 操作,达到将聚合的数据 I/O 延后的目的。I/O 次数少了,对电量的消耗也就减少了。
|
||||||
|
|
||||||
|
NSCache 的使用可以查看 SDWebImage 这个图片加载框架。在图片读取缓存处理时,没直接读取硬盘文件(I/O),而是使用系统的 NSCache。
|
||||||
|
|
||||||
|
```objective-c
|
||||||
|
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
|
||||||
|
return [self.memoryCache objectForKey:key];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
|
||||||
|
UIImage *diskImage = [self diskImageForKey:key];
|
||||||
|
if (diskImage && self.config.shouldCacheImagesInMemory) {
|
||||||
|
NSUInteger cost = diskImage.sd_memoryCost;
|
||||||
|
[self.memoryCache setObject:diskImage forKey:key cost:cost];
|
||||||
|
}
|
||||||
|
|
||||||
|
return diskImage;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
可以看到主要逻辑是先从磁盘中读取图片,如果配置允许开启内存缓存,则将图片保存到 NSCache 中,使用的时候也是从 NSCache 中读取图片。NSCache 的 `totalCostLimit、countLimit` 属性,
|
||||||
|
|
||||||
|
`- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;` 方法用来设置缓存条件。所以我们写磁盘、内存的文件操作时可以借鉴该策略,以优化耗电量。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 七、 Crash 监控
|
||||||
|
|
||||||
对于奔溃
|
对于奔溃
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*(base +offset)= 你imp
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 参考资料
|
## 参考资料
|
||||||
@@ -2942,3 +3180,4 @@ self.didCompleteObserver = [[NSNotificationCenter defaultCenter] addObserverForN
|
|||||||
- [iOS堆栈信息解析(函数地址与符号关联)](https://www.jianshu.com/p/df5b08330afd)
|
- [iOS堆栈信息解析(函数地址与符号关联)](https://www.jianshu.com/p/df5b08330afd)
|
||||||
- [Apple-CFNetwork Programming Guide](https://developer.apple.com/library/archive/documentation/Networking/Conceptual/CFNetwork/Introduction/Introduction.html#//apple_ref/doc/uid/TP30001132-CH1-DontLinkElementID_30)
|
- [Apple-CFNetwork Programming Guide](https://developer.apple.com/library/archive/documentation/Networking/Conceptual/CFNetwork/Introduction/Introduction.html#//apple_ref/doc/uid/TP30001132-CH1-DontLinkElementID_30)
|
||||||
|
|
||||||
|
|
||||||
6
Chapter1 - iOS/1.80.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# 打造一个通用、可配置的数据上报 SDK
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. https://mp.weixin.qq.com/s?__biz=MzI4MTY5NTk4Ng==&mid=2247489516&idx=1&sn=97c8b0fd84e5fc5b214539c135919884&source=41#wechat_redirect
|
||||||
|
2.
|
||||||
@@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
## 一、 NSURLProtocol 是什么
|
## 一、 NSURLProtocol 是什么
|
||||||
|
|
||||||
|
> An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
NSURLProtocol 是 Foundation 框架中 URL Loading System 的一部分。它可以让开发者可以在不修改应用内原始请求代码的情况下,去改变 URL 加载的全部细节。换句话说,NSURLProtocol 是一个被 Apple 默许的中间人攻击。
|
NSURLProtocol 是 Foundation 框架中 URL Loading System 的一部分。它可以让开发者可以在不修改应用内原始请求代码的情况下,去改变 URL 加载的全部细节。换句话说,NSURLProtocol 是一个被 Apple 默许的中间人攻击。
|
||||||
|
|
||||||
虽然 NSURLProtocol 叫“Protocol”,却不是协议,而是一个抽象类。
|
虽然 NSURLProtocol 叫“Protocol”,却不是协议,而是一个抽象类。
|
||||||
@@ -324,7 +328,38 @@ didCancelAuthenticationChallenge:challenge];
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 五、 补充内容
|
## 五、 读源码,学习 NSURLProtocol
|
||||||
|
|
||||||
|
iOS 中网络测试框架 [ OHHTTPStubs](https://github.com/AliSoftware/OHHTTPStubs)的实现就是利用了 NSURLProtocol 实现的。
|
||||||
|
|
||||||
|
几个主要类及其功能:HTTPStubsProtocol 拦截网络请求;HTTPStubs 单例管理 HTTPStubsDescriptor 实例对象;HTTPStubsResponse 伪造 HTTP 请求。
|
||||||
|
|
||||||
|
HTTPStubsProtocol 继承自 NSURLProtocol,可以在 HTTP 请求发送之前对 request 进行过滤处理
|
||||||
|
|
||||||
|
```objective-c
|
||||||
|
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
|
||||||
|
{
|
||||||
|
BOOL found = ([HTTPStubs.sharedInstance firstStubPassingTestForRequest:request] != nil);
|
||||||
|
if (!found && HTTPStubs.sharedInstance.onStubMissingBlock) {
|
||||||
|
HTTPStubs.sharedInstance.onStubMissingBlock(request);
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`firstStubPassingTestForRequest` 方法内部会判断请求是否需要被当前对象处理
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
紧接着开始发送网络请求。实际上在 `- (void)startLoading` 方法中可以用任何网络能力去完成请求,比如 NSURLSession、NSURLConnection、AFNetworking 或其他网络框架。OHHTTPStubs 的做法是获取 request、client 对象。如果 HTTPStubs 单例中包含 `onStubActivationBlock` 对象,则执行该 block,然后利用 responseBlock 对象返回一个 HTTPStubsResponse 响应对象。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[参考资料](https://github.com/draveness/analyze/blob/master/contents/OHHTTPStubs/如何进行%20HTTP%20Mock(iOS).md)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 六、 补充内容
|
||||||
|
|
||||||
### 1. 使用 NSURLSession 时的注意事项
|
### 1. 使用 NSURLSession 时的注意事项
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
* [77、iOS 打包系统构建加速](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.77.md)
|
* [77、iOS 打包系统构建加速](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.77.md)
|
||||||
* [78、App 上架包预检](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.78.md)
|
* [78、App 上架包预检](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.78.md)
|
||||||
* [79、深入理解各种锁](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.79.md)
|
* [79、深入理解各种锁](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.79.md)
|
||||||
* [80、打造功能强大的数据上报组件](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.80.md)
|
* [80、打造一个通用、可配置的数据上报 SDK](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.80.md)
|
||||||
* [81、__asm__ 重命名符号](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.81.md)
|
* [81、__asm__ 重命名符号](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.81.md)
|
||||||
* [82、runtime](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.82.md)
|
* [82、runtime](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.82.md)
|
||||||
* [83、NSURLProtocol 应用场景](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.83.md)
|
* [83、NSURLProtocol 应用场景](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.83.md)
|
||||||
|
|||||||
353
Chapter2 - Web FrontEnd/2.40.md
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
# electron-PC端多端融合方案
|
||||||
|
|
||||||
|
> 每天都要写第二天的 todoList。有一天在写的时候突然想到,为了让自己清楚知道自己需要做啥、做了多少、还剩多少没做,想写一个电脑端程序,在技术选型的时候就选了 electron。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 一、 方案选型
|
||||||
|
|
||||||
|
3天时间写了个 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 变化较多、体积限制不大、开发效率高的场景。
|
||||||
|
|
||||||
|
拿 C 系列写应用程序的体验不好,累到奔溃。再加上有 Hybrid、React Native、iOS、Vue、React 等开发经验,electron 是不二选择。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 二、 Quick start
|
||||||
|
执行下面命令快速体验 Hello world,也是官方给的一个 Demo。
|
||||||
|
```shell
|
||||||
|
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 中的逻辑。
|
||||||
|
|
||||||
|
```Javascript
|
||||||
|
// Modules to control application life and create native browser window
|
||||||
|
const {app, BrowserWindow} = require('electron')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
function createWindow () {
|
||||||
|
// Create the browser window.
|
||||||
|
const mainWindow = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'preload.js')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// and load the index.html of the app.
|
||||||
|
mainWindow.loadFile('index.html')
|
||||||
|
|
||||||
|
// Open the DevTools.
|
||||||
|
mainWindow.webContents.openDevTools()
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method will be called when Electron has finished
|
||||||
|
// initialization and is ready to create browser windows.
|
||||||
|
// Some APIs can only be used after this event occurs.
|
||||||
|
app.whenReady().then(createWindow)
|
||||||
|
|
||||||
|
// Quit when all windows are closed.
|
||||||
|
app.on('window-all-closed', function () {
|
||||||
|
// On macOS it is common for applications and their menu bar
|
||||||
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
|
if (process.platform !== 'darwin') app.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', function () {
|
||||||
|
// On macOS it's common to re-create a window in the app when the
|
||||||
|
// dock icon is clicked and there are no other windows open.
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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 等。
|
||||||
|
|
||||||
|
app 对象在 `whenReady` 的时候执行 createWindow 方法。内部创建了一个 `BrowserWindow` 对象,指定了大小和功能设置(webPreferences Object (可选) - 网页功能的设置。其中 preload String (可选) - 在页面运行其他脚本之前预先加载指定的脚本 无论页面是否集成 Node, 此脚本都可以访问所有 Node API 脚本路径为文件的绝对路径。 当 node integration 关闭时, 预加载的脚本将从全局范围重新引入 node 的全局引用标志)。
|
||||||
|
|
||||||
|
`mainWindow.loadFile('index.html')` 加载了同级目录下的 index.html 文件。也可以加载服务器资源(部署好的网页),比如 `win.loadURL('https://github.com/FantasticLBP')`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// All of the Node.js APIs are available in the preload process.
|
||||||
|
// It has the same sandbox as a Chrome extension.
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const replaceText = (selector, text) => {
|
||||||
|
const element = document.getElementById(selector)
|
||||||
|
if (element) element.innerText = text
|
||||||
|
}
|
||||||
|
console.table(process)
|
||||||
|
console.info(process.versions)
|
||||||
|
for (const type of ['chrome', 'node', 'electron']) {
|
||||||
|
replaceText(`${type}-version`, process.versions[type])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
接下去看看 preload.js。在页面运行其他脚本之前预先加载指定的脚本,无论页面是否集成 Node, 此脚本都可以访问所有 Node API 脚本路径为文件的绝对路径。Demo 中的逻辑很简单,就是读取 process.versions 对象中的 node、chrome、electron 的版本信息并展示出来。
|
||||||
|
|
||||||
|
|
||||||
|
index.html 中的内容就是主页面显示的内容。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 三、 实现原理
|
||||||
|
|
||||||
|
electron 分为渲染进程和主进程。 😂 和 Native 中的概念不一样的是 electron 中主进程只有一个,渲染进程(也就是 UI 进程) 有多个。主进程在后台运行,每次打开一个界面,会新开一个新的渲染进程。
|
||||||
|
|
||||||
|
- 渲染进程: 用户看到的 web 界面就是由渲染进程绘制出来的,包括 html、css、js。
|
||||||
|
- 主进程:electron 运行 package.json 中的 main.js 脚本的进程被称为主进程。在主进程中运行的脚本通过创建 web 页面来展示用户界面。一个 electron 应用程序总是只有一个主进程。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1. Chromium 架构
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
这张图是 chromium 多进程架构图。早在2007年之前,市面上的浏览器都是单进程架构。单进程浏览器指的是浏览器的所有功能模块都是运行在同一个进程里的,这些模块包括网络、插件、Javascript 运行环境、渲染引擎和页面等。如此复杂的功能都在一个进程内运行,所以导致浏览器出现不稳定、不安全、不流畅等问题。
|
||||||
|
|
||||||
|
多进程架构的浏览器解决了上述问题,至于如何解决的以后的文章会专门讲解,不是本文的主要内容。
|
||||||
|
|
||||||
|
简单描述下。
|
||||||
|
|
||||||
|
- 主进程中的 `RenderProcessHost` 和 render 进程中的 `RenderProcess` 是用来处理进程间通信的(IPC)。
|
||||||
|
- Render 进程中的 RenderView 内容基于 WebKit 排版展示出来的
|
||||||
|
- Render 进程中的 `ResourceDispatcher` 是用来处理资源请求的。Render 进程中如果有请求则创建一个请求 ID,转发到 IPC,由 Browser 进程中处理后返回
|
||||||
|
- Chromium 是多进程架构,包括一个主进程,多个渲染进程
|
||||||
|
|
||||||
|
对于 chromium 多进程架构感兴趣的可以点击这个链接查看更多资料-[Multi-process Architecture](http://www.chromium.org/developers/design-documents/multi-process-architecture)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2. Electron 架构
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Electron 架构和 Chromium 架构类似,也是具有1个主进程和多个渲染进程。但是也有区别
|
||||||
|
|
||||||
|
- 在各个进行中暴露了 Native API ,提供了 Native 能力。
|
||||||
|
- 引入了 Node.js,所以可以使用 Node 的能力
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**技术难点**:由于 Electron 内部整合了 Chromium 和 Node.js,主线程在某个时刻只可以执行一个事件循环,但是2者的事件循环机制不一样,Node.js 的事件循环基于 [libuv](https://github.com/libuv/libuv),但是 Chromium 基于 [message bump](https://chromium.googlesource.com/chromium/chromium/+/master/base/message_pump.h)。
|
||||||
|
|
||||||
|
所以 Electron 原理的重点就是「如何整合事件循环」。2种思路
|
||||||
|
|
||||||
|
- Chromium 集成到 Node.js 中:用 libuv 实现 messagebump(Node-Webkit 就是这么干的,缺点挺多)
|
||||||
|
- Node.js 集成到 Chromium 中(Electron 所采用的方式)
|
||||||
|
|
||||||
|
后来随着 libuv 引入 backend_fd 的概念,相当于是 libuv 轮询事件的文件描述符。通过轮训 backend_fd 可以知道 libuv 的新事件。所以 Electron 采取的做法就是将 Node.js 集成到 Chromium 中。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
上图描述了 Node.js 如何融入到 Chromium 中。描述下原理
|
||||||
|
|
||||||
|
- Electron 新起一个安全线程去轮训 backend_fd
|
||||||
|
- 当检测到一个新的 backend_fd,也就是一个新的 Node.js 事件之后,通过 PostTask 转发到 Chromium 的事件循环中
|
||||||
|
|
||||||
|
上述2个步骤完成了 Electron 的事件循环。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 四、 如何调试
|
||||||
|
|
||||||
|
调试分为主进程调试和渲染进程调试。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1. 渲染进程调试
|
||||||
|
|
||||||
|
看到 Demo 工程中执行 `npm start` 之后可以看到主界面,Mac 端快捷键 `comand + option + i`,唤出调试界面,类似于 chrome 下的 devtools。其实就是无头浏览器的那些东西。或者在代码里打开调试模式 `mainWindow.webContents.openDevTools()` 。
|
||||||
|
|
||||||
|
工程采用 Electron + Vue 技术,下面截图 Vue-devtools 很方便查看 Vue 组件层级等 Vue 相关的调试
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 2. 主进程调试方式
|
||||||
|
|
||||||
|
主进程调试有2种方法
|
||||||
|
|
||||||
|
方法一:利用 chrome inspect 功能进行调试
|
||||||
|
|
||||||
|
- 需要在启动的时候给 `package.json` 中的 scripts 节点下的 start 命令加上调试开关
|
||||||
|
```shell
|
||||||
|
--inspect=[port]
|
||||||
|
// electrom --inspect=8001 yourApp
|
||||||
|
```
|
||||||
|
- 然后打开浏览器,在地址栏输入 `chrome://inspect`
|
||||||
|
- 点击 `configure`,在弹出的面板中填写需要调试的端口信息
|
||||||
|

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

|
||||||
|
|
||||||
|
方法二:利用 VS Code 调试 electron 主进程。
|
||||||
|
|
||||||
|
- 在 VS Code 的左侧菜单栏,第四个功能模块就是调试,点击调试,弹出对话框让你添加调试配置文件 `launch.json`
|
||||||
|
|
||||||
|
- 编辑 launch.json 的文件内容。如下
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug main process",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
|
||||||
|
},
|
||||||
|
"args": ["."],
|
||||||
|
"outputCapture": "std"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 在调试模块电机绿色小三角,会运行程序,可以添加断点信息。整体界面如下所示。可以单步调试、可以暂停、鼠标移上去可以看到对象的各种信息。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3. 主进程调试之 hot reload
|
||||||
|
|
||||||
|
Electron 的渲染进程中的代码改变了,使用 Command + R 可以刷新,但是修改主进程中的代码则必须重新启动 `yarn run dev` 。效率低下,所以为了提升开发效率,有必要解决这个问题
|
||||||
|
|
||||||
|
Webpack 有一个 api: `watch-run`,可以针对代码文件检测,有变化则 Restart
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 五、 开发 tips
|
||||||
|
|
||||||
|
1. 或许会为网页添加事件代码,但是页面看到的内容是渲染进程,所以事件相关的逻辑代码应该写在 html 引入的 `render.js` 中。
|
||||||
|
|
||||||
|
2. 在 `render.js` 中写 Node 代码的时候需要在 `main.js` 初始化 BrowserWindow 的时候,在 webPreferences 节点下添加 `nodeIntegration: true` 。不然会报错:renderer.js:9 Uncaught ReferenceError: process is not defined。
|
||||||
|
|
||||||
|
3. 从 Chrome Extenstion V2 开始,不允许执行任何 inline javascript 代码在 html 中。不支持以内联方式写事件绑定代码。比如 `<button onclick="handleCPU">查看</button>`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
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 等。
|
||||||
|
|
||||||
|
5. 在工程化角度看,使用 yarn 比 npm 好一些,因为 yarn 会缓存已经安装过的依赖,其他项目只要发现存在缓存,则读取本地的包依赖,会更加快速。
|
||||||
|
|
||||||
|
6. 在使用 Vue、React 开发 electron 应用时,可以使用 npm 或 yarn install 包,也可以使用 electron-vue 脚手架工具。
|
||||||
|
|
||||||
|
```shell
|
||||||
|
vue init simulatedgreg/electron-vue my-project
|
||||||
|
cd my-project
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
7. 开发完毕后需要设置应用程序的图标信息、版本号等,打包需要指定不同的平台。
|
||||||
|
|
||||||
|
8. 新开项目创建后会报错.
|
||||||
|
- `ERROR in Template execution failed: ReferenceError: process is not defined`。解决办法是使用 nvm 将 node 版本将为 10。
|
||||||
|
- 报错如下
|
||||||
|
```shell
|
||||||
|
┏ Electron -------------------
|
||||||
|
|
||||||
|
[11000:0615/095124.922:ERROR:CONSOLE(7574)] "Extension server error: Object not found: <top>", source: chrome-devtools://devtools/bundled/inspector.js (7574)
|
||||||
|
|
||||||
|
┗ ----------------------------
|
||||||
|
```
|
||||||
|
解决办法是在 main/index.dev.js 修改代码
|
||||||
|
```javascript
|
||||||
|
- require('electron-debug')({ showDevTools: true });
|
||||||
|
+ // NB: Don't open dev tools with this, it is causing the error
|
||||||
|
+ require('electron-debug')();
|
||||||
|
```
|
||||||
|
在 In main/index.js in the createWindow() function:
|
||||||
|
```javascript
|
||||||
|
mainWindow.loadURL(winURL);
|
||||||
|
+ // Open dev tools initially when in development mode
|
||||||
|
+ if (process.env.NODE_ENV === "development") {
|
||||||
|
+ mainWindow.webContents.on("did-frame-finish-load", () => {
|
||||||
|
+ mainWindow.webContents.once("devtools-opened", () => {
|
||||||
|
+ mainWindow.focus();
|
||||||
|
+ });
|
||||||
|
+ mainWindow.webContents.openDevTools();
|
||||||
|
+ });
|
||||||
|
+ }
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
9. Electron 多窗口与单窗口应用区别
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
10. 知道 Electron 开发原理,所以大部分时间是在写前端代码。所以根据团队技术沉淀、选择对应的前端框架,比如 Vue、React、Angular。
|
||||||
|
|
||||||
|
11. 也许开发并不难,难在视觉和 UX。很多写过网页的开发者或者以前端的视觉去写 Electron App 难免会写出网页版的桌面应用程序,说难听点,就是四不像 😂。所以需要转变想法,这是在开发桌面应用程序。
|
||||||
|
|
||||||
|
12. Electron 和 Web 开发相比,各自有侧重点
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 六、 技术体系搭建
|
||||||
|
|
||||||
|
其实一个技术本身的难易程度并不是能否在自己企业、公司、团队内顺利使用的唯一标尺,其配套的 CI/CD、APM、埋点系统、发布更新、灰度测试等能否与现有的系统以较小成本融合才是很大的决定要素。因为某个技术并不是非常难,要是大多数开发者觉得很难,那它设计上就是失败的。
|
||||||
|
|
||||||
|
### 1. 构建
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2. 工程解耦
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3. 问题定位
|
||||||
|
|
||||||
|
Electron 提供的 crash 信息进行包装。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
引申资料
|
||||||
|
|
||||||
|
- [electron-vue架构解析-开发环境启动流程分析](https://dushaofeng.github.io/2018/06/08/electron-vue架构解析-开发环境启动流程分析/)
|
||||||
|
|
||||||
@@ -41,4 +41,4 @@
|
|||||||
* [37、前端工程化与 CI、CD](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.37.md)
|
* [37、前端工程化与 CI、CD](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.37.md)
|
||||||
* [38、npm 改进之工程化](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.38.md)
|
* [38、npm 改进之工程化](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.38.md)
|
||||||
* [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、electron-PC端多端融合方案](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.40.md)
|
||||||
|
|||||||
@@ -105,4 +105,17 @@ pip3 install requests
|
|||||||
python3 coronavirus.py
|
python3 coronavirus.py
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 三、 疑问点
|
||||||
|
|
||||||
|
当我在写完 markdown 文件后,执行快捷指令打开 Typora,报错了,信息为 `command not found`。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
os.system('OpenMDPreview ./coronavirus.md')
|
||||||
|
```
|
||||||
|
|
||||||
|
当时以为是我设置 iterm2 的 `.zshrc` 文件内 alias OpenMDPreview,但是没给 `terminal.app` 设置,为了验证猜想,给 `.bash_profile` 设置 alias 后还是无效。谷歌一下找到了问题,可以查看这个[链接](https://stackoverflow.com/questions/42677066/python-os-system-issue-sh-1-command-not-found-command-works-interactivel?answertab=active#tab-top)
|
||||||
|
|||||||
165
Chapter7 - Geek Talk/7.21.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# 如何画架构图
|
||||||
|
|
||||||
|
> 在软件开发生涯中,我们经常需要写技术文档,或者参与技术方案评审。不管是项目文档、技术文档、实施文档、技术方案文档,都会有架构图。本文谈谈架构图相关的东西。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 为什么关心架构图
|
||||||
|
|
||||||
|
很多工程师在日常写代码的时候也许只钻研技术的实现,几乎比较少关心宏观层面或者架构层面的东西。出于好学求知,可能看到了工程做的架构,但是不曾去用架构图的方式去总结梳理。
|
||||||
|
|
||||||
|
举个例子,当工程师进阶到一定阶段,参与到技术方案的设计与评审,你会发现在评审的时候,方案的设计者拿出 PPT,里面就是一些背景、现状、调研技术方案、结论等的关键词,很重要的一点是几乎每次评审都会看到几张架构图。看整个方案的成败都在这几张架构图上。
|
||||||
|
|
||||||
|
另一个例子是,当你去做管理,你给下属或者团队成员讲某个方案时,我所经历的架构师都是拿架构图讲的,没见过纯文字讲的。
|
||||||
|
|
||||||
|
举个极端的例子,你去面试,你的简历上描述了之前公司的工作内容。面试官对你的某个内容感兴趣时会和你细聊,如果说当时自己是该模块的 owner,他一般会让你画一个架构图,来判断你的设计是否合理、判断你是不是真的是该方案的设计者。
|
||||||
|
|
||||||
|
所以,不管是工作内容还是个人技能的提升,还是走向更高阶的工程师级别,学会画架构图是必经之路。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 基础知识
|
||||||
|
### 1. 什么是架构
|
||||||
|
|
||||||
|
架构就是对系统中的实体,以及实体与实体之间的关系所进行的抽象描述,是一系列的决策。
|
||||||
|
架构是结构和愿景。
|
||||||
|
|
||||||
|
系统架构是概念的体现,是对物/信息的功能与形式元素之间的对应情况所做的分配,是对元素之间的关系以及元素同周边环境之间的关系所做的定义。
|
||||||
|
|
||||||
|
做好架构是个复杂的任务,也是个很大的话题,本篇就不做深入了。有了架构之后,就需要让干系人理解、遵循相关决策。
|
||||||
|
|
||||||
|
### 2. 什么是架构图
|
||||||
|
|
||||||
|
系统架构图是为了抽象的表示软件系统的整体轮廓和各个组件之间的相互关系和约束边界,以及软件系统的物理部署和软件系统的演进方向的整体视图。
|
||||||
|
|
||||||
|
### 3. 架构图的作用
|
||||||
|
|
||||||
|
一图胜千言。要让干系人理解、遵循架构决策,就需要把架构信息传递出去。架构图就是一个很好的载体。那么,画架构图是为了:
|
||||||
|
|
||||||
|
- 解决沟通障碍
|
||||||
|
- 达成共识
|
||||||
|
- 减少歧义
|
||||||
|
|
||||||
|
### 4. 架构图分类
|
||||||
|
|
||||||
|
搜集了很多资料,分类有很多,有一种比较流行的是「4+1视图:场景视图、逻辑视图、物理视图、处理流程视图、开发视图。
|
||||||
|
|
||||||
|
- 场景视图
|
||||||
|
用于描述系统的参与者与功能用例间的关系,反映系统的最终需求和交互设计,通常由用例图表示。
|
||||||
|

|
||||||
|
|
||||||
|
- 逻辑视图
|
||||||
|
用于描述系统软件功能拆解后的组件关系,组件约束和边界,反映系统整体组成与系统如何构建的过程,通常由UML的组件图和类图来表示。
|
||||||
|

|
||||||
|
|
||||||
|
- 物理视图
|
||||||
|
用于描述系统软件到物理硬件的映射关系,反映出系统的组件是如何部署到一组可用的计算机节点上,用于指导软件系统的部署实施过程。
|
||||||
|

|
||||||
|
|
||||||
|
- 处理流程视图
|
||||||
|
用于描述系统软件组件之间的通信时序,数据的输入输出,反映系统的功能流程与数据流程,通常由时序图和流程图表示。
|
||||||
|

|
||||||
|
|
||||||
|
- 开发视图
|
||||||
|
用于描述系统的模块划分和组成,以及细化到内部包的组成设计,用于开发人员,反映系统的开发实施过程。
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
以上5种架构图,从不同角度表示一个软件系统的不同特征,组合搭配作为架构蓝图描述系统架构。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 什么是好的架构图
|
||||||
|
|
||||||
|
上面已经知道架构图分为「4+1」种,需要全部画吗?答案是不一定,我们需要明确受众,再想清楚需要给他们传递什么信息。所以根据受众、传递信息的不同去选择何种架构图。将信息用图的形式清晰的表达出来。
|
||||||
|
|
||||||
|
好的架构图的标准是:合适的受众有没有准确的接收到需要传递的信息。
|
||||||
|
|
||||||
|
从受众角度看,一个好的架构图是不需要解释的,跟优秀的代码一样,它是自描述的,硬切具备一致性和准确性。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 如何画架构图
|
||||||
|
|
||||||
|
很多人在画架构图的时候经常搞不清楚方框、虚线、实线、箭头、菱形等的使用场景,这其实是搞不清楚他们的真正含义。按照上面的示例图那样画图最好了,但是要求你经过大量的练习、记忆才可以。从网上看到一种简单画法。
|
||||||
|
|
||||||
|
下面的案例来自 C4 官网,然后加上了一些我们的理解,来看看如何更好的表达软件架构
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1. 语境图(System Context Diagram)
|
||||||
|

|
||||||
|
|
||||||
|
这是一个想象的待建设的互联网银行系统,它使用外部的大型机银行系统存取客户账户、交易信息,通过外部电邮系统给客户发邮件。可以看到,非常简单、清晰,相信不需要解释,都看的明白,里面包含了需要建设的系统本身、系统的客户、和这个系统有交互的周边系统。
|
||||||
|
|
||||||
|
用途
|
||||||
|
这样一个简单的图,告诉我们:要构建的系统是什么、用户是谁、谁会用他它、它要如何融入已有的 IT 环境。这个图的受众可以说开发团队内的内部成员、外部的非技术人员。
|
||||||
|
|
||||||
|
怎么画
|
||||||
|
中间是自己的系统,周围是用户、其他与之相互作用的系统。这个图的关键是梳理清楚待建设的系统的用户和高层次的依赖,梳理清楚了画可能需要几分钟时间就好了。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2. 容器图(Container Diagram)
|
||||||
|
容器图是把语境图里待建设的系统做了一个展开。
|
||||||
|

|
||||||
|
|
||||||
|
上图中,除了用户和外围系统,要建设的系统包括一个基于 java\spring mvc 的 web 应用提供系统的功能入口,基于 xamarin 架构的手机app 提供手机端的功能入口,一个基于 java 的 api 应用提供服务,一个 mysql 数据库用于存储,各个应用之间的交互都在箭头线上写明了。
|
||||||
|
|
||||||
|
看这张图的时候,不会去关注到图中是直角方框还是圆角方框,不会关注是实线箭头还是虚线箭头,甚至箭头的指向也没有引起太多注意。
|
||||||
|
|
||||||
|
我们有许多的画图方式,都对框、线的含义做了定义,这就需要画图的人和看图的人都清晰的理解这些定义,才能读全图里的信息,而现实是,这往往是非常高的一个要求,所以,很多图只能看个大概的含义。
|
||||||
|
|
||||||
|
用途
|
||||||
|
这个图的受众可以是团队内部或外部的开发人员,也可以是运维人员。用途可以罗列为:
|
||||||
|
- 展现了软件系统的整体形态
|
||||||
|
- 体现了高层次的技术决策
|
||||||
|
- 系统中的职责是如何分布的,容器间的是如何交互的
|
||||||
|
- 告诉开发者在哪里写代码
|
||||||
|
|
||||||
|
怎么画
|
||||||
|
用一个框图来表示,内部可能包括名称、技术选择、职责,以及这些框图之间的交互,如果涉及外部系统,最好明确边界。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3. 组件图(Component Diagram)
|
||||||
|

|
||||||
|
|
||||||
|
组件图是把某个容器进行展开,描述其内部的模块。
|
||||||
|
|
||||||
|
用途
|
||||||
|
这个图主要是给内部开发人员看的,怎么去做代码的组织和构建。其用途有:
|
||||||
|
- 描述了系统由哪些组件/服务组成
|
||||||
|
- 厘清了组件之间的关系和依赖
|
||||||
|
- 为软件开发如何分解交付提供了框架
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 4. 类图(Code/Class Diagram)
|
||||||
|

|
||||||
|
|
||||||
|
类图主要是给开发人员看的。用于如何具体设计类以及类的接口。达到多端统一、规范的目的。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 案例
|
||||||
|
|
||||||
|
下面是内部的一个实时数据工具的架构图。作为一个应该自描述的架构图,这里不多做解释了。如果有看不明白的,那肯定是还画的不够好。
|
||||||
|

|
||||||
|
|
||||||
|
画好架构图可能有许多方法论,本篇主要介绍了 C4 这种方法,C4 的理论也是不断进化的。但不论是哪种画图方法论,我们回到画图初衷,更好的交流,我们在画的过程中不必被条条框框所限制。简而言之,画之前想好:画图给谁看,看什么,怎么样不解释就看懂。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
参考资料
|
||||||
|
[机器之心:如何画出一张合格的技术架构图?](https://www.jiqizhixin.com/articles/2019-04-11-22?from=synced&keyword=架构图)
|
||||||
@@ -20,4 +20,5 @@
|
|||||||
* [16、如何写一份夺目的简历](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.17.md)
|
* [16、如何写一份夺目的简历](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.17.md)
|
||||||
* [17、一套开发规范](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.18.md)
|
* [17、一套开发规范](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.18.md)
|
||||||
* [18、云服务器靠谱推荐](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.19.md)
|
* [18、云服务器靠谱推荐](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.19.md)
|
||||||
* [19、规范化团队 git 提交信息](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.20.md)
|
* [19、规范化团队 git 提交信息](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.20.md)
|
||||||
|
* [20、如何画架构图](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.21.md)
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
* [77、iOS 打包系统构建加速](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.77.md)
|
* [77、iOS 打包系统构建加速](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.77.md)
|
||||||
* [78、上架包预检](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.78.md)
|
* [78、上架包预检](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.78.md)
|
||||||
* [79、深入理解各种锁](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.79.md)
|
* [79、深入理解各种锁](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.79.md)
|
||||||
* [80、打造功能强大的数据上报组件](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.80.md)
|
* [80、打造一个通用、可配置的数据上报 SDK](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.80.md)
|
||||||
* [81、__asm__ 重命名符号](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.81.md)
|
* [81、__asm__ 重命名符号](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.81.md)
|
||||||
* [82、runtime](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.82.md)
|
* [82、runtime](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.82.md)
|
||||||
* [83、NSURLProtocol 应用场景](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.83.md)
|
* [83、NSURLProtocol 应用场景](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.83.md)
|
||||||
@@ -128,6 +128,7 @@
|
|||||||
* [37、前端工程化与 CI、CD](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.37.md)
|
* [37、前端工程化与 CI、CD](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.37.md)
|
||||||
* [38、npm 改进之工程化](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.38.md)
|
* [38、npm 改进之工程化](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.38.md)
|
||||||
* [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、electron-PC端多端融合方案](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter2%20-%20Web%20FrontEnd/2.40.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)
|
||||||
@@ -175,7 +176,7 @@
|
|||||||
* [17、一套开发规范](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.18.md)
|
* [17、一套开发规范](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.18.md)
|
||||||
* [18、云服务器靠谱推荐](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.19.md)
|
* [18、云服务器靠谱推荐](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.19.md)
|
||||||
* [19、规范化团队 git 提交信息](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.20.md)
|
* [19、规范化团队 git 提交信息](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.20.md)
|
||||||
|
* [20、如何画架构图](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.21.md)
|
||||||
|
|
||||||
|
|
||||||
* [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)
|
||||||
|
|||||||
BIN
assets/2020-04-13-NSURLConnectionHook.jpeg
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
assets/2020-04-13-NSURLSessionHook.jpeg
Normal file
|
After Width: | Height: | Size: 365 KiB |
BIN
assets/2020-04-13-isaSwizzling.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
assets/2020-04-18-URL-loading-system.png
Normal file
|
After Width: | Height: | Size: 176 KiB |
BIN
assets/2020-04-21-electromDebugInVSCode.png
Normal file
|
After Width: | Height: | Size: 879 KiB |
BIN
assets/2020-04-21-electron-MainProcessInspect.png
Normal file
|
After Width: | Height: | Size: 692 KiB |
BIN
assets/2020-04-21-electron-packagejson.png
Normal file
|
After Width: | Height: | Size: 342 KiB |
BIN
assets/2020-04-21-electronChromeInspect.png
Normal file
|
After Width: | Height: | Size: 285 KiB |
BIN
assets/2020-04-24-ArchDiagram1.jpeg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
assets/2020-04-24-ArchDiagram10.jpeg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
assets/2020-04-24-ArchDiagram2.jpeg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
assets/2020-04-24-ArchDiagram3.jpeg
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
assets/2020-04-24-ArchDiagram4.jpeg
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
assets/2020-04-24-ArchDiagram5.jpeg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
assets/2020-04-24-ArchDiagram6.jpeg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
assets/2020-04-24-ArchDiagram7.jpeg
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
assets/2020-04-24-ArchDiagram8.jpeg
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
assets/2020-04-24-ArchDiagram9.jpeg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
assets/2020-05-03-ChromiumArch.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
assets/2020-05-03-ChromiumCommunicateWithNode.png
Normal file
|
After Width: | Height: | Size: 840 KiB |
BIN
assets/2020-05-03-ElectronArch.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
assets/2020-05-03-ElectronUsage.png
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
assets/2020-05-04-ElectronAndWeb.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
assets/2020-05-04-ElectronBuild.png
Normal file
|
After Width: | Height: | Size: 820 KiB |
BIN
assets/2020-05-04-ElectronCode.png
Normal file
|
After Width: | Height: | Size: 610 KiB |
BIN
assets/2020-05-04-ElectronCrash.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
assets/2020-05-04-ElectronMainProcessHotReload.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/2020-05-04-ElectronVue.png
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
assets/2020-05-04-TodoApp1.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
assets/2020-05-04-TodoApp2.png
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
assets/2020-05-04-TodoApp3.png
Normal file
|
After Width: | Height: | Size: 144 KiB |