docs: APM 监控部分

This commit is contained in:
杭城小刘
2020-04-06 22:35:27 +08:00
parent fb73c0acf4
commit 32d90b27fc
34 changed files with 2702 additions and 126 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -35,7 +35,6 @@ Web领域使用IconFont类似的技术已经多年当我在15年接触BootStr
1. 选择好之后在购物车查看,然后点击下载代码 1. 选择好之后在购物车查看,然后点击下载代码
2. 打开下载好的文件其机构如下我们在iOS项目开发过程中使用unicode的形式使用IconFont,所以打开demo\_unicode.html 2. 打开下载好的文件其机构如下我们在iOS项目开发过程中使用unicode的形式使用IconFont,所以打开demo\_unicode.html
![下载文件目录结构](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2017-05-28-iconfontWorkDirectory.png) ![下载文件目录结构](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2017-05-28-iconfontWorkDirectory.png)

View File

@@ -5,12 +5,90 @@
`NS_BLOCK_ASSERTIONS ` 表明在 Release 状态下过滤 NSAssert只需要这一个条件就可以过滤掉 NSAssert。 `NS_BLOCK_ASSERTIONS ` 表明在 Release 状态下过滤 NSAssert只需要这一个条件就可以过滤掉 NSAssert。
方法:在 “Build Settings” 下搜索 **Preprocessor Macros** ,然后在 Release 下面添加 NS_BLOCK_ASSERTIONS 方法:在 “Build Settings” 下搜索 **Preprocessor Macros** ,然后在 Release 下面添加 NS_BLOCK_ASSERTIONS
![](/Users/liubinpeng/Desktop/Github/knowledge-kit/assets/WX20180830-100631@2x.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WX20180830-100631@2x.png)
### BreakPoint ### BreakPoint
#### 分类 #### 分类
Breakpoint 分为 Normal Breakpoint、Exception Breakpoint、OpenGL ES Error Breakpoint、Symbolic Breakpoint、Test Failure breakpoint、WatchPoint。可以按照具体的情景使用不同类型的 Breakpoint ,解决问题为根本 Breakpoint 分为 Normal Breakpoint、Exception Breakpoint、OpenGL ES Error Breakpoint、Symbolic Breakpoint、Test Failure breakpoint、WatchPoint。可以按照具体的情景使用不同类型的 Breakpoint
### NSAssert 与 dispatch_once
开发中非常常见 NSAssert尤其是在 SDK 和类库的开发中,使用断言帮助在开发阶段发现问题,督促达到预期的结果。
NSAssert 的本质就是产生一个 ExceptionException 发生触发 `objc_exception_throw` 这个 c 函数。
![NSAssert 断言](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-0311-NSAssert.png)
callback 信息如下
```Objective-c
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23c7127e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff513fbb20 objc_exception_throw + 48
2 CoreFoundation 0x00007fff23c70ff8 +[NSException raise:format:arguments:] + 88
3 Foundation 0x00007fff256e9b51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
4 TEst 0x0000000106edfeef -[AppDelegate application:didFinishLaunchingWithOptions:] + 287
5 UIKitCore 0x00007fff48089ad8 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 232
6 UIKitCore 0x00007fff4808b460 -[UIApplication _callInitializationDelegatesWithActions:forCanvas:payload:fromOriginatingProcess:] + 3980
7 UIKitCore 0x00007fff48090f05 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1226
8 UIKitCore 0x00007fff477c57a6 -[_UISceneLifecycleMultiplexer completeApplicationLaunchWithFBSScene:transitionContext:] + 179
9 UIKitCore 0x00007fff4808d514 -[UIApplication _compellApplicationLaunchToCompleteUnconditionally] + 59
10 UIKitCore 0x00007fff4808d813 -[UIApplication _run] + 754
11 UIKitCore 0x00007fff48092d4d UIApplicationMain + 1621
12 TEst 0x0000000106ee0144 main + 116
13 libdyld.dylib 0x00007fff5227ec25 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
```
可以清楚看到当断言失败的时候Xcode 可以精确定位到 NSAssert 有问题的那行代码,这种情况下是有源代码。
其实有些场景下发生异常是定位不到真正产生异常的地方。比如当 App 规模较大,为了提高构建速度,很多人将 Pod 打成静态库,但是这种情况下产生的异常不会被精确定位到产生问题的行。如果断言在 GCD 的 block 中,而且上下文中没有源码,则也定位不到。
输出信息如下:
```Objective-c
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23c7127e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff513fbb20 objc_exception_throw + 48
2 CoreFoundation 0x00007fff23c70ff8 +[NSException raise:format:arguments:] + 88
3 Foundation 0x00007fff256e9b51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
4 TEst 0x000000010f242e95 __57-[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke + 229
5 libdispatch.dylib 0x000000010f55fdd4 _dispatch_call_block_and_release + 12
6 libdispatch.dylib 0x000000010f560d48 _dispatch_client_callout + 8
7 libdispatch.dylib 0x000000010f56ede6 _dispatch_main_queue_callback_4CF + 1500
8 CoreFoundation 0x00007fff23bd4049 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
9 CoreFoundation 0x00007fff23bceca9 __CFRunLoopRun + 2329
10 CoreFoundation 0x00007fff23bce066 CFRunLoopRunSpecific + 438
11 GraphicsServices 0x00007fff384c0bb0 GSEventRunModal + 65
12 UIKitCore 0x00007fff48092d4d UIApplicationMain + 1621
13 TEst 0x000000010f243134 main + 116
14 libdyld.dylib 0x00007fff5227ec25 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
```
给 Xcode 添加 **Symbolic Breakpoint** 类型的断点Symbol 为 `objc_exception_throw`,就可以在 Xcode 的左侧堆栈中看到了。
![objc_exception_throw](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-03-11-objc_exception_throw.png)
其实 GCD 的 方法 **_dispatch_client_callout** 去查看下有没有猫腻,再从这里找到 libdispatch 的代码https://opensource.apple.com/tarballs/libdispatch/。
```Objective-c
// object.m
#undef _dispatch_client_callout
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
@try {
return f(ctxt);
}
@catch (...) {
objc_terminate();
}
}
```
发现 _dispatch_client_callout 把 GCD block 中的 OC Exception try catch 捕获了,然后调用 objc_terminate导致了 CallStack 断开。
有个情景,工作 Crash 到 dispatch_once 这里了,因为 dispatch_once 中的代码 throw OC 异常。一般大公司初期这种情况经常遇到,后期一般都会针对断言专门开发一些代码用来定位 Owner。

View File

@@ -57,9 +57,9 @@ Hybrid 架构设计的第一要考虑的问题就是如何设计前端与 Native
[iOS调试技巧](https://www.jianshu.com/p/f430caa81fa8) [iOS调试技巧](https://www.jianshu.com/p/f430caa81fa8)
Android 调试技巧: Android 调试技巧:
1. App 中开启 Webview 调试(WebView.setWebContentsDebuggingEnabled(true); ) - App 中开启 Webview 调试(WebView.setWebContentsDebuggingEnabled(true); )
2. chrome 浏览器输入 chrome://inspect/#devices 访问可以调试的 webview 列表 - chrome 浏览器输入 chrome://inspect/#devices 访问可以调试的 webview 列表
3. 需要翻墙的环境 - 需要翻墙的环境
![结构](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HybridStructure.jpg) ![结构](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HybridStructure.jpg)

View File

@@ -1,4 +1,4 @@
# Pod # cocoapods 相关小技巧
## 1. 组件的地址 ## 1. 组件的地址
@@ -202,4 +202,69 @@ install! 'cocoapods', generate_multiple_pod_projects: true
```shell ```shell
sudo gem uninstall cocoapods-core cocoapods cocoapods-deintegrate cocoapods-downloader cocoapods-plugins cocoapods-search cocoapods-stats cocoapods-trunk cocoapods-try coderay colored2 concurrent-ruby cocoapods-clean sudo gem uninstall cocoapods-core cocoapods cocoapods-deintegrate cocoapods-downloader cocoapods-plugins cocoapods-search cocoapods-stats cocoapods-trunk cocoapods-try coderay colored2 concurrent-ruby cocoapods-clean
sudo gem install cocoapods sudo gem install cocoapods
``` ```
## 14. pod 拉取代码太慢
在平时开发的时候,使用 cocoapods 拉取代码经常会比较慢,偶尔一次两次也还可以忍受,但是某些哭每次都很慢,而且 install 的时候工程被修改,没办法编译开发。所以需要想个办法解决这种问题。
其实知道 cocoapods 的工作原理的话,我们可以投机取巧。做过 SDK 开发的同学一般都知道,代码开发、测试完毕后需要 lint将 podspec 文件提交到中心。项目中 Podfile 中添加依赖描述,依赖可以用 分支、tag、path 等形式指定。
方法一:
所以明白怎么工作的,我们可以在本地搭建一个静态服务,专门用来提供较大 SDK 的下载,拿 NodeJS、python、Java、php 都可以,很快写一个静态服务。然后从本地 `.cocoapods` 里找到对应的 SDK 文件夹,修改 podspec 文件,修改 **source** 为本地服务器地址资源地址。
由于本地静态服务里面的资源可能会停留在较早版本,所以可以使用定时服务拉取 github 项目最新代码。crontab + shell 做这个很容易。
方法二:
前端有 jsdeliver针对 js/css 加速访问。但是它也支持 github 上的仓库。
但是包大小限制jsdeliver 规定单个文件不能大于 20M仓库的某版本不能大于 50M。所以可以对大文件进行 **xz 压缩**。
Mac split 命令可以拆分包cat 可以合并包。
```shell
// 分割文件
split -b 10m xxxx.tar.xz xxxx.tar.xz .
// 查看效果
ll xxxx.xz*
// 合并
cat xxxx.tar.xz.* > xxxx.tar.xz
// 再次验证大小
ll xxxx.tar.xz
// 解压
mkdir unpackedDir
mv xxxx.tar.xz unpackedDir
cd unpackedDir
tar vxf xxxx.tar.xz
```
所以我们可以将较大的库拆分多个,部署到 jsdeliver然后使用的时候进行加速。
这一步可以是脚本自动完成,写脚本,将文件夹压缩,拆分,上传。
所以使用的时候类似
```shell
wget https://cdn.jsdelivr.net/mmm/xxxx.tar.xz.aa
wget https://cdn.jsdelivr.net/mmm/xxxx.tar.xz.ab
wget https://cdn.jsdelivr.net/mmm/xxxx.tar.xz.ac
cat xxxx.tar.xz.* > xxxx.tar.xz
tar xvf xxxx.tar.xz
rm xxxx.tar.xz.* xxxx.tar.xz
```
结合 cocoapods 的 **prepare_command** 使用。
比如
```ruby
Pod::Spec.new do |s|
s.name = 'xxxx'
s.prepare_command = <<-CMD
sh cat.sh
CMD
end
```
## 15.

View File

@@ -305,7 +305,7 @@ static char *lbp_viewController_close_time = "lbp_viewController_close_time";
```objective-c ```Objective-c
//UIResponder分类 //UIResponder分类
- (NSString *)lbp_identifierKa - (NSString *)lbp_identifierKa
{ {

View File

@@ -34,7 +34,7 @@
1773 -> “1*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “3*7+24” -> 313.1415926733.1415926733.141592645 1773 -> “1*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “3*7+24” -> 313.1415926733.1415926733.141592645
02 -> "0*7+24" + "3.1415926" + "2*7+24" -> 243.141592638 02 -> "0*7+24" + "3.1415926" + "2*7+24" -> 243.141592638
20 -> "2*7+24" + "3.1415926" + "0*7+24" -> 383.141592624 20 -> "2*7+24" + "3.1415926" + "0*7+24" -> 383.141592624
```` ```
#### 2.2 前端拿到数据后再解密,解密后根据自定义的字体 Render 页面 #### 2.2 前端拿到数据后再解密,解密后根据自定义的字体 Render 页面

View File

@@ -18,4 +18,23 @@
2. 当你修改完语法问题的时候,编译工程,发现还是存在一些问题。大体意思是说 Swift 的编译版本不再支持。所以我们需要选中 targets ,切换到 「Build Settings」 下面,搜索 「Swift Language Version」在后面勾选合适的 Swift 版本。在这里我选择了 Swift 5 2. 当你修改完语法问题的时候,编译工程,发现还是存在一些问题。大体意思是说 Swift 的编译版本不再支持。所以我们需要选中 targets ,切换到 「Build Settings」 下面,搜索 「Swift Language Version」在后面勾选合适的 Swift 版本。在这里我选择了 Swift 5
$$
## 手淘 App 落地 Swift推进生态
经过技术调研、基础设施建设、工具链升级、里程碑业务上线和社区培训,得出“拥有一把锤子可以敲一个钉子,拥有一个工具箱可以造一艘航母”。
首先是业务视角:
- 在业务需要快速迭代的时候,现有的 iOS 工程师主要以 Objective-C 为主,转战 Swift 需要一定的学习曲线,而且采用 Swift 效率是否一定有提高也有待考证;
- Swift 只能解决 iOS 侧 Native 研发问题,对于高迭代效率的跨平台技术,收益不足。
其次是技术视角:
- Swift 早期由于 ABI 不稳定,只能将 Runtime 内置在 IPA 包里面,占用约 3M 的下载空间,苹果还有 150M 的蜂窝网络下载大小的限制,且对启动性能有 150ms 的影响,在各家公司拼体验的时代,这些都会对公司的业务造成负担和损耗;
- 由于语法不固定,每次升级都会造成源码级别的 Break Change对开发者也会造成负担。淘宝、美团等巨型 App 都采用了二进制组建化研发模块Swift 只能固定开发工具版本,对大型团队是一种负担和制约,反而极大的降低了研发效率。
未来一两年内国内 Swift 生态还是会有巨大改善的,主要有以下几个方面:
- iOS 12.2 以上内置 Swift Runtime 包大小随着存量旧版本操作系统升级后得到缓解;
- iOS 12.2 以上也没有启动性能的影响;
- Swift 语法不再大变,不会对开发者造成负担;
- 苹果继续强势推进Swift 社区热度持续提升。
相信生产力大幅提高后没有人会放任好用的工具不用而去用一把快要锈掉的锤子Objective-C )。

View File

@@ -1,4 +1,4 @@
# iOS 瘦身之道 # App 瘦身之道
@@ -677,12 +677,4 @@ $ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain
参考文章: 参考文章:
- [Humble Assets Catalog](http://lingyuncxb.com/2019/04/14/HumbleAssetCatalog/) - [Humble Assets Catalog](http://lingyuncxb.com/2019/04/14/HumbleAssetCatalog/)
- [关于 Pod 库的资源引用 resource_bundles or resources](http://zhoulingyu.com/2018/02/02/pod-resource-reference/) - [关于 Pod 库的资源引用 resource_bundles or resources](http://zhoulingyu.com/2018/02/02/pod-resource-reference/)
- 部分图片或者文字内容引用来自网络(若有引用到,请告诉我地址,及时补充)
- 线程、队列、runloop 的关系?主串行队列,在 Mach 内核中创建
- block堆内存中的结构体变量
- 全局并发队列队列FIFO。所以最后加入到全局并发队列中的任务最后执行执行的时候就可以拿到结果

View File

@@ -2,55 +2,52 @@
# 对象在内存中的存储 # 对象在内存中的存储
* 栈、堆、BSS、数据段、代码段是什么 ## 一、 栈、堆、BSS、数据段、代码段是什么
* stack又称作堆栈用来存储程序的局部变量但不包括static声明的变量static修饰的数据存放于数据段中。除此之外在函数被调用时栈用来传递参数和返回值。 stack又称作堆栈用来存储程序的局部变量但不包括static声明的变量static修饰的数据存放于数据段中。除此之外在函数被调用时栈用来传递参数和返回值。
* heap用于存储程序运行中被动态分配的内存段它的大小并不固定可动态的扩张和缩减。操作函数(mallocfree) heap用于存储程序运行中被动态分配的内存段它的大小并不固定可动态的扩张和缩减。操作函数(mallocfree)
* BSS段bss segment通常用来存储程序中未被初始化的全局变量和静态变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段输入静态内存分配 BSS段bss segment通常用来存储程序中未被初始化的全局变量和静态变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段输入静态内存分配
* 数据段data segment通常用来存储程序中已被初始化的全局变量和静态变量和字符串的一块内存区域 数据段data segment通常用来存储程序中已被初始化的全局变量和静态变量和字符串的一块内存区域
* 代码段code segment通常是指用来存储程序可执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定并且内存区域通常属于只读某些架构也允许代码段为可写即允许修改程序。在代码段中也有可能包含一些只读的常数变量例如字符串常量。 代码段code segment通常是指用来存储程序可执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定并且内存区域通常属于只读某些架构也允许代码段为可写即允许修改程序。在代码段中也有可能包含一些只读的常数变量例如字符串常量。
![内存](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ram.png) ![内存](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ram.png)
* ##### 搞清楚上面的概念再来研究下对象在内存中如何存储?
```
## 二、研究下对象在内存中如何存储?
```Objective-C
Person *p1 = [Person new] Person *p1 = [Person new]
``` ```
看这行代码,先来看几个注意点: 看这行代码,先来看几个注意点:
* new底层做的事情 new底层做的事情
* 在堆内存中申请1块合适大小的空间 * 在堆内存中申请1块合适大小的空间
* 在这块内存上根据类模版创建对象。类模版中定义了什么属性就依次把这些属性声明在对象中;对象中还存在一个属性叫做**isa**,是一个指针,指向对象所属的类在代码段中地址 * 在这块内存上根据类模版创建对象。类模版中定义了什么属性就依次把这些属性声明在对象中;对象中还存在一个属性叫做**isa**,是一个指针,指向对象所属的类在代码段中地址
* 初始化对象的属性。这里初始化有几个原则a、如果属性的数据类型是基本数据类型则赋值为0b、如果属性的数据类型是C语言的指针类型则赋值为NULLc、如果属性的数据类型为OC的指针类型则赋值为nil。 * 初始化对象的属性。这里初始化有几个原则a、如果属性的数据类型是基本数据类型则赋值为0b、如果属性的数据类型是C语言的指针类型则赋值为NULLc、如果属性的数据类型为OC的指针类型则赋值为nil。
* 返回堆空间上对象的地址 * 返回堆空间上对象的地址
* 注意 注意:
* 对象只有属性没有方法。包括类本身的属性和一个指向代码段中的类isa指针 * 对象只有属性没有方法。包括类本身的属性和一个指向代码段中的类isa指针
* 如何访问对象的属性?指针名-&gt;属性名;本质:根据指针名找到指针指向的对象,再根据属性名查找来访问对象的属性值 * 如何访问对象的属性?指针名-&gt;属性名;本质:根据指针名找到指针指向的对象,再根据属性名查找来访问对象的属性值
* 如何调用方法?[指针名 方法];本质根据指针名找到指针指向的对象再发现对象需要调用方法再通过对象的isa指针找到代码段中的类再调用类里面方法 * 如何调用方法?[指针名 方法];本质根据指针名找到指针指向的对象再发现对象需要调用方法再通过对象的isa指针找到代码段中的类再调用类里面方法
* 为什么不把方法存储在对象中? 为什么不把方法存储在对象中?
* 因为以类为模版创建的对象只有属性可能不相同,而方法相同,如果堆区的对象里面也保存方法的话就会很重复,浪费了堆空间,因此将方法存储与代码段 * 因为以类为模版创建的对象只有属性可能不相同,而方法相同,如果堆区的对象里面也保存方法的话就会很重复,浪费了堆空间,因此将方法存储与代码段
* 所以一个类创建的n个对象的isa指针的地址值都相同都指向代码段中的类地址 * 所以一个类创建的n个对象的isa指针的地址值都相同都指向代码段中的类地址
**做个小实验** 做个小实验
``` ```Objective-C
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@interface Person : NSObject{ @interface Person : NSObject{
@public @public
@@ -85,20 +82,13 @@ int main(int argc, const char * argv[]) {
} }
``` ```
``` `Person *p1 = [Person new];` 这句代码在内存分配原理如下图所示
Person *p1 = [Person new];
```
**这句代码在内存分配原理如下图所示** ![解析图](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Untitled%20Diagram-2.png)
![解析图](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/Untitled%20Diagram-2.png)
**结论** **结论**
![p1](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/2017-05-15%20下午5.35.17.png) ![p1](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2017-05-15%20下午5.35.17.png)
![p2](https://fantasticlbp.gitbooks.io/knowledge-kit/assets/2017-05-15%20下午5.35.34.png) ![p2](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2017-05-15%20下午5.35.34.png)
**可以 看到Person类的3个对象p1、p2、p3的isa的值相同。** **可以 看到Person类的3个对象p1、p2、p3的isa的值相同。**
补充:
- [alloc与init区别](https://www.jianshu.com/p/daf668b76861)

File diff suppressed because it is too large Load Diff

View File

@@ -34,4 +34,28 @@ Thread 12 Crashed:
为了搞懂这种 Crash,这篇文章就诞生了. 为了搞懂这种 Crash,这篇文章就诞生了.
## ## crash 后的 backtrace 符号化
iOS 的奔溃日志结合 dsym 文件可以找到奔溃时的 backtrace这是解决奔溃的关键线索。如果是同一台 Mac 进行打包,导入 crash log 会自动将 backtrace 进行符号化,这样我们就可以看到类名、文件名、方法名和行号。
如果打包不是在同一台 Macxcode 找不到对应的符号表backtrace 是不会被解析成功的。
```
Last Exception Backtrace:
0 CoreFoundation 0x2cb535f2 __exceptionPreprocess + 122
1 libobjc.A.dylib 0x3a3c5c72 objc_exception_throw + 34
2 CoreFoundation 0x2ca67152 -[__NSArrayM objectAtIndex:] + 226
3 myapp 0x004fe736 0x9b000 + 4601654
4 myapp 0x00507ed4 0x9b000 + 4640468
5 myapp 0x004fd112 0x9b000 + 4595986
6 myapp 0x003275c6 0x9b000 + 2672070
```
方法:将 crash 对应的包比如 Test.app 文件和 crash 文件放在一个文件夹下。执行下面命令
```shell
atos -arch arm64 Test.app/Test -l 0x9b000 0x004fe736
```
其中:
arch 后代表指令架构集第一个数字取backtrace的要解析的行的第4列第二个数字取第3列, 就会得到对应的方法名,文件名,行号.

View File

@@ -1,5 +1,5 @@
# 长按UIWebView上的图片保存到相册 # 教你实现微信公众号效果:长按图片保存到相册
> 不知道各位对于这个需求要如何解决? > 不知道各位对于这个需求要如何解决?
> >
@@ -17,11 +17,11 @@
#### 开发步骤 #### 开发步骤
*给UIWebView添加长按手势 - 给UIWebView添加长按手势
*监听手势动作,拿到坐标点(x,y) - 监听手势动作,拿到坐标点(x,y)
*UIWebView注入js:Document.elementFromPoint(x,y).src拿到img标签的src - `UIWebView注入js:Document.elementFromPoint(x,y).src` 拿到 img 标签的 src
*判断拿到的src是否有值有值则代表点击的网页上的img标签此时弹出对话框是否保存到相册。如果src为空则代表点击网页上的非img标签则不需要弹出对话框 - 判断拿到的 src 是否有值,有值则代表点击的网页上的 img 标签,此时弹出对话框,是否保存到相册。如果 src为空则代表点击网页上的非img标签则不需要弹出对话框
*拿到图片的url生成UIImage再将图片保存到相册 - 拿到图片的 url生成 UIImage,再将图片保存到相册
#### 有巨坑 #### 有巨坑

93
Chapter1 - iOS/1.82.md Normal file
View File

@@ -0,0 +1,93 @@
# Runtime
> 做很多需求或者是技术细节验证的时候会用到 Runtime 技术,用了挺久的了,本文就写一些场景和源码分析相关的文章。
## 场景
最简单的一个场景就是防止按钮多次点击吧,比如短时间内点击了多次按钮,可以用「节流」来实现。用到的技术是 runtime。再举一个例子比如无痕埋点的实现里面对各种控件的点击、页面的跳转等也需要用到 runtime想看无痕埋点的设计与实现可以看我这篇[文章](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.55.md)。
这里不得不提的一个知识点就是为什么给类或者对象进行 hook 的操作,要放到 load 方法中进行了。
## 一、 load 和 initialize 方法
**load 方法**
对于加入运行期系统中的每个类及分类来说,必定会调用 load 方法而且仅调用1次。当包含类或分类的程序库载入系统时通常指应用程序启动的时候就会执行 load 方法。
- 类的 load 方法会在它所有父类 load 方法后调用
- 分类的 load 方法会在类本身的 load 方法后调用
- load 方法不遵循继承
- load 方法内部的实现必须简单,如果逻辑太复杂有可能会导致阻塞
load 方法有个需要注意的地方:执行该方法时,运行期系统处于脆弱状态。在执行子类的 load 方法之前,必须先执行完所有的超类的 load 方法,假如代码中还依赖了其他的程序库,那么程序库里的相关 load 方法也会先执行。在开发中load 方法中使用其他类是不安全的。
```objective-c
@implentation ClassB
+ (void)load
{
ClassA *classA = [[ClassA alloc] init];
[classA setUp];
}
@end
```
上面的代码不太推荐且不太安全,因为你没办法确定在执行 ClassB 的 load 方法的时候, ClassA 是否已经被加载到系统中。
load 方法并不像普通方法那样具备继承规则。普通的方法在面向对象程序设计中,父类的方法、属性等都会在子类中存在,假如 Person 类有 eat、sleep 方法Student 类继承子 Person 类,虽然 Person 类没有重写 eat 方法,但是你给 Student 类发送 eat 消息是可以响应的,因为方法被继承了。 Load 方法就不会,假如子类没有 load 方法,不管超类是否有 load 方法,子类都不会调用 load 方法。
load 方法内代码逻辑必须精简。因为整个应用程序在执行 load 方法的时候会被阻塞。如果 load 方法中包含繁杂的代码,那么应用程序可能会变得无法响应。更加不要使用锁。想通过 load 方法在类加载之前做些操作的,都属于错误的打开方式。它真正的用法应该是 Debug 吧,比如在 Category 中判断当前分类是否被成功 load 进去。
**inintialize 方法**
对于每个类来说,该方法会在程序首次调用该类之前调用,且只调用一次。它是由运行时系统来调用的,不应该通过代码的方式直接调用。
与 load 的区别:
- 惰性调用的。也就是说某个类的 initialize 方法也许永远不会被调用,当且仅当程序用到了该类的时候才会调用。 load 是加载进 runtime 肯定会调用initizlize 第一次使用前会被调用。对 load 来说,应用程序必须阻塞并且等着所有类 load 方法执行完毕才可以继续。
- 运行期系统在执行 initialize 方法时,是处于正常状态的,因此从运行期完整度方面来讲,此时可以安全使用并调用任何类的任意方法,而且 runtime 保证了在 initizlize 方法时期一定会在一个线程安全的环境中执行,也就是说执行 initialize 方法的这个线程可以操作类或者实例,其他线程先阻塞,等待执行完毕
- initialize 同其他方法一样,如果某个类并未实现它,而其实现了,那么就会运行超类实现的代码。
```objective-c
@implentation AClass
+ (void)initialize
{
NSLog(@"%@ initialize", self);
}
@end
@interface BClass:AClass
@end
@implentation BClass
@end
// log
AClass initialize
BClass initialize
```
基于上述特点,我们一般需要 initialize 方法中做些判断,如下
```objective-c
+ (void)initialize
{
if (self == [ACLass class]) {
// 确定了我是 AClass 的 initialize 方法执行时期
}
}
```
经常说不要在 App 中写太多的 load 方法,会影响 App 的启动时间。原因就是 load 方法的执行特点决定的。某个类的 load 方法执行是在它所有父类 load 方法执行后执行的,该类的分类的 load 方法执行是在当前类 load 方法之后执行的。如果某个类的 load 方法中引用了其他的类或者其他库的代码,则该类的 load 方法必须是其他类或者其他库中类的 load 方法执行后执行,所以类的 load 方法中最好做本类相关的逻辑,比如 runtime method swizzling。
TagPointerString 不走消息转发
CFString 走消息转发

439
Chapter1 - iOS/1.83.md Normal file
View File

@@ -0,0 +1,439 @@
# NSURLProtocol 应用场景
> 在做 Hybrid 的时候就使用到 NSURLProtocol对于网络监控依旧可以使用它所以本文就总结下 NSURLProtocol 的应用场景和如何用
## 一、 NSURLProtocol 是什么
NSURLProtocol 是 Foundation 框架中 URL Loading System 的一部分。它可以让开发者可以在不修改应用内原始请求代码的情况下,去改变 URL 加载的全部细节。换句话说NSURLProtocol 是一个被 Apple 默许的中间人攻击。
虽然 NSURLProtocol 叫“Protocol”却不是协议而是一个抽象类。
既然 NSURLProtocol 是一个抽象类,说明它无法被实例化,那么它又是如何实现网络请求拦截的?
答案就是通过子类化来定义新的或是已经存在的 URL 加载行为。如果当前的网络请求是可以被拦截的,那么开发者只需要将一个自定义的 NSURLProtocol 子类注册到 App 中,在这个子类中就可以拦截到所有请求并进行修改。
## 二、NSURLProtocol 使用场景
### 1. 技术层面
NSURLProtocol 是 URL Loading System 的一部分,所以它可以拦截所有基于 URL Loading System 的网络请求:
- NSURLSession
- NSURLConnection
- NSURLDownload
- NSURLResponse
- NSHTTPURLResponse
- NSURLRequest
- NSMutableURLRequest
所以,基础这些基础技术开发的网络框架比如 AFNetworking、Alamofire 也可以拦截。
想到了2种场景不能拦截
- 早期使用 CFNetwork 实现的 ASIHTTPRequest 框架就无法拦截
- UIWebView 也是可以被拦截的。但是 WKWebView 是基于 webkit不走底层 c socket。
### 2. 需求层面
#### 2.1 Hybrid
- 对 webview 上运行的资源进行监控(大小、时间等)
- Native 代理 WebView 上面的图片资源,使用和客户端一致的图片管理策略(比如 SDWebImage 管理)
- 访问提速。App 在 cd 阶段打包内置了项目技术栈的框架库、样式库、一些业务频道的基础包。配合一定的资源更新策略就可以给 Hybrid 提速
- 代理 WebView 上的网络请求。Native 针对网络请求具备更高的安全性和灵活性、网络请求收口策略
#### 2.2 针对网络进行监控
- 对 App 内的网络请求进行重定向,解决 DNS 域名劫持问题
- 针对全局网络请求设置。比如缓存管理、请求地址修改、header
- App 网络安全性。设置 App 网络白名单
- 自定义网络请求,过滤垃圾内容
- H5 加速,请求走本地离线包
## 三、NSURLProtocol 的相关方法
创建协议对象
```Objective-c
// 创建一个 URL 协议实例来处理 request 请求
- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client;
// 创建一个 URL 协议实例来处理 session task 请求
- (instancetype)initWithTask:(NSURLSessionTask *)task cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client;
```
注册和注销协议类
```Objective-c
// 尝试注册 NSURLProtocol 的子类,使之在 URL 加载系统中可见
+ (BOOL)registerClass:(Class)protocolClass;
// 注销 NSURLProtocol 的指定子类
+ (void)unregisterClass:(Class)protocolClass;
```
确定子类是否可以处理请求
子类化 NSProtocol 的首要任务就是告知它,需要控制什么类型的网络请求。
```objective-c
// 确定协议子类是否可以处理指定的 request 请求,如果返回 YES请求会被其控制返回 NO 则直接跳入下一个 protocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
// 确定协议子类是否可以处理指定的 task 请求
+ (BOOL)canInitWithTask:(NSURLSessionTask *)task;
```
获取和设置请求属性
NSURLProtocol 允许开发者去获取、添加、删除 request 对象的任意元数据。这几个方法常用来处理请求无限循环的问题。
```Objective-c
// 在指定的请求中获取与指定键关联的属性
+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;
// 设置与指定请求中的指定键关联的属性
+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;
// 删除与指定请求中的指定键关联的属性
+ (void)removePropertyForKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;
```
提供请求的规范版本
如果你想要用特定的某个方式来修改请求,可以用下面这个方法。
```Objective-c
// 返回指定请求的规范版本
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
```
确定请求是否相同
```Objective-c
// 判断两个请求是否相同,如果相同可以使用缓存数据,通常只需要调用父类的实现
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b;
```
启动和停止加载
这是子类中最重要的两个方法,不同的自定义子类在调用这两个方法时会传入不同的内容,但共同点都是围绕 protocol 客户端进行操作。
```Objective-c
// 开始加载
- (void)startLoading;
// 停止加载
- (void)stopLoading;
```
获取协议属性
```Objective-c
// 获取协议接收者的缓存
- (NSCachedURLResponse *)cachedResponse;
// 接受者用来与 URL 加载系统通信的对象,每个 NSProtocol 的子类实例都拥有它
- (id<NSURLProtocolClient>)client;
// 接收方的请求
- (NSURLRequest *)request;
// 接收方的任务
- (NSURLSessionTask *)task;
```
## 四、 如何利用 NSProtocol 拦截网络请求
NSURLProtocol 在实际应用中,主要是完成两步:拦截 URL 和 URL 转发。先来看如何拦截网络请求。
**创建 NSURLProtocol 子类**
这里创建一个名为 HTCustomURLProtocol 的子类。
```Objective-c
@interface HTCustomURLProtocol : NSURLProtocol
@end
```
**注册 NSURLProtocol 的子类**
在合适的位置注册这个子类。对基于 NSURLConnection 或者使用 [NSURLSession sharedSession] 初始化对象创建的网络请求,调用 registerClass 方法即可。
```objective-c
[NSURLProtocol registerClass:[NSClassFromString(@"CustomURLProtocol") class]];
// or
// [NSURLProtocol registerClass:[HTCustomURLProtocol class]];
```
如果需要全局监听,可以设置在 `AppDelegate.m` 的 `didFinishLaunchingWithOptions` 方法中。如果只需要在单个 UIViewController 中使用,记得在合适的时机注销监听:
```objective-c
[NSURLProtocol unregisterClass:[NSClassFromString(@"HTCustomURLProtocol") class]];
```
如果是基于 `NSURLSession` 的网络请求,且不是通过 `[NSURLSession sharedSession]` 方式创建的,就得配置 `NSURLSessionConfiguration` 对象的 `protocolClasses` 属性。
```objective-c
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.protocolClasses = @[[NSClassFromString(@"CustomProtocol") class]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
```
**实现 NSURLProtocol 子类**
> 注册 → 拦截 → 转发 → 回调 → 结束
以拦截 UIWebView 为例,这里需要重写父类的这五个核心方法。
```objective-c
// 定义一个协议 key
static NSString * const HTCustomURLProtocolHandledKey = @"HTCustomURLProtocolHandledKey";
// 在拓展中定义一个 NSURLConnection 属性。通过 NSURLSession 也可以拦截,这里只是以 NSURLConnection 为例。
@property (nonatomic, strong) NSURLConnection *connection;
// 定义一个可变的请求返回值,
@property (nonatomic, strong) NSMutableData *responseData;
// 方法 1在拦截到网络请求后会调用这一方法可以再次处理拦截的逻辑比如设置只针对 http 和 https 的请求进行处理。
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
// 只处理 http 和 https 请求
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame)) {
// 看看是否已经处理过了,防止无限循环
if ([NSURLProtocol propertyForKey:HTCustomURLProtocolHandledKey inRequest:request]) {
return NO;
}
// 如果还需要截取 DNS 解析请求中的链接,可以继续加判断,是否为拦截域名请求的链接,如果是返回 NO
return YES;
}
return NO;
}
// 方法 2【关键方法】可以在此对 request 进行处理,比如修改地址、提取请求信息、设置请求头等。
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
// 可以打印出所有的请求链接包括 CSS 和 Ajax 请求等
NSLog(@"request.URL.absoluteString = %@",request.URL.absoluteString);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
return mutableRequest;
}
// 方法 3【关键方法】在这里设置网络代理重新创建一个对象将处理过的 request 转发出去。这里对应的回调方法对应 <NSURLProtocolClient> 协议方法
- (void)startLoading {
// 可以修改 request 请求
NSMutableURLRequest *mutableRequest = [[self request] mutableCopy];
// 打 tag防止递归调用
[NSURLProtocol setProperty:@YES forKey:HTCustomURLProtocolHandledKey inRequest:mutableRequest];
// 也可以在这里检查缓存
// 将 request 转发,对于 NSURLConnection 来说,就是创建一个 NSURLConnection 对象;对于 NSURLSession 来说,就是发起一个 NSURLSessionTask。
self.connection = [NSURLConnection connectionWithRequest:mutableRequest delegate:self];
}
// 方法 4主要判断两个 request 是否相同,如果相同的话可以使用缓存数据,通常只需要调用父类的实现。
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return [super requestIsCacheEquivalent:a toRequest:b];
}
// 方法 5处理结束后停止相应请求清空 connection 或 session
- (void)stopLoading {
if (self.connection != nil) {
[self.connection cancel];
self.connection = nil;
}
}
// 按照在上面的方法中做的自定义需求,看情况对转发出来的请求在恰当的时机进行回调处理。
#pragma mark- NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
#pragma mark - NSURLConnectionDataDelegate
// 当接收到服务器的响应(连通了服务器)时会调用
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
self.responseData = [[NSMutableData alloc] init];
self.internalResponse = response;
// 可以处理不同的 statusCode 场景
// NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
// 可以设置 Cookie
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
// 接收到服务器的数据时会调用,可能会被调用多次,每次只传递部分数据
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.responseData appendData:data];
[self.client URLProtocol:self didLoadData:data];
}
// 服务器的数据加载完毕后调用
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.client URLProtocolDidFinishLoading:self];
}
// 请求错误(失败)的时候调用,比如出现请求超时、断网,一般指客户端错误
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response {
if (response != nil) {
[[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
}
return request;
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection {
return YES;
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
[[challenge sender] useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
[self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
}
- (void) connection:(NSURLConnection *)connection
didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
[self.client URLProtocol:self
didCancelAuthenticationChallenge:challenge];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse {
return cachedResponse;
}
```
注意NSURLConnection 已经被废弃,推荐使用 NSURLSession 进行网络请求,它好处多多,具体的自行查阅官方介绍。
## 五、 补充内容
### 1. 使用 NSURLSession 时的注意事项
如果在 NSURLProtocol 中使用 NSURLSession需要注意
• 拦截到的 request 请求的 HTTPBody 为 nil但可以借助 HTTPBodyStream 来获取 body
• 如果要用 registerClass 注册,只能通过 ` [NSURLSession sharedSession] `的方式创建网络请求。
### 2. 注册多个 NSURLProtocol 子类
当有多个自定义 NSURLProtocol 子类注册到系统中的话,会按照他们注册的反向顺序依次调用 URL 加载流程,也就是最后注册的 NSURLProtocol 会被优先判断。
对于通过配置 NSURLSessionConfiguration 对象的 protocolClasses 属性来注册的情况protocolClasses 数组中只有第一个 NSURLProtocol 会起作用,后续的 NSURLProtocol 就无法拦截到了。
### 3. 如何拦截 WKWebview
WKWebView 在独立于 app 进程之外的进程中执行网络请求,请求数据不经过主进程,因此,在 WKWebView 上直接使用 NSURLProtocol 无法拦截请求。苹果开源的 webKit2 源码暴露了 私有API
```objective-c
+ [WKBrowsingContextController registerSchemeForCustomProtocol:]
```
通过注册 http(s) scheme 后 WKWebView 将可以使用 NSURLProtocol 拦截 http(s) 请求。涉及到的私有 api `WKBrowsingContextControoler` 、`registerSchemeForCustomProtocol`
```objective-c
Class cls = NSClassFromString(@"WKBrowsingContextController");
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([cls respondsToSelector:sel]) {
// 通过 http 和 https 的请求,同理可通过其他的 Scheme 但是要满足 URL Loading System
[cls performSelector:sel withObject:@"http"];
[cls performSelector:sel withObject:@"https"];
}
```
因为使用了私有 api所以会无法过审我们可以对字符串进行处理比如对方法名进行加密。
该方案还存在2个严重缺陷
1. post 请求 body 数据被清空
由于 WKWebview 在独立的进程中执行网络请求,一旦注册 registerSchemeForCustomProtocol httphttps scheme 后,网络请求将从 Network Process 发送到 App Process这样 NSURLProtocol 才能拦截网络请求。在 Webkit2 的设计里使用 **MessageQueue** 进行进程间通信, Network Process 会将请求 encode 成一个 message然后通过 IPC 发送给 App Process出于性能角度的考虑encode 的时候 HTTPBody 和 HTTPBodyStream 这2个字段被丢弃掉了。可以查看 [webkit2 源码](https://github.com/WebKit/webkit/blob/fe39539b83d28751e86077b173abd5b7872ce3f9/Source/WebKit2/Shared/mac/WebCoreArgumentCodersMac.mm#L61-L88)。
2. 对 ATS 支持不足
info.plist 中打开 ATS 开关,设置 Allow Arbitrary Loads 选项为 NO设置 registerSchemeForCustomProtocol 注册了 httphttps schemeWKWebView 发起的所有 http 网络请求将被阻塞(即使 Allow Arbitrary Loads in Web Content 选项为 YES
WKWebView 可以注册 customScheme比如自定义 scheme`Hybrid://`,因此使用离线包但不使用 post 方式的请求可以通过 customScheme 发起。比如 `Hybrid://www.xxx.com/`,然后在 App 进程被 NSURLProtocol 拦截这个请求,然后加载离线包资源。
不足:使用 post 方式的请求需要修改 h5 侧代码scheme
### 4. WKWebView loadRequest 问题
在 WKWebView 上通过 loadRequest 发起的 post 请求,会丢失 body 数据。
```objective-c
//同样是由于进程间通信性能问题HTTPBody字段被丢弃
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[@"bodyData" dataUsingEncoding:NSUTF8StringEncoding]];
[wkwebview loadRequest: request];
```
通过上面的基础条件,其实可以麻烦一点解决 post 请求的 body 丢失问题。
假如 WKWebView loadRequest 要加载 post 请求 request1: http://h5.xxx.com/mobile/index。步骤如下
1. 子类继承自抽象类 NSURLProtocol并向 App 注册。
2. 将 request1 的 scheme 进行替换,生成新的请求 request2`post://h5.xxx.com/mobile/index`。同时将 rquest1 的 body 字段添加到 header 信息中webkit 不会丢弃 header 信息)。
3. WKWebView 加载新的 request2。 `[WKWebView loadRequest:request2];`
4. 通过 `[WKBrowsingContextController registerSchemeForCustomProtocol:]` 注册 scheme**post://**。
5. NSURLProtocl 拦截请求 ``post://h5.xxx.com/mobile/index`。做 scheme 还原的操作,也就是替换 scheme。生成新的请求 request3 `http://h5.xxx.com/mobile/index`,同时将 request2 header 中的 body 字段添加到 request3 的 body 中,并使用 NSURLConnection 加载 request3
6. 网络请求完成后,通过 NetworkProtocolClient 将请求结果返回给 WKWebView。
1.
关于 WKWebview 的各种问题可以查看这篇[文章](https://www.tuicool.com/articles/QbE3Mb7)。

2
Chapter1 - iOS/1.84.md Normal file
View File

@@ -0,0 +1,2 @@
# WKWebView 技巧集

View File

@@ -1,10 +1,14 @@
# hittest方法 # 事件响应者判定原理之 hittest 方法
## 一、 hittest 介绍
* 就是用来寻找最合适的view * 就是用来寻找最合适的view
* 当一个事件传递给一个控件就会调用这个控件的hitTest方法 * 当一个事件传递给一个控件就会调用这个控件的hitTest方法
* 点击了白色的view 触摸事件 -&gt; UIApplication -&gt; UIWindow 调用 \[UIWindow hitTest\] -&gt; 白色view \[WhteView hitTest\] * 点击了白色的view 触摸事件 -&gt; UIApplication -&gt; UIWindow 调用 \[UIWindow hitTest\] -&gt; 白色view \[WhteView hitTest\]
实验1:
## 二、 实验
### 2.1 实验一
定义 BaseView在里面实现方法touchBegan监听当前哪个类调用了该方法。 定义 BaseView在里面实现方法touchBegan监听当前哪个类调用了该方法。
@@ -15,9 +19,8 @@
![UI效果图](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Simulator%20Screen%20Shot%20-%20iPhone%206s%20Plus%20-%202017-10-11%20at%2010.14.37.png) ![UI效果图](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Simulator%20Screen%20Shot%20-%20iPhone%206s%20Plus%20-%202017-10-11%20at%2010.14.37.png)
``` ```Objective-C
//KeyWindow //KeyWindow
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *view = [super hitTest:point withEvent:event]; UIView *view = [super hitTest:point withEvent:event];
NSLog(@"fittest->%@",view); NSLog(@"fittest->%@",view);
@@ -25,11 +28,9 @@
} }
``` ```
结果:
点击了白色1: 点击了白色1:
``` ```shell
2017-10-11 16:48:52.882547+0800 主流App框架[16295:358790] BrownView--hitTest withEvent 2017-10-11 16:48:52.882547+0800 主流App框架[16295:358790] BrownView--hitTest withEvent
2017-10-11 16:48:59.646610+0800 主流App框架[16295:358790] GreenView--hitTest withEvent 2017-10-11 16:48:59.646610+0800 主流App框架[16295:358790] GreenView--hitTest withEvent
2017-10-11 16:48:59.647145+0800 主流App框架[16295:358790] fittest-><UIView: 0x7f8f23406510; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x60c000221840>> 2017-10-11 16:48:59.647145+0800 主流App框架[16295:358790] fittest-><UIView: 0x7f8f23406510; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x60c000221840>>
@@ -40,7 +41,7 @@
点击了蓝色3: 点击了蓝色3:
``` ```shell
2017-10-11 16:49:56.331024+0800 主流App框架[16295:358790] BrownView--hitTest withEvent 2017-10-11 16:49:56.331024+0800 主流App框架[16295:358790] BrownView--hitTest withEvent
2017-10-11 16:49:56.331335+0800 主流App框架[16295:358790] BView--hitTest withEvent 2017-10-11 16:49:56.331335+0800 主流App框架[16295:358790] BView--hitTest withEvent
2017-10-11 16:49:56.331617+0800 主流App框架[16295:358790] BlueView--hitTest withEvent 2017-10-11 16:49:56.331617+0800 主流App框架[16295:358790] BlueView--hitTest withEvent

View File

@@ -10,7 +10,7 @@
* [5、事件响应者链](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.5.md) * [5、事件响应者链](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.5.md)
* [6、外卖App双列表联动](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.6.md) * [6、外卖App双列表联动](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.6.md)
* [7、在内存剖析对象](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.7.md) * [7、在内存剖析对象](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.7.md)
* [8、长按UIWebView上的图片保存到相册](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.8.md) * [8、教你实现微信公众号效果:长按图片保存到相册](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.8.md)
* [9、hitTest和pointInside方法你真的熟吗](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.9.md) * [9、hitTest和pointInside方法你真的熟吗](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.9.md)
* [10、HyBrid探索](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.10.md) * [10、HyBrid探索](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.10.md)
* [11、iOS中的事件](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.11.md) * [11、iOS中的事件](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.11.md)
@@ -54,7 +54,7 @@
* [48、OC类别Catrgory和拓展Extension](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.48.md) * [48、OC类别Catrgory和拓展Extension](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.48.md)
* [49、MVC、MVP、MVVM](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.49.md) * [49、MVC、MVP、MVVM](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.49.md)
* [50、“静态库”和“动态库”](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.50.md) * [50、“静态库”和“动态库”](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.50.md)
* [51、cocopod](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.51.md) * [51、cocoapods 相关小技巧](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.51.md)
* [52、如何打造团队的代码风格统一以及开发效率的提升](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.52.md) * [52、如何打造团队的代码风格统一以及开发效率的提升](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.52.md)
* [53、iOS 数据持久化](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.53.md) * [53、iOS 数据持久化](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.53.md)
* [54、Xcode 设置作者信息](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.54.md) * [54、Xcode 设置作者信息](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.54.md)
@@ -85,4 +85,6 @@
* [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、打造功能强大的数据上报组件](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)
* [83、NSURLProtocol 应用场景](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.83.md)
* [84、WKWebView 技巧集合](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.84.md)

View File

@@ -218,8 +218,8 @@ Chrome开发者工具和Firebug都提供了书签功能用于显示你在元
``` ```
var arr = [{ num: 0 }]; var arr = [{ num: 0 }];
setInterval(function(){ setInterval(function(){
console.log(arr); console.log(arr);
arr[0].num += 1; arr[0].num += 1;
}, 1000); }, 1000);
``` ```

View File

@@ -126,8 +126,8 @@
?> ?>
``` ```
![css点击统计](/assets/2287777-c1d479c5171de2d0.png) ![css点击统计](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2287777-c1d479c5171de2d0.png)
![php代码统计](/assets/2287777-a50bd17a33290204.png) ![php代码统计](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2287777-a50bd17a33290204.png)

View File

@@ -1,6 +1,6 @@
# h5自定义对象 # h5自定义对象
### 一、方式一 ## 一、方式一
在很早以前我们自定义元素的属性要通过 `user-defined-attribute="value"`的方式来设置自己需要的属性 在很早以前我们自定义元素的属性要通过 `user-defined-attribute="value"`的方式来设置自己需要的属性

View File

@@ -50,8 +50,8 @@
|:- |:-|:-| |:- |:-|:-|
|↓ |0x100| 67| |↓ |0x100| 67|
|↓ |0x101| 45| |↓ |0x101| 45|
|↓ |0x102| 23| jvyyhr |↓ |0x102| 23 |
|↓|0x103| 01| |↓ |0x103| 01 |

View File

@@ -45,7 +45,7 @@
2. 安装所需依赖。`npm install --save-dev @commitlint/config-conventional @commitlint/cli husky` 2. 安装所需依赖。`npm install --save-dev @commitlint/config-conventional @commitlint/cli husky`
3. 在工程根目录下新建配置文件,名称为 `commitlint.config.js` 3. 在工程根目录下新建配置文件,名称为 `commitlint.config.js`
4. 在 commitlint.config.js 中添加配置信息 4. 在 commitlint.config.js 中添加配置信息
```shell ```javascript
const types = [ const types = [
'build', 'build',
'ci', 'ci',

View File

@@ -10,7 +10,7 @@
* [5、事件响应者链](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.5.md) * [5、事件响应者链](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.5.md)
* [6、外卖App双列表联动](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.6.md) * [6、外卖App双列表联动](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.6.md)
* [7、在内存剖析对象](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.7.md) * [7、在内存剖析对象](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.7.md)
* [8、长按UIWebView上的图片保存到相册](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.8.md) * [8、教你实现微信公众号效果:长按图片保存到相册](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.8.md)
* [9、hitTest和pointInside方法你真的熟吗](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.9.md) * [9、hitTest和pointInside方法你真的熟吗](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.9.md)
* [10、HyBrid探索](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.10.md) * [10、HyBrid探索](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.10.md)
* [11、iOS中的事件](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.11.md) * [11、iOS中的事件](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.11.md)
@@ -53,7 +53,7 @@
* [48、OC类别Catrgory和拓展Extension](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.48.md) * [48、OC类别Catrgory和拓展Extension](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.48.md)
* [49、MVC、MVP、MVVM](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.49.md) * [49、MVC、MVP、MVVM](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.49.md)
* [50、“静态库”和“动态库”](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.50.md) * [50、“静态库”和“动态库”](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.50.md)
* [51、cocopod](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.51.md) * [51、cocoapods 相关小技巧](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.51.md)
* [52、如何打造团队的代码风格统一以及开发效率的提升](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.52.md) * [52、如何打造团队的代码风格统一以及开发效率的提升](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.52.md)
* [53、iOS 数据持久化](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.53.md) * [53、iOS 数据持久化](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.53.md)
* [54、Xcode 设置作者信息](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.54.md) * [54、Xcode 设置作者信息](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.54.md)
@@ -84,7 +84,9 @@
* [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、打造功能强大的数据上报组件](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)
* [83、NSURLProtocol 应用场景](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.83.md)
* [84、WKWebView 技巧集合](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.84.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)
@@ -170,8 +172,7 @@
* [15、区块链技术和比特币](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.16.md) * [15、区块链技术和比特币](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.16.md)
* [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/ * [18、云服务器靠谱推荐](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter7%20-%20Geek%20Talk/7.19.md)
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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB