diff --git a/AddingTheSignInWithAppleFlowToYourApp/.DS_Store b/AddingTheSignInWithAppleFlowToYourApp/.DS_Store new file mode 100644 index 00000000..af98c8cb Binary files /dev/null and b/AddingTheSignInWithAppleFlowToYourApp/.DS_Store differ diff --git "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2211.gif" "b/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2211.gif" deleted file mode 100644 index 873677f6..00000000 Binary files "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2211.gif" and /dev/null differ diff --git "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2212.gif" "b/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2212.gif" deleted file mode 100644 index df799e77..00000000 Binary files "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2212.gif" and /dev/null differ diff --git "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2213.gif" "b/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2213.gif" deleted file mode 100644 index fd7fa1d9..00000000 Binary files "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2213.gif" and /dev/null differ diff --git "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2214.gif" "b/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2214.gif" deleted file mode 100644 index 28b388aa..00000000 Binary files "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2214.gif" and /dev/null differ diff --git "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2215.gif" "b/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2215.gif" deleted file mode 100644 index 711249ee..00000000 Binary files "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2215.gif" and /dev/null differ diff --git "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2216.gif" "b/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2216.gif" deleted file mode 100644 index d74b75be..00000000 Binary files "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2216.gif" and /dev/null differ diff --git "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2217.gif" "b/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2217.gif" deleted file mode 100644 index 067bf2c0..00000000 Binary files "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2217.gif" and /dev/null differ diff --git "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2218.gif" "b/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2218.gif" deleted file mode 100644 index 372ba45d..00000000 Binary files "a/PrviewPicture/3\343\200\201\345\260\217\350\247\206\351\242\2218.gif" and /dev/null differ diff --git "a/PrviewPicture/4\343\200\201\344\272\272\350\204\270\350\257\206\345\210\253.gif" "b/PrviewPicture/4\343\200\201\344\272\272\350\204\270\350\257\206\345\210\253.gif" deleted file mode 100644 index 4b99ad5e..00000000 Binary files "a/PrviewPicture/4\343\200\201\344\272\272\350\204\270\350\257\206\345\210\253.gif" and /dev/null differ diff --git "a/PrviewPicture/5\343\200\201\345\256\236\346\227\266\346\273\244\351\225\234\346\213\215\346\221\204.gif" "b/PrviewPicture/5\343\200\201\345\256\236\346\227\266\346\273\244\351\225\234\346\213\215\346\221\204.gif" deleted file mode 100644 index a0888aa8..00000000 Binary files "a/PrviewPicture/5\343\200\201\345\256\236\346\227\266\346\273\244\351\225\234\346\213\215\346\221\204.gif" and /dev/null differ diff --git "a/PrviewPicture/6\343\200\201GPUImage.gif" "b/PrviewPicture/6\343\200\201GPUImage.gif" deleted file mode 100644 index dd0fa132..00000000 Binary files "a/PrviewPicture/6\343\200\201GPUImage.gif" and /dev/null differ diff --git "a/PrviewPicture/7\343\200\201\351\237\263\350\247\206\351\242\221\347\274\226\347\240\201.gif" "b/PrviewPicture/7\343\200\201\351\237\263\350\247\206\351\242\221\347\274\226\347\240\201.gif" deleted file mode 100644 index fa83a78f..00000000 Binary files "a/PrviewPicture/7\343\200\201\351\237\263\350\247\206\351\242\221\347\274\226\347\240\201.gif" and /dev/null differ diff --git "a/PrviewPicture/8\343\200\201OpenGLES\345\255\246\344\271\2401.gif" "b/PrviewPicture/8\343\200\201OpenGLES\345\255\246\344\271\2401.gif" deleted file mode 100644 index 08ffa4af..00000000 Binary files "a/PrviewPicture/8\343\200\201OpenGLES\345\255\246\344\271\2401.gif" and /dev/null differ diff --git "a/PrviewPicture/8\343\200\201OpenGLES\345\255\246\344\271\2402.gif" "b/PrviewPicture/8\343\200\201OpenGLES\345\255\246\344\271\2402.gif" deleted file mode 100644 index a2278771..00000000 Binary files "a/PrviewPicture/8\343\200\201OpenGLES\345\255\246\344\271\2402.gif" and /dev/null differ diff --git "a/PrviewPicture/8\343\200\201OpenGLES\345\255\246\344\271\2403.gif" "b/PrviewPicture/8\343\200\201OpenGLES\345\255\246\344\271\2403.gif" deleted file mode 100644 index 6001dd62..00000000 Binary files "a/PrviewPicture/8\343\200\201OpenGLES\345\255\246\344\271\2403.gif" and /dev/null differ diff --git "a/PrviewPicture/\346\232\227\351\273\221\346\250\241\345\274\217.gif" "b/PrviewPicture/\346\232\227\351\273\221\346\250\241\345\274\217.gif" deleted file mode 100644 index ca6c0ed1..00000000 Binary files "a/PrviewPicture/\346\232\227\351\273\221\346\250\241\345\274\217.gif" and /dev/null differ diff --git "a/QQ\344\272\244\346\265\201\347\276\244.png" "b/QQ\344\272\244\346\265\201\347\276\244.png" new file mode 100644 index 00000000..75b16eed Binary files /dev/null and "b/QQ\344\272\244\346\265\201\347\276\244.png" differ diff --git a/README.md b/README.md index ab03d747..b53ce6c0 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,60 @@ -# iOS_Tips -iOS的一些示例,不定时更新~ 由于内容较多,文件会有点大,建议终端用git clone命令下载,这样以后只需git pull更新就行;有时是GitHub的原因,就从百度网盘:https://pan.baidu.com/s/1UOlN21zWKbQRtVe2I7pPvg 下载 -简书地址:https://www.jianshu.com/p/a2a04cabb98d +# [iOS_Tips](https://github.com/wsl2ls/iOS_Tips) +> iOS的一些示例,不定时更新~ + +| [简书 ](https://www.jianshu.com/u/e15d1f644bea) | [掘金](https://juejin.im/user/5c00d97b6fb9a049fb436288) | +| ---- | ---- | +| [CSDN](https://blog.csdn.net/wsl2ls) | [微博](https://weibo.com/5732733120/profile?rightmod=1&wvr=6&mod=personinfo&is_all=1) | ### 目录 -> 1、暗黑模式 -> 2、AppleID登录应用 -> 3、AVFoundation 高仿微信相机拍摄和编辑 -> 4、AVFoundation 人脸检测 -> 5、AVFoundation 实时滤镜 -> 6、GPUImage框架的使用 -> 7、VideoToolBox和AudioToolBox音视频编解码 -> 8、OpenGL ES学习 -> 9、LeetCode算法练习 - -## 1、 暗黑模式适配 - -![暗黑模式](PrviewPicture/暗黑模式.gif) +> 1、[暗黑模式](#1-暗黑模式适配) +> 2、[AppleID登录应用](#2-AppleID登录应用) +> 3、[AVFoundation相关](#3-AVFoundation相关) +>> 3.1、AVFoundation 高仿微信相机拍摄和编辑 +>> 3.2、AVFoundation 人脸检测 +>> 3.3、AVFoundation 实时滤镜 +>> 3.4、GPUImage框架的使用 +>> 3.5、VideoToolBox和AudioToolBox音视频编解码 +>> 3.6、AVFoundation 利用摄像头实时识别物体颜色 +>> 3.7、AVFoundation 原生二维码扫描识别和生成 + +> 4、[OpenGL ES学习](#4-OpenGLES学习) +> 5、[LeetCode算法练习](#5-LeetCode算法练习) +> 6、[工作中踩过的坑](#6-工作中踩过的坑) +>> 6.1、键盘和UIMenuController不能同时存在的问题 +>> 6.2、全屏侧滑手势/UIScrollView/UISlider间滑动手势冲突 +>> 6.3、UITableView/UICollectionView获取特定位置的cell +>> 6.4、UIScrollView视觉差动画 +>> 6.5、iOS 传感器集锦 +>> 6.6、iOS 自定义转场动画 +>> 6.7、二进制重排优化启动速度 +>> 6.8、iOS APM应用性能监控管理(doing) +>> 6.9、ipa瘦身之扫描无用资源 +>> 6.10、多个UIScrollView嵌套/个人中心页 + +> 7、[iOS Crash防护](#7-iOSCrash防护) +> 8、[WKWebView相关](#8-WKWebView相关) +>> 8.1、WKWebView的使用 +>> 8.2、WKWebView+UITableView混排 +>> 8.3、WKWebView离线缓存功能 +>> 8.4、Html非文本元素替换为原生组件展示 +>> 8.5、UIScrollView实现原理 +>> 8.6、UITableView的实现原理 + +> [高质量技术博客集合](iOS_Tips/DarkMode/WorkIssues/高质量技术博客.md) +> [结尾](#结尾) + + +## 1-暗黑模式适配 + +![暗黑模式](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/暗黑模式.gif) -## 2、AppleID登录应用 +## 2-AppleID登录应用 * 查看本仓库下的AddingTheSignInWithAppleFlowToYourApp -## 3、[微信相机拍摄照片、小视频以及编辑功能](https://www.jianshu.com/p/a2a04cabb98d) +## 3-AVFoundation相关 + +#### 3.1-[微信相机拍摄照片、小视频以及编辑功能](https://www.jianshu.com/p/a2a04cabb98d) > 效果描述: > * 1、自定义相机 拍摄视频和照片 > * 2、切换前后摄像头、调整焦距/设置聚焦点、横屏拍摄 @@ -30,42 +63,43 @@ iOS的一些示例,不定时更新~ 由于内容较多,文件会有点大, > 主要类:SLAvCaptureTool(音视频采集录制工具)、SLAvEditExport(导出编辑的音视频)。关于视频的压缩问题,可以通过降低采集时的分辨率sessionPreset、降低写入文件时的分辨率(AVVideoWidthKey宽AVVideoHeightKey高)和码率(AVVideoCodecKey)、指定高的FormatProfile(AVVideoProfileLevelKey)等方法来实现,同时也要保证一定的清晰度满足业务的需求, 可以看看这篇文章https://www.jianshu.com/p/4f69c22c6dce -|![拍摄视频.gif](PrviewPicture/3、小视频1.gif)|![拍摄照片](PrviewPicture/3、小视频2.gif)|![横屏视频](PrviewPicture/3、小视频3.gif)| +|![拍摄视频.gif](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/3、小视频1.gif)|![拍摄照片](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/3、小视频2.gif)|![横屏视频](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/3、小视频3.gif)| +| ---- | ---- | ---- | +|![视频编辑](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/3、小视频4.gif)|![视频编辑](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/3、小视频5.gif)|![图片编辑](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/3、小视频6.gif)| +|![图片编辑](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/3、小视频7.gif)|![图片裁剪](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/3、小视频8.gif)| -***** -|![视频编辑](PrviewPicture/3、小视频4.gif)|![视频编辑](PrviewPicture/3、小视频5.gif)|![图片编辑](PrviewPicture/3、小视频6.gif)| +#### 3.2-[人脸检测](https://www.jianshu.com/p/f236dc161a90) -**** +![人脸识别](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/4、人脸识别.gif) -|![图片编辑](PrviewPicture/3、小视频7.gif)|![图片裁剪](PrviewPicture/3、小视频8.gif)| +#### 3.3-[实时滤镜拍摄和导出](https://www.jianshu.com/p/f236dc161a90) +> 主要类: 是由SLAvCaptureTool拆分的 SLAvCaptureSession(采集) + SLAvWriterInput(录制) 两个工具类,方便扩展,录制写入实现的方式也略有不同 -## 4、[人脸检测](https://www.jianshu.com/p/f236dc161a90) +![人脸识别](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/5、实时滤镜拍摄.gif) -![人脸识别](PrviewPicture/4、人脸识别.gif) +#### 3.4-[GPUImage框架的使用](https://www.jianshu.com/p/97740cd381f7) -## 5、[实时滤镜拍摄和导出](https://www.jianshu.com/p/f236dc161a90) +> 效果描述:实时拍摄添加水印和滤镜、本地视频添加水印、GIF图水印 -> 主要类: 是由SLAvCaptureTool拆分的 SLAvCaptureSession(采集) + SLAvWriterInput(录制) 两个工具类,方便扩展,实现的方式也略有不同 +![GPUImage框架的使用](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/6、GPUImage.gif) -![人脸识别](PrviewPicture/5、实时滤镜拍摄.gif) +#### 3.5-VideoToolBox和AudioToolBox音视频编解码 -## 6、[GPUImage框架的使用](https://www.jianshu.com/p/97740cd381f7) +> 请查看本仓库下的 VideoEncoder&Decoder 文件 -> 效果描述:实时拍摄添加水印和滤镜、本地视频添加水印、GIF图水印 +![音视频编码](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/7、音视频编码.gif) -* 遗留问题:一个启动周期内,第一次启动摄像头时打开特慢,之后就特别块,还没找到原因,望知道到的告知一下🤝 +#### 3.6-AVFoundation 利用摄像头实时识别物体颜色 -![GPUImage框架的使用](PrviewPicture/6、GPUImage.gif) +![音视频编码](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/3.6、拾色器.gif) -## 7、VideoToolBox和AudioToolBox音视频编解码 +#### 3.7-[AVFoundation 原生二维码扫描识别和生成](https://juejin.im/post/5c0e1db651882539c60d0434) -> 请查看本仓库下的 VideoEncoder&Decoder 文件 + > 该代码地址在:https://github.com/wsl2ls/ScanQRcode -![音视频编码](PrviewPicture/7、音视频编码.gif) - -## 8、[OpenGL ES学习](https://www.jianshu.com/p/9259689cac06) +## 4-OpenGLES学习 > 示例描述: > * 1、GLKit 绘制图片和正方体 @@ -73,33 +107,69 @@ iOS的一些示例,不定时更新~ 由于内容较多,文件会有点大, > * 3、GLSL 滤镜集合:灰度、旋涡、正方形马赛克、六边形马赛克 > * 4 、GLSL 抖音部分特效:分屏、缩放、抖动、灵魂出窍、毛刺 -|![OpenGLES学习.gif](PrviewPicture/8、OpenGLES学习1.gif)|![OpenGLES学习](PrviewPicture/8、OpenGLES学习2.gif)|![OpenGLES学习](PrviewPicture/8、OpenGLES学习3.gif)| +|![OpenGLES学习.gif](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/8、OpenGLES学习1.gif)|![OpenGLES学习](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/8、OpenGLES学习2.gif)|![OpenGLES学习](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/8、OpenGLES学习3.gif)| -## 9、[LeetCode算法练习](https://github.com/wsl2ls/AlgorithmSet.git) +## 5-LeetCode算法练习 > [LeetCode算法练习集合(Swift版) ~ 每天一道算法题](https://github.com/wsl2ls/AlgorithmSet.git) +## 6-工作中踩过的坑 -## 推荐学习资料: +#### 6.1-[键盘和UIMenuController的并存问题](https://www.jianshu.com/p/ed1b57c4ecea) -> [Swift从入门到精通](https://ke.qq.com/course/392094?saleToken=1693443&from=pclink) +| ![问题描述.gif](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/10、键盘和UIMenuController不能同时出现的问题描述.gif) | ![并存问题解决](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/10、键盘和UIMenuController并存问题解决.gif) | -> [恋上数据结构与算法(一)](https://ke.qq.com/course/385223?saleToken=1887678&from=pclink) +#### 6.2-[全屏侧滑手势/UIScrollView/UISlider间滑动手势冲突](https://juejin.im/post/5c0e1e73f265da616413d828) +#### 6.3-[UITableView/UICollectionView获取特定位置的cell](https://juejin.im/post/5c0e1df95188250d2722a3bc) +#### 6.4-[UIScrollView视觉差动画](https://juejin.im/post/5c088b45f265da610e7fe156) +#### 6.5-[iOS 传感器集锦](https://juejin.im/post/5c088a1051882517165dd15d) +#### 6.6-[iOS 自定义转场动画](https://juejin.im/post/5c088ba36fb9a049fb43737b) +#### 6.7-[二进制重排优化启动速度](https://juejin.im/post/5ea79839f265da7bba509590) +#### 6.8-[iOS APM应用性能监控管理(doing)]() -> [恋上数据结构与算法(二)](https://ke.qq.com/course/421398?saleToken=1887679&from=pclink) +> CPU占用率、内存/磁盘使用率、卡顿监控定位、Crash防护、线程数量监控、网络监控(TCP 建立连接时间 、DNS 时间、 SSL时间、首包时间、响应时间 、流量)、ViewController启动耗时监测 、load方法的耗时、方法执行耗时...... -> [每周一道算法题](https://ke.qq.com/course/436549?saleToken=1887824&from=pclink) +#### 6.9、ipa瘦身之扫描无用资源 -## Welcome To Follow Me +> 扫描项目中无用的图片、类等文件资源, 此示例主要针对于此项目中的图片资源,其他类型资源实现原理相同。 -> 您的follow和start,是我前进的动力,Thanks♪(・ω・)ノ -> * [简书](https://www.jianshu.com/u/e15d1f644bea) -> * [微博](https://weibo.com/5732733120/profile?rightmod=1&wvr=6&mod=personinfo&is_all=1) -> * [掘金](https://juejin.im/user/5c00d97b6fb9a049fb436288) -> * [CSDN](https://blog.csdn.net/wsl2ls) -> * QQ交流群:835303405 +#### 6.10、多个UIScrollView嵌套/个人中心页 -> 欢迎扫描下方二维码关注——奔跑的程序猿iOSer——微信公众号:iOS2679114653 本公众号是一个iOS开发者们的分享,交流,学习平台,会不定时的发送技术干货,源码,也欢迎大家积极踊跃投稿,(择优上头条) ^_^分享自己开发攻城的过程,心得,相互学习,共同进步,成为攻城狮中的翘楚! +![多个UIScrollView嵌套](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/多个UIScrollView嵌套.gif) + +## 7-iOSCrash防护 + +> Crash防护内容涉及 NSArray/NSMutableArray、NSDictionary/NSMutableDictionary、NSString/NSMutableString、Unrecognized Selector、KVO、KVC 、异步线程刷新UI、野指针定位、内存泄漏/循环引用;主要是对常见易错的地方进行容错处理,避免崩溃,并保存出错时的函数调用栈,以方便快速定位代码,主要是利用的runtime和fishook知识。 + +![iOSCrash防护](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/iOSCrash防护.gif) -![iOS开发进阶之路.jpg](http://upload-images.jianshu.io/upload_images/1708447-c2471528cadd7c86.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 8-WKWebView相关 + +> [WKWebView的使用](https://juejin.im/post/5c0e1e2ae51d451d971743a1)、[WKWebView+UITableView混排](https://juejin.im/post/5ed999fd51882542f9389949)、WKWebView离线缓存功能、HTML非文本元素替换为原生组件展示、UIScrollView实现原理、UITableView的实现原理 + +![WKWebView相关](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/12、WKWebView.gif) + +## 结尾 + +> * 1、主工程就是iOS_Tips下的DarkMode,别怀疑🤣,历史遗留问题😁😀,大部分内容都在里面,run一下就明白了🤝; +> * 2、该demo里面有些功能还没有写博客介绍,后期有时间会补上,不过代码我一般喜欢写注释,所以我相信大家读起来应该也容易理解,建议大家看完之后,自己也可以写写,把整个流程过一遍,也许会比我写的更好哟; +> * 3、[看过的高质量技术博客集合](iOS_Tips/DarkMode/WorkIssues/高质量技术博客.md),这些博客质量都挺高的,都出自各个大厂、大佬之手,认真看完绝对干活满满; +> * 4、小视频拍摄录制失败,主要集中在plus和X系列手机上:可能是由于写入的视频宽高videoSize设置的问题,各位可以先试试这样设置 +avCaptureTool.videoSize = CGSizeMake(self.view.width * 0.8, self.view.height * 0.8); +> * 5、当你编译的时候,XCode出现Unable to load contents of file list 错误,导致出现此原因是pods版本不一致,请更新pods版本或者重新安装。 +> * 6、如果发现我简书或掘金上的文章无法查看了,请联系我。 + + +#### Welcome to you 👏 您的follow和start,是我前进的动力,Thanks♪(・ω・)ノ 🤝 + +| [简书 ](https://www.jianshu.com/u/e15d1f644bea) | [掘金](https://juejin.im/user/5c00d97b6fb9a049fb436288) | QQ交流群 | 微信公众号 | 微信交流群 | +| ---- | ---- | ---- | ---- | ---- | +| [CSDN](https://blog.csdn.net/wsl2ls) | [微博](https://weibo.com/5732733120/profile?rightmod=1&wvr=6&mod=personinfo&is_all=1) | [835303405](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/QQ交流群.png) | [iOS2679114653](https://github.com/wsl2ls/iOS_TipsPreview/blob/master/PrviewPicture/微信公众号.png) | w2679114653(加我拉入群) | + +[回到顶部](#iOS_Tips) + +![QQ交流群: 835303405](QQ交流群.png) + +> 欢迎扫描下方二维码关注——奔跑的程序猿iOSer——微信公众号:iOS2679114653 本公众号是一个iOS开发者们的分享,交流,学习平台,会不定时的发送技术干货,源码,也欢迎大家积极踊跃投稿,(择优上头条) ^_^分享自己开发攻城的过程,心得,相互学习,共同进步,成为攻城狮中的翘楚! +![奔跑的程序猿iOSer](微信公众号.png) diff --git a/iOS_Tips/.DS_Store b/iOS_Tips/.DS_Store index 1c5bbf33..36605046 100644 Binary files a/iOS_Tips/.DS_Store and b/iOS_Tips/.DS_Store differ diff --git a/iOS_Tips/DarkMode.xcodeproj/project.pbxproj b/iOS_Tips/DarkMode.xcodeproj/project.pbxproj index 9388bade..08387bb0 100644 --- a/iOS_Tips/DarkMode.xcodeproj/project.pbxproj +++ b/iOS_Tips/DarkMode.xcodeproj/project.pbxproj @@ -3,12 +3,23 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ + 2C5F11A7257BCB7000904B70 /* YYAnimatedImageView+iOS14.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C5F11A6257BCB7000904B70 /* YYAnimatedImageView+iOS14.m */; }; + 2CC813682553CE88009DAC9B /* 已浏览.md in Resources */ = {isa = PBXBuildFile; fileRef = 2CC813672553CE88009DAC9B /* 已浏览.md */; }; + 2CEBE85D24A6E65600BA21F3 /* SLTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CEBE85B24A6E65600BA21F3 /* SLTimer.m */; }; 780F5303236D8462000D0EA8 /* SLGridView.m in Sources */ = {isa = PBXBuildFile; fileRef = 780F5302236D8462000D0EA8 /* SLGridView.m */; }; 780F5306236D8570000D0EA8 /* SLImageClipController.m in Sources */ = {isa = PBXBuildFile; fileRef = 780F5305236D8570000D0EA8 /* SLImageClipController.m */; }; + 7811F76F24B2C791000AA044 /* SLBinaryResetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7811F76E24B2C791000AA044 /* SLBinaryResetViewController.m */; }; + 7811F77124B2C818000AA044 /* wsl.order in Resources */ = {isa = PBXBuildFile; fileRef = 7811F77024B2C818000AA044 /* wsl.order */; }; + 7811F7AD24B32A1E000AA044 /* Write Link Map File.png in Resources */ = {isa = PBXBuildFile; fileRef = 7811F7AA24B32A1E000AA044 /* Write Link Map File.png */; }; + 7811F7AE24B32A1E000AA044 /* Other c Flags.png in Resources */ = {isa = PBXBuildFile; fileRef = 7811F7AB24B32A1E000AA044 /* Other c Flags.png */; }; + 7811F7AF24B32A1E000AA044 /* Order File.png in Resources */ = {isa = PBXBuildFile; fileRef = 7811F7AC24B32A1E000AA044 /* Order File.png */; }; + 781D9D0624DD662E00FAE73D /* UIView+SLAsynUpdateUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 781D9D0524DD662E00FAE73D /* UIView+SLAsynUpdateUI.m */; }; + 781DA28B24DFD52500FAE73D /* SLAPMLoadTime.m in Sources */ = {isa = PBXBuildFile; fileRef = 781DA28924DFD52400FAE73D /* SLAPMLoadTime.m */; }; + 781DA28E24E00F9E00FAE73D /* QiCallTraceCore.c in Sources */ = {isa = PBXBuildFile; fileRef = 781DA28C24E00F9E00FAE73D /* QiCallTraceCore.c */; }; 7822CCF8235B054200E70C29 /* SLPaddingLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7822CCF7235B054200E70C29 /* SLPaddingLabel.m */; }; 782CFB1D239DDE95001B5528 /* SLSplitScreenViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 782CFB1C239DDE95001B5528 /* SLSplitScreenViewController.m */; }; 782CFB20239DEA05001B5528 /* SLSplitScreenCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 782CFB1F239DEA05001B5528 /* SLSplitScreenCell.m */; }; @@ -49,26 +60,89 @@ 782E3EB82373F911001E0DF9 /* SLFilterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 782E3EB72373F911001E0DF9 /* SLFilterViewController.m */; }; 7830A59E2358520200BC79BA /* SLEditTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7830A59D2358520200BC79BA /* SLEditTextView.m */; }; 7830A5A42358904600BC79BA /* UIView+SLImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 7830A5A32358904600BC79BA /* UIView+SLImage.m */; }; + 783504642452C20B0071283E /* SLMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 783504632452C20B0071283E /* SLMethod.m */; }; + 7835048424586BAC0071283E /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = 7835048024586BAC0071283E /* fishhook.c */; }; + 7835048524586BAC0071283E /* queue.c in Sources */ = {isa = PBXBuildFile; fileRef = 7835048124586BAC0071283E /* queue.c */; }; 783FB45C2394A4E10039AEFD /* SLShaderLanguageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 783FB45B2394A4E10039AEFD /* SLShaderLanguageViewController.m */; }; 783FB4602394A76B0039AEFD /* shaderv.vsh in Resources */ = {isa = PBXBuildFile; fileRef = 783FB45E2394A76B0039AEFD /* shaderv.vsh */; }; 783FB4612394A76B0039AEFD /* shaderf.fsh in Resources */ = {isa = PBXBuildFile; fileRef = 783FB45F2394A76B0039AEFD /* shaderf.fsh */; }; 784B72062334685E006AEE47 /* SLAvPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 784B72052334685E006AEE47 /* SLAvPlayer.m */; }; 784B720923348241006AEE47 /* SLAvCaptureTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 784B720823348241006AEE47 /* SLAvCaptureTool.m */; }; + 784C4BE124BC40D200D5C199 /* SLAPMViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 784C4BE024BC40D200D5C199 /* SLAPMViewController.m */; }; + 784C4BE424BC4C5E00D5C199 /* SLAPMManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 784C4BE324BC4C5E00D5C199 /* SLAPMManager.m */; }; + 784C4BE724BC53D500D5C199 /* SLAPMCpu.m in Sources */ = {isa = PBXBuildFile; fileRef = 784C4BE624BC53D500D5C199 /* SLAPMCpu.m */; }; + 784C4BEA24BDA2DE00D5C199 /* SLProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 784C4BE924BDA2DE00D5C199 /* SLProxy.m */; }; + 784C4BED24BDA5D700D5C199 /* SLAPMFluency.m in Sources */ = {isa = PBXBuildFile; fileRef = 784C4BEC24BDA5D700D5C199 /* SLAPMFluency.m */; }; + 784C4C3A24C2EE6600D5C199 /* SLAPMMemoryDisk.m in Sources */ = {isa = PBXBuildFile; fileRef = 784C4C3924C2EE6600D5C199 /* SLAPMMemoryDisk.m */; }; + 784C4C6E24C5B4F500D5C199 /* 笔记.md in Resources */ = {isa = PBXBuildFile; fileRef = 784C4C6D24C5B4F500D5C199 /* 笔记.md */; }; + 784C4C7024C5B5DD00D5C199 /* 高质量技术博客.md in Resources */ = {isa = PBXBuildFile; fileRef = 784C4C6F24C5B5DD00D5C199 /* 高质量技术博客.md */; }; 7851CB362331CC87002295B5 /* SLDarkModeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7851CB332331CC87002295B5 /* SLDarkModeViewController.m */; }; 7851CB372331CC87002295B5 /* SLDarkModeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7851CB352331CC87002295B5 /* SLDarkModeViewController.xib */; }; 7851CB43233222E2002295B5 /* UIView+SLFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 7851CB41233222E2002295B5 /* UIView+SLFrame.m */; }; 7857977823725C21004CD664 /* SLFaceDetectController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7857977723725C21004CD664 /* SLFaceDetectController.m */; }; + 7857FD4B24729E8500D3D986 /* BSBacktraceLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 7857FD4A24729E8500D3D986 /* BSBacktraceLogger.m */; }; + 785D06642522C32700F174BA /* 面试题.md in Resources */ = {isa = PBXBuildFile; fileRef = 785D06632522C32700F174BA /* 面试题.md */; }; 7860D78E2398D34E008C53EC /* SLMixColorTextureVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 7860D78D2398D34E008C53EC /* SLMixColorTextureVC.m */; }; 7860D791239922B4008C53EC /* SLGLKPyramidVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 7860D790239922B4008C53EC /* SLGLKPyramidVC.m */; }; 786147F12362A39500C5424C /* Resources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 786147F02362A39500C5424C /* Resources.bundle */; }; 786147F42362D79000C5424C /* SLMosaicView.m in Sources */ = {isa = PBXBuildFile; fileRef = 786147F32362D79000C5424C /* SLMosaicView.m */; }; 786147FA23630C4D00C5424C /* UIImage+SLCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 786147F923630C4D00C5424C /* UIImage+SLCommon.m */; }; + 7862533F24599D420017F8F1 /* NSObject+SLCrashProtector.m in Sources */ = {isa = PBXBuildFile; fileRef = 7862532C24599D420017F8F1 /* NSObject+SLCrashProtector.m */; }; + 7862534324599D420017F8F1 /* SLCrashHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 7862533224599D420017F8F1 /* SLCrashHandler.m */; }; + 7862534524599D420017F8F1 /* SLKVODelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7862533A24599D420017F8F1 /* SLKVODelegate.m */; }; + 78665C6A246D4B7C0001B749 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78665C69246D4B7C0001B749 /* Foundation.framework */; }; + 78665C6C246D4B880001B749 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78665C6B246D4B880001B749 /* Security.framework */; }; + 78665C6E246D4B950001B749 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78665C6D246D4B950001B749 /* CoreTelephony.framework */; }; + 78665C70246D4B9F0001B749 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78665C6F246D4B9F0001B749 /* SystemConfiguration.framework */; }; + 78665C72246D4BB10001B749 /* libicucore.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 78665C71246D4BB10001B749 /* libicucore.tbd */; }; + 78665C74246D4BBB0001B749 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 78665C73246D4BBB0001B749 /* libsqlite3.tbd */; }; + 7869C72224A8B6D200527546 /* SLPictureBrowseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7869C71E24A8B6D100527546 /* SLPictureBrowseController.m */; }; + 7869C72324A8B6D200527546 /* SLPictureTransitionAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 7869C72124A8B6D200527546 /* SLPictureTransitionAnimation.m */; }; 78777CE0238FEA48006FA671 /* SLOpenGLController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78777CDF238FEA48006FA671 /* SLOpenGLController.m */; }; 78777CE6238FF65C006FA671 /* SLDelayPerform.m in Sources */ = {isa = PBXBuildFile; fileRef = 78777CE5238FF65C006FA671 /* SLDelayPerform.m */; }; + 78799EA224EE7EEC00DA8C7A /* SLUnusedResourceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78799EA124EE7EEC00DA8C7A /* SLUnusedResourceViewController.m */; }; + 78799EAB24EFC60C00DA8C7A /* SLResourceInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 78799EAA24EFC60C00DA8C7A /* SLResourceInfo.m */; }; + 78799EB224F1027300DA8C7A /* unused.png in Resources */ = {isa = PBXBuildFile; fileRef = 78799EB124F1027300DA8C7A /* unused.png */; }; + 787C7DBE245D74E4005DF7ED /* NSMutableArray+SLCrashProtector.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DB2245D74E3005DF7ED /* NSMutableArray+SLCrashProtector.m */; }; + 787C7DBF245D74E4005DF7ED /* NSMutableDictionary+SLCrashProtector.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DB3245D74E3005DF7ED /* NSMutableDictionary+SLCrashProtector.m */; }; + 787C7DC0245D74E4005DF7ED /* NSDictionary+SLCrashProtector.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DB5245D74E3005DF7ED /* NSDictionary+SLCrashProtector.m */; }; + 787C7DC1245D74E4005DF7ED /* NSArray+SLCrashProtector.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DB7245D74E4005DF7ED /* NSArray+SLCrashProtector.m */; }; + 787C7DC2245D74E4005DF7ED /* NSMutableString+SLCrashProtector.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DBA245D74E4005DF7ED /* NSMutableString+SLCrashProtector.m */; }; + 787C7DC3245D74E4005DF7ED /* NSString+SLCrashProtector.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DBB245D74E4005DF7ED /* NSString+SLCrashProtector.m */; }; + 787C7DC8245D77D9005DF7ED /* SLZombieCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DC6245D77D8005DF7ED /* SLZombieCatcher.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 787C7DC9245D77D9005DF7ED /* SLZombieSafeFree.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DC7245D77D9005DF7ED /* SLZombieSafeFree.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 787C7DCC2462ADE5005DF7ED /* NSObject+SLMLeakFinder.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DCB2462ADE5005DF7ED /* NSObject+SLMLeakFinder.m */; }; + 787C7DCF2462B03D005DF7ED /* UINavigationController+SLMLeakFinder.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DCE2462B03D005DF7ED /* UINavigationController+SLMLeakFinder.m */; }; + 787C7DD22462C5F3005DF7ED /* UIViewController+SLMLeakFinder.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7DD12462C5F3005DF7ED /* UIViewController+SLMLeakFinder.m */; }; + 787C7E42246557A5005DF7ED /* SLZombieFinder.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7E41246557A5005DF7ED /* SLZombieFinder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 787C7E4524656524005DF7ED /* UIView+SLMLeakFinder.m in Sources */ = {isa = PBXBuildFile; fileRef = 787C7E4424656524005DF7ED /* UIView+SLMLeakFinder.m */; }; 787F01A3236AD8A5002AC1A9 /* SLEditImageController.m in Sources */ = {isa = PBXBuildFile; fileRef = 787F01A2236AD8A5002AC1A9 /* SLEditImageController.m */; }; 78804A7E237D5E3E0087E152 /* SLWaterMarkController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78804A7D237D5E3E0087E152 /* SLWaterMarkController.m */; }; + 78842454248F863500C2E505 /* SLTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78842453248F863500C2E505 /* SLTableViewController.m */; }; + 78842464248F964000C2E505 /* SLAVListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78842463248F964000C2E505 /* SLAVListViewController.m */; }; + 78842468248FAED100C2E505 /* SLColorPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78842467248FAED100C2E505 /* SLColorPickerViewController.m */; }; + 7884246B249070D900C2E505 /* UIColor+SLCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 7884246A249070D900C2E505 /* UIColor+SLCommon.m */; }; + 7884247F2492102900C2E505 /* SLWorkIssuesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7884247E2492102900C2E505 /* SLWorkIssuesViewController.m */; }; + 788424822492159F00C2E505 /* SLNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 788424812492159F00C2E505 /* SLNavigationController.m */; }; + 7884248B2493851B00C2E505 /* SLViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7884248A2493851B00C2E505 /* SLViewController.m */; }; + 7884248E24963FD200C2E505 /* SLReusableManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7884248D24963FD200C2E505 /* SLReusableManager.m */; }; + 788424C724978E9200C2E505 /* SLKeyChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 788424C624978E9200C2E505 /* SLKeyChain.m */; }; 788ACD622390BFD400737EC2 /* SLLoadImageVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 788ACD612390BFD400737EC2 /* SLLoadImageVC.m */; }; 788ACD652390EA3A00737EC2 /* SLCubeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 788ACD642390EA3A00737EC2 /* SLCubeViewController.m */; }; + 788ADB382441F48F00302CD9 /* SLCrashViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 788ADB362441F48F00302CD9 /* SLCrashViewController.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 78908DC324FFBE5D004164C2 /* SLScrollviewNesteVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 78908DC224FFBE5D004164C2 /* SLScrollviewNesteVC.m */; }; + 78908DCD2500F099004164C2 /* SLMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = 78908DCC2500F099004164C2 /* SLMenuView.m */; }; + 78908DFA25078F84004164C2 /* SLScrollViewJuejin.m in Sources */ = {isa = PBXBuildFile; fileRef = 78908DF925078F84004164C2 /* SLScrollViewJuejin.m */; }; + 78908DFD25079323004164C2 /* SLScrollViewWeibo.m in Sources */ = {isa = PBXBuildFile; fileRef = 78908DFC25079323004164C2 /* SLScrollViewWeibo.m */; }; + 78908E0025079451004164C2 /* SLPanTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 78908DFF25079451004164C2 /* SLPanTableView.m */; }; + 789AD16324C99DCA00CB0B4C /* SLAPMThreadCount.m in Sources */ = {isa = PBXBuildFile; fileRef = 789AD16224C99DCA00CB0B4C /* SLAPMThreadCount.m */; }; + 789AD1C824D7FA5F00CB0B4C /* SLAPMURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 789AD1C724D7FA5F00CB0B4C /* SLAPMURLProtocol.m */; }; + 789AD1CF24D9024300CB0B4C /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 789AD1CD24D9024300CB0B4C /* Reachability.m */; }; + 789AD1D224D9622B00CB0B4C /* UIViewController+SLAPMVCTime.m in Sources */ = {isa = PBXBuildFile; fileRef = 789AD1D124D9622B00CB0B4C /* UIViewController+SLAPMVCTime.m */; }; + 789AD1D524D97F8500CB0B4C /* SLSystemAppInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 789AD1D424D97F8500CB0B4C /* SLSystemAppInfo.m */; }; + 78A179A8249B0AA8006F52E3 /* NSDictionary+SLExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 78A179A6249B0AA8006F52E3 /* NSDictionary+SLExtension.m */; }; + 78A1998C2451BEE7005B2B4B /* UIButton+SLTitleImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 78A1998B2451BEE7005B2B4B /* UIButton+SLTitleImage.m */; }; + 78A1998F2451C049005B2B4B /* SLButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 78A1998E2451C049005B2B4B /* SLButton.m */; }; 78A35B1C23978A5D004BCCB7 /* SLShaderCubeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78A35B1B23978A5D004BCCB7 /* SLShaderCubeViewController.m */; }; 78A4BD45236C5DF20021AE32 /* SLEditVideoController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78A4BD44236C5DF20021AE32 /* SLEditVideoController.m */; }; 78A4BD48236C5DFE0021AE32 /* SLImageZoomView.m in Sources */ = {isa = PBXBuildFile; fileRef = 78A4BD47236C5DFD0021AE32 /* SLImageZoomView.m */; }; @@ -81,10 +155,16 @@ 78B10493232F57C50051579F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 78B10492232F57C50051579F /* main.m */; }; 78B1049D232F57C50051579F /* DarkModeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 78B1049C232F57C50051579F /* DarkModeTests.m */; }; 78B104A8232F57C50051579F /* DarkModeUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 78B104A7232F57C50051579F /* DarkModeUITests.m */; }; + 78BDE04B248A6A74002ED386 /* SLUrlProtocolAddCookie.m in Sources */ = {isa = PBXBuildFile; fileRef = 78BDE04A248A6A74002ED386 /* SLUrlProtocolAddCookie.m */; }; + 78BDE04D248BFBD7002ED386 /* wsl.png in Resources */ = {isa = PBXBuildFile; fileRef = 78BDE04C248BFBD7002ED386 /* wsl.png */; }; + 78BDE050248E0AFB002ED386 /* SLWebNativeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78BDE04F248E0AFB002ED386 /* SLWebNativeViewController.m */; }; + 78BDE052248E0BEC002ED386 /* WebNative.html in Resources */ = {isa = PBXBuildFile; fileRef = 78BDE051248E0BEB002ED386 /* WebNative.html */; }; + 78BDE058248E541E002ED386 /* WebNativeJson.txt in Resources */ = {isa = PBXBuildFile; fileRef = 78BDE057248E541E002ED386 /* WebNativeJson.txt */; }; 78D30964239688EF00DC373A /* GLESUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 78D3095E239688EF00DC373A /* GLESUtils.m */; }; 78D30965239688EF00DC373A /* GLESMath.c in Sources */ = {isa = PBXBuildFile; fileRef = 78D30960239688EF00DC373A /* GLESMath.c */; }; 78DAA113235FF61E00A60F64 /* SLEditVideoClipping.m in Sources */ = {isa = PBXBuildFile; fileRef = 78DAA112235FF61E00A60F64 /* SLEditVideoClipping.m */; }; 78DAA1162360256200A60F64 /* SLEditSelectedBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 78DAA1152360256200A60F64 /* SLEditSelectedBox.m */; }; + 78DF7F652510B45000F6C007 /* SLScrollViewJianShu.m in Sources */ = {isa = PBXBuildFile; fileRef = 78DF7F642510B45000F6C007 /* SLScrollViewJianShu.m */; }; 78E1791723517F700007E4BB /* SLImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E1791223517F700007E4BB /* SLImageView.m */; }; 78E1791823517F700007E4BB /* SLImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E1791323517F700007E4BB /* SLImage.m */; }; 78E1791923517F700007E4BB /* SLImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E1791423517F700007E4BB /* SLImageDecoder.m */; }; @@ -92,6 +172,24 @@ 78E179312351A4020007E4BB /* SLShotViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E1792F2351A4020007E4BB /* SLShotViewController.m */; }; 78E179362351A40D0007E4BB /* SLEditMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E179332351A40D0007E4BB /* SLEditMenuView.m */; }; 78E179372351A40D0007E4BB /* SLShotFocusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E179342351A40D0007E4BB /* SLShotFocusView.m */; }; + 78E72D5124766E4B00751373 /* SLWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D5024766E4B00751373 /* SLWebViewController.m */; }; + 78E72D542477A4E700751373 /* SLWebTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D532477A4E700751373 /* SLWebTableViewController.m */; }; + 78E72D59247BA29600751373 /* WebTableView.html in Resources */ = {isa = PBXBuildFile; fileRef = 78E72D58247BA29600751373 /* WebTableView.html */; }; + 78E72D5C247BDDE700751373 /* SLWebTableViewController2.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D5B247BDDE700751373 /* SLWebTableViewController2.m */; }; + 78E72D70247E536400751373 /* SLWebTableViewController3.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D6F247E536400751373 /* SLWebTableViewController3.m */; }; + 78E72D73247E861300751373 /* SLWebViewListController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D72247E861300751373 /* SLWebViewListController.m */; }; + 78E72D76247F9FFC00751373 /* SLWebTableViewController4.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D75247F9FFC00751373 /* SLWebTableViewController4.m */; }; + 78E72D792480B36800751373 /* SLScrollViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D782480B36800751373 /* SLScrollViewController.m */; }; + 78E72D7C24810E2D00751373 /* SLDynamicItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D7B24810E2D00751373 /* SLDynamicItem.m */; }; + 78E72D7F24810ECC00751373 /* UIScrollView+SLCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D7E24810ECC00751373 /* UIScrollView+SLCommon.m */; }; + 78E72D8224811F6600751373 /* SLWebCacheViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D8124811F6600751373 /* SLWebCacheViewController.m */; }; + 78E72D86248284B700751373 /* SLUrlProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D85248284B700751373 /* SLUrlProtocol.m */; }; + 78E72D8C2482958300751373 /* WKWebView+SLExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D8B2482958300751373 /* WKWebView+SLExtension.m */; }; + 78E72D8F2483DC2600751373 /* SLWebCacheManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D8E2483DC2600751373 /* SLWebCacheManager.m */; }; + 78E72D922484EEAA00751373 /* SLUrlCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 78E72D912484EEAA00751373 /* SLUrlCache.m */; }; + 78EF8B6024162B96008D0CD7 /* SLMenuViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78EF8B5E24162B96008D0CD7 /* SLMenuViewController.m */; }; + 78EF8B6124162B96008D0CD7 /* SLMenuViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 78EF8B5F24162B96008D0CD7 /* SLMenuViewController.xib */; }; + 78EF8B6424188767008D0CD7 /* SLAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = 78EF8B6324188767008D0CD7 /* SLAlertView.m */; }; 78F42023237915730093497C /* SLGPUImageController.m in Sources */ = {isa = PBXBuildFile; fileRef = 78F42022237915730093497C /* SLGPUImageController.m */; }; 78F54916233383D800910215 /* SLBlurView.m in Sources */ = {isa = PBXBuildFile; fileRef = 78F54915233383D800910215 /* SLBlurView.m */; }; 78FA0F9D235407E6003E456B /* SLDrawView.m in Sources */ = {isa = PBXBuildFile; fileRef = 78FA0F9C235407E6003E456B /* SLDrawView.m */; }; @@ -117,16 +215,34 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 2C5F11A5257BCB7000904B70 /* YYAnimatedImageView+iOS14.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "YYAnimatedImageView+iOS14.h"; sourceTree = ""; }; + 2C5F11A6257BCB7000904B70 /* YYAnimatedImageView+iOS14.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "YYAnimatedImageView+iOS14.m"; sourceTree = ""; }; + 2CC813672553CE88009DAC9B /* 已浏览.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "已浏览.md"; sourceTree = ""; }; + 2CC8137925540CB8009DAC9B /* HMLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HMLog.h; sourceTree = ""; }; + 2CEBE85B24A6E65600BA21F3 /* SLTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLTimer.m; sourceTree = ""; }; + 2CEBE85C24A6E65600BA21F3 /* SLTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLTimer.h; sourceTree = ""; }; 780F5301236D845B000D0EA8 /* SLGridView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLGridView.h; sourceTree = ""; }; 780F5302236D8462000D0EA8 /* SLGridView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLGridView.m; sourceTree = ""; }; 780F5304236D8570000D0EA8 /* SLImageClipController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLImageClipController.h; sourceTree = ""; }; 780F5305236D8570000D0EA8 /* SLImageClipController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLImageClipController.m; sourceTree = ""; }; + 7811F76D24B2C791000AA044 /* SLBinaryResetViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLBinaryResetViewController.h; sourceTree = ""; }; + 7811F76E24B2C791000AA044 /* SLBinaryResetViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLBinaryResetViewController.m; sourceTree = ""; }; + 7811F77024B2C818000AA044 /* wsl.order */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wsl.order; sourceTree = ""; }; + 7811F7AA24B32A1E000AA044 /* Write Link Map File.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Write Link Map File.png"; sourceTree = ""; }; + 7811F7AB24B32A1E000AA044 /* Other c Flags.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Other c Flags.png"; sourceTree = ""; }; + 7811F7AC24B32A1E000AA044 /* Order File.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Order File.png"; sourceTree = ""; }; + 781D9D0424DD662E00FAE73D /* UIView+SLAsynUpdateUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SLAsynUpdateUI.h"; sourceTree = ""; }; + 781D9D0524DD662E00FAE73D /* UIView+SLAsynUpdateUI.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SLAsynUpdateUI.m"; sourceTree = ""; }; + 781DA28924DFD52400FAE73D /* SLAPMLoadTime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLAPMLoadTime.m; sourceTree = ""; }; + 781DA28A24DFD52500FAE73D /* SLAPMLoadTime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLAPMLoadTime.h; sourceTree = ""; }; + 781DA28C24E00F9E00FAE73D /* QiCallTraceCore.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = QiCallTraceCore.c; sourceTree = ""; }; + 781DA28D24E00F9E00FAE73D /* QiCallTraceCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QiCallTraceCore.h; sourceTree = ""; }; 7822CCF6235B054200E70C29 /* SLPaddingLabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLPaddingLabel.h; sourceTree = ""; }; 7822CCF7235B054200E70C29 /* SLPaddingLabel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLPaddingLabel.m; sourceTree = ""; }; 782CFB1B239DDE95001B5528 /* SLSplitScreenViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLSplitScreenViewController.h; sourceTree = ""; }; 782CFB1C239DDE95001B5528 /* SLSplitScreenViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLSplitScreenViewController.m; sourceTree = ""; }; - 782CFB1E239DEA05001B5528 /* SLSplitScreenCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SLSplitScreenCell.h; path = DarkMode/OpenGL/Controller/SLSplitScreenCell.h; sourceTree = SOURCE_ROOT; }; - 782CFB1F239DEA05001B5528 /* SLSplitScreenCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SLSplitScreenCell.m; path = DarkMode/OpenGL/Controller/SLSplitScreenCell.m; sourceTree = SOURCE_ROOT; }; + 782CFB1E239DEA05001B5528 /* SLSplitScreenCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SLSplitScreenCell.h; path = DarkMode/OpenGL/View/SLSplitScreenCell.h; sourceTree = SOURCE_ROOT; }; + 782CFB1F239DEA05001B5528 /* SLSplitScreenCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SLSplitScreenCell.m; path = DarkMode/OpenGL/View/SLSplitScreenCell.m; sourceTree = SOURCE_ROOT; }; 782CFB21239DFD0F001B5528 /* SplitScreen_2.fsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = SplitScreen_2.fsh; sourceTree = ""; }; 782CFB22239DFD0F001B5528 /* SplitScreen_2.vsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = SplitScreen_2.vsh; sourceTree = ""; }; 782CFB25239DFD88001B5528 /* SplitScreen_1.fsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = SplitScreen_1.fsh; sourceTree = ""; }; @@ -171,6 +287,12 @@ 7830A59D2358520200BC79BA /* SLEditTextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLEditTextView.m; sourceTree = ""; }; 7830A5A22358904600BC79BA /* UIView+SLImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SLImage.h"; sourceTree = ""; }; 7830A5A32358904600BC79BA /* UIView+SLImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SLImage.m"; sourceTree = ""; }; + 783504622452C20B0071283E /* SLMethod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLMethod.h; sourceTree = ""; }; + 783504632452C20B0071283E /* SLMethod.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLMethod.m; sourceTree = ""; }; + 7835048024586BAC0071283E /* fishhook.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fishhook.c; sourceTree = ""; }; + 7835048124586BAC0071283E /* queue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = queue.c; sourceTree = ""; }; + 7835048224586BAC0071283E /* fishhook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fishhook.h; sourceTree = ""; }; + 7835048324586BAC0071283E /* queue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = ""; }; 783FB45A2394A4E10039AEFD /* SLShaderLanguageViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLShaderLanguageViewController.h; sourceTree = ""; }; 783FB45B2394A4E10039AEFD /* SLShaderLanguageViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLShaderLanguageViewController.m; sourceTree = ""; }; 783FB45E2394A76B0039AEFD /* shaderv.vsh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.glsl; path = shaderv.vsh; sourceTree = ""; }; @@ -179,6 +301,20 @@ 784B72052334685E006AEE47 /* SLAvPlayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAvPlayer.m; sourceTree = ""; }; 784B720723348241006AEE47 /* SLAvCaptureTool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAvCaptureTool.h; sourceTree = ""; }; 784B720823348241006AEE47 /* SLAvCaptureTool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAvCaptureTool.m; sourceTree = ""; }; + 784C4BDF24BC40D200D5C199 /* SLAPMViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAPMViewController.h; sourceTree = ""; }; + 784C4BE024BC40D200D5C199 /* SLAPMViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAPMViewController.m; sourceTree = ""; }; + 784C4BE224BC4C5E00D5C199 /* SLAPMManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAPMManager.h; sourceTree = ""; }; + 784C4BE324BC4C5E00D5C199 /* SLAPMManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAPMManager.m; sourceTree = ""; }; + 784C4BE524BC53D500D5C199 /* SLAPMCpu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAPMCpu.h; sourceTree = ""; }; + 784C4BE624BC53D500D5C199 /* SLAPMCpu.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAPMCpu.m; sourceTree = ""; }; + 784C4BE824BDA2DE00D5C199 /* SLProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLProxy.h; sourceTree = ""; }; + 784C4BE924BDA2DE00D5C199 /* SLProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLProxy.m; sourceTree = ""; }; + 784C4BEB24BDA5D700D5C199 /* SLAPMFluency.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAPMFluency.h; sourceTree = ""; }; + 784C4BEC24BDA5D700D5C199 /* SLAPMFluency.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAPMFluency.m; sourceTree = ""; }; + 784C4C3824C2EE6600D5C199 /* SLAPMMemoryDisk.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAPMMemoryDisk.h; sourceTree = ""; }; + 784C4C3924C2EE6600D5C199 /* SLAPMMemoryDisk.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAPMMemoryDisk.m; sourceTree = ""; }; + 784C4C6D24C5B4F500D5C199 /* 笔记.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "笔记.md"; sourceTree = ""; }; + 784C4C6F24C5B5DD00D5C199 /* 高质量技术博客.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "高质量技术博客.md"; sourceTree = ""; }; 7851CB332331CC87002295B5 /* SLDarkModeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLDarkModeViewController.m; sourceTree = ""; }; 7851CB342331CC87002295B5 /* SLDarkModeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLDarkModeViewController.h; sourceTree = ""; }; 7851CB352331CC87002295B5 /* SLDarkModeViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SLDarkModeViewController.xib; sourceTree = ""; }; @@ -186,6 +322,9 @@ 7851CB42233222E2002295B5 /* UIView+SLFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+SLFrame.h"; sourceTree = ""; }; 7857977623725C21004CD664 /* SLFaceDetectController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLFaceDetectController.h; sourceTree = ""; }; 7857977723725C21004CD664 /* SLFaceDetectController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLFaceDetectController.m; sourceTree = ""; }; + 7857FD4924729E8500D3D986 /* BSBacktraceLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSBacktraceLogger.h; sourceTree = ""; }; + 7857FD4A24729E8500D3D986 /* BSBacktraceLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSBacktraceLogger.m; sourceTree = ""; }; + 785D06632522C32700F174BA /* 面试题.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "面试题.md"; sourceTree = ""; }; 7860D78C2398D34E008C53EC /* SLMixColorTextureVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLMixColorTextureVC.h; sourceTree = ""; }; 7860D78D2398D34E008C53EC /* SLMixColorTextureVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLMixColorTextureVC.m; sourceTree = ""; }; 7860D78F239922B4008C53EC /* SLGLKPyramidVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLGLKPyramidVC.h; sourceTree = ""; }; @@ -195,18 +334,112 @@ 786147F32362D79000C5424C /* SLMosaicView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLMosaicView.m; sourceTree = ""; }; 786147F823630C4D00C5424C /* UIImage+SLCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIImage+SLCommon.h"; sourceTree = ""; }; 786147F923630C4D00C5424C /* UIImage+SLCommon.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIImage+SLCommon.m"; sourceTree = ""; }; + 7862532C24599D420017F8F1 /* NSObject+SLCrashProtector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SLCrashProtector.m"; sourceTree = ""; }; + 7862532F24599D420017F8F1 /* SLKVODelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLKVODelegate.h; sourceTree = ""; }; + 7862533224599D420017F8F1 /* SLCrashHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLCrashHandler.m; sourceTree = ""; }; + 7862533724599D420017F8F1 /* NSObject+SLCrashProtector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SLCrashProtector.h"; sourceTree = ""; }; + 7862533924599D420017F8F1 /* SLCrashProtector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLCrashProtector.h; sourceTree = ""; }; + 7862533A24599D420017F8F1 /* SLKVODelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLKVODelegate.m; sourceTree = ""; }; + 7862533D24599D420017F8F1 /* SLCrashHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLCrashHandler.h; sourceTree = ""; }; + 78665C69246D4B7C0001B749 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 78665C6B246D4B880001B749 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 78665C6D246D4B950001B749 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; + 78665C6F246D4B9F0001B749 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + 78665C71246D4BB10001B749 /* libicucore.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libicucore.tbd; path = usr/lib/libicucore.tbd; sourceTree = SDKROOT; }; + 78665C73246D4BBB0001B749 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; + 7869C71E24A8B6D100527546 /* SLPictureBrowseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLPictureBrowseController.m; sourceTree = ""; }; + 7869C71F24A8B6D100527546 /* SLPictureBrowseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLPictureBrowseController.h; sourceTree = ""; }; + 7869C72024A8B6D200527546 /* SLPictureTransitionAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLPictureTransitionAnimation.h; sourceTree = ""; }; + 7869C72124A8B6D200527546 /* SLPictureTransitionAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLPictureTransitionAnimation.m; sourceTree = ""; }; 78777CDE238FEA48006FA671 /* SLOpenGLController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLOpenGLController.h; sourceTree = ""; }; 78777CDF238FEA48006FA671 /* SLOpenGLController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLOpenGLController.m; sourceTree = ""; }; 78777CE4238FF65C006FA671 /* SLDelayPerform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLDelayPerform.h; sourceTree = ""; }; 78777CE5238FF65C006FA671 /* SLDelayPerform.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLDelayPerform.m; sourceTree = ""; }; + 78799EA024EE7EEC00DA8C7A /* SLUnusedResourceViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLUnusedResourceViewController.h; sourceTree = ""; }; + 78799EA124EE7EEC00DA8C7A /* SLUnusedResourceViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLUnusedResourceViewController.m; sourceTree = ""; }; + 78799EA924EFC60C00DA8C7A /* SLResourceInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLResourceInfo.h; sourceTree = ""; }; + 78799EAA24EFC60C00DA8C7A /* SLResourceInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLResourceInfo.m; sourceTree = ""; }; + 78799EB124F1027300DA8C7A /* unused.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = unused.png; sourceTree = ""; }; + 787C7DB2245D74E3005DF7ED /* NSMutableArray+SLCrashProtector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray+SLCrashProtector.m"; sourceTree = ""; }; + 787C7DB3245D74E3005DF7ED /* NSMutableDictionary+SLCrashProtector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableDictionary+SLCrashProtector.m"; sourceTree = ""; }; + 787C7DB4245D74E3005DF7ED /* NSString+SLCrashProtector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SLCrashProtector.h"; sourceTree = ""; }; + 787C7DB5245D74E3005DF7ED /* NSDictionary+SLCrashProtector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+SLCrashProtector.m"; sourceTree = ""; }; + 787C7DB6245D74E3005DF7ED /* NSMutableArray+SLCrashProtector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableArray+SLCrashProtector.h"; sourceTree = ""; }; + 787C7DB7245D74E4005DF7ED /* NSArray+SLCrashProtector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+SLCrashProtector.m"; sourceTree = ""; }; + 787C7DB8245D74E4005DF7ED /* NSMutableString+SLCrashProtector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableString+SLCrashProtector.h"; sourceTree = ""; }; + 787C7DB9245D74E4005DF7ED /* NSMutableDictionary+SLCrashProtector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableDictionary+SLCrashProtector.h"; sourceTree = ""; }; + 787C7DBA245D74E4005DF7ED /* NSMutableString+SLCrashProtector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableString+SLCrashProtector.m"; sourceTree = ""; }; + 787C7DBB245D74E4005DF7ED /* NSString+SLCrashProtector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SLCrashProtector.m"; sourceTree = ""; }; + 787C7DBC245D74E4005DF7ED /* NSDictionary+SLCrashProtector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+SLCrashProtector.h"; sourceTree = ""; }; + 787C7DBD245D74E4005DF7ED /* NSArray+SLCrashProtector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+SLCrashProtector.h"; sourceTree = ""; }; + 787C7DC4245D77D8005DF7ED /* SLZombieCatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLZombieCatcher.h; sourceTree = ""; }; + 787C7DC5245D77D8005DF7ED /* SLZombieSafeFree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLZombieSafeFree.h; sourceTree = ""; }; + 787C7DC6245D77D8005DF7ED /* SLZombieCatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLZombieCatcher.m; sourceTree = ""; }; + 787C7DC7245D77D9005DF7ED /* SLZombieSafeFree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLZombieSafeFree.m; sourceTree = ""; }; + 787C7DCA2462ADE5005DF7ED /* NSObject+SLMLeakFinder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+SLMLeakFinder.h"; sourceTree = ""; }; + 787C7DCB2462ADE5005DF7ED /* NSObject+SLMLeakFinder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SLMLeakFinder.m"; sourceTree = ""; }; + 787C7DCD2462B03D005DF7ED /* UINavigationController+SLMLeakFinder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UINavigationController+SLMLeakFinder.h"; sourceTree = ""; }; + 787C7DCE2462B03D005DF7ED /* UINavigationController+SLMLeakFinder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UINavigationController+SLMLeakFinder.m"; sourceTree = ""; }; + 787C7DD02462C5F3005DF7ED /* UIViewController+SLMLeakFinder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+SLMLeakFinder.h"; sourceTree = ""; }; + 787C7DD12462C5F3005DF7ED /* UIViewController+SLMLeakFinder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+SLMLeakFinder.m"; sourceTree = ""; }; + 787C7E40246557A5005DF7ED /* SLZombieFinder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLZombieFinder.h; sourceTree = ""; }; + 787C7E41246557A5005DF7ED /* SLZombieFinder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLZombieFinder.m; sourceTree = ""; }; + 787C7E4324656524005DF7ED /* UIView+SLMLeakFinder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+SLMLeakFinder.h"; sourceTree = ""; }; + 787C7E4424656524005DF7ED /* UIView+SLMLeakFinder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+SLMLeakFinder.m"; sourceTree = ""; }; 787F01A1236AD8A5002AC1A9 /* SLEditImageController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLEditImageController.h; sourceTree = ""; }; 787F01A2236AD8A5002AC1A9 /* SLEditImageController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLEditImageController.m; sourceTree = ""; }; 78804A7C237D5E3E0087E152 /* SLWaterMarkController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWaterMarkController.h; sourceTree = ""; }; 78804A7D237D5E3E0087E152 /* SLWaterMarkController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWaterMarkController.m; sourceTree = ""; }; + 78842452248F863500C2E505 /* SLTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLTableViewController.h; sourceTree = ""; }; + 78842453248F863500C2E505 /* SLTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLTableViewController.m; sourceTree = ""; }; + 78842462248F964000C2E505 /* SLAVListViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAVListViewController.h; sourceTree = ""; }; + 78842463248F964000C2E505 /* SLAVListViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAVListViewController.m; sourceTree = ""; }; + 78842466248FAED100C2E505 /* SLColorPickerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLColorPickerViewController.h; sourceTree = ""; }; + 78842467248FAED100C2E505 /* SLColorPickerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLColorPickerViewController.m; sourceTree = ""; }; + 78842469249070D900C2E505 /* UIColor+SLCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIColor+SLCommon.h"; sourceTree = ""; }; + 7884246A249070D900C2E505 /* UIColor+SLCommon.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+SLCommon.m"; sourceTree = ""; }; + 7884247D2492102900C2E505 /* SLWorkIssuesViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWorkIssuesViewController.h; sourceTree = ""; }; + 7884247E2492102900C2E505 /* SLWorkIssuesViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWorkIssuesViewController.m; sourceTree = ""; }; + 788424802492159F00C2E505 /* SLNavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLNavigationController.h; sourceTree = ""; }; + 788424812492159F00C2E505 /* SLNavigationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLNavigationController.m; sourceTree = ""; }; + 788424892493851B00C2E505 /* SLViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLViewController.h; sourceTree = ""; }; + 7884248A2493851B00C2E505 /* SLViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLViewController.m; sourceTree = ""; }; + 7884248C24963FD200C2E505 /* SLReusableManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLReusableManager.h; sourceTree = ""; }; + 7884248D24963FD200C2E505 /* SLReusableManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLReusableManager.m; sourceTree = ""; }; + 788424C524978E9200C2E505 /* SLKeyChain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLKeyChain.h; sourceTree = ""; }; + 788424C624978E9200C2E505 /* SLKeyChain.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLKeyChain.m; sourceTree = ""; }; 788ACD602390BFD400737EC2 /* SLLoadImageVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLLoadImageVC.h; sourceTree = ""; }; 788ACD612390BFD400737EC2 /* SLLoadImageVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLLoadImageVC.m; sourceTree = ""; }; 788ACD632390EA3A00737EC2 /* SLCubeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLCubeViewController.h; sourceTree = ""; }; 788ACD642390EA3A00737EC2 /* SLCubeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLCubeViewController.m; sourceTree = ""; }; + 788ADB352441F48F00302CD9 /* SLCrashViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLCrashViewController.h; sourceTree = ""; }; + 788ADB362441F48F00302CD9 /* SLCrashViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLCrashViewController.m; sourceTree = ""; }; + 78908DC124FFBE5D004164C2 /* SLScrollviewNesteVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLScrollviewNesteVC.h; sourceTree = ""; }; + 78908DC224FFBE5D004164C2 /* SLScrollviewNesteVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLScrollviewNesteVC.m; sourceTree = ""; }; + 78908DCB2500F099004164C2 /* SLMenuView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLMenuView.h; sourceTree = ""; }; + 78908DCC2500F099004164C2 /* SLMenuView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLMenuView.m; sourceTree = ""; }; + 78908DF825078F84004164C2 /* SLScrollViewJuejin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLScrollViewJuejin.h; sourceTree = ""; }; + 78908DF925078F84004164C2 /* SLScrollViewJuejin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLScrollViewJuejin.m; sourceTree = ""; }; + 78908DFB25079323004164C2 /* SLScrollViewWeibo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLScrollViewWeibo.h; sourceTree = ""; }; + 78908DFC25079323004164C2 /* SLScrollViewWeibo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLScrollViewWeibo.m; sourceTree = ""; }; + 78908DFE25079451004164C2 /* SLPanTableView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLPanTableView.h; sourceTree = ""; }; + 78908DFF25079451004164C2 /* SLPanTableView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLPanTableView.m; sourceTree = ""; }; + 789AD16124C99DCA00CB0B4C /* SLAPMThreadCount.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAPMThreadCount.h; sourceTree = ""; }; + 789AD16224C99DCA00CB0B4C /* SLAPMThreadCount.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAPMThreadCount.m; sourceTree = ""; }; + 789AD1C624D7FA5F00CB0B4C /* SLAPMURLProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAPMURLProtocol.h; sourceTree = ""; }; + 789AD1C724D7FA5F00CB0B4C /* SLAPMURLProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAPMURLProtocol.m; sourceTree = ""; }; + 789AD1CD24D9024300CB0B4C /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; + 789AD1CE24D9024300CB0B4C /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; + 789AD1D024D9622B00CB0B4C /* UIViewController+SLAPMVCTime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+SLAPMVCTime.h"; sourceTree = ""; }; + 789AD1D124D9622B00CB0B4C /* UIViewController+SLAPMVCTime.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+SLAPMVCTime.m"; sourceTree = ""; }; + 789AD1D324D97F8500CB0B4C /* SLSystemAppInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLSystemAppInfo.h; sourceTree = ""; }; + 789AD1D424D97F8500CB0B4C /* SLSystemAppInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLSystemAppInfo.m; sourceTree = ""; }; + 78A179A6249B0AA8006F52E3 /* NSDictionary+SLExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+SLExtension.m"; sourceTree = ""; }; + 78A179A7249B0AA8006F52E3 /* NSDictionary+SLExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+SLExtension.h"; sourceTree = ""; }; + 78A1998A2451BEE7005B2B4B /* UIButton+SLTitleImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIButton+SLTitleImage.h"; sourceTree = ""; }; + 78A1998B2451BEE7005B2B4B /* UIButton+SLTitleImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIButton+SLTitleImage.m"; sourceTree = ""; }; + 78A1998D2451C049005B2B4B /* SLButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLButton.h; sourceTree = ""; }; + 78A1998E2451C049005B2B4B /* SLButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLButton.m; sourceTree = ""; }; 78A35B1A23978A5D004BCCB7 /* SLShaderCubeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLShaderCubeViewController.h; sourceTree = ""; }; 78A35B1B23978A5D004BCCB7 /* SLShaderCubeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLShaderCubeViewController.m; sourceTree = ""; }; 78A4BD43236C5DF20021AE32 /* SLEditVideoController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLEditVideoController.h; sourceTree = ""; }; @@ -214,7 +447,7 @@ 78A4BD46236C5DFD0021AE32 /* SLImageZoomView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLImageZoomView.h; sourceTree = ""; }; 78A4BD47236C5DFD0021AE32 /* SLImageZoomView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLImageZoomView.m; sourceTree = ""; }; 78AA10F42371A7B50044A6F8 /* PrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrefixHeader.pch; sourceTree = ""; }; - 78B1047D232F57C30051579F /* iOS - Tips.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS - Tips.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 78B1047D232F57C30051579F /* SLTips.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SLTips.app; sourceTree = BUILT_PRODUCTS_DIR; }; 78B10480232F57C30051579F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 78B10481232F57C30051579F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 78B10483232F57C30051579F /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; @@ -232,6 +465,13 @@ 78B104A3232F57C50051579F /* DarkModeUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DarkModeUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 78B104A7232F57C50051579F /* DarkModeUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DarkModeUITests.m; sourceTree = ""; }; 78B104A9232F57C50051579F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 78BDE049248A6A74002ED386 /* SLUrlProtocolAddCookie.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLUrlProtocolAddCookie.h; sourceTree = ""; }; + 78BDE04A248A6A74002ED386 /* SLUrlProtocolAddCookie.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLUrlProtocolAddCookie.m; sourceTree = ""; }; + 78BDE04C248BFBD7002ED386 /* wsl.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wsl.png; sourceTree = ""; }; + 78BDE04E248E0AFB002ED386 /* SLWebNativeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWebNativeViewController.h; sourceTree = ""; }; + 78BDE04F248E0AFB002ED386 /* SLWebNativeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWebNativeViewController.m; sourceTree = ""; }; + 78BDE051248E0BEB002ED386 /* WebNative.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = WebNative.html; sourceTree = ""; }; + 78BDE057248E541E002ED386 /* WebNativeJson.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = WebNativeJson.txt; sourceTree = ""; }; 78D3095E239688EF00DC373A /* GLESUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLESUtils.m; sourceTree = ""; }; 78D3095F239688EF00DC373A /* Quaternion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Quaternion.h; sourceTree = ""; }; 78D30960239688EF00DC373A /* GLESMath.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = GLESMath.c; sourceTree = ""; }; @@ -242,6 +482,8 @@ 78DAA112235FF61E00A60F64 /* SLEditVideoClipping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLEditVideoClipping.m; sourceTree = ""; }; 78DAA1142360256200A60F64 /* SLEditSelectedBox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLEditSelectedBox.h; sourceTree = ""; }; 78DAA1152360256200A60F64 /* SLEditSelectedBox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLEditSelectedBox.m; sourceTree = ""; }; + 78DF7F632510B45000F6C007 /* SLScrollViewJianShu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLScrollViewJianShu.h; sourceTree = ""; }; + 78DF7F642510B45000F6C007 /* SLScrollViewJianShu.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLScrollViewJianShu.m; sourceTree = ""; }; 78E1791123517F700007E4BB /* SLImageDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLImageDecoder.h; sourceTree = ""; }; 78E1791223517F700007E4BB /* SLImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLImageView.m; sourceTree = ""; }; 78E1791323517F700007E4BB /* SLImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLImage.m; sourceTree = ""; }; @@ -255,8 +497,43 @@ 78E179332351A40D0007E4BB /* SLEditMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLEditMenuView.m; sourceTree = ""; }; 78E179342351A40D0007E4BB /* SLShotFocusView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SLShotFocusView.m; sourceTree = ""; }; 78E179352351A40D0007E4BB /* SLEditMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLEditMenuView.h; sourceTree = ""; }; + 78E72D4F24766E4B00751373 /* SLWebViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWebViewController.h; sourceTree = ""; }; + 78E72D5024766E4B00751373 /* SLWebViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWebViewController.m; sourceTree = ""; }; + 78E72D522477A4E700751373 /* SLWebTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWebTableViewController.h; sourceTree = ""; }; + 78E72D532477A4E700751373 /* SLWebTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWebTableViewController.m; sourceTree = ""; }; + 78E72D58247BA29600751373 /* WebTableView.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = WebTableView.html; sourceTree = ""; }; + 78E72D5A247BDDE700751373 /* SLWebTableViewController2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWebTableViewController2.h; sourceTree = ""; }; + 78E72D5B247BDDE700751373 /* SLWebTableViewController2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWebTableViewController2.m; sourceTree = ""; }; + 78E72D6E247E536400751373 /* SLWebTableViewController3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWebTableViewController3.h; sourceTree = ""; }; + 78E72D6F247E536400751373 /* SLWebTableViewController3.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWebTableViewController3.m; sourceTree = ""; }; + 78E72D71247E861300751373 /* SLWebViewListController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWebViewListController.h; sourceTree = ""; }; + 78E72D72247E861300751373 /* SLWebViewListController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWebViewListController.m; sourceTree = ""; }; + 78E72D74247F9FFC00751373 /* SLWebTableViewController4.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWebTableViewController4.h; sourceTree = ""; }; + 78E72D75247F9FFC00751373 /* SLWebTableViewController4.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWebTableViewController4.m; sourceTree = ""; }; + 78E72D772480B36800751373 /* SLScrollViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLScrollViewController.h; sourceTree = ""; }; + 78E72D782480B36800751373 /* SLScrollViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLScrollViewController.m; sourceTree = ""; }; + 78E72D7A24810E2D00751373 /* SLDynamicItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLDynamicItem.h; sourceTree = ""; }; + 78E72D7B24810E2D00751373 /* SLDynamicItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLDynamicItem.m; sourceTree = ""; }; + 78E72D7D24810ECC00751373 /* UIScrollView+SLCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+SLCommon.h"; sourceTree = ""; }; + 78E72D7E24810ECC00751373 /* UIScrollView+SLCommon.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+SLCommon.m"; sourceTree = ""; }; + 78E72D8024811F6600751373 /* SLWebCacheViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWebCacheViewController.h; sourceTree = ""; }; + 78E72D8124811F6600751373 /* SLWebCacheViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWebCacheViewController.m; sourceTree = ""; }; + 78E72D84248284B700751373 /* SLUrlProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLUrlProtocol.h; sourceTree = ""; }; + 78E72D85248284B700751373 /* SLUrlProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLUrlProtocol.m; sourceTree = ""; }; + 78E72D8A2482958300751373 /* WKWebView+SLExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WKWebView+SLExtension.h"; sourceTree = ""; }; + 78E72D8B2482958300751373 /* WKWebView+SLExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "WKWebView+SLExtension.m"; sourceTree = ""; }; + 78E72D8D2483DC2600751373 /* SLWebCacheManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLWebCacheManager.h; sourceTree = ""; }; + 78E72D8E2483DC2600751373 /* SLWebCacheManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLWebCacheManager.m; sourceTree = ""; }; + 78E72D902484EEAA00751373 /* SLUrlCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLUrlCache.h; sourceTree = ""; }; + 78E72D912484EEAA00751373 /* SLUrlCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLUrlCache.m; sourceTree = ""; }; + 78EF8B5D24162B96008D0CD7 /* SLMenuViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLMenuViewController.h; sourceTree = ""; }; + 78EF8B5E24162B96008D0CD7 /* SLMenuViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLMenuViewController.m; sourceTree = ""; }; + 78EF8B5F24162B96008D0CD7 /* SLMenuViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SLMenuViewController.xib; sourceTree = ""; }; + 78EF8B6224188767008D0CD7 /* SLAlertView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLAlertView.h; sourceTree = ""; }; + 78EF8B6324188767008D0CD7 /* SLAlertView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLAlertView.m; sourceTree = ""; }; 78F42021237915730093497C /* SLGPUImageController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLGPUImageController.h; sourceTree = ""; }; 78F42022237915730093497C /* SLGPUImageController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLGPUImageController.m; sourceTree = ""; }; + 78F4E41224E5404A000B2ADE /* SLToolMacro.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLToolMacro.h; sourceTree = ""; }; 78F54914233383D800910215 /* SLBlurView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SLBlurView.h; sourceTree = ""; }; 78F54915233383D800910215 /* SLBlurView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SLBlurView.m; sourceTree = ""; }; 78FA0F9B235407E6003E456B /* SLDrawView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SLDrawView.h; sourceTree = ""; }; @@ -273,6 +550,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78665C74246D4BBB0001B749 /* libsqlite3.tbd in Frameworks */, + 78665C72246D4BB10001B749 /* libicucore.tbd in Frameworks */, + 78665C70246D4B9F0001B749 /* SystemConfiguration.framework in Frameworks */, + 78665C6E246D4B950001B749 /* CoreTelephony.framework in Frameworks */, + 78665C6C246D4B880001B749 /* Security.framework in Frameworks */, + 78665C6A246D4B7C0001B749 /* Foundation.framework in Frameworks */, 78E1791B23517FC50007E4BB /* WebP.framework in Frameworks */, E355F901834CD33D6E9169A8 /* libPods-DarkMode.a in Frameworks */, ); @@ -298,11 +581,55 @@ 22F14068B7B884754D3106E5 /* Frameworks */ = { isa = PBXGroup; children = ( + 78665C73246D4BBB0001B749 /* libsqlite3.tbd */, + 78665C71246D4BB10001B749 /* libicucore.tbd */, + 78665C6F246D4B9F0001B749 /* SystemConfiguration.framework */, + 78665C6D246D4B950001B749 /* CoreTelephony.framework */, + 78665C6B246D4B880001B749 /* Security.framework */, + 78665C69246D4B7C0001B749 /* Foundation.framework */, FC41CA32573EE16859D535A1 /* libPods-DarkMode.a */, ); name = Frameworks; sourceTree = ""; }; + 2C09ED9425401113009E96B6 /* 设计模式 */ = { + isa = PBXGroup; + children = ( + ); + path = "设计模式"; + sourceTree = ""; + }; + 2C09ED95254011A2009E96B6 /* View */ = { + isa = PBXGroup; + children = ( + 782CFB1E239DEA05001B5528 /* SLSplitScreenCell.h */, + 782CFB1F239DEA05001B5528 /* SLSplitScreenCell.m */, + ); + path = View; + sourceTree = ""; + }; + 2C5F115C257BC96A00904B70 /* YYAnimatedImageViewCategory */ = { + isa = PBXGroup; + children = ( + 2C5F11A5257BCB7000904B70 /* YYAnimatedImageView+iOS14.h */, + 2C5F11A6257BCB7000904B70 /* YYAnimatedImageView+iOS14.m */, + ); + path = YYAnimatedImageViewCategory; + sourceTree = ""; + }; + 7811F76C24B2C70D000AA044 /* 二进制重排 */ = { + isa = PBXGroup; + children = ( + 7811F7AC24B32A1E000AA044 /* Order File.png */, + 7811F7AB24B32A1E000AA044 /* Other c Flags.png */, + 7811F7AA24B32A1E000AA044 /* Write Link Map File.png */, + 7811F77024B2C818000AA044 /* wsl.order */, + 7811F76D24B2C791000AA044 /* SLBinaryResetViewController.h */, + 7811F76E24B2C791000AA044 /* SLBinaryResetViewController.m */, + ); + path = "二进制重排"; + sourceTree = ""; + }; 782E3EB52373F892001E0DF9 /* Filter */ = { isa = PBXGroup; children = ( @@ -312,6 +639,29 @@ path = Filter; sourceTree = ""; }; + 7835047E24586BA30071283E /* ThirdLibary */ = { + isa = PBXGroup; + children = ( + 2C5F115C257BC96A00904B70 /* YYAnimatedImageViewCategory */, + 789AD1CC24D9024300CB0B4C /* Reachability */, + 7857FD4824729E8500D3D986 /* BSBacktraceLogger */, + 78D3095D239688EF00DC373A /* OpenGLUtils */, + 7835047F24586BAC0071283E /* Fishhook */, + ); + path = ThirdLibary; + sourceTree = ""; + }; + 7835047F24586BAC0071283E /* Fishhook */ = { + isa = PBXGroup; + children = ( + 7835048024586BAC0071283E /* fishhook.c */, + 7835048124586BAC0071283E /* queue.c */, + 7835048224586BAC0071283E /* fishhook.h */, + 7835048324586BAC0071283E /* queue.h */, + ); + path = Fishhook; + sourceTree = ""; + }; 783FB45D2394A7580039AEFD /* ShaderLanguage */ = { isa = PBXGroup; children = ( @@ -351,6 +701,16 @@ path = ShaderLanguage; sourceTree = ""; }; + 784C4BDE24BC409600D5C199 /* APM */ = { + isa = PBXGroup; + children = ( + 784C4BDF24BC40D200D5C199 /* SLAPMViewController.h */, + 784C4BE024BC40D200D5C199 /* SLAPMViewController.m */, + 789AD1C524D43A3700CB0B4C /* APMMonitor */, + ); + path = APM; + sourceTree = ""; + }; 7851CB322331CC87002295B5 /* Dark */ = { isa = PBXGroup; children = ( @@ -377,18 +737,96 @@ 7851CB41233222E2002295B5 /* UIView+SLFrame.m */, 7830A5A22358904600BC79BA /* UIView+SLImage.h */, 7830A5A32358904600BC79BA /* UIView+SLImage.m */, + 78A179A7249B0AA8006F52E3 /* NSDictionary+SLExtension.h */, + 78A179A6249B0AA8006F52E3 /* NSDictionary+SLExtension.m */, 786147F823630C4D00C5424C /* UIImage+SLCommon.h */, 786147F923630C4D00C5424C /* UIImage+SLCommon.m */, + 78A1998A2451BEE7005B2B4B /* UIButton+SLTitleImage.h */, + 78A1998B2451BEE7005B2B4B /* UIButton+SLTitleImage.m */, + 78E72D7D24810ECC00751373 /* UIScrollView+SLCommon.h */, + 78E72D7E24810ECC00751373 /* UIScrollView+SLCommon.m */, + 78E72D8A2482958300751373 /* WKWebView+SLExtension.h */, + 78E72D8B2482958300751373 /* WKWebView+SLExtension.m */, + 78842469249070D900C2E505 /* UIColor+SLCommon.h */, + 7884246A249070D900C2E505 /* UIColor+SLCommon.m */, ); path = SLCategory; sourceTree = ""; }; + 7857FD4824729E8500D3D986 /* BSBacktraceLogger */ = { + isa = PBXGroup; + children = ( + 7857FD4924729E8500D3D986 /* BSBacktraceLogger.h */, + 7857FD4A24729E8500D3D986 /* BSBacktraceLogger.m */, + ); + path = BSBacktraceLogger; + sourceTree = ""; + }; + 7862532B24599D420017F8F1 /* SLCrashProtector */ = { + isa = PBXGroup; + children = ( + 7862533924599D420017F8F1 /* SLCrashProtector.h */, + 7862533D24599D420017F8F1 /* SLCrashHandler.h */, + 7862533224599D420017F8F1 /* SLCrashHandler.m */, + 787C7DBD245D74E4005DF7ED /* NSArray+SLCrashProtector.h */, + 787C7DB7245D74E4005DF7ED /* NSArray+SLCrashProtector.m */, + 787C7DB6245D74E3005DF7ED /* NSMutableArray+SLCrashProtector.h */, + 787C7DB2245D74E3005DF7ED /* NSMutableArray+SLCrashProtector.m */, + 787C7DBC245D74E4005DF7ED /* NSDictionary+SLCrashProtector.h */, + 787C7DB5245D74E3005DF7ED /* NSDictionary+SLCrashProtector.m */, + 787C7DB9245D74E4005DF7ED /* NSMutableDictionary+SLCrashProtector.h */, + 787C7DB3245D74E3005DF7ED /* NSMutableDictionary+SLCrashProtector.m */, + 787C7DB4245D74E3005DF7ED /* NSString+SLCrashProtector.h */, + 787C7DBB245D74E4005DF7ED /* NSString+SLCrashProtector.m */, + 787C7DB8245D74E4005DF7ED /* NSMutableString+SLCrashProtector.h */, + 787C7DBA245D74E4005DF7ED /* NSMutableString+SLCrashProtector.m */, + 7862533724599D420017F8F1 /* NSObject+SLCrashProtector.h */, + 7862532C24599D420017F8F1 /* NSObject+SLCrashProtector.m */, + 781D9D0424DD662E00FAE73D /* UIView+SLAsynUpdateUI.h */, + 781D9D0524DD662E00FAE73D /* UIView+SLAsynUpdateUI.m */, + 7862532F24599D420017F8F1 /* SLKVODelegate.h */, + 7862533A24599D420017F8F1 /* SLKVODelegate.m */, + 787C7DC4245D77D8005DF7ED /* SLZombieCatcher.h */, + 787C7DC6245D77D8005DF7ED /* SLZombieCatcher.m */, + 787C7E40246557A5005DF7ED /* SLZombieFinder.h */, + 787C7E41246557A5005DF7ED /* SLZombieFinder.m */, + 787C7DC5245D77D8005DF7ED /* SLZombieSafeFree.h */, + 787C7DC7245D77D9005DF7ED /* SLZombieSafeFree.m */, + 787C7DCA2462ADE5005DF7ED /* NSObject+SLMLeakFinder.h */, + 787C7DCB2462ADE5005DF7ED /* NSObject+SLMLeakFinder.m */, + 787C7DCD2462B03D005DF7ED /* UINavigationController+SLMLeakFinder.h */, + 787C7DCE2462B03D005DF7ED /* UINavigationController+SLMLeakFinder.m */, + 787C7DD02462C5F3005DF7ED /* UIViewController+SLMLeakFinder.h */, + 787C7DD12462C5F3005DF7ED /* UIViewController+SLMLeakFinder.m */, + 787C7E4324656524005DF7ED /* UIView+SLMLeakFinder.h */, + 787C7E4424656524005DF7ED /* UIView+SLMLeakFinder.m */, + ); + path = SLCrashProtector; + sourceTree = ""; + }; + 7869C71D24A8B5CA00527546 /* WebNative */ = { + isa = PBXGroup; + children = ( + 78BDE051248E0BEB002ED386 /* WebNative.html */, + 78BDE057248E541E002ED386 /* WebNativeJson.txt */, + 78BDE04E248E0AFB002ED386 /* SLWebNativeViewController.h */, + 78BDE04F248E0AFB002ED386 /* SLWebNativeViewController.m */, + 7884248C24963FD200C2E505 /* SLReusableManager.h */, + 7884248D24963FD200C2E505 /* SLReusableManager.m */, + 7869C71F24A8B6D100527546 /* SLPictureBrowseController.h */, + 7869C71E24A8B6D100527546 /* SLPictureBrowseController.m */, + 7869C72024A8B6D200527546 /* SLPictureTransitionAnimation.h */, + 7869C72124A8B6D200527546 /* SLPictureTransitionAnimation.m */, + ); + path = WebNative; + sourceTree = ""; + }; 78777CDB238FEA15006FA671 /* OpenGL */ = { isa = PBXGroup; children = ( + 2C09ED95254011A2009E96B6 /* View */, 783FB45D2394A7580039AEFD /* ShaderLanguage */, 78777CDC238FEA15006FA671 /* Controller */, - 78777CDD238FEA15006FA671 /* View */, ); path = OpenGL; sourceTree = ""; @@ -420,22 +858,26 @@ path = Controller; sourceTree = ""; }; - 78777CDD238FEA15006FA671 /* View */ = { - isa = PBXGroup; - children = ( - 782CFB1E239DEA05001B5528 /* SLSplitScreenCell.h */, - 782CFB1F239DEA05001B5528 /* SLSplitScreenCell.m */, - ); - path = View; - sourceTree = ""; - }; 78777CE1238FF551006FA671 /* Tool */ = { isa = PBXGroup; children = ( 78AA10F42371A7B50044A6F8 /* PrefixHeader.pch */, - 78D3095D239688EF00DC373A /* OpenGLUtils */, + 2CC8137925540CB8009DAC9B /* HMLog.h */, + 78F4E41224E5404A000B2ADE /* SLToolMacro.h */, 78777CE4238FF65C006FA671 /* SLDelayPerform.h */, 78777CE5238FF65C006FA671 /* SLDelayPerform.m */, + 783504622452C20B0071283E /* SLMethod.h */, + 783504632452C20B0071283E /* SLMethod.m */, + 788424802492159F00C2E505 /* SLNavigationController.h */, + 788424812492159F00C2E505 /* SLNavigationController.m */, + 788424892493851B00C2E505 /* SLViewController.h */, + 7884248A2493851B00C2E505 /* SLViewController.m */, + 788424C524978E9200C2E505 /* SLKeyChain.h */, + 788424C624978E9200C2E505 /* SLKeyChain.m */, + 2CEBE85C24A6E65600BA21F3 /* SLTimer.h */, + 2CEBE85B24A6E65600BA21F3 /* SLTimer.m */, + 784C4BE824BDA2DE00D5C199 /* SLProxy.h */, + 784C4BE924BDA2DE00D5C199 /* SLProxy.m */, ); path = Tool; sourceTree = ""; @@ -464,10 +906,132 @@ 78F54915233383D800910215 /* SLBlurView.m */, 7822CCF6235B054200E70C29 /* SLPaddingLabel.h */, 7822CCF7235B054200E70C29 /* SLPaddingLabel.m */, + 78EF8B6224188767008D0CD7 /* SLAlertView.h */, + 78EF8B6324188767008D0CD7 /* SLAlertView.m */, + 78A1998D2451C049005B2B4B /* SLButton.h */, + 78A1998E2451C049005B2B4B /* SLButton.m */, ); path = UI; sourceTree = ""; }; + 78799E9F24EE7E7F00DA8C7A /* 查找项目无用资源 */ = { + isa = PBXGroup; + children = ( + 78799EB124F1027300DA8C7A /* unused.png */, + 78799EA024EE7EEC00DA8C7A /* SLUnusedResourceViewController.h */, + 78799EA124EE7EEC00DA8C7A /* SLUnusedResourceViewController.m */, + 78799EA924EFC60C00DA8C7A /* SLResourceInfo.h */, + 78799EAA24EFC60C00DA8C7A /* SLResourceInfo.m */, + ); + path = "查找项目无用资源"; + sourceTree = ""; + }; + 78842455248F92D400C2E505 /* AVFoundation */ = { + isa = PBXGroup; + children = ( + 78842462248F964000C2E505 /* SLAVListViewController.h */, + 78842463248F964000C2E505 /* SLAVListViewController.m */, + 78842465248FAEA000C2E505 /* ColorPicker */, + 7851CB392331CD0D002295B5 /* SmallVideo */, + 782E3EB52373F892001E0DF9 /* Filter */, + 78AA10F32371A1EF0044A6F8 /* FaceDetect */, + 78F4201E237913C80093497C /* GPUImageDemo */, + ); + path = AVFoundation; + sourceTree = ""; + }; + 78842465248FAEA000C2E505 /* ColorPicker */ = { + isa = PBXGroup; + children = ( + 78842466248FAED100C2E505 /* SLColorPickerViewController.h */, + 78842467248FAED100C2E505 /* SLColorPickerViewController.m */, + ); + path = ColorPicker; + sourceTree = ""; + }; + 7884247C24920FCA00C2E505 /* WorkIssues */ = { + isa = PBXGroup; + children = ( + 784C4C6F24C5B5DD00D5C199 /* 高质量技术博客.md */, + 784C4C6D24C5B4F500D5C199 /* 笔记.md */, + 785D06632522C32700F174BA /* 面试题.md */, + 2CC813672553CE88009DAC9B /* 已浏览.md */, + 7884247D2492102900C2E505 /* SLWorkIssuesViewController.h */, + 7884247E2492102900C2E505 /* SLWorkIssuesViewController.m */, + 78908DC024FFBD83004164C2 /* ScrollView嵌套 */, + 78799E9F24EE7E7F00DA8C7A /* 查找项目无用资源 */, + 784C4BDE24BC409600D5C199 /* APM */, + 7811F76C24B2C70D000AA044 /* 二进制重排 */, + 78EF8B5C24162B07008D0CD7 /* UIMenuController */, + ); + path = WorkIssues; + sourceTree = ""; + }; + 788ADB342441F46500302CD9 /* Crash */ = { + isa = PBXGroup; + children = ( + 7862532B24599D420017F8F1 /* SLCrashProtector */, + 788ADB352441F48F00302CD9 /* SLCrashViewController.h */, + 788ADB362441F48F00302CD9 /* SLCrashViewController.m */, + ); + path = Crash; + sourceTree = ""; + }; + 78908DC024FFBD83004164C2 /* ScrollView嵌套 */ = { + isa = PBXGroup; + children = ( + 78908DC124FFBE5D004164C2 /* SLScrollviewNesteVC.h */, + 78908DC224FFBE5D004164C2 /* SLScrollviewNesteVC.m */, + 78908DF825078F84004164C2 /* SLScrollViewJuejin.h */, + 78908DF925078F84004164C2 /* SLScrollViewJuejin.m */, + 78908DFB25079323004164C2 /* SLScrollViewWeibo.h */, + 78908DFC25079323004164C2 /* SLScrollViewWeibo.m */, + 78DF7F632510B45000F6C007 /* SLScrollViewJianShu.h */, + 78DF7F642510B45000F6C007 /* SLScrollViewJianShu.m */, + 78908DFE25079451004164C2 /* SLPanTableView.h */, + 78908DFF25079451004164C2 /* SLPanTableView.m */, + 78908DCB2500F099004164C2 /* SLMenuView.h */, + 78908DCC2500F099004164C2 /* SLMenuView.m */, + ); + path = "ScrollView嵌套"; + sourceTree = ""; + }; + 789AD1C524D43A3700CB0B4C /* APMMonitor */ = { + isa = PBXGroup; + children = ( + 784C4BE224BC4C5E00D5C199 /* SLAPMManager.h */, + 784C4BE324BC4C5E00D5C199 /* SLAPMManager.m */, + 784C4BE524BC53D500D5C199 /* SLAPMCpu.h */, + 784C4BE624BC53D500D5C199 /* SLAPMCpu.m */, + 784C4C3824C2EE6600D5C199 /* SLAPMMemoryDisk.h */, + 784C4C3924C2EE6600D5C199 /* SLAPMMemoryDisk.m */, + 784C4BEB24BDA5D700D5C199 /* SLAPMFluency.h */, + 784C4BEC24BDA5D700D5C199 /* SLAPMFluency.m */, + 789AD16124C99DCA00CB0B4C /* SLAPMThreadCount.h */, + 789AD16224C99DCA00CB0B4C /* SLAPMThreadCount.m */, + 789AD1C624D7FA5F00CB0B4C /* SLAPMURLProtocol.h */, + 789AD1C724D7FA5F00CB0B4C /* SLAPMURLProtocol.m */, + 789AD1D024D9622B00CB0B4C /* UIViewController+SLAPMVCTime.h */, + 789AD1D124D9622B00CB0B4C /* UIViewController+SLAPMVCTime.m */, + 781DA28A24DFD52500FAE73D /* SLAPMLoadTime.h */, + 781DA28924DFD52400FAE73D /* SLAPMLoadTime.m */, + 781DA28C24E00F9E00FAE73D /* QiCallTraceCore.c */, + 781DA28D24E00F9E00FAE73D /* QiCallTraceCore.h */, + 789AD1D324D97F8500CB0B4C /* SLSystemAppInfo.h */, + 789AD1D424D97F8500CB0B4C /* SLSystemAppInfo.m */, + ); + path = APMMonitor; + sourceTree = ""; + }; + 789AD1CC24D9024300CB0B4C /* Reachability */ = { + isa = PBXGroup; + children = ( + 789AD1CE24D9024300CB0B4C /* Reachability.h */, + 789AD1CD24D9024300CB0B4C /* Reachability.m */, + ); + path = Reachability; + sourceTree = ""; + }; 78AA10F32371A1EF0044A6F8 /* FaceDetect */ = { isa = PBXGroup; children = ( @@ -492,7 +1056,7 @@ 78B1047E232F57C30051579F /* Products */ = { isa = PBXGroup; children = ( - 78B1047D232F57C30051579F /* iOS - Tips.app */, + 78B1047D232F57C30051579F /* SLTips.app */, 78B10498232F57C50051579F /* DarkModeTests.xctest */, 78B104A3232F57C50051579F /* DarkModeUITests.xctest */, ); @@ -508,17 +1072,19 @@ 78B10484232F57C30051579F /* SceneDelegate.m */, 78B10486232F57C30051579F /* ViewController.h */, 78B10487232F57C30051579F /* ViewController.m */, + 7835047E24586BA30071283E /* ThirdLibary */, 78E1791E2351820F0007E4BB /* Recource */, 78F54913233383AC00910215 /* General */, 7851CB40233222E2002295B5 /* SLCategory */, + 78842455248F92D400C2E505 /* AVFoundation */, 7851CB322331CC87002295B5 /* Dark */, - 7851CB392331CD0D002295B5 /* SmallVideo */, - 78AA10F32371A1EF0044A6F8 /* FaceDetect */, - 782E3EB52373F892001E0DF9 /* Filter */, - 78F4201E237913C80093497C /* GPUImageDemo */, 78777CDB238FEA15006FA671 /* OpenGL */, - 78B10489232F57C30051579F /* Main.storyboard */, + 7884247C24920FCA00C2E505 /* WorkIssues */, + 788ADB342441F46500302CD9 /* Crash */, + 78E72D4E24766E1300751373 /* WKWebView */, + 2C09ED9425401113009E96B6 /* 设计模式 */, 78B1048C232F57C50051579F /* Assets.xcassets */, + 78B10489232F57C30051579F /* Main.storyboard */, 78B1048E232F57C50051579F /* LaunchScreen.storyboard */, 78B10491232F57C50051579F /* Info.plist */, 78B10492232F57C50051579F /* main.m */, @@ -619,6 +1185,70 @@ path = Recource; sourceTree = ""; }; + 78E72D4E24766E1300751373 /* WKWebView */ = { + isa = PBXGroup; + children = ( + 78BDE04C248BFBD7002ED386 /* wsl.png */, + 78E72D58247BA29600751373 /* WebTableView.html */, + 78E72D71247E861300751373 /* SLWebViewListController.h */, + 78E72D72247E861300751373 /* SLWebViewListController.m */, + 78E72D4F24766E4B00751373 /* SLWebViewController.h */, + 78E72D5024766E4B00751373 /* SLWebViewController.m */, + 78BDE049248A6A74002ED386 /* SLUrlProtocolAddCookie.h */, + 78BDE04A248A6A74002ED386 /* SLUrlProtocolAddCookie.m */, + 78842452248F863500C2E505 /* SLTableViewController.h */, + 78842453248F863500C2E505 /* SLTableViewController.m */, + 7869C71D24A8B5CA00527546 /* WebNative */, + 78E72D8324822E7500751373 /* WebTableView */, + 78E72D9324852C2F00751373 /* WebCache */, + ); + path = WKWebView; + sourceTree = ""; + }; + 78E72D8324822E7500751373 /* WebTableView */ = { + isa = PBXGroup; + children = ( + 78E72D7A24810E2D00751373 /* SLDynamicItem.h */, + 78E72D7B24810E2D00751373 /* SLDynamicItem.m */, + 78E72D772480B36800751373 /* SLScrollViewController.h */, + 78E72D782480B36800751373 /* SLScrollViewController.m */, + 78E72D522477A4E700751373 /* SLWebTableViewController.h */, + 78E72D532477A4E700751373 /* SLWebTableViewController.m */, + 78E72D5A247BDDE700751373 /* SLWebTableViewController2.h */, + 78E72D5B247BDDE700751373 /* SLWebTableViewController2.m */, + 78E72D6E247E536400751373 /* SLWebTableViewController3.h */, + 78E72D6F247E536400751373 /* SLWebTableViewController3.m */, + 78E72D74247F9FFC00751373 /* SLWebTableViewController4.h */, + 78E72D75247F9FFC00751373 /* SLWebTableViewController4.m */, + ); + path = WebTableView; + sourceTree = ""; + }; + 78E72D9324852C2F00751373 /* WebCache */ = { + isa = PBXGroup; + children = ( + 78E72D8024811F6600751373 /* SLWebCacheViewController.h */, + 78E72D8124811F6600751373 /* SLWebCacheViewController.m */, + 78E72D8D2483DC2600751373 /* SLWebCacheManager.h */, + 78E72D8E2483DC2600751373 /* SLWebCacheManager.m */, + 78E72D84248284B700751373 /* SLUrlProtocol.h */, + 78E72D85248284B700751373 /* SLUrlProtocol.m */, + 78E72D902484EEAA00751373 /* SLUrlCache.h */, + 78E72D912484EEAA00751373 /* SLUrlCache.m */, + ); + path = WebCache; + sourceTree = ""; + }; + 78EF8B5C24162B07008D0CD7 /* UIMenuController */ = { + isa = PBXGroup; + children = ( + 78EF8B5D24162B96008D0CD7 /* SLMenuViewController.h */, + 78EF8B5E24162B96008D0CD7 /* SLMenuViewController.m */, + 78EF8B5F24162B96008D0CD7 /* SLMenuViewController.xib */, + ); + path = UIMenuController; + sourceTree = ""; + }; 78F4201E237913C80093497C /* GPUImageDemo */ = { isa = PBXGroup; children = ( @@ -685,7 +1315,7 @@ ); name = DarkMode; productName = DarkMode; - productReference = 78B1047D232F57C30051579F /* iOS - Tips.app */; + productReference = 78B1047D232F57C30051579F /* SLTips.app */; productType = "com.apple.product-type.application"; }; 78B10497232F57C50051579F /* DarkModeTests */ = { @@ -779,29 +1409,43 @@ 78B1048D232F57C50051579F /* Assets.xcassets in Resources */, 783FB4602394A76B0039AEFD /* shaderv.vsh in Resources */, 7851CB372331CC87002295B5 /* SLDarkModeViewController.xib in Resources */, + 78EF8B6124162B96008D0CD7 /* SLMenuViewController.xib in Resources */, 782CFB30239E04A2001B5528 /* SplitScreen_4.fsh in Resources */, + 78BDE052248E0BEC002ED386 /* WebNative.html in Resources */, 782CFB63239E623D001B5528 /* Cirlce.fsh in Resources */, 782CFB23239DFD0F001B5528 /* SplitScreen_2.fsh in Resources */, 782CFB38239E22AC001B5528 /* SplitScreen_9.vsh in Resources */, + 7811F7AE24B32A1E000AA044 /* Other c Flags.png in Resources */, 782CFB53239E4587001B5528 /* SplitScreen_16.vsh in Resources */, 782CFB6E239E69F3001B5528 /* Scale.vsh in Resources */, + 78BDE04D248BFBD7002ED386 /* wsl.png in Resources */, 782CFB37239E22AC001B5528 /* SplitScreen_9.fsh in Resources */, + 784C4C7024C5B5DD00D5C199 /* 高质量技术博客.md in Resources */, + 7811F7AF24B32A1E000AA044 /* Order File.png in Resources */, 782CFB76239E6A02001B5528 /* SoulOut.vsh in Resources */, 782CFB54239E4587001B5528 /* SplitScreen_16.fsh in Resources */, + 78BDE058248E541E002ED386 /* WebNativeJson.txt in Resources */, 782CFB34239E04AD001B5528 /* SplitScreen_6.vsh in Resources */, 782CFB62239E623D001B5528 /* Cirlce.vsh in Resources */, 782CFB5F239E622E001B5528 /* Mosaic.fsh in Resources */, 782CFB6D239E69F3001B5528 /* Scale.fsh in Resources */, 782CFB71239E69FA001B5528 /* Shake.vsh in Resources */, 78B1048B232F57C30051579F /* Main.storyboard in Resources */, + 785D06642522C32700F174BA /* 面试题.md in Resources */, + 78799EB224F1027300DA8C7A /* unused.png in Resources */, 782CFB27239DFD89001B5528 /* SplitScreen_1.fsh in Resources */, 783FB4612394A76B0039AEFD /* shaderf.fsh in Resources */, + 7811F7AD24B32A1E000AA044 /* Write Link Map File.png in Resources */, 782CFB2F239E04A2001B5528 /* SplitScreen_4.vsh in Resources */, + 784C4C6E24C5B4F500D5C199 /* 笔记.md in Resources */, 782CFB67239E6651001B5528 /* HexagonMosaic.vsh in Resources */, 782CFB2B239E0479001B5528 /* SplitScreen_3.vsh in Resources */, 782CFB5B239E6227001B5528 /* Gray.vsh in Resources */, 782CFB5E239E622E001B5528 /* Mosaic.vsh in Resources */, + 7811F77124B2C818000AA044 /* wsl.order in Resources */, + 78E72D59247BA29600751373 /* WebTableView.html in Resources */, 782CFB7A239E6A11001B5528 /* Glitch.vsh in Resources */, + 2CC813682553CE88009DAC9B /* 已浏览.md in Resources */, 782CFB5A239E6227001B5528 /* Gray.fsh in Resources */, 782CFB66239E6651001B5528 /* HexagonMosaic.fsh in Resources */, 782CFB79239E6A11001B5528 /* Glitch.fsh in Resources */, @@ -834,24 +1478,11 @@ files = ( ); inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-DarkMode/Pods-DarkMode-resources.sh", - "${PODS_ROOT}/GPUImage/framework/Resources/lookup.png", - "${PODS_ROOT}/GPUImage/framework/Resources/lookup_amatorka.png", - "${PODS_ROOT}/GPUImage/framework/Resources/lookup_miss_etikate.png", - "${PODS_ROOT}/GPUImage/framework/Resources/lookup_soft_elegance_1.png", - "${PODS_ROOT}/GPUImage/framework/Resources/lookup_soft_elegance_2.png", + "${PODS_ROOT}/Target Support Files/Pods-DarkMode/Pods-DarkMode-resources-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - ); - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/lookup.png", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/lookup_amatorka.png", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/lookup_miss_etikate.png", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/lookup_soft_elegance_1.png", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/lookup_soft_elegance_2.png", + "${PODS_ROOT}/Target Support Files/Pods-DarkMode/Pods-DarkMode-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -887,55 +1518,133 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 781DA28B24DFD52500FAE73D /* SLAPMLoadTime.m in Sources */, 782CFB1D239DDE95001B5528 /* SLSplitScreenViewController.m in Sources */, 7857977823725C21004CD664 /* SLFaceDetectController.m in Sources */, + 7835048524586BAC0071283E /* queue.c in Sources */, + 7862534324599D420017F8F1 /* SLCrashHandler.m in Sources */, + 78A1998F2451C049005B2B4B /* SLButton.m in Sources */, 78B10488232F57C30051579F /* ViewController.m in Sources */, 78FA0FA02354141E003E456B /* SLAvEditExport.m in Sources */, + 78A179A8249B0AA8006F52E3 /* NSDictionary+SLExtension.m in Sources */, + 78BDE050248E0AFB002ED386 /* SLWebNativeViewController.m in Sources */, + 78DF7F652510B45000F6C007 /* SLScrollViewJianShu.m in Sources */, 78F42023237915730093497C /* SLGPUImageController.m in Sources */, 7860D791239922B4008C53EC /* SLGLKPyramidVC.m in Sources */, + 787C7DC3245D74E4005DF7ED /* NSString+SLCrashProtector.m in Sources */, 784B72062334685E006AEE47 /* SLAvPlayer.m in Sources */, 78DAA113235FF61E00A60F64 /* SLEditVideoClipping.m in Sources */, 782E3EB42373D8C7001E0DF9 /* SLAvWriterInput.m in Sources */, + 789AD1D524D97F8500CB0B4C /* SLSystemAppInfo.m in Sources */, + 78908DFD25079323004164C2 /* SLScrollViewWeibo.m in Sources */, 78804A7E237D5E3E0087E152 /* SLWaterMarkController.m in Sources */, + 78EF8B6024162B96008D0CD7 /* SLMenuViewController.m in Sources */, + 7869C72224A8B6D200527546 /* SLPictureBrowseController.m in Sources */, 78D30964239688EF00DC373A /* GLESUtils.m in Sources */, 784B720923348241006AEE47 /* SLAvCaptureTool.m in Sources */, 786147F42362D79000C5424C /* SLMosaicView.m in Sources */, 78A4BD48236C5DFE0021AE32 /* SLImageZoomView.m in Sources */, + 787C7DC1245D74E4005DF7ED /* NSArray+SLCrashProtector.m in Sources */, + 7862534524599D420017F8F1 /* SLKVODelegate.m in Sources */, 782E3EB82373F911001E0DF9 /* SLFilterViewController.m in Sources */, 78B10482232F57C30051579F /* AppDelegate.m in Sources */, + 78E72D7C24810E2D00751373 /* SLDynamicItem.m in Sources */, 78777CE0238FEA48006FA671 /* SLOpenGLController.m in Sources */, + 2C5F11A7257BCB7000904B70 /* YYAnimatedImageView+iOS14.m in Sources */, + 78908DC324FFBE5D004164C2 /* SLScrollviewNesteVC.m in Sources */, 7851CB362331CC87002295B5 /* SLDarkModeViewController.m in Sources */, 78D30965239688EF00DC373A /* GLESMath.c in Sources */, + 7811F76F24B2C791000AA044 /* SLBinaryResetViewController.m in Sources */, 788ACD652390EA3A00737EC2 /* SLCubeViewController.m in Sources */, + 7884248E24963FD200C2E505 /* SLReusableManager.m in Sources */, + 784C4BEA24BDA2DE00D5C199 /* SLProxy.m in Sources */, + 7857FD4B24729E8500D3D986 /* BSBacktraceLogger.m in Sources */, + 78E72D8224811F6600751373 /* SLWebCacheViewController.m in Sources */, + 78E72D8F2483DC2600751373 /* SLWebCacheManager.m in Sources */, + 787C7DC9245D77D9005DF7ED /* SLZombieSafeFree.m in Sources */, 786147FA23630C4D00C5424C /* UIImage+SLCommon.m in Sources */, 78FA0F9D235407E6003E456B /* SLDrawView.m in Sources */, + 784C4BE724BC53D500D5C199 /* SLAPMCpu.m in Sources */, 780F5303236D8462000D0EA8 /* SLGridView.m in Sources */, 7851CB43233222E2002295B5 /* UIView+SLFrame.m in Sources */, + 7884246B249070D900C2E505 /* UIColor+SLCommon.m in Sources */, + 787C7DBF245D74E4005DF7ED /* NSMutableDictionary+SLCrashProtector.m in Sources */, 7830A5A42358904600BC79BA /* UIView+SLImage.m in Sources */, 78A35B1C23978A5D004BCCB7 /* SLShaderCubeViewController.m in Sources */, + 78908E0025079451004164C2 /* SLPanTableView.m in Sources */, + 787C7DC0245D74E4005DF7ED /* NSDictionary+SLCrashProtector.m in Sources */, 78E1791723517F700007E4BB /* SLImageView.m in Sources */, 78E1791823517F700007E4BB /* SLImage.m in Sources */, + 787C7DBE245D74E4005DF7ED /* NSMutableArray+SLCrashProtector.m in Sources */, + 78A1998C2451BEE7005B2B4B /* UIButton+SLTitleImage.m in Sources */, + 78E72D70247E536400751373 /* SLWebTableViewController3.m in Sources */, + 78E72D8C2482958300751373 /* WKWebView+SLExtension.m in Sources */, + 7884247F2492102900C2E505 /* SLWorkIssuesViewController.m in Sources */, + 78E72D542477A4E700751373 /* SLWebTableViewController.m in Sources */, + 2CEBE85D24A6E65600BA21F3 /* SLTimer.m in Sources */, + 783504642452C20B0071283E /* SLMethod.m in Sources */, + 78908DFA25078F84004164C2 /* SLScrollViewJuejin.m in Sources */, + 784C4BE124BC40D200D5C199 /* SLAPMViewController.m in Sources */, + 787C7E4524656524005DF7ED /* UIView+SLMLeakFinder.m in Sources */, + 789AD1CF24D9024300CB0B4C /* Reachability.m in Sources */, + 7862533F24599D420017F8F1 /* NSObject+SLCrashProtector.m in Sources */, + 787C7DC2245D74E4005DF7ED /* NSMutableString+SLCrashProtector.m in Sources */, 782E3EB12373D889001E0DF9 /* SLAvCaptureSession.m in Sources */, + 789AD1C824D7FA5F00CB0B4C /* SLAPMURLProtocol.m in Sources */, + 78842464248F964000C2E505 /* SLAVListViewController.m in Sources */, 78E179362351A40D0007E4BB /* SLEditMenuView.m in Sources */, + 78BDE04B248A6A74002ED386 /* SLUrlProtocolAddCookie.m in Sources */, + 78E72D922484EEAA00751373 /* SLUrlCache.m in Sources */, 78F54916233383D800910215 /* SLBlurView.m in Sources */, 787F01A3236AD8A5002AC1A9 /* SLEditImageController.m in Sources */, 780F5306236D8570000D0EA8 /* SLImageClipController.m in Sources */, 782CFB6A239E68D5001B5528 /* SLSpecialEffectsViewController.m in Sources */, + 7884248B2493851B00C2E505 /* SLViewController.m in Sources */, + 784C4BED24BDA5D700D5C199 /* SLAPMFluency.m in Sources */, + 78E72D5124766E4B00751373 /* SLWebViewController.m in Sources */, 78B10493232F57C50051579F /* main.m in Sources */, + 788424C724978E9200C2E505 /* SLKeyChain.m in Sources */, + 7869C72324A8B6D200527546 /* SLPictureTransitionAnimation.m in Sources */, 78E179372351A40D0007E4BB /* SLShotFocusView.m in Sources */, + 787C7DC8245D77D9005DF7ED /* SLZombieCatcher.m in Sources */, + 78799EA224EE7EEC00DA8C7A /* SLUnusedResourceViewController.m in Sources */, 78A4BD45236C5DF20021AE32 /* SLEditVideoController.m in Sources */, 78B10485232F57C30051579F /* SceneDelegate.m in Sources */, 783FB45C2394A4E10039AEFD /* SLShaderLanguageViewController.m in Sources */, + 78908DCD2500F099004164C2 /* SLMenuView.m in Sources */, + 788424822492159F00C2E505 /* SLNavigationController.m in Sources */, + 7835048424586BAC0071283E /* fishhook.c in Sources */, + 787C7E42246557A5005DF7ED /* SLZombieFinder.m in Sources */, + 78E72D76247F9FFC00751373 /* SLWebTableViewController4.m in Sources */, 78E1791923517F700007E4BB /* SLImageDecoder.m in Sources */, + 78E72D73247E861300751373 /* SLWebViewListController.m in Sources */, 782CFB57239E5FE6001B5528 /* SLShaderFilterViewController.m in Sources */, 7822CCF8235B054200E70C29 /* SLPaddingLabel.m in Sources */, + 784C4BE424BC4C5E00D5C199 /* SLAPMManager.m in Sources */, + 784C4C3A24C2EE6600D5C199 /* SLAPMMemoryDisk.m in Sources */, + 78842468248FAED100C2E505 /* SLColorPickerViewController.m in Sources */, + 789AD1D224D9622B00CB0B4C /* UIViewController+SLAPMVCTime.m in Sources */, + 787C7DCF2462B03D005DF7ED /* UINavigationController+SLMLeakFinder.m in Sources */, + 789AD16324C99DCA00CB0B4C /* SLAPMThreadCount.m in Sources */, 788ACD622390BFD400737EC2 /* SLLoadImageVC.m in Sources */, + 78E72D5C247BDDE700751373 /* SLWebTableViewController2.m in Sources */, + 78842454248F863500C2E505 /* SLTableViewController.m in Sources */, 78777CE6238FF65C006FA671 /* SLDelayPerform.m in Sources */, + 787C7DD22462C5F3005DF7ED /* UIViewController+SLMLeakFinder.m in Sources */, + 788ADB382441F48F00302CD9 /* SLCrashViewController.m in Sources */, 78DAA1162360256200A60F64 /* SLEditSelectedBox.m in Sources */, + 78799EAB24EFC60C00DA8C7A /* SLResourceInfo.m in Sources */, + 787C7DCC2462ADE5005DF7ED /* NSObject+SLMLeakFinder.m in Sources */, + 78E72D792480B36800751373 /* SLScrollViewController.m in Sources */, 782CFB20239DEA05001B5528 /* SLSplitScreenCell.m in Sources */, + 781DA28E24E00F9E00FAE73D /* QiCallTraceCore.c in Sources */, + 781D9D0624DD662E00FAE73D /* UIView+SLAsynUpdateUI.m in Sources */, 7830A59E2358520200BC79BA /* SLEditTextView.m in Sources */, 7860D78E2398D34E008C53EC /* SLMixColorTextureVC.m in Sources */, + 78E72D7F24810ECC00751373 /* UIScrollView+SLCommon.m in Sources */, + 78EF8B6424188767008D0CD7 /* SLAlertView.m in Sources */, 78E179312351A4020007E4BB /* SLShotViewController.m in Sources */, + 78E72D86248284B700751373 /* SLUrlProtocol.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1111,18 +1820,22 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/DarkMode/General/SLAnimateImageView", + "$(PROJECT_DIR)/DarkMode/WorkIssues/APM/APMMonitor", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SRCROOT)/DarkMode/General/Tool/PrefixHeader.pch"; INFOPLIST_FILE = DarkMode/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_GENERATE_MAP_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); NEW_SETTING = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.wsl.demo; - PRODUCT_NAME = "iOS - Tips"; + ORDER_FILE = "$(SRCROOT)/DarkMode/WorkIssues/二进制重排/wsl.order"; + OTHER_CFLAGS = "-fsanitize-coverage=func,trace-pc-guard"; + PRODUCT_BUNDLE_IDENTIFIER = com.wsl2ls.iostip; + PRODUCT_NAME = SLTips; PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1139,18 +1852,22 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/DarkMode/General/SLAnimateImageView", + "$(PROJECT_DIR)/DarkMode/WorkIssues/APM/APMMonitor", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "$(SRCROOT)/DarkMode/General/Tool/PrefixHeader.pch"; INFOPLIST_FILE = DarkMode/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_GENERATE_MAP_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); NEW_SETTING = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.wsl.demo; - PRODUCT_NAME = "iOS - Tips"; + ORDER_FILE = "$(SRCROOT)/DarkMode/WorkIssues/二进制重排/wsl.order"; + OTHER_CFLAGS = "-fsanitize-coverage=func,trace-pc-guard"; + PRODUCT_BUNDLE_IDENTIFIER = com.wsl2ls.iostip; + PRODUCT_NAME = SLTips; PROVISIONING_PROFILE_SPECIFIER = ""; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/iOS_Tips/DarkMode.xcodeproj/xcshareddata/xcschemes/DarkMode.xcscheme b/iOS_Tips/DarkMode.xcodeproj/xcshareddata/xcschemes/DarkMode.xcscheme new file mode 100644 index 00000000..55ea58eb --- /dev/null +++ b/iOS_Tips/DarkMode.xcodeproj/xcshareddata/xcschemes/DarkMode.xcscheme @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS_Tips/DarkMode.xcworkspace/contents.xcworkspacedata b/iOS_Tips/DarkMode.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index a8c127c2..00000000 --- a/iOS_Tips/DarkMode.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/iOS_Tips/DarkMode.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iOS_Tips/DarkMode.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/iOS_Tips/DarkMode.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/iOS_Tips/DarkMode/.DS_Store b/iOS_Tips/DarkMode/.DS_Store index 7280fcc9..05e7465d 100644 Binary files a/iOS_Tips/DarkMode/.DS_Store and b/iOS_Tips/DarkMode/.DS_Store differ diff --git a/iOS_Tips/DarkMode/AVFoundation/ColorPicker/SLColorPickerViewController.h b/iOS_Tips/DarkMode/AVFoundation/ColorPicker/SLColorPickerViewController.h new file mode 100644 index 00000000..85d0f6ee --- /dev/null +++ b/iOS_Tips/DarkMode/AVFoundation/ColorPicker/SLColorPickerViewController.h @@ -0,0 +1,18 @@ +// +// SLColorPickerViewController.h +// DarkMode +// +// Created by wsl on 2020/6/9. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///拾色器 利用摄像头实时识别颜色 +@interface SLColorPickerViewController : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/AVFoundation/ColorPicker/SLColorPickerViewController.m b/iOS_Tips/DarkMode/AVFoundation/ColorPicker/SLColorPickerViewController.m new file mode 100644 index 00000000..29ca571d --- /dev/null +++ b/iOS_Tips/DarkMode/AVFoundation/ColorPicker/SLColorPickerViewController.m @@ -0,0 +1,104 @@ +// +// SLColorPickerViewController.m +// DarkMode +// +// Created by wsl on 2020/6/9. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLColorPickerViewController.h" +#import "SLAvCaptureSession.h" +#import "UIImage+SLCommon.h" +#import "UIColor+SLCommon.h" +#import "SLBlurView.h" + +@interface SLColorPickerViewController () +@property (nonatomic, strong) SLAvCaptureSession *avCaptureSession; //摄像头采集工具 +@property (nonatomic, strong) UIView *preview; //摄像头采集内容视图 +@property (nonatomic, strong) SLBlurView *colorPickerView; //识别的颜色中心 +@property (nonatomic, strong) CIContext* context; +@property (nonatomic, assign) int hexColor; //识别的16进制的颜色值 +@end + +@implementation SLColorPickerViewController + +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUI]; +} +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + [_avCaptureSession stopRunning]; + _avCaptureSession = nil; +} +- (void)dealloc { + NSLog(@"%@释放了",NSStringFromClass(self.class)); +} +#pragma mark - UI +- (void)setupUI { + self.navigationController.navigationBar.translucent = NO; + [self.view addSubview:self.preview]; + [self.view addSubview:self.colorPickerView]; + self.avCaptureSession.preview = self.preview; + [self.avCaptureSession startRunning]; +} + +#pragma mark - Getter +- (SLAvCaptureSession *)avCaptureSession { + if (_avCaptureSession == nil) { + _avCaptureSession = [[SLAvCaptureSession alloc] init]; + _avCaptureSession.delegate = self; + } + return _avCaptureSession; +} +- (UIView *)preview { + if (!_preview) { + _preview = [[UIView alloc] initWithFrame:self.view.bounds]; + } + return _preview; +} +- (SLBlurView *)colorPickerView { + if (!_colorPickerView) { + _colorPickerView = [[SLBlurView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; + _colorPickerView.blurView.alpha = 0.5; + _colorPickerView.layer.cornerRadius = 20; + _colorPickerView.layer.masksToBounds = YES; + _colorPickerView.layer.borderWidth = 1; + _colorPickerView.center = self.view.center; + } + return _colorPickerView; +} +-(CIContext *)context{ + // default creates a context based on GPU + if (_context == nil) { + _context = [CIContext contextWithOptions:nil]; + } + return _context; +} + +#pragma mark - SLAvCaptureSessionDelegate 音视频实时输出代理 +//实时输出视频样本 +- (void)captureSession:(SLAvCaptureSession * _Nullable)captureSession didOutputVideoSampleBuffer:(CMSampleBufferRef _Nullable)sampleBuffer fromConnection:(AVCaptureConnection * _Nullable)connection { + @autoreleasepool { + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CIImage* ciimage = [CIImage imageWithCVImageBuffer:imageBuffer]; + CGImageRef imageRef = [self.context createCGImage:ciimage fromRect:ciimage.extent]; + UIImage *img = [UIImage imageWithCGImage:imageRef]; + SL_DISPATCH_ON_MAIN_THREAD((^{ + UIColor *color = [img sl_colorAtPixel:CGPointMake(img.size.width/2.0,img.size.height/2.0)]; + int hex = [UIColor sl_hexValueWithColor:color]; + //误差在0x080808之内,可看作颜色没变化 + if (abs(self.hexColor-hex) >= 0x080808) { + //每次摄像头采集的图像帧都不是完全一样的,虽然人眼看着一样,但是像素在细微处会有差距,所以采集到的每一帧的颜色也会有误差 + self.hexColor = hex; + // NSLog(@"%x %x",self.hexColor, hex); + } + self.navigationController.navigationBar.barTintColor = [UIColor sl_colorWithHex: self.hexColor alpha:1.0]; + self.navigationItem.title = [NSString stringWithFormat:@"识别的颜色:0x%x", self.hexColor]; + CGImageRelease(imageRef); + })); + } +} + +@end diff --git a/iOS_Tips/DarkMode/FaceDetect/SLFaceDetectController.h b/iOS_Tips/DarkMode/AVFoundation/FaceDetect/SLFaceDetectController.h similarity index 100% rename from iOS_Tips/DarkMode/FaceDetect/SLFaceDetectController.h rename to iOS_Tips/DarkMode/AVFoundation/FaceDetect/SLFaceDetectController.h diff --git a/iOS_Tips/DarkMode/FaceDetect/SLFaceDetectController.m b/iOS_Tips/DarkMode/AVFoundation/FaceDetect/SLFaceDetectController.m similarity index 99% rename from iOS_Tips/DarkMode/FaceDetect/SLFaceDetectController.m rename to iOS_Tips/DarkMode/AVFoundation/FaceDetect/SLFaceDetectController.m index 8964e222..2a222c9f 100644 --- a/iOS_Tips/DarkMode/FaceDetect/SLFaceDetectController.m +++ b/iOS_Tips/DarkMode/AVFoundation/FaceDetect/SLFaceDetectController.m @@ -274,7 +274,7 @@ - (CALayer *)overlayLayer { } - (UIButton *)switchCameraBtn { if (_switchCameraBtn == nil) { - _switchCameraBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_w - 30 - 30, 44 , 30, 30)]; + _switchCameraBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_width - 30 - 30, 44 , 30, 30)]; [_switchCameraBtn setImage:[UIImage imageNamed:@"cameraAround"] forState:UIControlStateNormal]; [_switchCameraBtn addTarget:self action:@selector(switchCameraClicked:) forControlEvents:UIControlEventTouchUpInside]; } diff --git a/iOS_Tips/DarkMode/Filter/SLFilterViewController.h b/iOS_Tips/DarkMode/AVFoundation/Filter/SLFilterViewController.h similarity index 100% rename from iOS_Tips/DarkMode/Filter/SLFilterViewController.h rename to iOS_Tips/DarkMode/AVFoundation/Filter/SLFilterViewController.h diff --git a/iOS_Tips/DarkMode/Filter/SLFilterViewController.m b/iOS_Tips/DarkMode/AVFoundation/Filter/SLFilterViewController.m similarity index 92% rename from iOS_Tips/DarkMode/Filter/SLFilterViewController.m rename to iOS_Tips/DarkMode/AVFoundation/Filter/SLFilterViewController.m index 131223d6..5811ee03 100644 --- a/iOS_Tips/DarkMode/Filter/SLFilterViewController.m +++ b/iOS_Tips/DarkMode/AVFoundation/Filter/SLFilterViewController.m @@ -116,6 +116,7 @@ - (SLAvCaptureSession *)avCaptureSession { - (SLAvWriterInput *)avWriterInput { if (!_avWriterInput) { _avWriterInput = [[SLAvWriterInput alloc] init]; + _avWriterInput.videoSize = CGSizeMake(SL_kScreenWidth*0.8, SL_kScreenHeight*0.8); _avWriterInput.delegate = self; } return _avWriterInput; @@ -137,7 +138,7 @@ - (UIButton *)backBtn { if (_backBtn == nil) { _backBtn = [[UIButton alloc] init]; _backBtn.frame = CGRectMake(0, 0, 30, 30); - _backBtn.center = CGPointMake((self.view.sl_w/2 - 70/2.0)/2.0, self.view.sl_h - 80); + _backBtn.center = CGPointMake((self.view.sl_width/2 - 70/2.0)/2.0, self.view.sl_height - 80); [_backBtn setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal]; [_backBtn addTarget:self action:@selector(backBtn:) forControlEvents:UIControlEventTouchUpInside]; } @@ -148,9 +149,9 @@ - (UIView *)shotBtn { _shotBtn = [[SLBlurView alloc] init]; _shotBtn.userInteractionEnabled = YES; _shotBtn.frame = CGRectMake(0, 0, 70, 70); - _shotBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); + _shotBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); _shotBtn.clipsToBounds = YES; - _shotBtn.layer.cornerRadius = _shotBtn.sl_w/2.0; + _shotBtn.layer.cornerRadius = _shotBtn.sl_width/2.0; //轻触拍照,长按摄像 UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(takePicture:)]; [_shotBtn addGestureRecognizer:tap]; @@ -159,7 +160,7 @@ - (UIView *)shotBtn { [_shotBtn addGestureRecognizer:longPress]; //中心白色 self.whiteView.frame = CGRectMake(0, 0, 50, 50); - self.whiteView.center = CGPointMake(_shotBtn.sl_w/2.0, _shotBtn.sl_h/2.0); + self.whiteView.center = CGPointMake(_shotBtn.sl_width/2.0, _shotBtn.sl_height/2.0); self.whiteView.layer.cornerRadius = self.whiteView.frame.size.width/2.0; [_shotBtn addSubview:self.whiteView]; } @@ -167,7 +168,7 @@ - (UIView *)shotBtn { } - (UIButton *)switchCameraBtn { if (_switchCameraBtn == nil) { - _switchCameraBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_w - 30 - 30, 44 , 30, 30)]; + _switchCameraBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_width - 30 - 30, 44 , 30, 30)]; [_switchCameraBtn setImage:[UIImage imageNamed:@"cameraAround"] forState:UIControlStateNormal]; [_switchCameraBtn addTarget:self action:@selector(switchCameraClicked:) forControlEvents:UIControlEventTouchUpInside]; } @@ -218,7 +219,7 @@ - (SLShotFocusView *)focusView { } - (UILabel *)tipsLabel { if (_tipsLabel == nil) { - _tipsLabel = [[UILabel alloc] initWithFrame:CGRectMake((self.view.sl_w - 140)/2.0, self.shotBtn.sl_y - 20 - 30, 140, 20)]; + _tipsLabel = [[UILabel alloc] initWithFrame:CGRectMake((self.view.sl_width - 140)/2.0, self.shotBtn.sl_y - 20 - 30, 140, 20)]; _tipsLabel.textColor = [UIColor whiteColor]; _tipsLabel.font = [UIFont systemFontOfSize:14]; _tipsLabel.textAlignment = NSTextAlignmentCenter; @@ -292,18 +293,17 @@ - (void)startTimer{ NSLog(@"时长 %f", self->_durationOfVideo); SL_DISPATCH_ON_MAIN_THREAD(^{ self.progressLayer.strokeEnd = 1; + //暂停定时器 + // dispatch_suspend(_gcdTimer); + //取消计时器 + dispatch_source_cancel(self->_gcdTimer); + self->_durationOfVideo = 0; + [self.progressLayer removeFromSuperlayer]; + //停止录制 + [self.avWriterInput finishWriting]; + [self.avCaptureSession stopRunning]; + self.isRecording = NO; }); - - //暂停定时器 - // dispatch_suspend(_gcdTimer); - //取消计时器 - dispatch_source_cancel(self->_gcdTimer); - self->_durationOfVideo = 0; - [self.progressLayer removeFromSuperlayer]; - //停止录制 - [self.avWriterInput finishWriting]; - [self.avCaptureSession stopRunning]; - self.isRecording = NO; } }); // 启动任务,GCD计时器创建后需要手动启动 @@ -322,7 +322,7 @@ - (void)tapFocusing:(UITapGestureRecognizer *)tap { return; } CGPoint point = [tap locationInView:self.captureView]; - if(point.y > self.shotBtn.sl_y || point.y < self.switchCameraBtn.sl_y + self.switchCameraBtn.sl_h) { + if(point.y > self.shotBtn.sl_y || point.y < self.switchCameraBtn.sl_y + self.switchCameraBtn.sl_height) { return; } [self focusAtPoint:point]; @@ -395,11 +395,11 @@ - (void)recordVideo:(UILongPressGestureRecognizer *)longPress { switch (longPress.state) { case UIGestureRecognizerStateBegan:{ self.shotBtn.sl_size = CGSizeMake(100, 100); - self.shotBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); - self.shotBtn.layer.cornerRadius = self.shotBtn.sl_h/2.0; + self.shotBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); + self.shotBtn.layer.cornerRadius = self.shotBtn.sl_height/2.0; self.whiteView.sl_size = CGSizeMake(40, 40); - self.whiteView.center = CGPointMake(self.shotBtn.sl_w/2.0, self.shotBtn.sl_h/2.0); - self.whiteView.layer.cornerRadius = self.whiteView.sl_w/2.0; + self.whiteView.center = CGPointMake(self.shotBtn.sl_width/2.0, self.shotBtn.sl_height/2.0); + self.whiteView.layer.cornerRadius = self.whiteView.sl_width/2.0; //开始计时 [self startTimer]; //添加进度条 @@ -418,11 +418,11 @@ - (void)recordVideo:(UILongPressGestureRecognizer *)longPress { break; case UIGestureRecognizerStateEnded:{ self.shotBtn.sl_size = CGSizeMake(70, 70); - self.shotBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); - self.shotBtn.layer.cornerRadius = self.shotBtn.sl_h/2.0; + self.shotBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); + self.shotBtn.layer.cornerRadius = self.shotBtn.sl_height/2.0; self.whiteView.sl_size = CGSizeMake(50, 50); - self.whiteView.center = CGPointMake(self.shotBtn.sl_w/2.0, self.shotBtn.sl_h/2.0); - self.whiteView.layer.cornerRadius = self.whiteView.sl_w/2.0; + self.whiteView.center = CGPointMake(self.shotBtn.sl_width/2.0, self.shotBtn.sl_height/2.0); + self.whiteView.layer.cornerRadius = self.whiteView.sl_width/2.0; //取消计时器 dispatch_source_cancel(self->_gcdTimer); self->_durationOfVideo = 0; @@ -497,7 +497,11 @@ - (void)writerInput:(SLAvWriterInput *)writerInput didFinishRecordingToOutputFil SLEditVideoController * editViewController = [[SLEditVideoController alloc] init]; editViewController.videoPath = outputFileURL; editViewController.modalPresentationStyle = UIModalPresentationFullScreen; - [self presentViewController:editViewController animated:NO completion:nil]; + [self presentViewController:editViewController animated:NO completion:^{ + NSString *result = error ? @"录制失败" : @"录制成功"; + NSLog(@"%@ %@", result , error.localizedDescription); + [SLAlertView showAlertViewWithText:result delayHid:1]; + }]; } @end diff --git a/iOS_Tips/DarkMode/GPUImageDemo/Controller/SLGPUImageController.h b/iOS_Tips/DarkMode/AVFoundation/GPUImageDemo/Controller/SLGPUImageController.h similarity index 100% rename from iOS_Tips/DarkMode/GPUImageDemo/Controller/SLGPUImageController.h rename to iOS_Tips/DarkMode/AVFoundation/GPUImageDemo/Controller/SLGPUImageController.h diff --git a/iOS_Tips/DarkMode/GPUImageDemo/Controller/SLGPUImageController.m b/iOS_Tips/DarkMode/AVFoundation/GPUImageDemo/Controller/SLGPUImageController.m similarity index 95% rename from iOS_Tips/DarkMode/GPUImageDemo/Controller/SLGPUImageController.m rename to iOS_Tips/DarkMode/AVFoundation/GPUImageDemo/Controller/SLGPUImageController.m index b51f7d9f..9f6462b7 100644 --- a/iOS_Tips/DarkMode/GPUImageDemo/Controller/SLGPUImageController.m +++ b/iOS_Tips/DarkMode/AVFoundation/GPUImageDemo/Controller/SLGPUImageController.m @@ -175,7 +175,7 @@ - (UIButton *)backBtn { if (_backBtn == nil) { _backBtn = [[UIButton alloc] init]; _backBtn.frame = CGRectMake(0, 0, 30, 30); - _backBtn.center = CGPointMake((self.view.sl_w/2 - 70/2.0)/2.0, self.view.sl_h - 80); + _backBtn.center = CGPointMake((self.view.sl_width/2 - 70/2.0)/2.0, self.view.sl_height - 80); [_backBtn setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal]; [_backBtn addTarget:self action:@selector(backBtn:) forControlEvents:UIControlEventTouchUpInside]; } @@ -186,9 +186,9 @@ - (UIView *)shotBtn { _shotBtn = [[SLBlurView alloc] init]; _shotBtn.userInteractionEnabled = YES; _shotBtn.frame = CGRectMake(0, 0, 70, 70); - _shotBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); + _shotBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); _shotBtn.clipsToBounds = YES; - _shotBtn.layer.cornerRadius = _shotBtn.sl_w/2.0; + _shotBtn.layer.cornerRadius = _shotBtn.sl_width/2.0; //轻触拍照,长按摄像 UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(takePicture:)]; [_shotBtn addGestureRecognizer:tap]; @@ -197,7 +197,7 @@ - (UIView *)shotBtn { [_shotBtn addGestureRecognizer:longPress]; //中心白色 self.whiteView.frame = CGRectMake(0, 0, 50, 50); - self.whiteView.center = CGPointMake(_shotBtn.sl_w/2.0, _shotBtn.sl_h/2.0); + self.whiteView.center = CGPointMake(_shotBtn.sl_width/2.0, _shotBtn.sl_height/2.0); self.whiteView.layer.cornerRadius = self.whiteView.frame.size.width/2.0; [_shotBtn addSubview:self.whiteView]; } @@ -205,7 +205,7 @@ - (UIView *)shotBtn { } - (UIButton *)switchCameraBtn { if (_switchCameraBtn == nil) { - _switchCameraBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_w - 30 - 30, 44 , 30, 30)]; + _switchCameraBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_width - 30 - 30, 44 , 30, 30)]; [_switchCameraBtn setImage:[UIImage imageNamed:@"cameraAround"] forState:UIControlStateNormal]; [_switchCameraBtn addTarget:self action:@selector(switchCameraClicked:) forControlEvents:UIControlEventTouchUpInside]; } @@ -256,7 +256,7 @@ - (SLShotFocusView *)focusView { } - (UILabel *)tipsLabel { if (_tipsLabel == nil) { - _tipsLabel = [[UILabel alloc] initWithFrame:CGRectMake((self.view.sl_w - 140)/2.0, self.shotBtn.sl_y - 20 - 30, 140, 20)]; + _tipsLabel = [[UILabel alloc] initWithFrame:CGRectMake((self.view.sl_width - 140)/2.0, self.shotBtn.sl_y - 20 - 30, 140, 20)]; _tipsLabel.textColor = [UIColor whiteColor]; _tipsLabel.font = [UIFont systemFontOfSize:14]; _tipsLabel.textAlignment = NSTextAlignmentCenter; @@ -374,16 +374,15 @@ - (void)startTimer{ NSLog(@"时长 %f", self->_durationOfVideo); SL_DISPATCH_ON_MAIN_THREAD(^{ self.progressLayer.strokeEnd = 1; + //暂停定时器 + // dispatch_suspend(_gcdTimer); + //取消计时器 + dispatch_source_cancel(self->_gcdTimer); + self->_durationOfVideo = 0; + [self.progressLayer removeFromSuperlayer]; + //停止录制 + [self endRecord]; }); - - //暂停定时器 - // dispatch_suspend(_gcdTimer); - //取消计时器 - dispatch_source_cancel(self->_gcdTimer); - self->_durationOfVideo = 0; - [self.progressLayer removeFromSuperlayer]; - //停止录制 - [self endRecord]; } }); // 启动任务,GCD计时器创建后需要手动启动 @@ -426,7 +425,13 @@ - (void)endRecord { waterMarkController.videoPath = [NSURL fileURLWithPath:KRecordVideoFilePath]; waterMarkController.modalPresentationStyle = UIModalPresentationFullScreen; waterMarkController.videoOrientation = self.shootingOrientation; - [self presentViewController:waterMarkController animated:NO completion:nil]; + [self presentViewController:waterMarkController animated:NO completion:^{ + NSString *result = @"录制成功"; + NSLog(@"%@", result); + [SLAlertView showAlertViewWithText:result delayHid:1]; + }]; + }else { + [SLAlertView showAlertViewWithText:@"录制失败" delayHid:1]; } } // 添加水印 @@ -443,7 +448,7 @@ - (void)addWatermark { transform = CGAffineTransformMakeRotation(0); } // 水印层 - UIView *watermarkView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.sl_w, self.view.sl_h)]; + UIView *watermarkView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.sl_width, self.view.sl_height)]; watermarkView.backgroundColor = [UIColor clearColor]; UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 60, 80)]; label.text = @"iOS2679114653"; @@ -483,7 +488,7 @@ - (void)tapFocusing:(UITapGestureRecognizer *)tap { return; } CGPoint point = [tap locationInView:self.captureView]; - if(point.y > self.shotBtn.sl_y || point.y < self.switchCameraBtn.sl_y + self.switchCameraBtn.sl_h) { + if(point.y > self.shotBtn.sl_y || point.y < self.switchCameraBtn.sl_y + self.switchCameraBtn.sl_height) { return; } [self focusAtPoint:point]; @@ -582,11 +587,11 @@ - (void)recordVideo:(UILongPressGestureRecognizer *)longPress { switch (longPress.state) { case UIGestureRecognizerStateBegan:{ self.shotBtn.sl_size = CGSizeMake(100, 100); - self.shotBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); - self.shotBtn.layer.cornerRadius = self.shotBtn.sl_h/2.0; + self.shotBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); + self.shotBtn.layer.cornerRadius = self.shotBtn.sl_height/2.0; self.whiteView.sl_size = CGSizeMake(40, 40); - self.whiteView.center = CGPointMake(self.shotBtn.sl_w/2.0, self.shotBtn.sl_h/2.0); - self.whiteView.layer.cornerRadius = self.whiteView.sl_w/2.0; + self.whiteView.center = CGPointMake(self.shotBtn.sl_width/2.0, self.shotBtn.sl_height/2.0); + self.whiteView.layer.cornerRadius = self.whiteView.sl_width/2.0; //开始计时 [self startTimer]; //添加进度条 @@ -604,11 +609,11 @@ - (void)recordVideo:(UILongPressGestureRecognizer *)longPress { break; case UIGestureRecognizerStateEnded:{ self.shotBtn.sl_size = CGSizeMake(70, 70); - self.shotBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); - self.shotBtn.layer.cornerRadius = self.shotBtn.sl_h/2.0; + self.shotBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); + self.shotBtn.layer.cornerRadius = self.shotBtn.sl_height/2.0; self.whiteView.sl_size = CGSizeMake(50, 50); - self.whiteView.center = CGPointMake(self.shotBtn.sl_w/2.0, self.shotBtn.sl_h/2.0); - self.whiteView.layer.cornerRadius = self.whiteView.sl_w/2.0; + self.whiteView.center = CGPointMake(self.shotBtn.sl_width/2.0, self.shotBtn.sl_height/2.0); + self.whiteView.layer.cornerRadius = self.whiteView.sl_width/2.0; //取消计时器 dispatch_source_cancel(self->_gcdTimer); self->_durationOfVideo = 0; diff --git a/iOS_Tips/DarkMode/GPUImageDemo/Controller/SLWaterMarkController.h b/iOS_Tips/DarkMode/AVFoundation/GPUImageDemo/Controller/SLWaterMarkController.h similarity index 100% rename from iOS_Tips/DarkMode/GPUImageDemo/Controller/SLWaterMarkController.h rename to iOS_Tips/DarkMode/AVFoundation/GPUImageDemo/Controller/SLWaterMarkController.h diff --git a/iOS_Tips/DarkMode/GPUImageDemo/Controller/SLWaterMarkController.m b/iOS_Tips/DarkMode/AVFoundation/GPUImageDemo/Controller/SLWaterMarkController.m similarity index 92% rename from iOS_Tips/DarkMode/GPUImageDemo/Controller/SLWaterMarkController.m rename to iOS_Tips/DarkMode/AVFoundation/GPUImageDemo/Controller/SLWaterMarkController.m index 22be8b04..f9260f8a 100644 --- a/iOS_Tips/DarkMode/GPUImageDemo/Controller/SLWaterMarkController.m +++ b/iOS_Tips/DarkMode/AVFoundation/GPUImageDemo/Controller/SLWaterMarkController.m @@ -120,8 +120,8 @@ - (GPUImageMovieWriter *)movieWriter { - (SLBlurView *)addWatermark { if (_addWatermark == nil) { _addWatermark = [[SLBlurView alloc] initWithFrame:CGRectMake(0, 0, 70, 70)]; - _addWatermark.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); - _addWatermark.layer.cornerRadius = _addWatermark.sl_w/2.0; + _addWatermark.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); + _addWatermark.layer.cornerRadius = _addWatermark.sl_width/2.0; UIButton * btn = [[UIButton alloc] initWithFrame:_addWatermark.bounds]; [btn setTitle:@"add水印" forState:UIControlStateNormal]; [btn addTarget:self action:@selector(addWatermarkClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -132,8 +132,8 @@ - (SLBlurView *)addWatermark { - (SLBlurView *)againShotBtn { if (_againShotBtn == nil) { _againShotBtn = [[SLBlurView alloc] initWithFrame:CGRectMake(0, 0, 70, 70)]; - _againShotBtn.center = CGPointMake((self.view.sl_w/2 - 70/2.0)/2.0, self.view.sl_h - 80); - _againShotBtn.layer.cornerRadius = _againShotBtn.sl_w/2.0; + _againShotBtn.center = CGPointMake((self.view.sl_width/2 - 70/2.0)/2.0, self.view.sl_height - 80); + _againShotBtn.layer.cornerRadius = _againShotBtn.sl_width/2.0; UIButton * btn = [[UIButton alloc] initWithFrame:_againShotBtn.bounds]; [btn setImage:[UIImage imageNamed:@"cancle"] forState:UIControlStateNormal]; [btn addTarget:self action:@selector(againShotBtnClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -144,8 +144,8 @@ - (SLBlurView *)againShotBtn { - (UIButton *)saveAlbumBtn { if (_saveAlbumBtn == nil) { _saveAlbumBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 70, 70)]; - _saveAlbumBtn.center = CGPointMake(self.view.sl_w/2.0 + 70/2.0+ (self.view.sl_w/2 - 70/2.0)/2.0, self.view.sl_h - 80); - _saveAlbumBtn.layer.cornerRadius = _saveAlbumBtn.sl_w/2.0; + _saveAlbumBtn.center = CGPointMake(self.view.sl_width/2.0 + 70/2.0+ (self.view.sl_width/2 - 70/2.0)/2.0, self.view.sl_height - 80); + _saveAlbumBtn.layer.cornerRadius = _saveAlbumBtn.sl_width/2.0; _saveAlbumBtn.backgroundColor = [UIColor whiteColor]; [_saveAlbumBtn setImage:[UIImage imageNamed:@"save"] forState:UIControlStateNormal]; [_saveAlbumBtn addTarget:self action:@selector(saveAlbumBtnClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -177,14 +177,14 @@ - (void)addWatermarkClicked:(id)sender { // 水印层 UIView *watermarkView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, SL_kScreenHeight)]; watermarkView.backgroundColor = [UIColor clearColor]; - UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 200, self.view.sl_w, 80)]; + UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 200, self.view.sl_width, 80)]; label.text = @"简书:且行且珍惜_iOS \n GitHub:wsl2ls"; label.font = [UIFont systemFontOfSize:24]; label.numberOfLines = 0; label.textAlignment = NSTextAlignmentCenter; label.textColor = [UIColor redColor]; SLImageView *imageView = [[SLImageView alloc] initWithFrame:CGRectMake(0, 88, 100, 100)]; - imageView.sl_centerX = self.view.sl_w/2.0; + imageView.sl_centerX = self.view.sl_width/2.0; NSString *myBundlePath = [[NSBundle mainBundle] pathForResource:@"Resources" ofType:@"bundle"]; NSBundle *myBundle = [NSBundle bundleWithPath:myBundlePath]; NSString *imagePath = [myBundle pathForResource:@"stickers_1" ofType:@"gif" inDirectory:@"StickingImages"]; @@ -251,13 +251,11 @@ - (void)saveAlbumBtnClicked:(id)sender { [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:self.videoPath]; } completionHandler:^(BOOL success, NSError * _Nullable error) { SL_DISPATCH_ON_MAIN_THREAD(^{ - [self againShotBtnClicked:nil]; + [self dismissViewControllerAnimated:NO completion:^{ + NSString *result = success ? @"视频保存至相册 成功" : @"保存视频到相册 失败 "; + [SLAlertView showAlertViewWithText:result delayHid:1]; + }]; }); - if (success) { - NSLog(@"视频保存至相册 成功"); - } else { - NSLog(@"保存视频到相册 失败 "); - } }]; } @@ -271,6 +269,7 @@ - (void)movieRecordingCompleted { self.movieWriter = nil; NSLog(@"add水印成功"); SL_DISPATCH_ON_MAIN_THREAD(^{ + [SLAlertView showAlertViewWithText:@"add水印成功" delayHid:1]; self.videoPath = [NSURL fileURLWithPath:KWatermarkVideoFilePath]; self.avPlayer.url = self.videoPath; [self.activityIndicatorView stopAnimating]; @@ -281,6 +280,7 @@ - (void)movieRecordingCompleted { - (void)movieRecordingFailedWithError:(NSError *)error { NSLog(@"add水印失败"); SL_DISPATCH_ON_MAIN_THREAD(^{ + [SLAlertView showAlertViewWithText:@"add水印失败" delayHid:1]; [self.activityIndicatorView stopAnimating]; [self.activityIndicatorView removeFromSuperview]; }); diff --git a/iOS_Tips/DarkMode/AVFoundation/SLAVListViewController.h b/iOS_Tips/DarkMode/AVFoundation/SLAVListViewController.h new file mode 100644 index 00000000..d8062998 --- /dev/null +++ b/iOS_Tips/DarkMode/AVFoundation/SLAVListViewController.h @@ -0,0 +1,17 @@ +// +// SLAVListViewController.h +// DarkMode +// +// Created by wsl on 2020/6/9. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SLAVListViewController : UITableViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/AVFoundation/SLAVListViewController.m b/iOS_Tips/DarkMode/AVFoundation/SLAVListViewController.m new file mode 100644 index 00000000..41d87296 --- /dev/null +++ b/iOS_Tips/DarkMode/AVFoundation/SLAVListViewController.m @@ -0,0 +1,112 @@ +// +// SLAVListTableViewController.m +// DarkMode +// +// Created by wsl on 2020/6/9. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLAVListViewController.h" + +#import "SLShotViewController.h" +#import "SLFaceDetectController.h" +#import "SLFilterViewController.h" +#import "SLGPUImageController.h" +#import "SLColorPickerViewController.h" +#import "SLWebViewController.h" + +@interface SLAVListViewController () +@property (nonatomic, strong) NSMutableArray *dataSource; +@property (nonatomic, strong) NSMutableArray *classArray; +@end + +@implementation SLAVListViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUI]; + [self getData]; +} +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.navigationController.navigationBar.hidden = NO; +} +- (BOOL)prefersStatusBarHidden { + return NO; +} + +#pragma mark - UI +- (void)setupUI { + self.navigationItem.title = @"AVFoundation 音视频"; + self.navigationController.navigationBar.translucent = YES; + self.tableView.estimatedRowHeight = 1; + [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellID"]; +} + +#pragma mark - Data +- (void)getData { + [self.dataSource addObjectsFromArray:@[ + @"AVFoundation 高仿微信相机拍摄和编辑功能", + @"AVFoundation 人脸检测", + @"AVFoundation 实时滤镜拍摄和导出", + @"GPUImage框架的使用", + @"VideoToolBox和AudioToolBox音视频编解码", + @"AVFoundation 利用摄像头实时识别物体颜色", + @"AVFoundation 原生二维码扫描识别和生成"]]; + [self.classArray addObjectsFromArray:@[[SLShotViewController class], + [SLFaceDetectController class], + [SLFilterViewController class], + [SLGPUImageController class], + [UIViewController class], + [SLColorPickerViewController class], + [SLWebViewController class]]]; + [self.tableView reloadData]; +} + +#pragma mark - Getter +- (NSMutableArray *)dataSource { + if (_dataSource == nil) { + _dataSource = [NSMutableArray array]; + } + return _dataSource; +} +- (NSMutableArray *)classArray { + if (_classArray == nil) { + _classArray = [NSMutableArray array]; + } + return _classArray; +} + +#pragma mark - UITableViewDelegate, UITableViewDataSource +- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.dataSource.count; +} +- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellID" forIndexPath:indexPath]; + cell.textLabel.numberOfLines = 0; + cell.textLabel.text = [NSString stringWithFormat:@"%ld、%@",(long)indexPath.row ,self.dataSource[indexPath.row]]; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:NO]; + UIViewController *nextVc = [[self.classArray[indexPath.row] alloc] init]; + switch (indexPath.row) { + case 0: + case 1: + case 2: + case 3: { + nextVc.modalPresentationStyle = UIModalPresentationFullScreen; + [self presentViewController:nextVc animated:YES completion:nil]; + } + break; + case 4: + [SLAlertView showAlertViewWithText:@"查看本仓库下的VideoEncoder&Decoder" delayHid:2]; + break; + case 6: + ((SLWebViewController *)nextVc).urlString = @"https://juejin.im/post/5c0e1db651882539c60d0434"; + default: + [self.navigationController pushViewController:nextVc animated:YES]; + break; + } + } +@end diff --git a/iOS_Tips/DarkMode/SmallVideo/.DS_Store b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/.DS_Store similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/.DS_Store rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/.DS_Store diff --git a/PrviewPicture/.DS_Store b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/.DS_Store similarity index 100% rename from PrviewPicture/.DS_Store rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/.DS_Store diff --git a/iOS_Tips/DarkMode/SmallVideo/Controller/SLEditImageController.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLEditImageController.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/Controller/SLEditImageController.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLEditImageController.h diff --git a/iOS_Tips/DarkMode/SmallVideo/Controller/SLEditImageController.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLEditImageController.m similarity index 94% rename from iOS_Tips/DarkMode/SmallVideo/Controller/SLEditImageController.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLEditImageController.m index eb587425..47e17433 100644 --- a/iOS_Tips/DarkMode/SmallVideo/Controller/SLEditImageController.m +++ b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLEditImageController.m @@ -68,10 +68,10 @@ - (void)setupUI { self.zoomView.pinchGestureRecognizer.enabled = NO; self.zoomView.image = self.image; if (self.image.size.width > 0) { - self.zoomView.imageView.frame = CGRectMake(0, 0, self.zoomView.sl_w, self.zoomView.sl_w * self.image.size.height/self.image.size.width); + self.zoomView.imageView.frame = CGRectMake(0, 0, self.zoomView.sl_width, self.zoomView.sl_width * self.image.size.height/self.image.size.width); } - if (self.zoomView.imageView.sl_h <= self.zoomView.sl_h) { - self.zoomView.imageView.center = CGPointMake(self.zoomView.sl_w/2.0, self.zoomView.sl_h/2.0); + if (self.zoomView.imageView.sl_height <= self.zoomView.sl_height) { + self.zoomView.imageView.center = CGPointMake(self.zoomView.sl_width/2.0, self.zoomView.sl_height/2.0); } //添加裁剪完成监听 @@ -184,8 +184,8 @@ - (SLImageZoomView *)zoomView { - (SLBlurView *)editBtn { if (_editBtn == nil) { _editBtn = [[SLBlurView alloc] initWithFrame:CGRectMake(0, 0, 70, 70)]; - _editBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); - _editBtn.layer.cornerRadius = _editBtn.sl_w/2.0; + _editBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); + _editBtn.layer.cornerRadius = _editBtn.sl_width/2.0; UIButton * btn = [[UIButton alloc] initWithFrame:_editBtn.bounds]; [btn setImage:[UIImage imageNamed:@"edit"] forState:UIControlStateNormal]; [btn addTarget:self action:@selector(editBtnClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -196,8 +196,8 @@ - (SLBlurView *)editBtn { - (SLBlurView *)againShotBtn { if (_againShotBtn == nil) { _againShotBtn = [[SLBlurView alloc] initWithFrame:CGRectMake(0, 0, 70, 70)]; - _againShotBtn.center = CGPointMake((self.view.sl_w/2 - 70/2.0)/2.0, self.view.sl_h - 80); - _againShotBtn.layer.cornerRadius = _againShotBtn.sl_w/2.0; + _againShotBtn.center = CGPointMake((self.view.sl_width/2 - 70/2.0)/2.0, self.view.sl_height - 80); + _againShotBtn.layer.cornerRadius = _againShotBtn.sl_width/2.0; UIButton * btn = [[UIButton alloc] initWithFrame:_againShotBtn.bounds]; [btn setImage:[UIImage imageNamed:@"cancle"] forState:UIControlStateNormal]; [btn addTarget:self action:@selector(againShotBtnClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -208,8 +208,8 @@ - (SLBlurView *)againShotBtn { - (UIButton *)saveAlbumBtn { if (_saveAlbumBtn == nil) { _saveAlbumBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 70, 70)]; - _saveAlbumBtn.center = CGPointMake(self.view.sl_w/2.0 + 70/2.0+ (self.view.sl_w/2 - 70/2.0)/2.0, self.view.sl_h - 80); - _saveAlbumBtn.layer.cornerRadius = _saveAlbumBtn.sl_w/2.0; + _saveAlbumBtn.center = CGPointMake(self.view.sl_width/2.0 + 70/2.0+ (self.view.sl_width/2 - 70/2.0)/2.0, self.view.sl_height - 80); + _saveAlbumBtn.layer.cornerRadius = _saveAlbumBtn.sl_width/2.0; _saveAlbumBtn.backgroundColor = [UIColor whiteColor]; [_saveAlbumBtn setImage:[UIImage imageNamed:@"save"] forState:UIControlStateNormal]; [_saveAlbumBtn addTarget:self action:@selector(saveAlbumBtnClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -229,7 +229,7 @@ - (UIButton *)cancleEditBtn { } - (UIButton *)doneEditBtn { if (_doneEditBtn == nil) { - _doneEditBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_w - 50 - 15, 30, 40, 30)]; + _doneEditBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_width - 50 - 15, 30, 40, 30)]; _doneEditBtn.hidden = YES; _doneEditBtn.backgroundColor = [UIColor colorWithRed:45/255.0 green:175/255.0 blue:45/255.0 alpha:1]; [_doneEditBtn setTitle:@"完成" forState:UIControlStateNormal]; @@ -242,7 +242,7 @@ - (UIButton *)doneEditBtn { } - (SLEditMenuView *)editMenuView { if (!_editMenuView) { - _editMenuView = [[SLEditMenuView alloc] initWithFrame:CGRectMake(0, self.view.sl_h - 80 - 60, self.view.sl_w, 80 + 60)]; + _editMenuView = [[SLEditMenuView alloc] initWithFrame:CGRectMake(0, self.view.sl_height - 80 - 60, self.view.sl_width, 80 + 60)]; _editMenuView.hidden = YES; __weak typeof(self) weakSelf = self; _editMenuView.editObject = SLEditObjectPicture; @@ -270,12 +270,12 @@ - (SLEditMenuView *)editMenuView { imageView.userInteractionEnabled = YES; CGRect imageRect = [weakSelf.zoomView convertRect:weakSelf.zoomView.imageView.frame toView:weakSelf.view]; CGPoint center = CGPointZero; - center.x = fabs(imageRect.origin.x)+weakSelf.zoomView.sl_w/2.0; + center.x = fabs(imageRect.origin.x)+weakSelf.zoomView.sl_width/2.0; center.y = 0; - if (imageRect.origin.y >= 0 && imageRect.size.height <= weakSelf.zoomView.sl_h) { + if (imageRect.origin.y >= 0 && imageRect.size.height <= weakSelf.zoomView.sl_height) { center.y = imageRect.size.height/2.0; }else { - center.y = fabs(imageRect.origin.y) + weakSelf.zoomView.sl_h/2.0; + center.y = fabs(imageRect.origin.y) + weakSelf.zoomView.sl_height/2.0; } imageView.transform = CGAffineTransformMakeScale(1/weakSelf.zoomView.zoomScale, 1/weakSelf.zoomView.zoomScale); center = CGPointMake(center.x/weakSelf.zoomView.zoomScale, center.y/weakSelf.zoomView.zoomScale); @@ -300,12 +300,12 @@ - (SLEditMenuView *)editMenuView { } CGRect imageRect = [weakSelf.zoomView convertRect:weakSelf.zoomView.imageView.frame toView:weakSelf.view]; CGPoint center = CGPointZero; - center.x = fabs(imageRect.origin.x)+weakSelf.zoomView.sl_w/2.0; + center.x = fabs(imageRect.origin.x)+weakSelf.zoomView.sl_width/2.0; center.y = 0; - if (imageRect.origin.y >= 0 && imageRect.size.height <= weakSelf.zoomView.sl_h) { + if (imageRect.origin.y >= 0 && imageRect.size.height <= weakSelf.zoomView.sl_height) { center.y = imageRect.size.height/2.0; }else { - center.y = fabs(imageRect.origin.y) + weakSelf.zoomView.sl_h/2.0; + center.y = fabs(imageRect.origin.y) + weakSelf.zoomView.sl_height/2.0; } label.transform = CGAffineTransformMakeScale(1/weakSelf.zoomView.zoomScale, 1/weakSelf.zoomView.zoomScale); center = CGPointMake(center.x/weakSelf.zoomView.zoomScale, center.y/weakSelf.zoomView.zoomScale); @@ -451,11 +451,11 @@ - (void)cancleEditBtnClicked:(id)sender { self.zoomView.zoomScale = 1; self.zoomView.image = self.image; self.zoomView.pinchGestureRecognizer.enabled = NO; - self.zoomView.imageView.frame = CGRectMake(0, 0, self.zoomView.sl_w, self.zoomView.sl_w * self.image.size.height/self.image.size.width); - if (self.zoomView.imageView.sl_h <= self.zoomView.sl_h) { - self.zoomView.imageView.center = CGPointMake(self.zoomView.sl_w/2.0, self.zoomView.sl_h/2.0); + self.zoomView.imageView.frame = CGRectMake(0, 0, self.zoomView.sl_width, self.zoomView.sl_width * self.image.size.height/self.image.size.width); + if (self.zoomView.imageView.sl_height <= self.zoomView.sl_height) { + self.zoomView.imageView.center = CGPointMake(self.zoomView.sl_width/2.0, self.zoomView.sl_height/2.0); } - self.zoomView.contentSize = CGSizeMake(self.zoomView.imageView.sl_w, self.zoomView.imageView.sl_h); + self.zoomView.contentSize = CGSizeMake(self.zoomView.imageView.sl_width, self.zoomView.imageView.sl_height); } //完成编辑 导出编辑后的对象 - (void)doneEditBtnClicked:(id)sender { @@ -533,12 +533,12 @@ - (void)dragAction:(UIPanGestureRecognizer *)pan { }else if (!CGRectIntersectsRect(imageRect, rect)) { //如果出了父视图zoomView的范围,则置于父视图中心 CGPoint center = CGPointZero; - center.x = fabs(imageRect.origin.x)+self.zoomView.sl_w/2.0; + center.x = fabs(imageRect.origin.x)+self.zoomView.sl_width/2.0; center.y = 0; - if (imageRect.origin.y >= 0 && imageRect.size.height <= self.zoomView.sl_h) { + if (imageRect.origin.y >= 0 && imageRect.size.height <= self.zoomView.sl_height) { center.y = imageRect.size.height/2.0; }else { - center.y = fabs(imageRect.origin.y) + self.zoomView.sl_h/2.0; + center.y = fabs(imageRect.origin.y) + self.zoomView.sl_height/2.0; } center = CGPointMake(center.x/self.zoomView.zoomScale, center.y/self.zoomView.zoomScale); pan.view.center = center; @@ -583,11 +583,11 @@ - (void)imageClippingComplete:(NSNotification *)notification { UIImage *clipImage = notification.userInfo[@"image"]; self.zoomView.zoomScale = 1; self.zoomView.image = clipImage; - self.zoomView.imageView.frame = CGRectMake(0, 0, self.zoomView.sl_w, self.zoomView.sl_w * clipImage.size.height/clipImage.size.width); - if (self.zoomView.imageView.sl_h <= self.zoomView.sl_h) { - self.zoomView.imageView.center = CGPointMake(self.zoomView.sl_w/2.0, self.zoomView.sl_h/2.0); + self.zoomView.imageView.frame = CGRectMake(0, 0, self.zoomView.sl_width, self.zoomView.sl_width * clipImage.size.height/clipImage.size.width); + if (self.zoomView.imageView.sl_height <= self.zoomView.sl_height) { + self.zoomView.imageView.center = CGPointMake(self.zoomView.sl_width/2.0, self.zoomView.sl_height/2.0); } - self.zoomView.contentSize = CGSizeMake(self.zoomView.imageView.sl_w, self.zoomView.imageView.sl_h); + self.zoomView.contentSize = CGSizeMake(self.zoomView.imageView.sl_width, self.zoomView.imageView.sl_height); _drawView.frame = self.zoomView.imageView.bounds; _mosaicView.frame = self.zoomView.imageView.bounds; diff --git a/iOS_Tips/DarkMode/SmallVideo/Controller/SLEditVideoController.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLEditVideoController.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/Controller/SLEditVideoController.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLEditVideoController.h diff --git a/iOS_Tips/DarkMode/SmallVideo/Controller/SLEditVideoController.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLEditVideoController.m similarity index 93% rename from iOS_Tips/DarkMode/SmallVideo/Controller/SLEditVideoController.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLEditVideoController.m index 9ef900e6..820334ae 100644 --- a/iOS_Tips/DarkMode/SmallVideo/Controller/SLEditVideoController.m +++ b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLEditVideoController.m @@ -72,10 +72,10 @@ - (void)setupUI { self.avPlayer.url = self.videoPath; self.avPlayer.delegate = self; if (self.avPlayer.naturalSize.width != CGSizeZero.width) { - self.preview.sl_h = self.preview.sl_w * self.avPlayer.naturalSize.height/ self.avPlayer.naturalSize.width; + self.preview.sl_height = self.preview.sl_width * self.avPlayer.naturalSize.height/ self.avPlayer.naturalSize.width; } self.avPlayer.monitor = self.preview; - self.preview.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h/2.0); + self.preview.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height/2.0); [self.avPlayer play]; [self.view addSubview:self.againShotBtn]; @@ -143,7 +143,7 @@ - (CALayer *)graffitiLayer { graffitiLayer.frame = self.drawView.bounds; // 把水印在预览层上的坐标转换为视频资源文件上的坐标 // 视频Layer上的坐标系原点在左下角,单位是px像素 - CGSize scaleSize = CGSizeMake(self.avPlayer.naturalSize.width/self.preview.sl_w, self.avPlayer.naturalSize.height/self.preview.sl_h); + CGSize scaleSize = CGSizeMake(self.avPlayer.naturalSize.width/self.preview.sl_width, self.avPlayer.naturalSize.height/self.preview.sl_height); CGRect changeRect = CGRectMake(0, 0, CGRectGetWidth(graffitiLayer.frame)*scaleSize.width, CGRectGetHeight(graffitiLayer.frame)*scaleSize.height); graffitiLayer.frame = changeRect; UIImage *image = [self.drawView sl_imageByViewInRect:self.drawView.bounds]; @@ -164,10 +164,10 @@ - (NSMutableArray *)watermarkLayers { animatedLayer.frame = view.bounds; // 把水印在预览层上的坐标转换为视频资源文件上的坐标 // 视频Layer上的坐标系原点在左下角,单位是px像素 - CGSize scaleSize = CGSizeMake(self.avPlayer.naturalSize.width/self.preview.sl_w, self.avPlayer.naturalSize.height/self.preview.sl_h); + CGSize scaleSize = CGSizeMake(self.avPlayer.naturalSize.width/self.preview.sl_width, self.avPlayer.naturalSize.height/self.preview.sl_height); CGRect changeRect = CGRectMake(0, 0, CGRectGetWidth(animatedLayer.frame)*scaleSize.width, CGRectGetHeight(animatedLayer.frame)*scaleSize.height); animatedLayer.frame = changeRect; - animatedLayer.position = CGPointMake(view.center.x*scaleSize.width, (self.preview.sl_h - view.center.y)*scaleSize.height); + animatedLayer.position = CGPointMake(view.center.x*scaleSize.width, (self.preview.sl_height - view.center.y)*scaleSize.height); //形变 CGAffineTransform transform = view.transform; @@ -240,8 +240,8 @@ - (SLAvPlayer *)avPlayer { - (SLBlurView *)editBtn { if (_editBtn == nil) { _editBtn = [[SLBlurView alloc] initWithFrame:CGRectMake(0, 0, 70, 70)]; - _editBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); - _editBtn.layer.cornerRadius = _editBtn.sl_w/2.0; + _editBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); + _editBtn.layer.cornerRadius = _editBtn.sl_width/2.0; UIButton * btn = [[UIButton alloc] initWithFrame:_editBtn.bounds]; [btn setImage:[UIImage imageNamed:@"edit"] forState:UIControlStateNormal]; [btn addTarget:self action:@selector(editBtnClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -252,8 +252,8 @@ - (SLBlurView *)editBtn { - (SLBlurView *)againShotBtn { if (_againShotBtn == nil) { _againShotBtn = [[SLBlurView alloc] initWithFrame:CGRectMake(0, 0, 70, 70)]; - _againShotBtn.center = CGPointMake((self.view.sl_w/2 - 70/2.0)/2.0, self.view.sl_h - 80); - _againShotBtn.layer.cornerRadius = _againShotBtn.sl_w/2.0; + _againShotBtn.center = CGPointMake((self.view.sl_width/2 - 70/2.0)/2.0, self.view.sl_height - 80); + _againShotBtn.layer.cornerRadius = _againShotBtn.sl_width/2.0; UIButton * btn = [[UIButton alloc] initWithFrame:_againShotBtn.bounds]; [btn setImage:[UIImage imageNamed:@"cancle"] forState:UIControlStateNormal]; [btn addTarget:self action:@selector(againShotBtnClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -264,8 +264,8 @@ - (SLBlurView *)againShotBtn { - (UIButton *)saveAlbumBtn { if (_saveAlbumBtn == nil) { _saveAlbumBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 70, 70)]; - _saveAlbumBtn.center = CGPointMake(self.view.sl_w/2.0 + 70/2.0+ (self.view.sl_w/2 - 70/2.0)/2.0, self.view.sl_h - 80); - _saveAlbumBtn.layer.cornerRadius = _saveAlbumBtn.sl_w/2.0; + _saveAlbumBtn.center = CGPointMake(self.view.sl_width/2.0 + 70/2.0+ (self.view.sl_width/2 - 70/2.0)/2.0, self.view.sl_height - 80); + _saveAlbumBtn.layer.cornerRadius = _saveAlbumBtn.sl_width/2.0; _saveAlbumBtn.backgroundColor = [UIColor whiteColor]; [_saveAlbumBtn setImage:[UIImage imageNamed:@"save"] forState:UIControlStateNormal]; [_saveAlbumBtn addTarget:self action:@selector(saveAlbumBtnClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -285,7 +285,7 @@ - (UIButton *)cancleEditBtn { } - (UIButton *)doneEditBtn { if (_doneEditBtn == nil) { - _doneEditBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_w - 50 - 15, 30, 40, 30)]; + _doneEditBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_width - 50 - 15, 30, 40, 30)]; _doneEditBtn.hidden = YES; _doneEditBtn.backgroundColor = [UIColor colorWithRed:45/255.0 green:175/255.0 blue:45/255.0 alpha:1]; [_doneEditBtn setTitle:@"完成" forState:UIControlStateNormal]; @@ -298,7 +298,7 @@ - (UIButton *)doneEditBtn { } - (SLEditMenuView *)editMenuView { if (!_editMenuView) { - _editMenuView = [[SLEditMenuView alloc] initWithFrame:CGRectMake(0, self.view.sl_h - 80 - 60, self.view.sl_w, 80 + 60)]; + _editMenuView = [[SLEditMenuView alloc] initWithFrame:CGRectMake(0, self.view.sl_height - 80 - 60, self.view.sl_width, 80 + 60)]; _editMenuView.hidden = YES; _editMenuView.editObject = SLEditObjectVideo; __weak typeof(self) weakSelf = self; @@ -321,7 +321,7 @@ - (SLEditMenuView *)editMenuView { SLImageView *imageView = [[SLImageView alloc] initWithFrame:CGRectMake(0, 0, image.size.width/[UIScreen mainScreen].scale, image.size.height/[UIScreen mainScreen].scale)]; imageView.autoPlayAnimatedImage = YES; imageView.userInteractionEnabled = YES; - imageView.center = CGPointMake(weakSelf.preview.sl_w/2.0, weakSelf.preview.sl_h/2.0); + imageView.center = CGPointMake(weakSelf.preview.sl_width/2.0, weakSelf.preview.sl_height/2.0); imageView.image = image; [weakSelf.watermarkArray addObject:imageView]; [weakSelf.preview addSubview:imageView]; @@ -339,7 +339,7 @@ - (SLEditMenuView *)editMenuView { if (label.text.length == 0 || label == nil) { return; } - label.center = CGPointMake(weakSelf.preview.sl_w/2.0, weakSelf.preview.sl_h/2.0); + label.center = CGPointMake(weakSelf.preview.sl_width/2.0, weakSelf.preview.sl_height/2.0); [weakSelf.preview addSubview:label]; [weakSelf.watermarkArray addObject:label]; [weakSelf addRotateAndPinchGestureRecognizer:label]; @@ -357,7 +357,7 @@ - (SLEditMenuView *)editMenuView { }; [weakSelf hiddenEditMenus:YES]; weakSelf.preview.transform = CGAffineTransformMakeScale((SL_kScreenWidth - 30 * 2)/SL_kScreenWidth, (SL_kScreenWidth - 30 * 2)/SL_kScreenWidth); - weakSelf.preview.center = CGPointMake(SL_kScreenWidth/2.0, (SL_kScreenHeight - weakSelf.videoClippingView.sl_h)/2.0); + weakSelf.preview.center = CGPointMake(SL_kScreenWidth/2.0, (SL_kScreenHeight - weakSelf.videoClippingView.sl_height)/2.0); weakSelf.videoClippingView.asset = [AVAsset assetWithURL:weakSelf.videoPath]; [weakSelf.view addSubview:weakSelf.videoClippingView]; } @@ -461,24 +461,23 @@ - (void)saveAlbumBtnClicked:(id)sender { SL_DISPATCH_ON_MAIN_THREAD(^{ [self againShotBtnClicked:nil]; }); - if (success) { - NSLog(@"视频保存至相册 成功"); - } else { - NSLog(@"保存视频到相册 失败 "); - } + NSString *result = success ? @"视频保存至相册 成功" : @"保存视频到相册 失败 "; + NSLog(@"%@", result); + SL_DISPATCH_ON_MAIN_THREAD(^{ + [SLAlertView showAlertViewWithText:result delayHid:1]; + }); }]; } //保存图片完成后调用的方法 - (void)savedPhotoImage:(UIImage*)image didFinishSavingWithError:(NSError *)error contextInfo: (void *)contextInfo { SL_DISPATCH_ON_MAIN_THREAD(^{ - [self againShotBtnClicked:nil]; + [self dismissViewControllerAnimated:NO completion:^{ + NSString *result = error ? @"图片保存至相册 失败" : @"图片保存到相册 成功"; + NSLog(@"%@", result); + [SLAlertView showAlertViewWithText:result delayHid:1]; + }]; }); - if (error) { - NSLog(@"保存图片出错%@", error.localizedDescription); - } else { - NSLog(@"保存图片成功"); - } } //取消编辑 - (void)cancleEditBtnClicked:(id)sender { @@ -529,6 +528,8 @@ - (void)exportEditVideo { [self cancleEditBtnClicked:nil]; [activityIndicatorView stopAnimating]; [activityIndicatorView removeFromSuperview]; + NSString *result = error ? @"导出失败" : @"导出成功"; + [SLAlertView showAlertViewWithText:result delayHid:1]; } progress:^(float progress) { // NSLog(@"视频导出进度 %f",progress); }]; @@ -602,7 +603,7 @@ - (void)dragAction:(UIPanGestureRecognizer *)pan { [self.watermarkArray removeObject:(SLImageView *)pan.view]; }else if (!CGRectIntersectsRect(previewRect, rect)) { //如果出了父视图preview的范围,则置于父视图中心 - pan.view.center = CGPointMake(self.preview.sl_w/2.0, self.preview.sl_h/2.0); + pan.view.center = CGPointMake(self.preview.sl_width/2.0, self.preview.sl_height/2.0); } [self.trashTips removeFromSuperview]; [SLDelayPerform sl_startDelayPerform:^{ diff --git a/iOS_Tips/DarkMode/SmallVideo/Controller/SLImageClipController.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLImageClipController.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/Controller/SLImageClipController.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLImageClipController.h diff --git a/iOS_Tips/DarkMode/SmallVideo/Controller/SLImageClipController.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLImageClipController.m similarity index 89% rename from iOS_Tips/DarkMode/SmallVideo/Controller/SLImageClipController.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLImageClipController.m index c2aedd74..c86484d5 100644 --- a/iOS_Tips/DarkMode/SmallVideo/Controller/SLImageClipController.m +++ b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLImageClipController.m @@ -59,17 +59,17 @@ - (void)dealloc { #pragma mark - UI - (void)setupUI { self.zoomView.image = self.image; - self.maxGridRect = CGRectMake(KGridLRMargin, KGridTopMargin, self.view.sl_w - KGridLRMargin * 2, self.view.sl_h - KGridTopMargin - KGridBottomMargin- KBottomMenuHeight); + self.maxGridRect = CGRectMake(KGridLRMargin, KGridTopMargin, self.view.sl_width - KGridLRMargin * 2, self.view.sl_height - KGridTopMargin - KGridBottomMargin- KBottomMenuHeight); - CGSize newSize = CGSizeMake(self.view.sl_w - 2 * KGridLRMargin, (self.view.sl_w - 2 * KGridLRMargin)*self.image.size.height/self.image.size.width); + CGSize newSize = CGSizeMake(self.view.sl_width - 2 * KGridLRMargin, (self.view.sl_width - 2 * KGridLRMargin)*self.image.size.height/self.image.size.width); if (newSize.height > self.maxGridRect.size.height) { newSize = CGSizeMake(self.maxGridRect.size.height*self.image.size.width/self.image.size.height, self.maxGridRect.size.height); self.zoomView.sl_size = newSize; self.zoomView.sl_y = KGridTopMargin; - self.zoomView.sl_centerX = self.view.sl_w/2.0; + self.zoomView.sl_centerX = self.view.sl_width/2.0; }else { self.zoomView.sl_size = newSize; - self.zoomView.center = CGPointMake(self.view.sl_w/2.0, (self.view.sl_h - KBottomMenuHeight)/2.0); + self.zoomView.center = CGPointMake(self.view.sl_width/2.0, (self.view.sl_height - KBottomMenuHeight)/2.0); } [self.view addSubview:self.zoomView]; @@ -88,8 +88,8 @@ - (void)setupUI { #pragma mark - Getter - (SLImageZoomView *)zoomView { if (!_zoomView) { - _zoomView = [[SLImageZoomView alloc] initWithFrame:CGRectMake(KGridLRMargin, KGridTopMargin, self.view.sl_w - KGridLRMargin *2,( self.view.sl_w - KGridLRMargin *2)*self.image.size.height/self.image.size.width)]; - _zoomView.sl_centerY = (self.view.sl_h - KBottomMenuHeight)/2.0; + _zoomView = [[SLImageZoomView alloc] initWithFrame:CGRectMake(KGridLRMargin, KGridTopMargin, self.view.sl_width - KGridLRMargin *2,( self.view.sl_width - KGridLRMargin *2)*self.image.size.height/self.image.size.width)]; + _zoomView.sl_centerY = (self.view.sl_height - KBottomMenuHeight)/2.0; _zoomView.backgroundColor = [UIColor blackColor]; _zoomView.zoomViewDelegate = self; } @@ -104,7 +104,7 @@ - (SLGridView *)gridView { } - (UIButton *)rotateBtn { if (_rotateBtn == nil) { - _rotateBtn = [[UIButton alloc] initWithFrame:CGRectMake(30, self.view.sl_h - KBottomMenuHeight, 40, 30)]; + _rotateBtn = [[UIButton alloc] initWithFrame:CGRectMake(30, self.view.sl_height - KBottomMenuHeight, 40, 30)]; [_rotateBtn setTitle:@"旋转" forState:UIControlStateNormal]; [_rotateBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; _rotateBtn.titleLabel.font = [UIFont boldSystemFontOfSize:16]; @@ -114,7 +114,7 @@ - (UIButton *)rotateBtn { } - (UIButton *)cancleClipBtn { if (_cancleClipBtn == nil) { - _cancleClipBtn = [[UIButton alloc] initWithFrame:CGRectMake(30, self.view.sl_h - 30 - 20, 40, 30)]; + _cancleClipBtn = [[UIButton alloc] initWithFrame:CGRectMake(30, self.view.sl_height - 30 - 20, 40, 30)]; [_cancleClipBtn setImage:[UIImage imageNamed:@"EditImageClipCancel"] forState:UIControlStateNormal]; _cancleClipBtn.titleLabel.font = [UIFont systemFontOfSize:14]; [_cancleClipBtn addTarget:self action:@selector(cancleClipClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -124,7 +124,7 @@ - (UIButton *)cancleClipBtn { - (UIButton *)recoveryBtn { if (_recoveryBtn == nil) { _recoveryBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 40, 30)]; - _recoveryBtn.sl_centerX = self.view.sl_w/2.0; + _recoveryBtn.sl_centerX = self.view.sl_width/2.0; _recoveryBtn.sl_centerY = self.cancleClipBtn.center.y; [_recoveryBtn setTitle:@"还原" forState:UIControlStateNormal]; [_recoveryBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; @@ -135,7 +135,7 @@ - (UIButton *)recoveryBtn { } - (UIButton *)doneClipBtn { if (_doneClipBtn == nil) { - _doneClipBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_w - 30 - 40, self.view.sl_h - 30 - 20, 40, 30)]; + _doneClipBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_width - 30 - 40, self.view.sl_height - 30 - 20, 40, 30)]; [_doneClipBtn setImage:[UIImage imageNamed:@"EditImageClipDone"] forState:UIControlStateNormal]; [_doneClipBtn addTarget:self action:@selector(doneClipClicked:) forControlEvents:UIControlEventTouchUpInside]; } @@ -252,15 +252,15 @@ - (void)rotateBtnClicked:(id)sender { CGFloat width = CGRectGetWidth(self.zoomView.frame); CGFloat height = CGRectGetHeight(self.zoomView.frame); //计算旋转之后 - CGSize newSize = CGSizeMake(self.view.sl_w - 2 * KGridLRMargin, (self.view.sl_w - 2 * KGridLRMargin)*height/width); + CGSize newSize = CGSizeMake(self.view.sl_width - 2 * KGridLRMargin, (self.view.sl_width - 2 * KGridLRMargin)*height/width); if (newSize.height > self.gridView.maxGridRect.size.height) { newSize = CGSizeMake(self.gridView.maxGridRect.size.height*width/height, self.gridView.maxGridRect.size.height); self.zoomView.sl_size = newSize; self.zoomView.sl_y = KGridTopMargin; - self.zoomView.sl_centerX = self.view.sl_w/2.0; + self.zoomView.sl_centerX = self.view.sl_width/2.0; }else { self.zoomView.sl_size = newSize; - self.zoomView.center = CGPointMake(self.view.sl_w/2.0, (self.view.sl_h - KBottomMenuHeight)/2.0); + self.zoomView.center = CGPointMake(self.view.sl_width/2.0, (self.view.sl_height - KBottomMenuHeight)/2.0); } self.gridView.gridRect = self.zoomView.frame; @@ -312,21 +312,21 @@ - (void)gridViewDidEndResizing:(SLGridView *)gridView { delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - CGSize newSize = CGSizeMake(self.view.sl_w - 2 * KGridLRMargin, (self.view.sl_w - 2 * KGridLRMargin)*gridView.gridRect.size.height/gridView.gridRect.size.width); + CGSize newSize = CGSizeMake(self.view.sl_width - 2 * KGridLRMargin, (self.view.sl_width - 2 * KGridLRMargin)*gridView.gridRect.size.height/gridView.gridRect.size.width); if (newSize.height > self.gridView.maxGridRect.size.height) { newSize = CGSizeMake(self.gridView.maxGridRect.size.height*gridView.gridRect.size.width/gridView.gridRect.size.height, self.gridView.maxGridRect.size.height); self.zoomView.sl_size = newSize; self.zoomView.sl_y = KGridTopMargin; - self.zoomView.sl_centerX = self.view.sl_w/2.0; + self.zoomView.sl_centerX = self.view.sl_width/2.0; }else { self.zoomView.sl_size = newSize; - self.zoomView.center = CGPointMake(self.view.sl_w/2.0, (self.view.sl_h - KBottomMenuHeight)/2.0); + self.zoomView.center = CGPointMake(self.view.sl_width/2.0, (self.view.sl_height - KBottomMenuHeight)/2.0); } //重置最小缩放系数 [self resetMinimumZoomScale]; [self.zoomView setZoomScale:self.zoomView.zoomScale]; // 调整contentOffset - CGFloat zoomScale = self.zoomView.sl_w/gridView.gridRect.size.width; + CGFloat zoomScale = self.zoomView.sl_width/gridView.gridRect.size.width; gridView.gridRect = self.zoomView.frame; [self.zoomView setZoomScale:self.zoomView.zoomScale * zoomScale]; self.zoomView.contentOffset = CGPointMake(gridRectOfImage.origin.x*self.zoomView.zoomScale, gridRectOfImage.origin.y*self.zoomView.zoomScale); diff --git a/iOS_Tips/DarkMode/SmallVideo/Controller/SLShotViewController.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLShotViewController.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/Controller/SLShotViewController.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLShotViewController.h diff --git a/iOS_Tips/DarkMode/SmallVideo/Controller/SLShotViewController.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLShotViewController.m similarity index 90% rename from iOS_Tips/DarkMode/SmallVideo/Controller/SLShotViewController.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLShotViewController.m index 36afe862..1581a843 100644 --- a/iOS_Tips/DarkMode/SmallVideo/Controller/SLShotViewController.m +++ b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/Controller/SLShotViewController.m @@ -13,7 +13,7 @@ #import "SLEditVideoController.h" #import "SLEditImageController.h" -#define KMaxDurationOfVideo 15.0 //录制最大时长 s +#define KMaxDurationOfVideo 4.0 //录制最大时长 s @interface SLShotViewController () { @@ -98,6 +98,7 @@ - (SLAvCaptureTool *)avCaptureTool { _avCaptureTool = [[SLAvCaptureTool alloc] init]; _avCaptureTool.preview = self.captureView; _avCaptureTool.delegate = self; + _avCaptureTool.videoSize = CGSizeMake(SL_kScreenWidth*0.8, SL_kScreenHeight*0.8); } return _avCaptureTool; } @@ -119,7 +120,7 @@ - (UIButton *)backBtn { if (_backBtn == nil) { _backBtn = [[UIButton alloc] init]; _backBtn.frame = CGRectMake(0, 0, 30, 30); - _backBtn.center = CGPointMake((self.view.sl_w/2 - 70/2.0)/2.0, self.view.sl_h - 80); + _backBtn.center = CGPointMake((self.view.sl_width/2 - 70/2.0)/2.0, self.view.sl_height - 80); [_backBtn setImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal]; [_backBtn addTarget:self action:@selector(backBtn:) forControlEvents:UIControlEventTouchUpInside]; } @@ -130,9 +131,9 @@ - (UIView *)shotBtn { _shotBtn = [[SLBlurView alloc] init]; _shotBtn.userInteractionEnabled = YES; _shotBtn.frame = CGRectMake(0, 0, 70, 70); - _shotBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); + _shotBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); _shotBtn.clipsToBounds = YES; - _shotBtn.layer.cornerRadius = _shotBtn.sl_w/2.0; + _shotBtn.layer.cornerRadius = _shotBtn.sl_width/2.0; //轻触拍照,长按摄像 UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(takePicture:)]; [_shotBtn addGestureRecognizer:tap]; @@ -141,7 +142,7 @@ - (UIView *)shotBtn { [_shotBtn addGestureRecognizer:longPress]; //中心白色 self.whiteView.frame = CGRectMake(0, 0, 50, 50); - self.whiteView.center = CGPointMake(_shotBtn.sl_w/2.0, _shotBtn.sl_h/2.0); + self.whiteView.center = CGPointMake(_shotBtn.sl_width/2.0, _shotBtn.sl_height/2.0); self.whiteView.layer.cornerRadius = self.whiteView.frame.size.width/2.0; [_shotBtn addSubview:self.whiteView]; } @@ -149,7 +150,7 @@ - (UIView *)shotBtn { } - (UIButton *)switchCameraBtn { if (_switchCameraBtn == nil) { - _switchCameraBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_w - 30 - 30, 44 , 30, 30)]; + _switchCameraBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.sl_width - 30 - 30, 44 , 30, 30)]; [_switchCameraBtn setImage:[UIImage imageNamed:@"cameraAround"] forState:UIControlStateNormal]; [_switchCameraBtn addTarget:self action:@selector(switchCameraClicked:) forControlEvents:UIControlEventTouchUpInside]; } @@ -190,7 +191,7 @@ - (SLShotFocusView *)focusView { } - (UILabel *)tipsLabel { if (_tipsLabel == nil) { - _tipsLabel = [[UILabel alloc] initWithFrame:CGRectMake((self.view.sl_w - 140)/2.0, self.shotBtn.sl_y - 20 - 30, 140, 20)]; + _tipsLabel = [[UILabel alloc] initWithFrame:CGRectMake((self.view.sl_width - 140)/2.0, self.shotBtn.sl_y - 20 - 30, 140, 20)]; _tipsLabel.textColor = [UIColor whiteColor]; _tipsLabel.font = [UIFont systemFontOfSize:14]; _tipsLabel.textAlignment = NSTextAlignmentCenter; @@ -237,17 +238,16 @@ - (void)startTimer{ NSLog(@"时长 %f", self->_durationOfVideo); SL_DISPATCH_ON_MAIN_THREAD(^{ self.progressLayer.strokeEnd = 1; + //暂停定时器 + // dispatch_suspend(_gcdTimer); + //取消计时器 + dispatch_source_cancel(self->_gcdTimer); + self->_durationOfVideo = 0; + [self.progressLayer removeFromSuperlayer]; + //停止录制 + [self.avCaptureTool stopRecordVideo]; + [self.avCaptureTool stopRunning]; }); - - //暂停定时器 - // dispatch_suspend(_gcdTimer); - //取消计时器 - dispatch_source_cancel(self->_gcdTimer); - self->_durationOfVideo = 0; - [self.progressLayer removeFromSuperlayer]; - //停止录制 - [self.avCaptureTool stopRecordVideo]; - [self.avCaptureTool stopRunning]; } }); // 启动任务,GCD计时器创建后需要手动启动 @@ -266,7 +266,7 @@ - (void)tapFocusing:(UITapGestureRecognizer *)tap { return; } CGPoint point = [tap locationInView:self.captureView]; - if(point.y > self.shotBtn.sl_y || point.y < self.switchCameraBtn.sl_y + self.switchCameraBtn.sl_h) { + if(point.y > self.shotBtn.sl_y || point.y < self.switchCameraBtn.sl_y + self.switchCameraBtn.sl_height) { return; } [self focusAtPoint:point]; @@ -313,11 +313,11 @@ - (void)recordVideo:(UILongPressGestureRecognizer *)longPress { switch (longPress.state) { case UIGestureRecognizerStateBegan:{ self.shotBtn.sl_size = CGSizeMake(100, 100); - self.shotBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); - self.shotBtn.layer.cornerRadius = self.shotBtn.sl_h/2.0; + self.shotBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); + self.shotBtn.layer.cornerRadius = self.shotBtn.sl_height/2.0; self.whiteView.sl_size = CGSizeMake(40, 40); - self.whiteView.center = CGPointMake(self.shotBtn.sl_w/2.0, self.shotBtn.sl_h/2.0); - self.whiteView.layer.cornerRadius = self.whiteView.sl_w/2.0; + self.whiteView.center = CGPointMake(self.shotBtn.sl_width/2.0, self.shotBtn.sl_height/2.0); + self.whiteView.layer.cornerRadius = self.whiteView.sl_width/2.0; //开始计时 [self startTimer]; //添加进度条 @@ -335,11 +335,11 @@ - (void)recordVideo:(UILongPressGestureRecognizer *)longPress { break; case UIGestureRecognizerStateEnded:{ self.shotBtn.sl_size = CGSizeMake(70, 70); - self.shotBtn.center = CGPointMake(self.view.sl_w/2.0, self.view.sl_h - 80); - self.shotBtn.layer.cornerRadius = self.shotBtn.sl_h/2.0; + self.shotBtn.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height - 80); + self.shotBtn.layer.cornerRadius = self.shotBtn.sl_height/2.0; self.whiteView.sl_size = CGSizeMake(50, 50); - self.whiteView.center = CGPointMake(self.shotBtn.sl_w/2.0, self.shotBtn.sl_h/2.0); - self.whiteView.layer.cornerRadius = self.whiteView.sl_w/2.0; + self.whiteView.center = CGPointMake(self.shotBtn.sl_width/2.0, self.shotBtn.sl_height/2.0); + self.whiteView.layer.cornerRadius = self.whiteView.sl_width/2.0; //取消计时器 dispatch_source_cancel(self->_gcdTimer); self->_durationOfVideo = 0; @@ -391,16 +391,20 @@ - (void)captureTool:(SLAvCaptureTool *)captureTool didOutputPhoto:(UIImage *)ima } //音视频输出完成 - (void)captureTool:(SLAvCaptureTool *)captureTool didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL error:(NSError *)error { -// self.videoPath = outputFileURL; + [self.avCaptureTool stopRunning]; NSLog(@"结束录制"); SLEditVideoController * editViewController = [[SLEditVideoController alloc] init]; editViewController.videoPath = outputFileURL; editViewController.modalPresentationStyle = UIModalPresentationFullScreen; - [self presentViewController:editViewController animated:NO completion:nil]; + [self presentViewController:editViewController animated:NO completion:^{ + NSString *result = error ? @"录制失败" : @"录制成功"; + NSLog(@"%@ %@", result , error.localizedDescription); + [SLAlertView showAlertViewWithText:result delayHid:1]; + }]; NSInteger fileSize = (NSInteger)[[NSFileManager defaultManager] attributesOfItemAtPath:outputFileURL.path error:nil].fileSize; - NSLog(@"视频文件大小 === %fM",fileSize/(1024.0*1024.0)); + NSLog(@"视频文件大小 === %.2fM",fileSize/(1024.0*1024.0)); } diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLDrawView.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLDrawView.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLDrawView.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLDrawView.h diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLDrawView.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLDrawView.m similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLDrawView.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLDrawView.m diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLEditMenuView.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditMenuView.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLEditMenuView.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditMenuView.h diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLEditMenuView.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditMenuView.m similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLEditMenuView.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditMenuView.m diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLEditSelectedBox.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditSelectedBox.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLEditSelectedBox.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditSelectedBox.h diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLEditSelectedBox.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditSelectedBox.m similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLEditSelectedBox.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditSelectedBox.m diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLEditTextView.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditTextView.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLEditTextView.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditTextView.h diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLEditTextView.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditTextView.m similarity index 91% rename from iOS_Tips/DarkMode/SmallVideo/View/SLEditTextView.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditTextView.m index 3671b7e0..70b288ff 100644 --- a/iOS_Tips/DarkMode/SmallVideo/View/SLEditTextView.m +++ b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditTextView.m @@ -66,21 +66,22 @@ - (void)setupUI { [self addSubview:self.doneEditBtn]; //监听键盘frame改变 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; + //添加键盘消失监听事件 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; } //颜色选择菜单视图 - (void)colorSelectionView:(CGFloat)keyboardHeight { for (UIView *subView in self.subviews) { if (subView != self.doneEditBtn || subView != self.cancleEditBtn || subView != self.textView) { continue; - }else { - [subView removeFromSuperview]; } + [subView removeFromSuperview]; } int count = (int)_colors.count + 1; CGSize itemSize = CGSizeMake(20, 20); CGFloat space = (self.frame.size.width - count * itemSize.width)/(count + 1); for (int i = 0; i < count; i++) { - UIButton * colorBtn = [[UIButton alloc] initWithFrame:CGRectMake(space + (itemSize.width + space)*i, self.sl_h - keyboardHeight - 20 - 20, itemSize.width, itemSize.height)]; + UIButton * colorBtn = [[UIButton alloc] initWithFrame:CGRectMake(space + (itemSize.width + space)*i, self.sl_height - keyboardHeight - 20 - 20, itemSize.width, itemSize.height)]; [self addSubview:colorBtn]; if (i == 0) { [colorBtn addTarget:self action:@selector(colorSwitchBtnClicked:) forControlEvents:UIControlEventTouchUpInside]; @@ -116,7 +117,7 @@ - (UIButton *)cancleEditBtn { } - (UIButton *)doneEditBtn { if (_doneEditBtn == nil) { - _doneEditBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.sl_w - 50 - 15, 30, 40, 30)]; + _doneEditBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.sl_width - 50 - 15, 30, 40, 30)]; _doneEditBtn.backgroundColor = [UIColor colorWithRed:45/255.0 green:175/255.0 blue:45/255.0 alpha:1]; [_doneEditBtn setTitle:@"完成" forState:UIControlStateNormal]; [_doneEditBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; @@ -209,18 +210,26 @@ - (void)keyboardWillShow:(NSNotification *)notification { _keyboardHeight = keyboardRect.size.height; [self colorSelectionView:_keyboardHeight]; } +//键盘即将消失 +- (void)keyboardWillHide:(NSNotification *)notification{ + [self.textView resignFirstResponder]; + if (self.editTextCompleted) { + self.editTextCompleted(nil); + } + [self removeFromSuperview]; +} #pragma mark - UITextViewDelegate -(void)textViewDidChange:(UITextView *)textView{ //最大高度 - CGFloat maxHeight = self.sl_h - 100 - _keyboardHeight - 20 - 20 - 20; - CGSize constraintSize = CGSizeMake(self.sl_w - 2*30, MAXFLOAT); + CGFloat maxHeight = self.sl_height - 100 - _keyboardHeight - 20 - 20 - 20; + CGSize constraintSize = CGSizeMake(self.sl_width - 2*30, MAXFLOAT); CGSize size = [textView sizeThatFits:constraintSize]; if (size.height >= maxHeight) { textView.sl_y = 100 - (size.height - maxHeight); } else { textView.sl_y = 100; } - textView.sl_h = size.height; - textView.sl_w = size.width; + textView.sl_height = size.height; + textView.sl_width = size.width; } @end diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLEditVideoClipping.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditVideoClipping.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLEditVideoClipping.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditVideoClipping.h diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLEditVideoClipping.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditVideoClipping.m similarity index 89% rename from iOS_Tips/DarkMode/SmallVideo/View/SLEditVideoClipping.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditVideoClipping.m index bdaf7f25..61cdf72c 100644 --- a/iOS_Tips/DarkMode/SmallVideo/View/SLEditVideoClipping.m +++ b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLEditVideoClipping.m @@ -77,14 +77,14 @@ -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event { } - (void)setFrame:(CGRect)frame{ [super setFrame:frame]; - self.boxInside.frame = CGRectMake(0, 0, self.sl_w, self.sl_h); - self.leftTime.frame = CGRectMake(-10, 0, 10, self.sl_h); - self.rightTime.frame = CGRectMake(self.sl_w, 0, 10, self.sl_h); + self.boxInside.frame = CGRectMake(0, 0, self.sl_width, self.sl_height); + self.leftTime.frame = CGRectMake(-10, 0, 10, self.sl_height); + self.rightTime.frame = CGRectMake(self.sl_width, 0, 10, self.sl_height); } #pragma mark - Getter - (UIView *)boxInside { if (!_boxInside) { - _boxInside = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.sl_w, self.sl_h)]; + _boxInside = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.sl_width, self.sl_height)]; _boxInside.backgroundColor = [UIColor clearColor]; _boxInside.layer.borderColor = [UIColor whiteColor].CGColor; _boxInside.layer.borderWidth = 2; @@ -93,7 +93,7 @@ - (UIView *)boxInside { } - (UILabel *)leftTime { if (!_leftTime) { - _leftTime = [[UILabel alloc] initWithFrame:CGRectMake(-10, 0, 10, self.sl_h)]; + _leftTime = [[UILabel alloc] initWithFrame:CGRectMake(-10, 0, 10, self.sl_height)]; _leftTime.text = @"||"; _leftTime.textAlignment = NSTextAlignmentCenter; _leftTime.backgroundColor = [UIColor whiteColor]; @@ -108,7 +108,7 @@ - (UILabel *)leftTime { } - (UILabel *)rightTime { if (!_rightTime) { - _rightTime = [[UILabel alloc] initWithFrame:CGRectMake(self.sl_w, 0, 10, self.sl_h)]; + _rightTime = [[UILabel alloc] initWithFrame:CGRectMake(self.sl_width, 0, 10, self.sl_height)]; _rightTime.text = @"||"; _rightTime.tag = 1; _rightTime.textAlignment = NSTextAlignmentCenter; @@ -126,26 +126,26 @@ - (UILabel *)rightTime { - (void)changeClippingRange:(UIPanGestureRecognizer *)pan { CGPoint transP = [pan translationInView:self]; if (pan.state == UIGestureRecognizerStateBegan) { - self.changeClippingRange(SLVideoClippingStateMake(pan.view.tag, (pan.view.tag == 0 ? self.boxInside.sl_x/self.sl_w : self.rightTime.sl_x/self.sl_w), UIGestureRecognizerStateBegan)); + self.changeClippingRange(SLVideoClippingStateMake(pan.view.tag, (pan.view.tag == 0 ? self.boxInside.sl_x/self.sl_width : self.rightTime.sl_x/self.sl_width), UIGestureRecognizerStateBegan)); } else if (pan.state == UIGestureRecognizerStateChanged ) { if (pan.view == self.leftTime ) { //裁剪的视频时长必须>=1秒 - if (pan.view.sl_x + transP.x < -10 || (self.rightTime.sl_x - self.boxInside.sl_x - transP.x)/self.sl_w*self.totalDuration < 1) { + if (pan.view.sl_x + transP.x < -10 || (self.rightTime.sl_x - self.boxInside.sl_x - transP.x)/self.sl_width*self.totalDuration < 1) { NSLog(@"视频超出了裁剪范围或裁剪后的时长小于1s"); }else { self.boxInside.sl_x = self.boxInside.sl_x + transP.x; - self.boxInside.sl_w = self.boxInside.sl_w - transP.x; + self.boxInside.sl_width = self.boxInside.sl_width - transP.x; pan.view.center = CGPointMake(pan.view.center.x + transP.x, pan.view.center.y); } }else if (pan.view == self.rightTime) { - if (pan.view.sl_x + transP.x > self.sl_w || (self.rightTime.sl_x + transP.x - self.boxInside.sl_x)/self.sl_w*self.totalDuration < 1) { + if (pan.view.sl_x + transP.x > self.sl_width || (self.rightTime.sl_x + transP.x - self.boxInside.sl_x)/self.sl_width*self.totalDuration < 1) { NSLog(@"视频超出了裁剪范围或裁剪后的时长小于1s"); }else { - self.boxInside.sl_w = self.boxInside.sl_w + transP.x; + self.boxInside.sl_width = self.boxInside.sl_width + transP.x; pan.view.center = CGPointMake(pan.view.center.x + transP.x, pan.view.center.y); } } - self.changeClippingRange(SLVideoClippingStateMake(pan.view.tag, (pan.view.tag == 0 ? self.boxInside.sl_x/self.sl_w : self.rightTime.sl_x/self.sl_w), UIGestureRecognizerStateChanged)); + self.changeClippingRange(SLVideoClippingStateMake(pan.view.tag, (pan.view.tag == 0 ? self.boxInside.sl_x/self.sl_width : self.rightTime.sl_x/self.sl_width), UIGestureRecognizerStateChanged)); [self setNeedsDisplay]; [pan setTranslation:CGPointZero inView:self]; }else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateFailed || pan.state == UIGestureRecognizerStateCancelled) { @@ -154,11 +154,11 @@ - (void)changeClippingRange:(UIPanGestureRecognizer *)pan { pan.view.sl_x = -10; } }else if (pan.view == self.rightTime) { - if (pan.view.sl_x > self.sl_w) { - pan.view.sl_x = self.sl_w; + if (pan.view.sl_x > self.sl_width) { + pan.view.sl_x = self.sl_width; } } - self.changeClippingRange(SLVideoClippingStateMake(pan.view.tag, (pan.view.tag == 0 ? self.boxInside.sl_x/self.sl_w : self.rightTime.sl_x/self.sl_w), UIGestureRecognizerStateEnded)); + self.changeClippingRange(SLVideoClippingStateMake(pan.view.tag, (pan.view.tag == 0 ? self.boxInside.sl_x/self.sl_width : self.rightTime.sl_x/self.sl_width), UIGestureRecognizerStateEnded)); } } @end @@ -209,7 +209,7 @@ - (void)setAsset:(AVAsset *)asset { #pragma mark - Getter - (UIView *)contentView { if (!_contentView) { - _contentView = [[UIView alloc] initWithFrame:CGRectMake(40, 10, self.sl_w - 40 * 2, 50)]; + _contentView = [[UIView alloc] initWithFrame:CGRectMake(40, 10, self.sl_width - 40 * 2, 50)]; _contentView.backgroundColor = [UIColor blackColor]; _contentView.layer.borderColor = [UIColor lightGrayColor].CGColor; _contentView.clipsToBounds = YES; @@ -253,7 +253,7 @@ - (UIView *)clippingBox { } - (UIButton *)cancleBtn { if (_cancleBtn == nil) { - _cancleBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, self.sl_h - 10 - 30, 30, 30)]; + _cancleBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, self.sl_height - 10 - 30, 30, 30)]; [_cancleBtn setTitle:@"取消" forState:UIControlStateNormal]; [_cancleBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; _cancleBtn.titleLabel.font = [UIFont systemFontOfSize:14]; @@ -263,7 +263,7 @@ - (UIButton *)cancleBtn { } - (UIButton *)doneBtn { if (_doneBtn == nil) { - _doneBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.sl_w - 30 - 10, self.sl_h - 10 - 30, 30, 30)]; + _doneBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.sl_width - 30 - 10, self.sl_height - 10 - 30, 30, 30)]; [_doneBtn setTitle:@"完成" forState:UIControlStateNormal]; [_doneBtn setTitleColor:[UIColor colorWithRed:45/255.0 green:175/255.0 blue:45/255.0 alpha:1] forState:UIControlStateNormal]; _doneBtn.titleLabel.font = [UIFont systemFontOfSize:14]; @@ -300,12 +300,12 @@ - (void)generateImage { } if (maxImageCount * maximumSize.width/[UIScreen mainScreen].scale < self.contentView.frame.size.width) { maxImageCount = self.contentView.frame.size.width * [UIScreen mainScreen].scale / maximumSize.width; - self.contentView.sl_w = maxImageCount * maximumSize.width/[UIScreen mainScreen].scale; - self.contentView.sl_x = (self.sl_w - self.contentView.sl_w)/2.0; + self.contentView.sl_width = maxImageCount * maximumSize.width/[UIScreen mainScreen].scale; + self.contentView.sl_x = (self.sl_width - self.contentView.sl_width)/2.0; self.clippingBox.frame = self.contentView.frame; - imageViewSize = CGSizeMake(self.contentView.sl_h*maximumSize.width/maximumSize.height, self.contentView.sl_h); + imageViewSize = CGSizeMake(self.contentView.sl_height*maximumSize.width/maximumSize.height, self.contentView.sl_height); }else { - imageViewSize = CGSizeMake(self.contentView.sl_w/maxImageCount, self.contentView.sl_h); + imageViewSize = CGSizeMake(self.contentView.sl_width/maxImageCount, self.contentView.sl_height); } //视频帧大小 像素 diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLGridView.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLGridView.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLGridView.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLGridView.h diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLGridView.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLGridView.m similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLGridView.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLGridView.m diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLImageZoomView.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLImageZoomView.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLImageZoomView.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLImageZoomView.h diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLImageZoomView.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLImageZoomView.m similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLImageZoomView.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLImageZoomView.m diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLMosaicView.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLMosaicView.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLMosaicView.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLMosaicView.h diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLMosaicView.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLMosaicView.m similarity index 98% rename from iOS_Tips/DarkMode/SmallVideo/View/SLMosaicView.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLMosaicView.m index 94fec5c5..770323ca 100644 --- a/iOS_Tips/DarkMode/SmallVideo/View/SLMosaicView.m +++ b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLMosaicView.m @@ -68,7 +68,7 @@ - (void)drawInContext:(CGContextRef)context { if (image) { /** 创建颜色图片 */ CGColorSpaceRef colorRef = CGColorSpaceCreateDeviceRGB(); - CGContextRef contextRef = CGBitmapContextCreate(nil, image.size.width, image.size.height, 8, image.size.width*4, colorRef, kCGImageAlphaPremultipliedFirst); + CGContextRef contextRef = CGBitmapContextCreate(nil, image.size.width, image.size.height, 8, 0, colorRef, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height); CGContextClipToMask(contextRef, imageRect, image.CGImage); @@ -207,7 +207,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { } else if (self.mosaicType == SLMosaicTypePaintbrush) { SLMosaicPointElement *blur = [ SLMosaicPointElement new]; blur.rect = CGRectMake(point.x-self.paintSize.width/2, point.y-self.paintSize.height/2, self.paintSize.width, self.paintSize.height); - blur.imageName = @"EditMosaicBrush.png"; + blur.imageName = @"EditMosaicBrush"; blur.color = self.brushColor ? self.brushColor(blur.rect.origin) : nil; SLMosaicLineLayer *layer = [SLMosaicLineLayer layer]; layer.frame = self.bounds; @@ -257,7 +257,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { //2、创建LFSplashBlur SLMosaicPointElement *blur = [SLMosaicPointElement new]; - blur.imageName = @"EditMosaicBrush.png"; + blur.imageName = @"EditMosaicBrush"; blur.color = self.brushColor ? self.brushColor(point) : nil; /** 新增随机位置 */ int x = self.paintSize.width + MIN(1, (int)(self.paintSize.width*0.4)); diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLShotFocusView.h b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLShotFocusView.h similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLShotFocusView.h rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLShotFocusView.h diff --git a/iOS_Tips/DarkMode/SmallVideo/View/SLShotFocusView.m b/iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLShotFocusView.m similarity index 100% rename from iOS_Tips/DarkMode/SmallVideo/View/SLShotFocusView.m rename to iOS_Tips/DarkMode/AVFoundation/SmallVideo/View/SLShotFocusView.m diff --git a/iOS_Tips/DarkMode/AppDelegate.m b/iOS_Tips/DarkMode/AppDelegate.m index 9796a3cb..f6470d77 100644 --- a/iOS_Tips/DarkMode/AppDelegate.m +++ b/iOS_Tips/DarkMode/AppDelegate.m @@ -8,6 +8,7 @@ #import "AppDelegate.h" + @interface AppDelegate () @end @@ -16,10 +17,31 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. + + SLSetUncaughtExceptionHandler(); return YES; } +///系统异常捕获处理 +void HandleException(NSException *exception) { + // 异常的堆栈信息 + NSArray *stackArray = [exception callStackSymbols]; + // 出现异常的原因 + NSString *reason = [exception reason]; + // 异常名称 + NSString *name = [exception name]; + NSString *exceptionInfo = [NSString stringWithFormat:@"程序异常:%@ \nException reason:%@ \nException stack:%@",name, reason, stackArray]; + NSLog(@"%@", exceptionInfo); +} +//设置异常捕获处理方法 +void SLSetUncaughtExceptionHandler(void) { + NSSetUncaughtExceptionHandler(&HandleException); +} +//内存警告 +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { + // free_some_mem(1024*1024*10); + NSLog(@"内存警告"); +} #pragma mark - UISceneSession lifecycle @@ -40,7 +62,7 @@ - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet< // Use this method to release any resources that were specific to the discarded scenes, as they will not return. NSLog(@"didDiscardSceneSessions "); - + } diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/120-1.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/120-1.png new file mode 100644 index 00000000..ca22535d Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/120-1.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/120.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 00000000..ca22535d Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/180.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 00000000..6327000a Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/20.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 00000000..46f762b3 Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/29.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 00000000..8aabab5a Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/40-1.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/40-1.png new file mode 100644 index 00000000..586d20f5 Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/40-1.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/40.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 00000000..586d20f5 Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/58-1.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/58-1.png new file mode 100644 index 00000000..c9ea4568 Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/58-1.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/58.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 00000000..c9ea4568 Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/60.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 00000000..5c18c4ea Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/80-1.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/80-1.png new file mode 100644 index 00000000..7e4bc746 Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/80-1.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/80.png b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 00000000..0d8ad8dc Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/Contents.json index d8db8d65..4d575c16 100644 --- a/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/iOS_Tips/DarkMode/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,98 +1,110 @@ { "images" : [ { + "filename" : "40.png", "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { + "filename" : "60.png", "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" + "scale" : "3x", + "size" : "20x20" }, { + "filename" : "58.png", "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { + "filename" : "80-1.png", "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" + "scale" : "3x", + "size" : "29x29" }, { + "filename" : "80.png", "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" + "scale" : "2x", + "size" : "40x40" }, { + "filename" : "120-1.png", "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" + "scale" : "3x", + "size" : "40x40" }, { + "filename" : "120.png", "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" + "scale" : "2x", + "size" : "60x60" }, { + "filename" : "180.png", "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" + "scale" : "3x", + "size" : "60x60" }, { + "filename" : "20.png", "idiom" : "ipad", - "size" : "20x20", - "scale" : "1x" + "scale" : "1x", + "size" : "20x20" }, { + "filename" : "40-1.png", "idiom" : "ipad", - "size" : "20x20", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { + "filename" : "29.png", "idiom" : "ipad", - "size" : "29x29", - "scale" : "1x" + "scale" : "1x", + "size" : "29x29" }, { + "filename" : "58-1.png", "idiom" : "ipad", - "size" : "29x29", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { "idiom" : "ipad", - "size" : "40x40", - "scale" : "1x" + "scale" : "1x", + "size" : "40x40" }, { "idiom" : "ipad", - "size" : "40x40", - "scale" : "2x" + "scale" : "2x", + "size" : "40x40" }, { "idiom" : "ipad", - "size" : "76x76", - "scale" : "1x" + "scale" : "1x", + "size" : "76x76" }, { "idiom" : "ipad", - "size" : "76x76", - "scale" : "2x" + "scale" : "2x", + "size" : "76x76" }, { "idiom" : "ipad", - "size" : "83.5x83.5", - "scale" : "2x" + "scale" : "2x", + "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", - "size" : "1024x1024", - "scale" : "1x" + "scale" : "1x", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/iOS_Tips/DarkMode/Assets.xcassets/nav_return_white.imageset/Contents.json b/iOS_Tips/DarkMode/Assets.xcassets/nav_return_white.imageset/Contents.json new file mode 100644 index 00000000..87b7a55a --- /dev/null +++ b/iOS_Tips/DarkMode/Assets.xcassets/nav_return_white.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "nav_return_white@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "nav_return_white@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS_Tips/DarkMode/Assets.xcassets/nav_return_white.imageset/nav_return_white@2x.png b/iOS_Tips/DarkMode/Assets.xcassets/nav_return_white.imageset/nav_return_white@2x.png new file mode 100644 index 00000000..c949317d Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/nav_return_white.imageset/nav_return_white@2x.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/nav_return_white.imageset/nav_return_white@3x.png b/iOS_Tips/DarkMode/Assets.xcassets/nav_return_white.imageset/nav_return_white@3x.png new file mode 100644 index 00000000..8ef689b3 Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/nav_return_white.imageset/nav_return_white@3x.png differ diff --git a/iOS_Tips/DarkMode/Assets.xcassets/play.imageset/Contents.json b/iOS_Tips/DarkMode/Assets.xcassets/play.imageset/Contents.json new file mode 100644 index 00000000..222061f5 --- /dev/null +++ b/iOS_Tips/DarkMode/Assets.xcassets/play.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "play.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS_Tips/DarkMode/Assets.xcassets/play.imageset/play.png b/iOS_Tips/DarkMode/Assets.xcassets/play.imageset/play.png new file mode 100644 index 00000000..287cd637 Binary files /dev/null and b/iOS_Tips/DarkMode/Assets.xcassets/play.imageset/play.png differ diff --git a/iOS_Tips/DarkMode/Base.lproj/LaunchScreen.storyboard b/iOS_Tips/DarkMode/Base.lproj/LaunchScreen.storyboard index a7c98bc2..36cf6dc9 100644 --- a/iOS_Tips/DarkMode/Base.lproj/LaunchScreen.storyboard +++ b/iOS_Tips/DarkMode/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + @@ -20,7 +20,7 @@ - + + - + + + - + + + + diff --git a/iOS_Tips/DarkMode/Base.lproj/Main.storyboard b/iOS_Tips/DarkMode/Base.lproj/Main.storyboard index 0bfd9a89..b9933e47 100644 --- a/iOS_Tips/DarkMode/Base.lproj/Main.storyboard +++ b/iOS_Tips/DarkMode/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -75,7 +75,7 @@ - + diff --git a/iOS_Tips/DarkMode/SmallVideo/Controller/.DS_Store b/iOS_Tips/DarkMode/Crash/.DS_Store similarity index 91% rename from iOS_Tips/DarkMode/SmallVideo/Controller/.DS_Store rename to iOS_Tips/DarkMode/Crash/.DS_Store index 5008ddfc..e3235eeb 100644 Binary files a/iOS_Tips/DarkMode/SmallVideo/Controller/.DS_Store and b/iOS_Tips/DarkMode/Crash/.DS_Store differ diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSArray+SLCrashProtector.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSArray+SLCrashProtector.h new file mode 100644 index 00000000..70ad548c --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSArray+SLCrashProtector.h @@ -0,0 +1,18 @@ +// +// NSArray+SLCrashProtector.h +// DarkMode +// +// Created by wsl on 2020/4/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 不可变数组 越界、nil值 Crash 防护 +@interface NSArray (SLCrashProtector) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSArray+SLCrashProtector.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSArray+SLCrashProtector.m new file mode 100644 index 00000000..1fc0b6b2 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSArray+SLCrashProtector.m @@ -0,0 +1,95 @@ +// +// NSArray+SLCrashProtector.m +// DarkMode +// +// Created by wsl on 2020/4/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "NSArray+SLCrashProtector.h" +#import "SLCrashProtector.h" + + +@implementation NSArray (SLCrashProtector) + ++ (void)load { + // 不可变数组 + // 越界保护 + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:), NSClassFromString(@"__NSArrayI"), @selector(sl_objectAtIndex:)); + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndexedSubscript:), NSClassFromString(@"__NSArrayI"), @selector(sl_objectAtIndexedSubscript:)); + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSSingleObjectArrayI"), @selector(objectAtIndex:), NSClassFromString(@"__NSSingleObjectArrayI"), @selector(sl_singleObjectAtIndex:)); + // nil值保护 + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSPlaceholderArray"), @selector(initWithObjects:count:), NSClassFromString(@"__NSPlaceholderArray"), @selector(sl_initWithObjects:count:)); + }); +} + +#pragma mark - Array Safe Methods +//[array objectAtIndex:0] 越界 +- (id)sl_objectAtIndex:(NSInteger)index { + if (index >= self.count || !self.count) { + //可能抛出异常的代码 + @try { + return [self sl_objectAtIndex:index]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeArray errorDesc:[NSString stringWithFormat:@"异常:数组越界 %@",exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + return self.lastObject; + } + }else { + return [self sl_objectAtIndex:index]; + } +} +// 越界 +- (id)sl_singleObjectAtIndex:(NSInteger)index { + if (index >= self.count || !self.count) { + //可能抛出异常的代码 + @try { + return [self sl_singleObjectAtIndex:index]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeArray errorDesc:[NSString stringWithFormat:@"异常:数组越界 %@",exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + return self.lastObject; + } + }else { + return [self sl_singleObjectAtIndex:index]; + } +} +//array[0] 越界 +- (id)sl_objectAtIndexedSubscript:(NSInteger)index { + if (index >= self.count || !self.count) { + //记录错误 + //NSString *errorInfo = [NSString stringWithFormat:@"*** -[__NSArrayI objectAtIndexedSubscript:]: index %ld beyond bounds [0 .. %ld]'",(unsigned long)index,(unsigned long)self.count]; + @try { + return [self sl_objectAtIndexedSubscript:index]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeArray errorDesc:[NSString stringWithFormat:@"异常:数组越界 %@",exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + return self.lastObject; + } + } + return [self sl_objectAtIndexedSubscript:index]; +} +// nil值 +- (id)sl_initWithObjects:(id _Nonnull const [])objects count:(NSUInteger)cnt{ + NSUInteger index = 0; + id _Nonnull objectsNew[cnt]; + for (int i = 0; i + +NS_ASSUME_NONNULL_BEGIN + +// 不可变字典 越界、nil值 Crash防护 +@interface NSDictionary (SLCrashProtector) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSDictionary+SLCrashProtector.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSDictionary+SLCrashProtector.m new file mode 100644 index 00000000..54c66476 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSDictionary+SLCrashProtector.m @@ -0,0 +1,71 @@ +// +// NSDictionary+SLCrash.m +// DarkMode +// +// Created by wsl on 2020/4/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "NSDictionary+SLCrashProtector.h" +#import "SLCrashProtector.h" + +@implementation NSDictionary (SLCrashProtector) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class dictionaryClass = NSClassFromString(@"NSDictionary"); + SL_ExchangeInstanceMethod(dictionaryClass, @selector(initWithObjects:forKeys:), dictionaryClass, @selector(sl_initWithObjects:forKeys:)); + + Class __NSPlaceholderDictionaryClass = NSClassFromString(@"__NSPlaceholderDictionary"); + SL_ExchangeInstanceMethod(__NSPlaceholderDictionaryClass, @selector(initWithObjects:forKeys:count:), __NSPlaceholderDictionaryClass, @selector(sl_initWithObjects:forKeys:count:)); + }); +} + +#pragma mark - Dictionary Safe Methods +//nil值 +- (id)sl_initWithObjects:(NSArray *)objects forKeys:(NSArray> *)keys { + if (objects.count != keys.count) { + NSString *errorInfo = [NSString stringWithFormat:@"异常:字典key/value个数不匹配 *** -[NSDictionary initWithObjects:forKeys:]: count of objects (%ld) differs from count of keys (%ld)",(unsigned long)objects.count,(unsigned long)keys.count]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeDictionary errorDesc:errorInfo exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + + return nil;//huicha + } + NSUInteger index = 0; + id _Nonnull objectsNew[objects.count]; + id _Nonnull keysNew[keys.count]; + for (int i = 0; i _Nonnull const [])keys count:(NSUInteger)cnt{ + NSUInteger index = 0; + id _Nonnull objectsNew[cnt]; + id _Nonnull keysNew[cnt]; + //'*** -[NSDictionary initWithObjects:forKeys:]: count of objects (1) differs from count of keys (0)' + for (int i = 0; i + +NS_ASSUME_NONNULL_BEGIN + +/// 可变数组 越界、nil值 Crash防护 +@interface NSMutableArray (SLCrashProtector) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSMutableArray+SLCrashProtector.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSMutableArray+SLCrashProtector.m new file mode 100644 index 00000000..62b806e5 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSMutableArray+SLCrashProtector.m @@ -0,0 +1,159 @@ +// +// NSMutableArray+Crash.m +// DarkMode +// +// Created by wsl on 2020/4/12. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "NSMutableArray+SLCrashProtector.h" +#import "SLCrashProtector.h" + +@implementation NSMutableArray (SLCrashProtector) + ++ (void)load { + // 可变数组 + // nil值、越界保护 + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(objectAtIndex:), NSClassFromString(@"__NSArrayM"), @selector(sl_mObjectAtIndex:)); + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(objectAtIndexedSubscript:), NSClassFromString(@"__NSArrayM"), @selector(sl_mObjectAtIndexedSubscript:)); + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(insertObject:atIndex:), NSClassFromString(@"__NSArrayM"), @selector(sl_insertObject:atIndex:)); + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(insertObjects:atIndexes:), NSClassFromString(@"__NSArrayM"), @selector(sl_insertObjects:atIndexes:)); + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(replaceObjectAtIndex:withObject:), NSClassFromString(@"__NSArrayM"), @selector(sl_replaceObjectAtIndex:withObject:)); + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(replaceObjectsInRange:withObjectsFromArray:), NSClassFromString(@"__NSArrayM"), @selector(sl_replaceObjectsInRange:withObjectsFromArray:)); + SL_ExchangeInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(removeObjectsInRange:), NSClassFromString(@"__NSArrayM"), @selector(sl_removeObjectsInRange:)); + }); +} + +#pragma mark - MutableArray Safe Methods +//越界 +- (id)sl_mObjectAtIndex:(NSInteger)index { + if (index >= self.count || !self.count) { + @try { + return [self sl_mObjectAtIndex:index]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMArray errorDesc:[NSString stringWithFormat:@"异常:数组越界 %@",exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + return self.lastObject; + } + }else { + return [self sl_mObjectAtIndex:index]; + } +} +//越界 +- (id)sl_mObjectAtIndexedSubscript:(NSInteger)index { + if (index >= self.count || !self.count) { + //记录错误 + //NSString *errorInfo = [NSString stringWithFormat:@"*** -[__NSArrayI objectAtIndexedSubscript:]: index %ld beyond bounds [0 .. %ld]'",(unsigned long)index,(unsigned long)self.count]; + @try { + return [self sl_mObjectAtIndexedSubscript:index]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMArray errorDesc:[NSString stringWithFormat:@"异常:数组越界 %@",exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + return self.lastObject; + } + } + return [self sl_mObjectAtIndexedSubscript:index]; +} +//越界 +- (void)sl_removeObjectsInRange:(NSRange)range { + if (range.location+range.length>self.count) { + NSString *errorInfo = [NSString stringWithFormat:@"异常:数组越界 *** -[__NSArrayM removeObjectsInRange:]: range {%ld, %ld} extends beyond bounds [0 .. %ld]",(unsigned long)range.location,(unsigned long)range.length,(unsigned long)self.count]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMArray errorDesc:errorInfo exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + return; + } + [self sl_removeObjectsInRange:range]; +} +//越界 nil值 +- (void)sl_replaceObjectAtIndex:(NSInteger)index withObject:(id)object { + if (object == nil) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMArray errorDesc:@"异常:数组nil值 *** -[__NSArrayM replaceObjectAtIndex:withObject:]: object cannot be nil" exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + return; + } + if (index >= self.count) { + @try { + return [self sl_replaceObjectAtIndex:index withObject:object]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMArray errorDesc:[NSString stringWithFormat:@"异常:数组越界 %@",exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + }else { + [self sl_replaceObjectAtIndex:index withObject:object]; + } +} +//越界 +- (void)sl_replaceObjectsInRange:(NSRange)range withObjectsFromArray:(NSArray *)otherArray { + if (range.location+range.length > self.count) { + @try { + return [self sl_replaceObjectsInRange:range withObjectsFromArray:otherArray]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMArray errorDesc:[NSString stringWithFormat:@"异常:数组越界 %@",exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + }else{ + [self sl_replaceObjectsInRange:range withObjectsFromArray:otherArray]; + } +} + +//越界 nil值 +- (void)sl_insertObject:(id)object atIndex:(NSInteger)index { + if (object == nil) { + + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMArray errorDesc:@"异常:数组nil值 *** -[__NSArrayM insertObject:atIndex:]: object cannot be nil" exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + + return; + } + if (index > self.count) { + @try { + return [self sl_insertObject:object atIndex:index]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMArray errorDesc:[NSString stringWithFormat:@"异常:数组越界 %@",exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + }else { + [self sl_insertObject:object atIndex:index];; + } +} +//越界 +- (void)sl_insertObjects:(NSArray *)objects atIndexes:(NSIndexSet *)indexes { + if (indexes.firstIndex > self.count || objects.count != (indexes.count)) { + @try { + return [self sl_insertObjects:objects atIndexes:indexes]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMArray errorDesc:[NSString stringWithFormat:@"异常:数组越界 %@",exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + return; + } + [self sl_insertObjects:objects atIndexes:indexes]; +} +// nil值 +- (id)sl_initWithObjects:(id _Nonnull const [])objects count:(NSUInteger)cnt{ + NSUInteger index = 0; + id _Nonnull objectsNew[cnt]; + for (int i = 0; i + +NS_ASSUME_NONNULL_BEGIN + +//可变字典 越界、nil值 Crash防护 +@interface NSMutableDictionary (SLCrashProtector) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSMutableDictionary+SLCrashProtector.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSMutableDictionary+SLCrashProtector.m new file mode 100644 index 00000000..8b9863c2 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSMutableDictionary+SLCrashProtector.m @@ -0,0 +1,88 @@ +// +// NSMutableDictionary+SLCrash.m +// DarkMode +// +// Created by wsl on 2020/4/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "NSMutableDictionary+SLCrashProtector.h" +#import "SLCrashProtector.h" + +@implementation NSMutableDictionary (SLCrashProtector) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class __NSDictionaryMM = NSClassFromString(@"__NSDictionaryM"); + SL_ExchangeInstanceMethod(__NSDictionaryMM, @selector(setObject:forKey:), __NSDictionaryMM, @selector(sl_setObject:forKey:)); + SL_ExchangeInstanceMethod(__NSDictionaryMM, @selector(removeObjectForKey:), __NSDictionaryMM, @selector(sl_removeObjectForKey:)); + SL_ExchangeInstanceMethod(__NSDictionaryMM, @selector(setObject:forKeyedSubscript:), __NSDictionaryMM, @selector(sl_setObject:forKeyedSubscript:)); + }); +} + +#pragma mark - MutableDictionary Safe Methods + +- (void)sl_setObject:(id)anObject forKey:(id)aKey { + if (anObject == nil || aKey == nil) { + @try { + [self sl_setObject:anObject forKey:aKey]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMDictionary errorDesc:[@"异常:字典nil值 " stringByAppendingString:exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + }else{ + [self sl_setObject:anObject forKey:aKey]; + } +} + +- (void)sl_removeObjectForKey:(id)aKey { + if (aKey == nil) { + @try { + [self sl_removeObjectForKey:aKey]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMDictionary errorDesc:[@"异常:字典nil值 " stringByAppendingString:exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + }else{ + [self sl_removeObjectForKey:aKey]; + } +} + +- (void)sl_setObject:(id)anObject forKeyedSubscript:(id)key { + if (anObject == nil || key == nil) { + @try { + [self sl_setObject:anObject forKeyedSubscript:key]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMDictionary errorDesc:[@"异常:字典nil值 " stringByAppendingString:exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + }else{ + [self sl_setObject:anObject forKeyedSubscript:key]; + } +} + +//nil 值 +- (id)sl_initWithObjects:(id _Nonnull const [])objects forKeys:(id _Nonnull const [])keys count:(NSUInteger)cnt{ + NSUInteger index = 0; + id _Nonnull objectsNew[cnt]; + id _Nonnull keysNew[cnt]; + //'*** -[NSDictionary initWithObjects:forKeys:]: count of objects (1) differs from count of keys (0)' + for (int i = 0; i + +NS_ASSUME_NONNULL_BEGIN + +///可变字符串 越界crash 防护 +@interface NSMutableString (SLCrashProtector) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSMutableString+SLCrashProtector.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSMutableString+SLCrashProtector.m new file mode 100644 index 00000000..ecad62f6 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSMutableString+SLCrashProtector.m @@ -0,0 +1,57 @@ +// +// NSMutableString+SLCrashProtector.m +// DarkMode +// +// Created by wsl on 2020/4/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "NSMutableString+SLCrashProtector.h" +#import "SLCrashProtector.h" + +@implementation NSMutableString (SLCrashProtector) + ++ (void)load { + //越界防护 + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class stringClass = NSClassFromString(@"__NSCFString"); + SL_ExchangeInstanceMethod(stringClass, @selector(insertString:atIndex:), stringClass, @selector(sl_insertString:atIndex:)); + SL_ExchangeInstanceMethod(stringClass, @selector(deleteCharactersInRange:), stringClass, @selector(sl_deleteCharactersInRange:)); + }); +} + +#pragma mark - NSMutableString Safe Methods + +- (void)sl_insertString:(NSString *)aString atIndex:(NSUInteger)loc{ + if (loc > self.length) { + @try { + [self sl_insertString:aString atIndex:loc]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMString errorDesc:[@"异常:MutableString越界 " stringByAppendingString:exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + }else{ + [self sl_insertString:aString atIndex:loc]; + } +} + +- (void)sl_deleteCharactersInRange:(NSRange)range{ + if (range.location+range.length > self.length) { + @try { + [self sl_deleteCharactersInRange:range]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeMString errorDesc:[@"异常:MutableString越界 " stringByAppendingString:exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + if (range.location < self.length) { + [self sl_deleteCharactersInRange:NSMakeRange(range.location, self.length-range.location)]; + } + }else{ + [self sl_deleteCharactersInRange:range]; + } +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLCrashProtector.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLCrashProtector.h new file mode 100644 index 00000000..316c07ce --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLCrashProtector.h @@ -0,0 +1,18 @@ +// +// NSObject+SLCrashProtector.h +// DarkMode +// +// Created by wsl on 2020/4/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 未识别方法 KVO KVC crash防护 +@interface NSObject (SLCrashProtector) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLCrashProtector.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLCrashProtector.m new file mode 100644 index 00000000..d0abfe5e --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLCrashProtector.m @@ -0,0 +1,218 @@ +// +// NSObject+SLCrashProtector.m +// DarkMode +// +// Created by wsl on 2020/4/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "NSObject+SLCrashProtector.h" +#import "SLCrashProtector.h" +#import "SLCrashHandler.h" +#import "SLKVODelegate.h" + +@implementation NSObject (SLCrashProtector) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + /// Unrecognized Selector 未识别方法防护 + [NSObject unrecognizedSelectorCrashProtector]; + /// KVO 防护 + [NSObject KVOCrashProtector]; + /// KVC 防护 + [NSObject KVCCrashProtector]; + }); +} + +#pragma mark - Unrecognized Selector +/// Unrecognized Selector 未识别方法防护 不包括系统类 ++ (void)unrecognizedSelectorCrashProtector { + //实例方法防护 + SL_ExchangeInstanceMethod([NSObject class], @selector(forwardingTargetForSelector:), [NSObject class], @selector(sl_forwardingTargetForSelector:)); + //类方法防护 + SL_ExchangeClassMethod([[NSObject class] class], @selector(forwardingTargetForSelector:), @selector(sl_forwardingTargetForSelector:)); +} +/// 将未识别的实例方法重定向转发给SLCrashHandler执行 +- (id)sl_forwardingTargetForSelector:(SEL)aSelector { + //判断当前类是否重写了消息转发的相关方法,如果重写了,就走正常的消息转发流程 + if (![self isOverideForwardingMethods:[self class]] && !IsSystemClass(self.class)) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeUnrecognizedSelector errorDesc:[NSString stringWithFormat:@"异常:未识别方法 [%@ +%@]",NSStringFromClass([self class]),NSStringFromSelector(aSelector)] exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + //如果SLCrashHandler也没有实现aSelector,就动态添加上aSelector + if (!class_getInstanceMethod([SLCrashHandler class], aSelector)) { + class_addMethod([SLCrashHandler class], aSelector, (IMP)SL_DynamicAddMethodIMP, "v@:"); + } + // 把aSelector转发给SLCrashHandler实例执行 + return [[SLCrashHandler alloc] init]; + } + return [self sl_forwardingTargetForSelector:aSelector]; +} +/// 将未识别的类方法重定向转发给SLCrashHandler执行 ++ (id)sl_forwardingTargetForSelector:(SEL)aSelector { + //判断当前类是否重写了消息转发的相关方法,如果重写了,就走正常的消息转发流程 + if (![self isOverideForwardingMethods:[[self class] class]] && !IsSystemClass(self.class)) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeUnrecognizedSelector errorDesc:[NSString stringWithFormat:@"异常:未识别方法 [%@ +%@]",NSStringFromClass([self class]),NSStringFromSelector(aSelector)] exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + //如果SLCrashHandler也没有实现aSelector,就动态添加上aSelector + if (!class_getInstanceMethod([[SLCrashHandler class] class], aSelector)) { + class_addMethod([[SLCrashHandler class] class], aSelector, (IMP)SL_DynamicAddMethodIMP, "v@:"); + } + // 把aSelector转发给SLCrashHandler实例执行 + return [[SLCrashHandler alloc] init]; + } + return [[self class] sl_forwardingTargetForSelector:aSelector]; +} +//class类是否重写了消息转发的相关方法 +- (BOOL)isOverideForwardingMethods:(Class)class{ + BOOL overide = NO; + overide = (class_getMethodImplementation([NSObject class], @selector(forwardInvocation:)) != class_getMethodImplementation(class, @selector(forwardInvocation:))) || + (class_getMethodImplementation([NSObject class], @selector(forwardingTargetForSelector:)) != class_getMethodImplementation(class, @selector(forwardingTargetForSelector:))); + return overide; +} +/*动态添加方法的imp*/ +static inline int SL_DynamicAddMethodIMP(id self,SEL _cmd,...){ + return 0; +} + +#pragma mark - KVO +/// KVO 防护 ++ (void)KVOCrashProtector { + SL_ExchangeInstanceMethod([NSObject class], @selector(addObserver:forKeyPath:options:context:), [NSObject class], @selector(sl_addObserver:forKeyPath:options:context:)); + SL_ExchangeInstanceMethod([NSObject class], @selector(removeObserver:forKeyPath:), [NSObject class], @selector(sl_removeObserver:forKeyPath:)); + SL_ExchangeInstanceMethod([NSObject class], @selector(removeObserver:forKeyPath:context:), [NSObject class], @selector(sl_removeObserver:forKeyPath:context:)); + SL_ExchangeInstanceMethod([NSObject class], NSSelectorFromString(@"dealloc"), [NSObject class], @selector(sl_KVODealloc)); +} + +static void *KVODelegateKey = &KVODelegateKey; +static NSString *const KVODefenderValue = @"SL_KVOCrashProtector"; +static void *KVODefenderKey = &KVODefenderKey; + +// KVODelegate setter 方法 +- (void)setKVODelegate:(SLKVODelegate *)KVODelegate { + objc_setAssociatedObject(self, KVODelegateKey, KVODelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} +// KVODelegate getter 方法 +- (SLKVODelegate *)KVODelegate { + id KVODelegate = objc_getAssociatedObject(self, KVODelegateKey); + if (KVODelegate == nil) { + KVODelegate = [[SLKVODelegate alloc] init]; + self.KVODelegate = KVODelegate; + } + return KVODelegate; +} + +// 添加监听者 +- (void)sl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{ + if (!IsSystemClass(self.class)) { + objc_setAssociatedObject(self, KVODefenderKey, KVODefenderValue, OBJC_ASSOCIATION_RETAIN); + if ([self.KVODelegate addInfoToMapWithObserver:observer forKeyPath:keyPath options:options context:context]) { + // 如果添加 KVO 信息操作成功,则调用系统添加方法 + [self sl_addObserver:self.KVODelegate forKeyPath:keyPath options:options context:context]; + } else { + // 添加 KVO 信息操作失败:重复添加 + NSString *className = (NSStringFromClass(self.class) == nil) ? @"" : NSStringFromClass(self.class); + NSString *errorReason = [NSString stringWithFormat:@"异常 KVO: Repeated additions to the observer:%@ for the key path:'%@' from %@", + observer, keyPath, className]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeKVO errorDesc:errorReason exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + + } + } else { + [self sl_addObserver:observer forKeyPath:keyPath options:options context:context]; + } +} +// 移除监听者 +- (void)sl_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{ + if (!IsSystemClass(self.class)) { + if ([self.KVODelegate removeInfoInMapWithObserver:observer forKeyPath:keyPath]) { + // 如果移除 KVO 信息操作成功,则调用系统移除方法 + [self sl_removeObserver:self.KVODelegate forKeyPath:keyPath]; + } else { + // 移除 KVO 信息操作失败:移除了未注册的观察者 + NSString *className = NSStringFromClass(self.class) == nil ? @"" : NSStringFromClass(self.class); + NSString *errorReason = [NSString stringWithFormat:@"异常 KVO: Cannot remove an observer %@ for the key path '%@' from %@ , because it is not registered as an observer", observer, keyPath, className]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeKVO errorDesc:errorReason exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + } else { + [self sl_removeObserver:observer forKeyPath:keyPath]; + } +} +// 移除监听者 +- (void)sl_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context{ + if (!IsSystemClass(self.class)) { + if ([self.KVODelegate removeInfoInMapWithObserver:observer forKeyPath:keyPath context:context]) { + // 如果移除 KVO 信息操作成功,则调用系统移除方法 + [self sl_removeObserver:self.KVODelegate forKeyPath:keyPath context:context]; + } else { + // 移除 KVO 信息操作失败:移除了未注册的观察者 + NSString *className = NSStringFromClass(self.class) == nil ? @"" : NSStringFromClass(self.class); + NSString *errorReason = [NSString stringWithFormat:@"异常 KVO: Cannot remove an observer %@ for the key path '%@' from %@ , because it is not registered as an observer", observer, keyPath, className]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeKVO errorDesc:errorReason exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + } else { + [self sl_removeObserver:observer forKeyPath:keyPath context:context]; + } +} +// 释放 +- (void)sl_KVODealloc{ + @autoreleasepool { + // if (!IsSystemClass(self.class)) { + NSString *value = (NSString *)objc_getAssociatedObject(self, KVODefenderKey); + if ([value isEqualToString:KVODefenderValue]) { + NSArray *keyPaths = [self.KVODelegate getAllKeyPaths]; + // 被观察者在 dealloc 时仍然注册着 KVO + if (keyPaths.count > 0) { + NSString *errorReason = [NSString stringWithFormat:@"异常 KVO: An instance %@ was deallocated while key value observers were still registered with it. The Keypaths is:'%@'", self, [keyPaths componentsJoinedByString:@","]]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeKVO errorDesc:errorReason exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + // 移除多余的观察者 + for (NSString *keyPath in keyPaths) { + [self sl_removeObserver:self.KVODelegate forKeyPath:keyPath]; + } + } + } + // } + [self sl_KVODealloc]; +} + +#pragma mark - KVC +/// KVC 防护 ++ (void)KVCCrashProtector { + SL_ExchangeInstanceMethod([NSObject class], @selector(setValue:forKey:), [NSObject class], @selector(sl_setValue:forKey:)); +} +- (void)sl_setValue:(id)value forKey:(NSString *)key { + if (key == nil) { + @try { + [self sl_setValue:value forKey:key]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeKVC errorDesc:[@"异常 KVC: " stringByAppendingString:exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + + } + return; + } + [self sl_setValue:value forKey:key]; +} +- (void)setNilValueForKey:(NSString *)key { + NSString *crashMessages = [NSString stringWithFormat:@"异常 KVC: [<%@ %p> setNilValueForKey]: could not set nil as the value for the key %@.",NSStringFromClass([self class]),self,key]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeKVC errorDesc:crashMessages exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; +} +- (void)setValue:(id)value forUndefinedKey:(NSString *)key { + NSString *crashMessages = [NSString stringWithFormat:@"异常 KVC: [<%@ %p> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key: %@,value:%@'",NSStringFromClass([self class]),self,key,value]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeKVC errorDesc:crashMessages exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; +} +- (nullable id)valueForUndefinedKey:(NSString *)key { + NSString *crashMessages = [NSString stringWithFormat:@"异常 KVC: [<%@ %p> valueForUndefinedKey:]: this class is not key value coding-compliant for the key: %@",NSStringFromClass([self class]),self,key]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeKVC errorDesc:crashMessages exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + return self; +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLMLeakFinder.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLMLeakFinder.h new file mode 100644 index 00000000..4c737b2e --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLMLeakFinder.h @@ -0,0 +1,30 @@ +// +// NSObject+SLMLeakFinder.h +// DarkMode +// +// Created by wsl on 2020/5/6. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 思路来源: https://github.com/Tencent/MLeaksFinder +@interface NSObject (SLMLeakFinder) +///即将释放时调用此方法 +- (BOOL)willDealloc; +// +/////即将释放子对象 +//- (void)willReleaseChild:(id)child; +/////即将释放子对象集合 +//- (void)willReleaseChildren:(NSArray *)children; +// +/////返回视图堆栈信息 +//- (NSArray *)viewStack; +///添加需要监测内存泄漏的白名单类 ++ (void)addClassNamesToWhitelist:(NSArray *)classNames; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLMLeakFinder.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLMLeakFinder.m new file mode 100644 index 00000000..52454ad0 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSObject+SLMLeakFinder.m @@ -0,0 +1,70 @@ +// +// NSObject+SLMLeakFinder.m +// DarkMode +// +// Created by wsl on 2020/5/6. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "NSObject+SLMLeakFinder.h" +#import "SLCrashProtector.h" + +@implementation NSObject (SLMLeakFinder) + +//对象即将释放时调用此方法,定义一个2秒后执行的block,如果正常释放了,weakSelf为nil,不执行notDealloc,否则如果调用了notDealloc,就表示没有释放,出现了内存泄漏。 +//如果不需要监测内存泄漏或者对象本身就不要释放,就返回NO +- (BOOL)willDealloc { + //在白名单的类 + NSString *className = NSStringFromClass([self class]); + if ([[NSObject classNamesWhitelist] containsObject:className]) + return NO; + + // NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey); + // if ([senderPtr isEqualToNumber:@((uintptr_t)self)]) + // return NO; + + //用弱指针,不影响引用计数和对象的释放 + __weak id weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [weakSelf notDealloc]; + }); + return YES; +} +//如果没释放,就执行此方法 +- (void)notDealloc { + NSString *className = NSStringFromClass([self class]); + // NSLog(@"有内存没释放:\n 如果%@不应该被释放, 请重写[%@ -willDealloc] 并 returning NO .\nView-ViewController stack: %@", className, className, [self viewStack]); + NSString *desc = [NSString stringWithFormat:@"内存泄漏/循环引用:如果%@不应该被释放, 请重写[%@ -willDealloc] 并 returning NO .\n", className, className]; + NSLog(@"%@",desc); + NSException *exception = [NSException exceptionWithName:NSInternalInconsistencyException reason:desc userInfo:nil]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeLeak errorDesc:desc exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; +} + +//不需要监测内存泄漏的白名单类 ++ (NSMutableSet *)classNamesWhitelist { + static NSMutableSet *whitelist = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + whitelist = [NSMutableSet setWithObjects: + @"UIFieldEditor", // UIAlertControllerTextField + @"UINavigationBar", + @"_UIAlertControllerActionView", + @"_UIVisualEffectBackdropView", + nil]; + + // System's bug since iOS 10 and not fixed yet up to this ci. + NSString *systemVersion = [UIDevice currentDevice].systemVersion; + if ([systemVersion compare:@"10.0" options:NSNumericSearch] != NSOrderedAscending) { + [whitelist addObject:@"UISwitch"]; + } + }); + return whitelist; +} + +//添加白名单类 ++ (void)addClassNamesToWhitelist:(NSArray *)classNames { + [[self classNamesWhitelist] addObjectsFromArray:classNames]; +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSString+SLCrashProtector.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSString+SLCrashProtector.h new file mode 100644 index 00000000..7f24d335 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSString+SLCrashProtector.h @@ -0,0 +1,18 @@ +// +// NSString+SLCrashProtector.h +// DarkMode +// +// Created by wsl on 2020/4/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///不可变字符串 越界crash 防护 +@interface NSString (SLCrashProtector) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSString+SLCrashProtector.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSString+SLCrashProtector.m new file mode 100644 index 00000000..0faab0c4 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/NSString+SLCrashProtector.m @@ -0,0 +1,85 @@ +// +// NSString+SLCrashProtector.m +// DarkMode +// +// Created by wsl on 2020/4/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "NSString+SLCrashProtector.h" +#import "SLCrashProtector.h" + +@implementation NSString (SLCrashProtector) + ++ (void)load { + //越界防护 + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class stringClass = NSClassFromString(@"__NSCFConstantString"); + SL_ExchangeInstanceMethod(stringClass, @selector(characterAtIndex:), stringClass, @selector(sl_characterAtIndex:)); + SL_ExchangeInstanceMethod(stringClass, @selector(substringFromIndex:), stringClass, @selector(sl_substringFromIndex:)); + SL_ExchangeInstanceMethod(stringClass, @selector(substringToIndex:), stringClass, @selector(sl_substringToIndex:)); + SL_ExchangeInstanceMethod(stringClass, @selector(substringWithRange:), stringClass, @selector(sl_substringWithRange:)); + }); +} + +#pragma mark - NSString Safe Methods + +- (unichar)sl_characterAtIndex:(NSUInteger)index{ + if (index>=self.length) { + @try { + [self sl_characterAtIndex:index]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeString errorDesc:[@"异常:String越界 " stringByAppendingString:exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + return 0; + } + return [self sl_characterAtIndex:index]; +} + +- (NSString *)sl_substringFromIndex:(NSUInteger)from{ + id instance = nil; + @try { + instance = [self sl_substringFromIndex:from]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeString errorDesc:[@"异常:String越界 " stringByAppendingString:exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + @finally { + return instance; + } +} + +- (NSString *)sl_substringToIndex:(NSUInteger)to{ + id instance = nil; + @try { + instance = [self sl_substringToIndex:to]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeString errorDesc:[@"异常:String越界 " stringByAppendingString:exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + @finally { + return instance; + } +} + +- (NSString *)sl_substringWithRange:(NSRange)range{ + id instance = nil; + @try { + instance = [self sl_substringWithRange:range]; + } + @catch (NSException *exception) { + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeString errorDesc:[@"异常:String越界 " stringByAppendingString:exception.reason] exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + @finally { + return instance; + } +} + + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLCrashHandler.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLCrashHandler.h new file mode 100644 index 00000000..e346238d --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLCrashHandler.h @@ -0,0 +1,74 @@ +// +// SLCrashHandler.h +// DarkMode +// +// Created by wsl on 2020/4/15. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, SLCrashErrorType) { + /*未知错误*/ + SLCrashErrorTypeUnknow = 0, + /*Array*/ + SLCrashErrorTypeArray, + /*NSMutableArray*/ + SLCrashErrorTypeMArray, + /*NSDictionary*/ + SLCrashErrorTypeDictionary, + /*NSMutableDictionary*/ + SLCrashErrorTypeMDictionary, + /*NSString*/ + SLCrashErrorTypeString, + /*NSMutableString*/ + SLCrashErrorTypeMString, + /*UnrecognizedSelector异常*/ + SLCrashErrorTypeUnrecognizedSelector, + /*KVO异常*/ + SLCrashErrorTypeKVO, + /*KVC异常*/ + SLCrashErrorTypeKVC, + /*异步线程更新UI*/ + SLCrashErrorTypeAsynUpdateUI, + /*野指针*/ + SLCrashErrorTypeZombie, + /*内存泄漏/循环引用*/ + SLCrashErrorTypeLeak +}; + +/// 崩溃信息 +@interface SLCrashError : NSObject +/// 错误类型 +@property (nonatomic, assign) SLCrashErrorType errorType; +/// 错误描述 +@property (nonatomic, copy) NSString *errorDesc; +/// 异常对象 +@property (nonatomic, strong) NSException *exception; +/// 当前线程的函数调用栈 +@property (nonatomic, copy) NSArray *callStackSymbol; +///初始化 ++ (instancetype)errorWithErrorType:(SLCrashErrorType)errorType errorDesc:(NSString *)errorDesc exception:(nullable NSException *)exception callStack:(NSArray*)callStackSymbol; +@end + + +@class SLCrashHandler; +@protocol SLCrashHandlerDelegate +///捕获到错误信息,交给外界delegate处理 +- (void)crashHandlerDidOutputCrashError:(SLCrashError *)crashError; +@end + +/// 崩溃处理程序 注意:部分防护功能还不完善,比如野指针和内存泄漏/循环引用 +@interface SLCrashHandler : NSObject + +///异常捕获回调 提供给外界实现自定义处理 ,日志上报等(注意线程安全) +@property (nonatomic, weak) iddelegate; + +/// 单例 ++ (instancetype)defaultCrashHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLCrashHandler.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLCrashHandler.m new file mode 100644 index 00000000..500bc02e --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLCrashHandler.m @@ -0,0 +1,204 @@ +// +// SLCrashHandler.m +// DarkMode +// +// Created by wsl on 2020/4/15. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLCrashHandler.h" +#import "BSBacktraceLogger.h" + +#import +#import + +@implementation SLCrashError ++ (instancetype)errorWithErrorType:(SLCrashErrorType)errorType errorDesc:(NSString *)errorDesc exception:(NSException *)exception callStack:(NSArray*)callStackSymbol { + SLCrashError *crashError = [SLCrashError new]; + crashError.errorDesc = errorDesc; + crashError.errorType = errorType; + crashError.exception = exception; + //获取当前线程的函数调用栈 + crashError.callStackSymbol = callStackSymbol; + return crashError; +} +@end + +@interface SLCrashHandler () +@end + +@implementation SLCrashHandler + ++ (instancetype)defaultCrashHandler { + static SLCrashHandler *crashHandler; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + crashHandler = [[SLCrashHandler alloc] init]; + [crashHandler setCaughtCrashHandler]; + }); + return crashHandler; +} + +/// 捕获Mach、Signal、NSException 异常Crash +- (void)setCaughtCrashHandler { + [self setMachHandler]; + [self setSignalHandler]; + [self setExceptionHandler]; +} + +#pragma mark - Mach异常捕获 +// 创建Mach Port并监听消息 +- (void)setMachHandler { + mach_port_t server_port; + ///创建异常端口 + kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &server_port); + assert(kr == KERN_SUCCESS); + NSLog(@"创建异常消息监听端口: %d", server_port); + + ///申请set_exception_ports 的权限 + kr = mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND); + assert(kr == KERN_SUCCESS); + + ///设置异常端口 EXC_MASK_CRASH 捕获Mach_CRASH时会导致死锁 + kr = task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, server_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); + + ///循环等待异常消息 + [self setMachPortListener:server_port]; +} + +- (void)setMachPortListener:(mach_port_t)mach_port { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + __Request__exception_raise_state_identity_t mach_message; + + mach_message.Head.msgh_size = 1024; + mach_message.Head.msgh_local_port = mach_port; + + mach_msg_return_t mr; + + while (true) { + mr = mach_msg(&mach_message.Head, + MACH_RCV_MSG | MACH_RCV_LARGE, + 0, + mach_message.Head.msgh_size, + mach_message.Head.msgh_local_port, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + + if (mr != MACH_MSG_SUCCESS && mr != MACH_RCV_TOO_LARGE) { + NSLog(@"error!"); + } + + mach_msg_id_t msg_id = mach_message.Head.msgh_id; + mach_port_t remote_port = mach_message.Head.msgh_remote_port; + mach_port_t local_port = mach_message.Head.msgh_local_port; + + NSLog(@"Receive a mach message:[%d], remote_port: %d, local_port: %d, exception code: %d", + msg_id, + remote_port, + local_port, + mach_message.exception); + + NSString * callStack = [BSBacktraceLogger bs_backtraceOfAllThread]; + NSString *exceptionInfo = [NSString stringWithFormat:@"mach异常:%@",callStack]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeUnknow errorDesc:exceptionInfo exception:nil callStack:@[callStack]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + //mach异常就终止当前程序 +// abort(); + } + }); +} + +#pragma mark - Unix signal信号捕获 +- (void)setSignalHandler { + ///注册信号Handler,Unix 信号捕获 + signal(SIGABRT, SL_UncaughtSignalHandler); + signal(SIGILL, SL_UncaughtSignalHandler); + signal(SIGSEGV, SL_UncaughtSignalHandler); + signal(SIGFPE, SL_UncaughtSignalHandler); + signal(SIGBUS, SL_UncaughtSignalHandler); + signal(SIGPIPE, SL_UncaughtSignalHandler); + signal(SIGKILL, SL_UncaughtSignalHandler); + signal(SIGTRAP, SL_UncaughtSignalHandler); +} +///异常信号处理回调 +void SL_UncaughtSignalHandler(int signal) { + NSString *exceptionInfo = [NSString stringWithFormat:@"异常信号:%@ Crash",signalName(signal)]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeUnknow errorDesc:exceptionInfo exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + +} +///信号名称 关于信号这部分可以看 https://mp.weixin.qq.com/s/hVj-j61Br3dox37SN79fDQ +NSString *signalName(int signal) { + switch (signal) { + case SIGABRT: + /* + abort() 发生的信号: + 典型的软件信号,通过 pthread_kill() 发送 + */ + return @"SIGABRT"; + case SIGILL: + /* + 非法指令,即机器码指令不正确: + 1, iOS 上偶现的问题,遇到之后用户会连续闪退,直到应用二进制的缓存重新加载 或重启手机。此问题挺影响体验,但是报给苹果不认,因为苹果那边没有收集到,目前没有太好办法。因为 iOS 应用内无法对一片内存同时获取 w+x 权限的,因此应用无法造成此类问题,所以判断是苹果的问题。 + */ + return @"SIGILL"; + case SIGSEGV: + /*段错误: + 1、访问未申请的虚拟内存地址 + 2、没有写权限的内存写入 + */ + return @"SIGSEGV"; + case SIGFPE: + /* + 算术运算出错,比如除0错误: + iOS 默认是不启用的,所以我们一般不会遇到 + */ + return @"SIGFPE"; + case SIGBUS: + /*总线错误 + 1、内存地址对齐出错 2、试图执行没有执行权限的代码地址 + */ + return @"SIGBUS"; + case SIGPIPE: + /*管道破裂 + 1, Socket通信是可能遇到,如读进程以及终止时,写进程继续写入数据。 + */ + return @"SIGPIPE"; + case SIGKILL: + /* + 进程内无法拦截: + 1, exit(), kill(9) 等函数调用 2, iOS系统杀进程用的,比如 watchDog 杀进程 + */ + return @"SIGKILL"; + case SIGTRAP: + /* + 由断点指令或其它trap指令产生: + 部分系统框架里面会用 __builtin_trap() 来产生一个 SIGTRAP 类型的 Crash + */ + return @"SIGTRAP"; + default: + return @"UNKNOWN"; + } +} +#pragma mark - NSException捕获 +/// 其他三方注册的异常处理 handler +static NSUncaughtExceptionHandler *otherUncaughtExceptionHandler = NULL; +- (void)setExceptionHandler { + ///先获取保留其他三方的异常Handler + otherUncaughtExceptionHandler = NSGetUncaughtExceptionHandler(); + ///注册自己的异常Handler + NSSetUncaughtExceptionHandler (SL_UncaughtExceptionHandler); +} +///异常捕获处理 +void SL_UncaughtExceptionHandler(NSException *exception) { + if (otherUncaughtExceptionHandler) { + //如果其他三方也有注册,则也执行其他三方的Handle,然后在执行自己的 + otherUncaughtExceptionHandler(exception); + } + NSString *exceptionInfo = [NSString stringWithFormat:@"异常:%@, %@",exception.name, exception.reason]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeUnknow errorDesc:exceptionInfo exception:exception callStack:exception.callStackSymbols]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLCrashProtector.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLCrashProtector.h new file mode 100644 index 00000000..93575826 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLCrashProtector.h @@ -0,0 +1,58 @@ +// +// SLCrashProtector.h +// DarkMode +// +// Created by wsl on 2020/4/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#ifndef SLCrashProtector_h +#define SLCrashProtector_h + +#import + +#import "SLCrashHandler.h" +#import "SLZombieCatcher.h" +#import "SLZombieFinder.h" + +/*交换实例方法*/ +static inline void SL_ExchangeInstanceMethod(Class _originalClass ,SEL _originalSel, Class _targetClass, SEL _targetSel){ + //此处获得的方法可能是父类对象的 + Method methodOriginal = class_getInstanceMethod(_originalClass, _originalSel); + Method methodNew = class_getInstanceMethod(_targetClass, _targetSel); + // class_addMethod 返回成功表示被替换的方法没实现,然后会通过 class_addMethod 方法先实现;返回失败则表示被替换方法已存在,可以直接进行 IMP 指针交换 + BOOL didAddMethod = class_addMethod(_originalClass, _originalSel, method_getImplementation(methodNew), method_getTypeEncoding(methodNew)); + if (didAddMethod) { + // 进行方法的替换 + class_replaceMethod(_targetClass, _targetSel, method_getImplementation(methodOriginal), method_getTypeEncoding(methodOriginal)); + }else{ + // 交换 IMP 指针 + method_exchangeImplementations(methodOriginal, methodNew); + } +} +/*交换类方法*/ +static inline void SL_ExchangeClassMethod(Class _class ,SEL _originalSel,SEL _exchangeSel){ + Method methodOriginal = class_getClassMethod(_class, _originalSel); + Method methodNew = class_getClassMethod(_class, _exchangeSel); + method_exchangeImplementations(methodOriginal, methodNew); +} + +/*是否是系统类*/ +static inline BOOL IsSystemClass(Class cls){ + __block BOOL isSystem = NO; + NSString *className = NSStringFromClass(cls); + if ([className hasPrefix:@"NS"]) { + isSystem = YES; + return isSystem; + } + NSBundle *mainBundle = [NSBundle bundleForClass:cls]; + if (mainBundle == [NSBundle mainBundle]) { + isSystem = NO; + }else{ + isSystem = YES; + } + return isSystem; +} + + +#endif /* SLCrashProtector_h */ diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLKVODelegate.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLKVODelegate.h new file mode 100644 index 00000000..584d496d --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLKVODelegate.h @@ -0,0 +1,59 @@ +// +// SLKVODelegate.h +// DarkMode +// +// Created by wsl on 2020/4/16. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 观察者代理 存储管理KVO的信息 +@interface SLKVODelegate : NSObject +/** + 将添加kvo时的相关信息加入到关系maps中,对应原有的添加观察者 + + @param observer observer观察者 + @param keyPath keyPath + @param options options + @param context context + */ +- (BOOL)addInfoToMapWithObserver:(NSObject *)observer + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context ; + +/** +从关系maps中移除观察者 对应原有的移除观察者操作 + +@param observer 实际观察者 +@param keyPath keypath +@param context context +@return 是否移除成功 +如果重复移除,会返回NO + */ +- (BOOL)removeInfoInMapWithObserver:(NSObject *)observer + forKeyPath:(NSString *)keyPath + context:(void *)context; + +/** + 从关系maps中移除观察者 对应原有的移除观察者操作 + + @param observer 实际观察者 + @param keyPath keypath + @return 是否移除成功 + 如果重复移除,会返回NO + */ +- (BOOL)removeInfoInMapWithObserver:(NSObject *)observer + forKeyPath:(NSString *)keyPath; + +/* + 获取所有被观察的 keyPaths + */ +- (NSArray *)getAllKeyPaths; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLKVODelegate.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLKVODelegate.m new file mode 100644 index 00000000..e731a827 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLKVODelegate.m @@ -0,0 +1,126 @@ +// +// SLKVODelegate.m +// DarkMode +// +// Created by wsl on 2020/4/16. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLKVODelegate.h" +#import "SLCrashProtector.h" + +@interface SLKVODelegate () +{ + // 关系数据表结构:{keypath : [observer1, observer2 , ...](NSHashTable)} +@private + NSMutableDictionary *> *_kvoInfoMap; +} +@end + +@implementation SLKVODelegate + +- (instancetype)init { + self = [super init]; + if (self) { + _kvoInfoMap = [NSMutableDictionary dictionary]; + } + return self; +} + +// 添加 KVO 信息操作, 添加成功返回 YES +- (BOOL)addInfoToMapWithObserver:(NSObject *)observer + forKeyPath:(NSString *)keyPath + options:(NSKeyValueObservingOptions)options + context:(void *)context { + @synchronized (self) { + if (!observer || !keyPath || + ([keyPath isKindOfClass:[NSString class]] && keyPath.length <= 0)) { + return NO; + } + NSHashTable *info = _kvoInfoMap[keyPath]; + if (info.count == 0) { + //NSHashTable 弱持有observer + info = [NSHashTable weakObjectsHashTable]; + [info addObject:observer]; + _kvoInfoMap[keyPath] = info; + return YES; + } + //如果已记录的keyPath的观察者不包含observer,就记录 + if (![info containsObject:observer]) { + [info addObject:observer]; + return YES; + } + return NO; + } +} + +// 移除 KVO 信息操作, 成功返回 YES +- (BOOL)removeInfoInMapWithObserver:(NSObject *)observer + forKeyPath:(NSString *)keyPath { + @synchronized (self) { + if (!observer || !keyPath || + ([keyPath isKindOfClass:[NSString class]] && keyPath.length <= 0)) { + return NO; + } + + NSHashTable *info = _kvoInfoMap[keyPath]; + if (info.count == 0) { + //移除失败 + return NO; + } + [info removeObject:observer]; + //如果keyPath 的观察者个数为0,就移除这条记录 + if (info.count == 0) { + [_kvoInfoMap removeObjectForKey:keyPath]; + return YES; + } + return NO; + } +} + +// 移除 KVO 信息操作, 成功返回 YES +- (BOOL)removeInfoInMapWithObserver:(NSObject *)observer + forKeyPath:(NSString *)keyPath + context:(void *)context { + @synchronized (self) { + if (!observer || !keyPath || + ([keyPath isKindOfClass:[NSString class]] && keyPath.length <= 0)) { + return NO; + } + NSHashTable *info = _kvoInfoMap[keyPath]; + if (info.count == 0) { + return NO; + } + [info removeObject:observer]; + if (info.count == 0) { + [_kvoInfoMap removeObjectForKey:keyPath]; + return YES; + } + return NO; + } +} + +// 实际观察者 SLKVODelegate 进行监听,并分发 +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { + NSHashTable *info = _kvoInfoMap[keyPath]; + for (NSObject *observer in info) { + @try { + [observer observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } @catch (NSException *exception) { + NSString *reason = [NSString stringWithFormat:@"异常 KVO: %@",[exception description]]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeKVO errorDesc:reason exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + } + } +} + +// 获取所有被观察的 keyPaths +- (NSArray *)getAllKeyPaths { + NSArray *keyPaths = _kvoInfoMap.allKeys; + return keyPaths; +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieCatcher.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieCatcher.h new file mode 100644 index 00000000..c067898e --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieCatcher.h @@ -0,0 +1,21 @@ +// +// SLZombieCatcher.h +// DarkMode +// +// Created by wsl on 2020/4/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +/* + -fno-objc-arc 记得设置此类编译方式支持MRC + */ +///僵尸对象,处理发向野指针的消息 定位到方法 +@interface SLZombieCatcher : NSProxy +///原类 +@property (nonatomic, assign) Class originClass; +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieCatcher.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieCatcher.m new file mode 100644 index 00000000..ab077688 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieCatcher.m @@ -0,0 +1,133 @@ +// +// SLZombieCatcher.m +// DarkMode +// +// Created by wsl on 2020/4/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLZombieCatcher.h" +#include +#import "SLCrashProtector.h" + +@implementation SLZombieCatcher + +- (BOOL)respondsToSelector: (SEL)aSelector +{ + return [self.originClass instancesRespondToSelector:aSelector]; +} + +- (NSMethodSignature *)methodSignatureForSelector: (SEL)sel +{ + return [self.originClass instanceMethodSignatureForSelector:sel]; +} + +- (void)forwardInvocation: (NSInvocation *)invocation +{ + [self _throwMessageSentExceptionWithSelector: invocation.selector]; +} + + +#define SLZombieThrowMesssageSentException() [self _throwMessageSentExceptionWithSelector: _cmd] +- (Class)class +{ + SLZombieThrowMesssageSentException(); + return nil; +} + +- (BOOL)isEqual:(id)object +{ + SLZombieThrowMesssageSentException(); + return NO; +} + +- (NSUInteger)hash +{ + SLZombieThrowMesssageSentException(); + return 0; +} + +- (id)self +{ + SLZombieThrowMesssageSentException(); + return nil; +} + +- (BOOL)isKindOfClass:(Class)aClass +{ + SLZombieThrowMesssageSentException(); + return NO; +} + +- (BOOL)isMemberOfClass:(Class)aClass +{ + SLZombieThrowMesssageSentException(); + return NO; +} + +- (BOOL)conformsToProtocol:(Protocol *)aProtocol +{ + SLZombieThrowMesssageSentException(); + return NO; +} + +- (BOOL)isProxy +{ + SLZombieThrowMesssageSentException(); + + return NO; +} + +- (id)retain +{ + SLZombieThrowMesssageSentException(); + return nil; +} + +- (oneway void)release +{ + SLZombieThrowMesssageSentException(); +} + +- (id)autorelease +{ + SLZombieThrowMesssageSentException(); + return nil; +} + +- (void)dealloc +{ + SLZombieThrowMesssageSentException(); + [super dealloc]; +} + +- (NSUInteger)retainCount +{ + SLZombieThrowMesssageSentException(); + return 0; +} + +- (NSZone *)zone +{ + SLZombieThrowMesssageSentException(); + return nil; +} + +- (NSString *)description +{ + SLZombieThrowMesssageSentException(); + return nil; +} + + +#pragma mark - Private +- (void)_throwMessageSentExceptionWithSelector: (SEL)selector +{ + NSException *exception = [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"( 野指针必现定位:-[%@ %@]) was sent to a zombie object at address: %p", NSStringFromClass(self.originClass), NSStringFromSelector(selector), self] userInfo:nil]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeZombie errorDesc:exception.reason exception:exception callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + //是否需要强制抛出异常 +// @throw exception; +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieFinder.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieFinder.h new file mode 100644 index 00000000..5d44bdeb --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieFinder.h @@ -0,0 +1,30 @@ +// +// SLZombieFinder.h +// DarkMode +// +// Created by wsl on 2020/5/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/* + -fno-objc-arc 记得设置此类编译方式支持MRC +*/ +///zombie/野指针对象嗅探器 目前还不完善,不推荐使用 ,仅做交流学习 来源:https://github.com/sindrilin/LXDZombieSniffer.git +@interface SLZombieFinder : NSObject + +///启动zombie嗅探 ++ (void)startSniffer; + +///关闭zombie嗅探 ++ (void)closeSniffer; + + ///添加嗅探白名单类 不嗅探名单之内的类 ++ (void)appendIgnoreClass: (Class)cls; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieFinder.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieFinder.m new file mode 100644 index 00000000..a4735a42 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieFinder.m @@ -0,0 +1,151 @@ +// +// SLZombieFinder.m +// DarkMode +// +// Created by wsl on 2020/5/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLZombieFinder.h" +#import "SLZombieCatcher.h" +#import +#import "SLCrashProtector.h" + +typedef void (*SLDeallocPointer) (id obj); ///指向Dealloc实现IMP的指针 +static BOOL _enabled = NO; //嗅探器是否已开启 +static NSArray *_rootClasses = nil; //根/基类 +static NSDictionary *_rootClassDeallocImps = nil; //存储根类Dealloc的方法实现IMP +static BOOL isOnlySnifferMyClass = NO; ///是否仅嗅探自己创建的类 默认NO + +/// 嗅探/延迟释放实例的白名单类,不对在此名单中的类进行僵尸对象嗅探/延迟释放 +static inline NSMutableSet *sl_sniff_white_list() { + static NSMutableSet *lxd_sniff_white_list; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + lxd_sniff_white_list = [[NSMutableSet alloc] init]; + + [lxd_sniff_white_list addObject:@"_UITextSizeCache"]; + [lxd_sniff_white_list addObject:@"NSConcreteValue"]; + [lxd_sniff_white_list addObject:@"SLZombieCatcher"]; + [lxd_sniff_white_list addObject:@"OS_dispatch_data"]; + + [lxd_sniff_white_list addObject:@"__NSGlobalBlock__"]; + [lxd_sniff_white_list addObject:@"__NSStackBlock__ "]; + [lxd_sniff_white_list addObject:@"__NSMallocBlock__"]; + [lxd_sniff_white_list addObject:@"NSBlock"]; + [lxd_sniff_white_list addObject:@"NSValue"]; + + }); + return lxd_sniff_white_list; +} +///释放实例 +static inline void sl_dealloc(__unsafe_unretained id obj) { + Class currentCls = [obj class]; + Class rootCls = currentCls; + + while (rootCls != [NSObject class] && rootCls != [NSProxy class]) { + rootCls = class_getSuperclass(rootCls); + } + NSString *clsName = NSStringFromClass(rootCls); + SLDeallocPointer deallocImp = NULL; + [[_rootClassDeallocImps objectForKey: clsName] getValue: &deallocImp]; + + if (deallocImp != NULL) { + deallocImp(obj); + } +} +///交换IMP,并返回method的原始IMP +static inline IMP sl_swizzleMethodWithBlock(Method method, void *block) { + IMP blockImplementation = imp_implementationWithBlock(block); + return method_setImplementation(method, blockImplementation); +} + + +@implementation SLZombieFinder + ++ (void)initialize { + _rootClasses = [@[[NSObject class], [NSProxy class]] retain]; +} + +#pragma mark - Public ++ (void)startSniffer { + @synchronized(self) { + if (!_enabled) { + [self _swizzleDealloc]; + _enabled = YES; + } + } +} ++ (void)closeSniffer { + @synchronized(self) { + if (_enabled) { + [self _unswizzleDealloc]; + _enabled = NO; + } + } +} ++ (void)appendIgnoreClass: (Class)cls { + @synchronized(self) { + NSMutableSet *whiteList = sl_sniff_white_list(); + NSString *clsName = NSStringFromClass(cls); + [clsName retain]; + [whiteList addObject: clsName]; + } +} + +#pragma mark - Private +///hook基类NSObject/NSProxy的dealloc方法,并指向swizzledDeallocBlock对应的IMP ++ (void)_swizzleDealloc { + static void *swizzledDeallocBlock = NULL; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + ///NSObject/NSProxy的dealloc交换后的方法实现 + swizzledDeallocBlock = [^void(id obj) { + Class currentClass = [obj class]; + NSString *clsName = NSStringFromClass(currentClass); + //_UITextSizeCache 这个私有类的实例对象在dispatch_after里释放会崩溃,故排除 + if ([sl_sniff_white_list() containsObject:clsName] || [clsName hasPrefix:@"OS_xpc"] || [clsName hasPrefix:@"WK"]) { + sl_dealloc(obj); + } else { + NSValue *objVal = [NSValue valueWithBytes: &obj objCType: @encode(typeof(obj))]; + ///动态转换obj对象的isa类对象为SLZombieCatcher,让SLZombieCatcher去捕获异常的消息并抛出异常 + object_setClass(obj, [SLZombieCatcher class]); + ///保存原来的类 + ((SLZombieCatcher *)obj).originClass = currentClass; + + ///延迟5秒释放此obj对象内存空间,如果释放前,有新消息发送给此地址的对象(SLZombieCatcher),就说明出现了野指针/坏内存访问 + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + __unsafe_unretained id deallocObj = nil; + [objVal getValue: &deallocObj]; + object_setClass(deallocObj, currentClass); + sl_dealloc(deallocObj); + }); + } + } copy]; + }); + + NSMutableDictionary *deallocImps = [NSMutableDictionary dictionary]; + for (Class rootClass in _rootClasses) { + //设置方法dealloc的实现IMP为swizzledDeallocBlock的方法实现IMP,并存储原有的dealloc的IMP,适当时机去执行 + IMP originalDeallocImp = sl_swizzleMethodWithBlock(class_getInstanceMethod(rootClass, @selector(dealloc)), swizzledDeallocBlock); + [deallocImps setObject: [NSValue valueWithBytes: &originalDeallocImp objCType: @encode(typeof(IMP))] forKey: NSStringFromClass(rootClass)]; + } + _rootClassDeallocImps = [deallocImps copy]; +} + +///恢复原来的IMP指向 ++ (void)_unswizzleDealloc { + [_rootClasses enumerateObjectsUsingBlock:^(Class rootClass, NSUInteger idx, BOOL *stop) { + IMP originalDeallocImp = NULL; + NSString *clsName = NSStringFromClass(rootClass); + [[_rootClassDeallocImps objectForKey: clsName] getValue: &originalDeallocImp]; + + NSParameterAssert(originalDeallocImp); + method_setImplementation(class_getInstanceMethod(rootClass, @selector(dealloc)), originalDeallocImp); + }]; + [_rootClassDeallocImps release]; + _rootClassDeallocImps = nil; +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieSafeFree.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieSafeFree.h new file mode 100644 index 00000000..e459f9e7 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieSafeFree.h @@ -0,0 +1,23 @@ +// +// SLZombieSafeFree.h +// DarkMode +// +// Created by wsl on 2020/4/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/* + 此类依赖于三方 fishhook + -fno-objc-arc 记得设置此类编译方式支持MRC + */ +/// 捕获C函数的free内存释放方法 该类已弃用,有Hook冲突问题 +@interface SLZombieSafeFree : NSObject +//系统内存警告的时候调用这个函数释放一些内存 +void free_some_mem(size_t freeNum); +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieSafeFree.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieSafeFree.m new file mode 100644 index 00000000..44a7e0dd --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/SLZombieSafeFree.m @@ -0,0 +1,103 @@ +// +// SLZombieSafeFree.m +// DarkMode +// +// Created by wsl on 2020/4/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLZombieSafeFree.h" + +#import "SLZombieCatcher.h" +#import "queue.h" +#import "fishhook.h" +#import +#include +#include + +static Class sYHCatchIsa; +static size_t sYHCatchSize; + +static void(* orig_free)(void *p); +struct DSQueue* _unfreeQueue = NULL;//用来保存自己偷偷保留的内存:1这个队列要线程安全或者自己加锁;2这个队列内部应该尽量少申请和释放堆内存。 +int unfreeSize = 0;//用来记录我们偷偷保存的内存的大小 +#define MAX_STEAL_MEM_SIZE 1024*1024*100//最多存这么多内存,大于这个值就释放一部分 +#define MAX_STEAL_MEM_NUM 1024*1024*10//最多保留这么多个指针,再多就释放一部分 +#define BATCH_FREE_NUM 100//每次释放的时候释放指针数量 + +/// 该类已弃用,有Hook冲突问题 +@implementation SLZombieSafeFree + +#pragma mark -------------------------- Life Circle ++ (void)load{ + + +#ifdef DEBUG +// loadCatchProxyClass(); +// init_safe_free(); +#endif + +} + + +#pragma mark -------------------------- Public Methods +//系统内存警告的时候调用这个函数释放一些内存 +void free_some_mem(size_t freeNum){ +#ifdef DEBUG + size_t count = ds_queue_length(_unfreeQueue); + freeNum= freeNum > count ? count:freeNum; + for (int i=0; i MAX_STEAL_MEM_NUM*0.9 || unfreeSize>MAX_STEAL_MEM_SIZE) { + free_some_mem(BATCH_FREE_NUM); + } + else{ + size_t memSiziee = malloc_size(p); + if (memSiziee > sYHCatchSize) {//有足够的空间才覆盖 + id obj=(id)p; + Class origClass= object_getClass(obj); + // 判断是不是objc对象 + char *type = @encode(typeof(obj)); + if (strcmp("@", type) == 0) { + memset(obj, 0x55, memSiziee); + memcpy(obj, &sYHCatchIsa, sizeof(void*));//把我们自己的类的isa复制过去 + object_setClass(obj, [SLZombieCatcher class]); + ((SLZombieCatcher *)obj).originClass = origClass; + __sync_fetch_and_add(&unfreeSize,(int)memSiziee);//多线程下int的原子加操作,多线程对全局变量进行自加,不用理线程锁了 + ds_queue_put(_unfreeQueue, p); + }else{ + orig_free(p); + } + }else{ + orig_free(p); + } + } +} + +void loadCatchProxyClass() { + sYHCatchIsa = objc_getClass("SLZombieCatcher"); + sYHCatchSize = class_getInstanceSize(sYHCatchIsa); +} + + +bool init_safe_free() { + _unfreeQueue = ds_queue_create(MAX_STEAL_MEM_NUM); + orig_free = (void(*)(void*))dlsym(RTLD_DEFAULT, "free"); + rebind_symbols((struct rebinding[]){{"free", (void*)safe_free}}, 1); + return true; +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/UINavigationController+SLMLeakFinder.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UINavigationController+SLMLeakFinder.h new file mode 100644 index 00000000..cb67044c --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UINavigationController+SLMLeakFinder.h @@ -0,0 +1,17 @@ +// +// UINavigationController+SLMLeakFinder.h +// DarkMode +// +// Created by wsl on 2020/5/6. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UINavigationController (SLMLeakFinder) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/UINavigationController+SLMLeakFinder.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UINavigationController+SLMLeakFinder.m new file mode 100644 index 00000000..81728984 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UINavigationController+SLMLeakFinder.m @@ -0,0 +1,83 @@ +// +// UINavigationController+SLMLeakFinder.m +// DarkMode +// +// Created by wsl on 2020/5/6. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "UINavigationController+SLMLeakFinder.h" +#import "SLCrashProtector.h" +#import "NSObject+SLMLeakFinder.h" + +static const void *const kSLPoppedDetailVCKey = &kSLPoppedDetailVCKey; + +@implementation UINavigationController (SLMLeakFinder) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + SL_ExchangeInstanceMethod([UINavigationController class], @selector(pushViewController:animated:), [UINavigationController class], @selector(sl_pushViewController:animated:)); + SL_ExchangeInstanceMethod([UINavigationController class], @selector(popViewControllerAnimated:), [UINavigationController class], @selector(sl_popViewControllerAnimated:)); + SL_ExchangeInstanceMethod([UINavigationController class], @selector(popToViewController:animated:), [UINavigationController class], @selector(sl_popToViewController:animated:)); + SL_ExchangeInstanceMethod([UINavigationController class], @selector(popToRootViewControllerAnimated:), [UINavigationController class], @selector(sl_popToRootViewControllerAnimated:)); + }); +} + +- (void)sl_pushViewController:(UIViewController *)viewController animated:(BOOL)animated { + if (self.splitViewController) { + id detailViewController = objc_getAssociatedObject(self, kSLPoppedDetailVCKey); + if ([detailViewController isKindOfClass:[UIViewController class]]) { + [detailViewController willDealloc]; + objc_setAssociatedObject(self, kSLPoppedDetailVCKey, nil, OBJC_ASSOCIATION_RETAIN); + } + } + [self sl_pushViewController:viewController animated:animated]; +} + +- (UIViewController *)sl_popViewControllerAnimated:(BOOL)animated { + UIViewController *poppedViewController = [self sl_popViewControllerAnimated:animated]; + [poppedViewController willDealloc]; + + if (!poppedViewController) { + return nil; + } + + // Detail VC in UISplitViewController is not dealloced until another detail VC is shown + if (self.splitViewController && + self.splitViewController.viewControllers.firstObject == self && + self.splitViewController == poppedViewController.splitViewController) { + objc_setAssociatedObject(self, kSLPoppedDetailVCKey, poppedViewController, OBJC_ASSOCIATION_RETAIN); + return poppedViewController; + } + + // VC is not dealloced until disappear when popped using a left-edge swipe gesture + extern const void *const kSLHasBeenPoppedKey; + objc_setAssociatedObject(poppedViewController, kSLHasBeenPoppedKey, @(YES), OBJC_ASSOCIATION_RETAIN); + + return poppedViewController; +} +- (NSArray *)sl_popToViewController:(UIViewController *)viewController animated:(BOOL)animated { + NSArray *poppedViewControllers = [self sl_popToViewController:viewController animated:animated]; + for (UIViewController *viewController in poppedViewControllers) { + [viewController willDealloc]; + } + return poppedViewControllers; +} +- (NSArray *)sl_popToRootViewControllerAnimated:(BOOL)animated { + NSArray *poppedViewControllers = [self sl_popToRootViewControllerAnimated:animated]; + for (UIViewController *viewController in poppedViewControllers) { + [viewController willDealloc]; + } + return poppedViewControllers; +} + +- (BOOL)willDealloc { + //如果该对象不需要释放,就return NO + if (![super willDealloc]) { + return NO; + } + return YES; +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLAsynUpdateUI.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLAsynUpdateUI.h new file mode 100644 index 00000000..64d866bc --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLAsynUpdateUI.h @@ -0,0 +1,18 @@ +// +// UIView+SLAsynUpdateUI.h +// DarkMode +// +// Created by wsl on 2020/8/7. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///异步更新UI 防护 +@interface UIView (SLAsynUpdateUI) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLAsynUpdateUI.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLAsynUpdateUI.m new file mode 100644 index 00000000..207dc33d --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLAsynUpdateUI.m @@ -0,0 +1,64 @@ +// +// UIView+SLAsynUpdateUI.m +// DarkMode +// +// Created by wsl on 2020/8/7. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "UIView+SLAsynUpdateUI.h" +#import "SLCrashProtector.h" + +@implementation UIView (SLAsynUpdateUI) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + SL_ExchangeInstanceMethod([UIView class], @selector(setNeedsLayout), [UIView class], @selector(sl_setNeedsLayout)); + SL_ExchangeInstanceMethod([UIView class], @selector(setNeedsDisplay), [UIView class], @selector(sl_setNeedsDisplay)); + SL_ExchangeInstanceMethod([UIView class], @selector(setNeedsDisplayInRect:), [UIView class], @selector(sl_setNeedsDisplayInRect:)); + }); +} + +- (void)sl_setNeedsLayout{ + if ([self isAsynUpdateUI]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self sl_setNeedsLayout]; + }); + }else { + [self sl_setNeedsLayout]; + } +} +- (void)sl_setNeedsDisplay{ + if ([self isAsynUpdateUI]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self sl_setNeedsDisplay]; + }); + }else { + [self sl_setNeedsDisplay]; + } + +} +- (void)sl_setNeedsDisplayInRect:(CGRect)rect{ + if ([self isAsynUpdateUI]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self sl_setNeedsDisplayInRect:rect]; + }); + }else { + [self sl_setNeedsDisplayInRect:rect]; + } + +} + +- (BOOL)isAsynUpdateUI{ + if(![NSThread isMainThread]){ + NSString *reason = [NSString stringWithFormat:@"异常:异步线程刷新UI"]; + SLCrashError *crashError = [SLCrashError errorWithErrorType:SLCrashErrorTypeAsynUpdateUI errorDesc:reason exception:nil callStack:[NSThread callStackSymbols]]; + [[SLCrashHandler defaultCrashHandler].delegate crashHandlerDidOutputCrashError:crashError]; + return YES; + } + return NO; +} + + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLMLeakFinder.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLMLeakFinder.h new file mode 100644 index 00000000..45a72933 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLMLeakFinder.h @@ -0,0 +1,17 @@ +// +// UIView+SLMLeakFinder.h +// DarkMode +// +// Created by wsl on 2020/5/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (SLMLeakFinder) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLMLeakFinder.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLMLeakFinder.m new file mode 100644 index 00000000..6074d0dc --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIView+SLMLeakFinder.m @@ -0,0 +1,21 @@ +// +// UIView+SLMLeakFinder.m +// DarkMode +// +// Created by wsl on 2020/5/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "UIView+SLMLeakFinder.h" +#import "NSObject+SLMLeakFinder.h" + +@implementation UIView (SLMLeakFinder) + +- (BOOL)willDealloc { + if (![super willDealloc]) { + return NO; + } + return YES; +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIViewController+SLMLeakFinder.h b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIViewController+SLMLeakFinder.h new file mode 100644 index 00000000..81d21b93 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIViewController+SLMLeakFinder.h @@ -0,0 +1,17 @@ +// +// UIViewController+SLMLeakFinder.h +// DarkMode +// +// Created by wsl on 2020/5/6. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIViewController (SLMLeakFinder) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIViewController+SLMLeakFinder.m b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIViewController+SLMLeakFinder.m new file mode 100644 index 00000000..fe6b4227 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashProtector/UIViewController+SLMLeakFinder.m @@ -0,0 +1,44 @@ +// +// UIViewController+SLMLeakFinder.m +// DarkMode +// +// Created by wsl on 2020/5/6. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "UIViewController+SLMLeakFinder.h" +#import "SLCrashProtector.h" +#import "NSObject+SLMLeakFinder.h" + +const void *const kSLHasBeenPoppedKey = &kSLHasBeenPoppedKey; + +@implementation UIViewController (SLMLeakFinder) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // SL_ExchangeInstanceMethod([UIViewController class], @selector(viewDidDisappear:), [UIViewController class], @selector(sl_viewDidDisappear:)); + // SL_ExchangeInstanceMethod([UIViewController class], @selector(viewWillAppear:), [UIViewController class], @selector(sl_viewWillAppear:)); + SL_ExchangeInstanceMethod([UIViewController class], @selector(dismissViewControllerAnimated:completion:), [UIViewController class], @selector(sl_dismissViewControllerAnimated:completion:)); + }); +} + +///dismiss时即将释放,调用willDealloc +- (void)sl_dismissViewControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion { + [self sl_dismissViewControllerAnimated:flag completion:completion]; + UIViewController *dismissedViewController = self.presentedViewController; + if (!dismissedViewController && self.presentingViewController) { + dismissedViewController = self; + } + if (!dismissedViewController) return; + [dismissedViewController willDealloc]; +} + +- (BOOL)willDealloc { + if (![super willDealloc]) { + return NO; + } + return YES; +} + +@end diff --git a/iOS_Tips/DarkMode/Crash/SLCrashViewController.h b/iOS_Tips/DarkMode/Crash/SLCrashViewController.h new file mode 100644 index 00000000..37f2d938 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashViewController.h @@ -0,0 +1,18 @@ +// +// SLCrashViewController.h +// DarkMode +// +// Created by wsl on 2020/4/11. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +//iOS Crash防护 注意:部分防护功能还不完善,比如野指针和内存泄漏/循环引用 +@interface SLCrashViewController : SLViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/Crash/SLCrashViewController.m b/iOS_Tips/DarkMode/Crash/SLCrashViewController.m new file mode 100644 index 00000000..daee8442 --- /dev/null +++ b/iOS_Tips/DarkMode/Crash/SLCrashViewController.m @@ -0,0 +1,322 @@ +// +// SLCrashViewController.m +// DarkMode +// +// Created by wsl on 2020/4/11. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLCrashViewController.h" +#import "SLCrashProtector.h" +#import "BSBacktraceLogger.h" + +/* + 参考资料: + https://www.jianshu.com/p/29051908c74b iOS Crash分析 + https://juejin.im/post/5d81fac66fb9a06af7126a44 iOS获取任意线程调用栈 + https://blog.csdn.net/jasonblog/article/details/49909209 iOS中线程Call Stack的捕获和解析(二) + https://www.jianshu.com/p/b5304d3412e4 iOS app崩溃捕获 + https://www.jianshu.com/p/8d43b4b47913 Crash产生原因 + https://developer.aliyun.com/article/499180 iOS Mach异常和signal信号 + */ +@interface SLCrashViewController () + +@property (nonatomic, strong) UITextView *textView; + +@property (nonatomic, copy) void(^testBlock)(void); //测试循环引用 +@property (nonatomic, strong) NSMutableArray *testMArray; //测试循环引用 + +//未实现的实例方法 +- (id)undefineInstanceMethodTest:(id)sender; +//未实现的类方法 ++ (id)undefineClassMethodTest:(id)sender; + +@end + +@implementation SLCrashViewController + +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUI]; +} + +#pragma mark - UI +- (void)setupUI { + self.navigationItem.title = @"iOS Crash防护"; + self.view.backgroundColor = [UIColor whiteColor]; + + NSArray *methods = @[@"testArray", + @"testMutableArray", + @"testDictionary", + @"testMutableDictionary", + @"testString", + @"testMutableString", + @"testUnrecognizedSelector", + @"testKVO", + @"testKVC", + @"testAsynUpdateUI", + @"testWildPointer", + @"testMemoryLeak"]; + NSArray *titles = @[@"数组越界、空值", + @"可变数组越界、空值", + @"字典越界、空值", + @"可变字典越界、空值", + @"字符串越界、空值", + @"可变字符串越界、空值", + @"未实现方法", + @"KVO", + @"KVC", + @"异步刷新UI", + @"野指针", + @"内存泄漏/循环引用"]; + CGSize size = CGSizeMake(self.view.sl_width/4.0, 66); + int i = 0; + for (NSString *method in methods) { + UIButton *testBtn = [[UIButton alloc] initWithFrame:CGRectMake(i%4*size.width, SL_TopNavigationBarHeight+ i/4*size.height, size.width, size.height)]; + [testBtn setTitle:titles[i] forState:UIControlStateNormal]; + testBtn.backgroundColor = UIColor.orangeColor; + testBtn.titleLabel.numberOfLines = 0; + testBtn.titleLabel.textAlignment = NSTextAlignmentCenter; + testBtn.layer.borderColor = [UIColor blackColor].CGColor; + testBtn.layer.borderWidth = 1.0; + [testBtn addTarget:self action:NSSelectorFromString(method) forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:testBtn]; + i++; + } + self.textView = [[UITextView alloc] initWithFrame:CGRectMake(0, SL_TopNavigationBarHeight+ i/4*size.height, self.view.sl_width, self.view.sl_height - (SL_TopNavigationBarHeight+ i/4*size.height))]; + self.textView.editable = NO; + self.textView.text = @"点击上方测试内容按钮,在此输出异常捕获结果..."; + [self.view addSubview:self.textView]; + + [SLCrashHandler defaultCrashHandler].delegate = self; + +} + +#pragma mark - SLCrashHandlerDelegate +///异常捕获回调 提供给外界实现自定义处理 ,日志上报等(注意线程安全) +- (void)crashHandlerDidOutputCrashError:(SLCrashError *)crashError { + NSString *errorInfo = [NSString stringWithFormat:@" 错误描述:%@ \n 调用栈:%@" ,crashError.errorDesc, crashError.callStackSymbol]; + + SL_DISPATCH_ON_MAIN_THREAD((^{ + [self.textView scrollsToTop]; + self.textView.text = errorInfo; + })); + ///日志写入缓存,适当时机上传后台 + NSString *logPath = [SL_CachesDir stringByAppendingFormat:@"/com.wsl2ls.CrashLog"]; + if(![[NSFileManager defaultManager] fileExistsAtPath:logPath]) { + [[NSFileManager defaultManager] createDirectoryAtPath:logPath withIntermediateDirectories:NO attributes:nil error:nil]; + } + if(![[NSFileManager defaultManager] fileExistsAtPath:[logPath stringByAppendingFormat:@"/log"]]) { + NSError *error; + [errorInfo writeToFile:[logPath stringByAppendingFormat:@"/log"] atomically:YES encoding:NSUTF8StringEncoding error:&error]; + }else { + NSFileHandle * fileHandle = [NSFileHandle fileHandleForWritingAtPath:[logPath stringByAppendingFormat:@"/log"]]; + [fileHandle seekToEndOfFile]; + [fileHandle writeData:[errorInfo dataUsingEncoding:NSUTF8StringEncoding]]; + [fileHandle closeFile]; + } + + //调试模式时,强制抛出异常,提醒开发者代码有问题 + #if DEBUG +// @throw crashError.exception; + #endif + +} + +#pragma mark - Container Crash +///思路来源: https://xiaozhuanlan.com/topic/6280793154 +///不可变数组防护 越界和nil值 +- (void)testArray { + //越界 + NSArray *array = @[@"且行且珍惜"]; + id elem1 = array[3]; + id elem2 = [array objectAtIndex:2]; + //nil值 + NSString *nilStr = nil; + NSArray *array1 = @[nilStr]; + NSString *strings[2]; + strings[0] = @"wsl"; + strings[1] = nilStr; + NSArray *array2 = [NSArray arrayWithObjects:strings count:2]; + NSArray *array3 = [NSArray arrayWithObject:nil]; +} +///可变数组防护 越界和nil值 +- (void)testMutableArray { + //越界 + NSMutableArray *mArray = [NSMutableArray array]; + [mArray objectAtIndex:2]; + id nilObj = mArray[2]; + [mArray insertObject:@"wsl" atIndex:1]; + [mArray removeObjectAtIndex:3]; + [mArray insertObjects:@[@"w",@"s",@"l"] atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(5, 3)]]; + [mArray replaceObjectAtIndex:5 withObject:@"wsl"]; + [mArray replaceObjectAtIndex:5 withObject:nil]; + [mArray replaceObjectsInRange:NSMakeRange(5, 3) withObjectsFromArray:@[@"w",@"s",@"l"]]; + //nil值 + [mArray insertObject:nil atIndex:3]; + NSMutableArray *mArray1 = [NSMutableArray arrayWithObject:nil]; + NSMutableArray *mArray2 = [NSMutableArray arrayWithObject:@[nilObj]]; + [mArray addObject:nilObj]; + +} + +///不可变字典防护 nil值 +- (void)testDictionary { + NSString *nilValue = nil; + NSString *nilKey = nil; + NSDictionary *dic = @{@"key":nilValue}; + dic = @{nilKey:@"value"}; + [NSDictionary dictionaryWithObject:@"value" forKey:nilKey]; + [NSDictionary dictionaryWithObject:nilValue forKey:@"key"]; + [NSDictionary dictionaryWithObjects:@[@"w",@"s",@"l"] forKeys:@[@"1",@"2",nilKey]]; +} +///可变字典防护 nil值 +- (void)testMutableDictionary { + NSString *nilValue = nil; + NSString *nilKey = nil; + NSMutableDictionary *mDict = [NSMutableDictionary dictionary]; + [mDict setValue:nilValue forKey:@"key"]; + [mDict setValue:@"value" forKey:nilKey]; + [mDict setValue:nilValue forKey:nilKey]; + [mDict removeObjectForKey:nilKey]; + mDict[nilKey] = nilValue; + NSMutableDictionary *mDict1 = [NSMutableDictionary dictionaryWithDictionary:@{nilKey:nilValue}]; +} + +///不可变字符串防护 +- (void)testString { + NSString *string = @"wsl2ls"; + [string characterAtIndex:10]; + [string substringFromIndex:20]; + [string substringToIndex:20]; + [string substringWithRange:NSMakeRange(10, 10)]; + [string substringWithRange:NSMakeRange(2, 10)]; +} +///可变字符串防护 +- (void)testMutableString { + NSMutableString *stringM = [NSMutableString stringWithFormat:@"wsl2ls"]; + stringM = [NSMutableString stringWithFormat:@"wsl"]; + [stringM insertString:@"😍" atIndex:10]; + + stringM = [NSMutableString stringWithFormat:@"2"]; + [stringM deleteCharactersInRange:NSMakeRange(2, 20)]; + + stringM = [NSMutableString stringWithFormat:@"ls"]; + [stringM deleteCharactersInRange:NSMakeRange(10, 10)]; +} + +#pragma mark - Unrecognized Selector +/// 测试未识别方法 crash防护 +- (void)testUnrecognizedSelector { + //未定义、未实现的实例方法 + [self performSelector:@selector(undefineInstanceMethodTest:)]; + //未定义、未实现的类方法 + [[self class] performSelector:@selector(undefineClassMethodTest:)]; +} + +#pragma mark - KVO +/// 测试KVO防护 +- (void)testKVO { + //被观察对象提前释放 导致Crash + UILabel *label = [[UILabel alloc] init]; + [label addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil]; + //没有移除观察者 + [self addObserver:self forKeyPath:@"view" options:NSKeyValueObservingOptionNew context:nil]; + + [self addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil]; + + //重复移除 导致Crash + [self removeObserver:self forKeyPath:@"title"]; + [self removeObserver:self forKeyPath:@"title" context:nil]; + //移除未注册的观察者 + [self removeObserver:self forKeyPath:@"modalTransitionStyle"]; +} + +#pragma mark - KVC +/// 测试KVC防护 +- (void)testKVC { + NSString *nilKey = nil; + NSString *nilValue = nil; + // key 为nil + [self setValue:@"wsl" forKey:nilKey]; + // Value 为nil + [self setValue:nilValue forKey:@"name"]; + // key 不是对象的属性 + [self setValue:@"wsl" forKey:@"noProperty"]; + [self setValue:@"wsl" forKeyPath:@"self.noProperty"]; +} + +#pragma mark - 异步刷新UI +///异步刷新UI +- (void)testAsynUpdateUI { + dispatch_async(dispatch_get_global_queue(0, 0), ^{ + UILabel* newView = [[UILabel alloc] initWithFrame:CGRectMake(0,0,150, 88)]; + newView.center = CGPointMake(self.view.sl_width/2.0, self.view.sl_height/2.0); + newView.backgroundColor = [UIColor greenColor]; + newView.text = @"异步刷新UI"; + [self.view addSubview:newView]; + [SLDelayPerform sl_startDelayPerform:^{ + [newView removeFromSuperview]; + } afterDelay:2.0]; + }); +} + +#pragma mark - 野指针 +///野指针 随机性太强,不方便复现和定位问题,我们需要做的就是把随机变为必现,并且定位到对应的代码,方便查找解决 +///思路来源: https://www.jianshu.com/p/9fd4dc046046?utm_source=oschina-app +- (void)testWildPointer { + //开启僵尸对象嗅探定位 可以打开或关闭此开关看看效果就知道了 + // 目前还不完善,不推荐使用 ,仅做交流学习 + [SLZombieFinder startSniffer]; + + + UILabel *label = [[UILabel alloc] init]; + //-fno-objc-arc 记得设置此类编译方式支持MRC + //testObj对象所在的内存空间已释放 + [label release]; + + //这时新建一个示例对象,覆盖掉了野指针label所指向的内存空间,如果此时没有创建此同类,就会崩溃 + UILabel* newView = [[UILabel alloc] initWithFrame:CGRectMake(0,SL_kScreenHeight- 60,SL_kScreenWidth, 60)]; + newView.backgroundColor = [UIColor greenColor]; + newView.text = @"startSniffer开启 显示正常"; + [self.view addSubview:newView]; + + //向野指针label指向的内存对象发送修改颜色的消息,结果是newView接收到了,因为newView和label是同类,可以处理此消息,所以没有崩溃; 在不开启startSniffer时,就把newView的backgroundColor修改了,开启startSniffer后,阻断了向野指针发消息的过程 + label.backgroundColor = [UIColor orangeColor]; + label.text = @"startSniffer关闭 我是野指针,显示错误"; + + + [SLDelayPerform sl_startDelayPerform:^{ + [newView removeFromSuperview]; + } afterDelay:2.0]; + +} + +#pragma mark - 内存泄漏/循环引用 +///测试是否内存泄漏/循环引用 +//思路来源:https://github.com/Tencent/MLeaksFinder.git +//查找循引用连 FBRetainCycleDetector https://yq.aliyun.com/articles/66857 、 https://blog.csdn.net/majiakun1/article/details/78747226 +- (void)testMemoryLeak { + + //执行此方法后,返回上一级界面,发现SLCrashViewController对象没释放 + self.testBlock = ^{ + self; + }; + // self.testMArray = [[NSMutableArray alloc] initWithObjects:self, nil]; +} + +#pragma mark - 获取函数调用栈 +///获取任意线程的函数调用栈 https://toutiao.io/posts/aveig6/preview +- (void)testCallStack { + //打印当前线程调用栈 + BSLOG; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + //在子线程中 打印主线程调用栈,会发现栈基本是空的,因为都已释放了 + // BSLOG_MAIN + // BSLOG; + }); + // BSLOG_MAIN +} + +@end diff --git a/.DS_Store b/iOS_Tips/DarkMode/General/.DS_Store similarity index 63% rename from .DS_Store rename to iOS_Tips/DarkMode/General/.DS_Store index 5395b3f8..51725728 100644 Binary files a/.DS_Store and b/iOS_Tips/DarkMode/General/.DS_Store differ diff --git a/iOS_Tips/DarkMode/General/AV/SLAvCaptureTool.m b/iOS_Tips/DarkMode/General/AV/SLAvCaptureTool.m index 36d714f0..55d8ff68 100644 --- a/iOS_Tips/DarkMode/General/AV/SLAvCaptureTool.m +++ b/iOS_Tips/DarkMode/General/AV/SLAvCaptureTool.m @@ -408,17 +408,17 @@ - (void)stopRecordVideo { if (_isRecording) { _isRecording = NO; __weak typeof(self) weakSelf = self; - if(_assetWriter && _assetWriter.status == AVAssetWriterStatusWriting) { + if(_assetWriter && self.canWrite && self.assetWriter.status != AVAssetWriterStatusUnknown) { [_assetWriter finishWritingWithCompletionHandler:^{ - weakSelf.canWrite = NO; - weakSelf.assetWriter = nil; - weakSelf.assetWriterAudioInput = nil; - weakSelf.assetWriterVideoInput = nil; if ([weakSelf.delegate respondsToSelector:@selector(captureTool:didFinishRecordingToOutputFileAtURL:error:)]) { SL_DISPATCH_ON_MAIN_THREAD(^{ [weakSelf.delegate captureTool:weakSelf didFinishRecordingToOutputFileAtURL:weakSelf.outputFileURL error:weakSelf.assetWriter.error]; }); } + weakSelf.canWrite = NO; + weakSelf.assetWriter = nil; + weakSelf.assetWriterAudioInput = nil; + weakSelf.assetWriterVideoInput = nil; }]; } } @@ -448,17 +448,17 @@ - (void)stopRecordAudio { if (_isRecording) { _isRecording = NO; __weak typeof(self) weakSelf = self; - if(_assetWriter && _assetWriter.status == AVAssetWriterStatusWriting) { + if(_assetWriter && self.canWrite && self.assetWriter.status != AVAssetWriterStatusUnknown) { [_assetWriter finishWritingWithCompletionHandler:^{ - weakSelf.canWrite = NO; - weakSelf.assetWriter = nil; - weakSelf.assetWriterAudioInput = nil; - weakSelf.assetWriterVideoInput = nil; if ([weakSelf.delegate respondsToSelector:@selector(captureTool:didFinishRecordingToOutputFileAtURL:error:)]) { SL_DISPATCH_ON_MAIN_THREAD(^{ [weakSelf.delegate captureTool:weakSelf didFinishRecordingToOutputFileAtURL:weakSelf.outputFileURL error:weakSelf.assetWriter.error]; }); } + weakSelf.canWrite = NO; + weakSelf.assetWriter = nil; + weakSelf.assetWriterAudioInput = nil; + weakSelf.assetWriterVideoInput = nil; }]; } } @@ -571,10 +571,14 @@ - (void)writerVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:( if (self.assetWriterVideoInput.readyForMoreMediaData) { BOOL success = [self.assetWriterVideoInput appendSampleBuffer:sampleBuffer]; if (!success) { - @synchronized (self) { - [self stopRecordVideo]; - } + NSLog(@"视频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription); } + + // if (!success) { + // @synchronized (self) { + // [self stopRecordVideo]; + // } + // } } } } @@ -592,14 +596,17 @@ - (void)writerAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:( //写入音频数据 BOOL success = [self.assetWriterAudioInput appendSampleBuffer:sampleBuffer]; if (!success) { - @synchronized (self) { - if (self.avCaptureType == SLAvCaptureTypeAudio) { - [self stopRecordAudio]; - }else if (self.avCaptureType == SLAvCaptureTypeAv || self.avCaptureType == SLAvCaptureTypeVideo) { - [self stopRecordVideo]; - } - } + NSLog(@"音频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription); } + // if (!success) { + // @synchronized (self) { + // if (self.avCaptureType == SLAvCaptureTypeAudio) { + // [self stopRecordAudio]; + // }else if (self.avCaptureType == SLAvCaptureTypeAv || self.avCaptureType == SLAvCaptureTypeVideo) { + // [self stopRecordVideo]; + // } + // } + // } } } } diff --git a/iOS_Tips/DarkMode/General/AV/SLAvWriterInput.m b/iOS_Tips/DarkMode/General/AV/SLAvWriterInput.m index ef75a44b..5f88bc70 100644 --- a/iOS_Tips/DarkMode/General/AV/SLAvWriterInput.m +++ b/iOS_Tips/DarkMode/General/AV/SLAvWriterInput.m @@ -179,6 +179,7 @@ - (void)writingVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection: if (self.assetWriter.status == AVAssetWriterStatusWriting) { BOOL success = [self.inputPixelBufferAdptor appendPixelBuffer:newPixelBuffer withPresentationTime:self.currentSampleTime]; if (!success) { + NSLog(@"视频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription); [self finishWriting]; } } @@ -204,6 +205,7 @@ - (void)writingVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection: BOOL success = [self.assetWriterVideoInput appendSampleBuffer:sampleBuffer]; if (!success) { @synchronized (self) { + NSLog(@"视频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription); [self finishWriting]; } } @@ -227,6 +229,7 @@ - (void)writingAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection: BOOL success = [self.assetWriterAudioInput appendSampleBuffer:sampleBuffer]; if (!success) { @synchronized (self) { + NSLog(@"音频写入失败, 错误:%@" ,self.assetWriter.error.localizedDescription); [self finishWriting]; } } @@ -237,19 +240,19 @@ - (void)writingAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection: /// 完成写入 - (void)finishWriting { __weak typeof(self) weakSelf = self; - if(_assetWriter && _assetWriter.status == AVAssetWriterStatusWriting) { + if(_assetWriter && self.isStartWriting && self.assetWriter.status != AVAssetWriterStatusUnknown) { [_assetWriter finishWritingWithCompletionHandler:^{ + if ([weakSelf.delegate respondsToSelector:@selector(writerInput:didFinishRecordingToOutputFileAtURL:error:)]) { + SL_DISPATCH_ON_MAIN_THREAD(^{ + [weakSelf.delegate writerInput:weakSelf didFinishRecordingToOutputFileAtURL:weakSelf.outputFileURL error:weakSelf.assetWriter.error]; + }); + } weakSelf.isStartWriting = NO; weakSelf.inputPixelBufferAdptor = nil; weakSelf.context = nil; weakSelf.assetWriter = nil; weakSelf.assetWriterAudioInput = nil; weakSelf.assetWriterVideoInput = nil; - if ([weakSelf.delegate respondsToSelector:@selector(writerInput:didFinishRecordingToOutputFileAtURL:error:)]) { - SL_DISPATCH_ON_MAIN_THREAD(^{ - [weakSelf.delegate writerInput:weakSelf didFinishRecordingToOutputFileAtURL:weakSelf.outputFileURL error:weakSelf.assetWriter.error]; - }); - } }]; } } diff --git a/iOS_Tips/DarkMode/General/Tool/HMLog.h b/iOS_Tips/DarkMode/General/Tool/HMLog.h new file mode 100644 index 00000000..ef1bf35f --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/HMLog.h @@ -0,0 +1,236 @@ +// The MIT License (MIT) +// +// Copyright (c) 2020 Huimao Chen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// GCC_C_LANGUAGE_STANDARD = gnu99 + +#ifndef HMLog_h +#define HMLog_h + +#import + +#pragma mark - Parameters + +// All optional parameters should be defined before import "HMLog.h", or you can modify the source code + +#ifndef HMLogEnable +#define HMLogEnable 1 +#endif // HMLogEnable + +#ifndef HMPrintEnable +#define HMPrintEnable 1 +#endif // HMPrintEnable + +#ifndef HMLogPrefix +#define HMLogPrefix(index, valueString) [NSString stringWithFormat:@"%d: %s = ", index, valueString] +#endif // HMLogPrefix + +#ifndef HMLogHeaderFormatString +#define HMLogHeaderFormatString(FUNC, LINE) \ + [NSString stringWithFormat:@"================ %s [%d] ================\n", FUNC, LINE] +#endif // HMLogHeaderFormatString + +#ifndef HMLogTypeExtension +#define HMLogTypeExtension +#endif // HMLogTypeExtension + +#pragma mark - Core + +// format, private macro +#define _HMLogFormat(VAR) \ + , HMStringify(VAR), @encode(__typeof__(VAR)), (VAR) + +// HMFormatString +#define HMFormatString(...) \ + HMExpand(_HMFormatString(__func__, __LINE__, HMArgCount(__VA_ARGS__) HMForeach(_HMLogFormat, __VA_ARGS__))) + +// HMLog +#if HMLogEnable + #define HMLog(...) \ + _HMLog(HMFormatString(__VA_ARGS__)) +#else + #define HMLog(...) +#endif // HMLogEnable + +// HMPrint +#if HMPrintEnable + #define HMPrint(...) \ + _HMPrint(HMFormatString(__VA_ARGS__)) +#else + #define HMPrint(...) +#endif // HMPrintEnable + + +static inline NSString * _HMFormatString(const char *func, int line, int count, ...) { // func, line, count, [valueString, TypeEncode, value] + NSMutableString *result = [[NSMutableString alloc] init]; + + // handle header + [result appendString:HMLogHeaderFormatString(func, line)]; + + // handle arguments + va_list v; + va_start(v, count); + for (int i = 0; i < count; ++i) { + char *valueString = va_arg(v, char *); + char *type = va_arg(v, char *); + + id obj = nil; + if (strcmp(type, @encode(id)) == 0) { // "@" id + id actual = va_arg(v, id); + obj = actual; + + } else if (strcmp(type, @encode(CGPoint)) == 0) { // "{CGPoint=dd}" CGPoint + CGPoint actual = (CGPoint)va_arg(v, CGPoint); + obj = [NSValue value:&actual withObjCType:type]; + + } else if (strcmp(type, @encode(CGSize)) == 0) { // "{CGSize=dd}" CGSize + CGSize actual = (CGSize)va_arg(v, CGSize); + obj = [NSValue value:&actual withObjCType:type]; + + } else if (strcmp(type, @encode(CGRect)) == 0) { // "{CGRect={CGPoint=dd}{CGSize=dd}}" CGRect + CGRect actual = (CGRect)va_arg(v, CGRect); + obj = [NSValue value:&actual withObjCType:type]; + + } else if (strcmp(type, @encode(UIEdgeInsets)) == 0) { // "{UIEdgeInsets=dddd}" UIEdgeInsets + UIEdgeInsets actual = (UIEdgeInsets)va_arg(v, UIEdgeInsets); + obj = NSStringFromUIEdgeInsets(actual); + + } else if (strcmp(type, @encode(NSRange)) == 0) { // "{_NSRange=QQ}" NSRange + NSRange actual = (NSRange)va_arg(v, NSRange); + obj = NSStringFromRange(actual); + + } else if (strcmp(type, @encode(SEL)) == 0) { // ":" SEL + SEL actual = (SEL)va_arg(v, SEL); + obj = [NSString stringWithFormat:@"SEL: %@", NSStringFromSelector(actual)]; + + } else if (strcmp(type, @encode(Class)) == 0) { // "#" Class + Class actual = (Class)va_arg(v, Class); + obj = NSStringFromClass(actual); + + } else if (strcmp(type, @encode(char *)) == 0) { // "*" char * + char * actual = (char *)va_arg(v, char *); + obj = [NSString stringWithFormat:@"%s", actual]; + + } else if (strcmp(type, @encode(double)) == 0) { // "d" double + double actual = (double)va_arg(v, double); + obj = [NSNumber numberWithDouble:actual]; + + } else if (strcmp(type, @encode(float)) == 0) { // "f" float + float actual = (float)va_arg(v, double); + obj = [NSNumber numberWithFloat:actual]; + + } else if (strcmp(type, @encode(int)) == 0) { // "i" int + int actual = (int)va_arg(v, int); + obj = [NSNumber numberWithInt:actual]; + + } else if (strcmp(type, @encode(long)) == 0) { // "q" long + long actual = (long)va_arg(v, long); + obj = [NSNumber numberWithLong:actual]; + + } else if (strcmp(type, @encode(long long)) == 0) { // "q" long long + long long actual = (long long)va_arg(v, long long); + obj = [NSNumber numberWithLongLong:actual]; + + } else if (strcmp(type, @encode(short)) == 0) { // "s" short + short actual = (short)va_arg(v, int); + obj = [NSNumber numberWithShort:actual]; + + } else if (strcmp(type, @encode(char)) == 0) { // "c" char & BOOL(32bit) + char actual = (char)va_arg(v, int); + obj = [NSString stringWithFormat:@"%d char:%c", actual, actual]; + + } else if (strcmp(type, @encode(bool)) == 0) { // "B" bool & BOOL(64bit) + bool actual = (bool)va_arg(v, int); + obj = actual ? @"YES" : @"NO"; + + } else if (strcmp(type, @encode(unsigned char)) == 0) { // "C" unsigned char + unsigned char actual = (unsigned char)va_arg(v, unsigned int); + obj = [NSString stringWithFormat:@"%d unsigned char:%c", actual, actual]; + + } else if (strcmp(type, @encode(unsigned int)) == 0) { // "I" unsigned int + unsigned int actual = (unsigned int)va_arg(v, unsigned int); + obj = [NSNumber numberWithUnsignedInt:actual]; + + } else if (strcmp(type, @encode(unsigned long)) == 0) { // "Q" unsigned long + unsigned long actual = (unsigned long)va_arg(v, unsigned long); + obj = [NSNumber numberWithUnsignedLong:actual]; + + } else if (strcmp(type, @encode(unsigned long long)) == 0) { // "Q" unsigned long long + unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long); + obj = [NSNumber numberWithUnsignedLongLong:actual]; + + } else if (strcmp(type, @encode(unsigned short)) == 0) { // "S" unsigned short + unsigned short actual = (unsigned short)va_arg(v, unsigned int); + obj = [NSNumber numberWithUnsignedShort:actual]; + + } HMLogTypeExtension else { + [result appendString:@"Error: unknown type"]; + break; + } + + [result appendFormat:@"%@%@\n", ((void)(valueString), HMLogPrefix(i, valueString)), obj]; + } + va_end(v); + + return [result copy]; +} + +static inline void _HMLog(NSString *str) { + NSLog(@"\n%@", str); +} + +static inline void _HMPrint(NSString *str) { + printf("%s\n", str.UTF8String); +} + +#pragma mark - Helper + +#define HMStringify(VALUE) _HMStringify(VALUE) +#define _HMStringify(VALUE) # VALUE + +#define HMConcat(A, B) _HMConcat(A, B) +#define _HMConcat(A, B) A ## B + +// Return the number of arguments (up to twenty) provided to the macro. +#define HMArgCount(...) _HMArgCount(A, ##__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define _HMArgCount(A, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, COUNT, ...) COUNT + +// If the number of arguments is 0, return 0, otherwise return N. +#define HMArgCheck(...) _HMArgCheck(A, ##__VA_ARGS__, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, 0) +#define _HMArgCheck(A, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, OBJ, ...) OBJ + +// Each argument will be passed to the MACRO, the MACRO must be this form: MACRO(arg). Inspired by https://stackoverflow.com/questions/3136686/is-the-c99-preprocessor-turing-complete/10526117#10526117 +#define HMForeach(MACRO, ...) HMConcat(_HMForeach, HMArgCheck(__VA_ARGS__)) (MACRO, ##__VA_ARGS__) +#define _HMForeach() HMForeach +#define _HMForeach0(MACRO) +#define _HMForeachN(MACRO, A, ...) MACRO(A) HMDefer(_HMForeach)() (MACRO, ##__VA_ARGS__) + +#define HMEmpty() +#define HMDefer(ID) ID HMEmpty() + +// For more scans +#define HMExpand(...) _HMExpand1(_HMExpand1(_HMExpand1(__VA_ARGS__))) +#define _HMExpand1(...) _HMExpand2(_HMExpand2(_HMExpand2(__VA_ARGS__))) +#define _HMExpand2(...) _HMExpand3(_HMExpand3(_HMExpand3(__VA_ARGS__))) +#define _HMExpand3(...) __VA_ARGS__ + + +#endif // HMLog_h diff --git a/iOS_Tips/DarkMode/General/Tool/PrefixHeader.pch b/iOS_Tips/DarkMode/General/Tool/PrefixHeader.pch index 3bb75f28..b2851d91 100644 --- a/iOS_Tips/DarkMode/General/Tool/PrefixHeader.pch +++ b/iOS_Tips/DarkMode/General/Tool/PrefixHeader.pch @@ -9,61 +9,26 @@ #ifndef PrefixHeader_pch #define PrefixHeader_pch -// Include any system framework and library headers here that should be included in all compilation units. -// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. +//如何安装: +//1.创建新pch文件,默认名字即可: "PrefixHeader.pch". +//2.点击下一步再去Build Settings 搜索Prefix Header. +//3.找到Prefix Header并且双击,输入$(SRCROOT)/$(PROJECT_NAME)/PrefixHeader.pch +//现在你可以在项目内任何文件调用宏定义了,祝一切顺利. #ifdef __OBJC__ // 只被object-c文件所引用 -// 这个定义全工程都可以调用,不用重复引入 - -///我的简书和Github地址 -#define SL_JianShuUrl @"https://www.jianshu.com/u/e15d1f644bea" -#define SL_GithubUrl @"https://github.com/wsl2ls/iOS_Tips.git" - -//----------------------About UI ---------------------------- - -/// 屏幕宽高 -#define SL_kScreenWidth [UIScreen mainScreen].bounds.size.width -#define SL_kScreenHeight [UIScreen mainScreen].bounds.size.height - -/// 弱引用对象 -#define SL_WeakSelf __weak typeof(self) weakSelf = self; - -///主线程操作 -#define SL_DISPATCH_ON_MAIN_THREAD(mainQueueBlock) dispatch_async(dispatch_get_main_queue(),mainQueueBlock); - -//----------------------About Color 颜色 ---------------------------- - -/// 随机颜色 -#define SL_UIColorFromRandomColor [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1.0] -/// rgb颜色 -#define SL_UIColorFromRGB(r,g,b,a) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:(a)] -/// 16进制 颜色 -#define SL_UIColorFromHex(rgbValue, a) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:(a)] - -//----------------------About Log 打印日志 ---------------------------- - -/// 打印 -#define SL_NSLog(...) printf("%f %s %ld :%s\n",[[NSDate date]timeIntervalSince1970],strrchr(__FILE__,'/'),[[NSNumber numberWithInt:__LINE__] integerValue],[[NSString stringWithFormat:__VA_ARGS__]UTF8String]); -#ifdef DEBUG -# define SL_Log(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); -#else -# define SL_Log(...) -#endif - -//----------------------About Shader 着色器 ---------------------------- -//#x 将参数x字符串化 -#define STRINGIZE(x) #x -#define STRINGIZE2(x) STRINGIZE(x) -#define Shader_String(text) @ STRINGIZE2(text) +#import "SLToolMacro.h" #import "UIView+SLFrame.h" #import "SLDelayPerform.h" - -#endif +#import "SLAlertView.h" +#import "Masonry.h" +#import "SLMethod.h" +#import "SLViewController.h" +#endif #endif /* PrefixHeader_pch */ diff --git a/iOS_Tips/DarkMode/General/Tool/SLKeyChain.h b/iOS_Tips/DarkMode/General/Tool/SLKeyChain.h new file mode 100644 index 00000000..fafe9f4b --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLKeyChain.h @@ -0,0 +1,40 @@ +// +// SLKeyChain.h +// DarkMode +// +// Created by wsl on 2020/6/15. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXTERN NSString* const SLkeychainService; + +///存储管理用户账号和密码到钥匙串 +@interface SLKeyChain : NSObject + +/// 保存用户信息到钥匙串中 +/// @param service 存储服务的key,一个service可以存储多个account/password键值对 +/// @param account 账号 +/// @param password 密码 ++ (NSError *)saveKeychainWithService:(NSString *)service + account:(NSString *)account + password:(NSString *)password; +///从钥匙串中删除这条用户信息 ++ (NSError *)deleteWithService:(NSString *)service + account:(NSString *)account; + +///查询用户信息 查到的结果存在NSError中 ++ (NSError *)queryKeychainWithService:(NSString *)service + account:(NSString *)account; + +///更新钥匙串中的用户名和密码 ++ (NSError *)updateKeychainWithService:(NSString *)service + account:(NSString *)account + password:(NSString *)password; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/General/Tool/SLKeyChain.m b/iOS_Tips/DarkMode/General/Tool/SLKeyChain.m new file mode 100644 index 00000000..d3cd1acf --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLKeyChain.m @@ -0,0 +1,141 @@ +// +// SLKeyChain.m +// DarkMode +// +// Created by wsl on 2020/6/15. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLKeyChain.h" +#import +#import + +NSString* const SLkeychainService = @"com.wsl.ios.keychain"; +static NSString* const keychainErrorDomain = @"com.wsl.ios.keychain.errorDomain"; +static NSInteger const kErrorCodeKeychainSomeArgumentsInvalid = 1000; //! 传入的部分参数无效 + +@implementation SLKeyChain + +///更新钥匙串中的用户名和密码 ++ (NSError *)updateKeychainWithService:(NSString *)service account:(NSString *)account password:(NSString *)password { + + if (!account || !password || !service) { + NSError *error = [self errorWithErrorCode:kErrorCodeKeychainSomeArgumentsInvalid]; + return error; + } + NSDictionary *queryItems = @{(id)kSecClass: (id)kSecClassGenericPassword, + (id)kSecAttrService: service, + (id)kSecAttrAccount: account + }; + NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *updatedItems = @{ + (id)kSecValueData: passwordData, + }; + OSStatus updateStatus = SecItemUpdate((CFDictionaryRef)queryItems, (CFDictionaryRef)updatedItems); + return [self errorWithErrorCode:updateStatus]; +} +///删除用户信息 ++ (NSError *)deleteWithService:(NSString *)service account:(NSString *)account { + + if (!service || !account) { + return [self errorWithErrorCode:kErrorCodeKeychainSomeArgumentsInvalid]; + } + NSDictionary *deleteSecItems = @{ + (id)kSecClass: (id)kSecClassGenericPassword, + (id)kSecAttrService: service, + (id)kSecAttrAccount: account + }; + OSStatus errorCode = SecItemDelete((CFDictionaryRef)deleteSecItems); + return [self errorWithErrorCode:errorCode]; +} +///查询用户信息 查到的结果存在NSError中 ++ (NSError *)queryKeychainWithService:(NSString *)service account:(NSString *)account { + + if (!service || !account) { + return [self errorWithErrorCode:kErrorCodeKeychainSomeArgumentsInvalid]; + } + NSDictionary *matchSecItems = @{ + (id)kSecClass: (id)kSecClassGenericPassword, + (id)kSecAttrService: service, + (id)kSecAttrAccount: account, + (id)kSecMatchLimit: (id)kSecMatchLimitOne, + (id)kSecReturnData: @(YES) + }; + CFTypeRef dataRef = nil; + OSStatus errorCode = SecItemCopyMatching((CFDictionaryRef)matchSecItems, (CFTypeRef *)&dataRef); + if (errorCode == errSecSuccess) { + NSString *password = [[NSString alloc] initWithData:CFBridgingRelease(dataRef) encoding:NSUTF8StringEncoding]; + return [self errorWithErrorCode:errSecSuccess errorMessage:password]; + } + return [self errorWithErrorCode:errorCode]; +} +///保存用户信息 ++ (NSError *)saveKeychainWithService:(NSString *)service account:(NSString *)account password:(NSString *)password { + + if (!account || !password || !service) { + NSError *error = [self errorWithErrorCode:kErrorCodeKeychainSomeArgumentsInvalid]; + return error; + } + + NSError *queryError = [self queryKeychainWithService:service account:account]; + if (queryError.code == errSecSuccess) { + // update + return [self updateKeychainWithService:service account:account password:password]; + } + + NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding]; + // save + NSDictionary *saveSecItems = @{(id)kSecClass: (id)kSecClassGenericPassword, + (id)kSecAttrService: service, + (id)kSecAttrAccount: account, + (id)kSecValueData: passwordData + }; + OSStatus saveStatus = SecItemAdd((CFDictionaryRef)saveSecItems, NULL); + return [self errorWithErrorCode:saveStatus]; +} +///错误信息 ++ (NSError *)errorWithErrorCode:(OSStatus)errorCode { + + NSString *errorMsg = nil; + + switch (errorCode) { + case errSecSuccess: { + NSLog(@"操作成功"); + return nil; + break; + } + case kErrorCodeKeychainSomeArgumentsInvalid: + errorMsg = NSLocalizedString(@"参数无效", nil); + break; + case errSecDuplicateItem: // -25299 + errorMsg = NSLocalizedString(@"The specified item already exists in the keychain. ", nil); + break; + case errSecItemNotFound: // -25300 + errorMsg = NSLocalizedString(@"The specified item could not be found in the keychain. ", nil); + break; + default: { + if (@available(iOS 11.3, *)) { + errorMsg = (__bridge_transfer NSString *)SecCopyErrorMessageString(errorCode, NULL); + } + break; + } + } + NSDictionary *errorUserInfo = nil; + if (errorMsg) { + errorUserInfo = @{NSLocalizedDescriptionKey: errorMsg}; + NSLog(@"%s--Line:%d--错误码:%d--错误信息:%@", __FUNCTION__, __LINE__, errorCode, errorMsg); + } + return [NSError errorWithDomain:keychainErrorDomain code:kErrorCodeKeychainSomeArgumentsInvalid userInfo:errorUserInfo]; +} + ++ (NSError *)errorWithErrorCode:(OSStatus)errCode errorMessage:(NSString *)errorMsg { + + if (errCode == errSecSuccess && errorMsg) { + NSLog(@"操作成功"); + return [NSError errorWithDomain:keychainErrorDomain code:errSecSuccess userInfo:@{NSLocalizedDescriptionKey: errorMsg}]; + } else { + return [self errorWithErrorCode:errCode]; + } +} + +@end diff --git a/iOS_Tips/DarkMode/General/Tool/SLMethod.h b/iOS_Tips/DarkMode/General/Tool/SLMethod.h new file mode 100644 index 00000000..616e0520 --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLMethod.h @@ -0,0 +1,65 @@ +// +// SLMethod.h +// DarkMode +// +// Created by wsl on 2020/4/24. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +//四个圆角半径 +struct SLCornerRadii { + CGFloat topLeft; //左上 + CGFloat topRight; //右上 + CGFloat bottomLeft; //左下 + CGFloat bottomRight; //右下 +}; +typedef struct CG_BOXABLE SLCornerRadii SLCornerRadii; +//SLCornerRadii初始化函数 +CG_INLINE SLCornerRadii SLCornerRadiiMake(CGFloat topLeft,CGFloat topRight,CGFloat bottomLeft,CGFloat bottomRight){ + return (SLCornerRadii){ + topLeft, + topRight, + bottomLeft, + bottomRight, + }; +} + +static NSString * const SLUserDefaultsKey = @"SLUserDefaultsKey"; + +/// 辅助公共方法集合 +@interface SLMethod : NSObject + +/// 以SLUserDefaultsKey为根key,统一管理userDefaults存储的数据 ++ (void)userDefaultsSetObject:(nullable id)value forKey:(NSString *)key; ++ (id)userDefaultsObjectForKey:(NSString *)key; + +/** + * 动态计算文字的宽高 + * @param text 文字 + * @param font 文字的font + * @param maxSize 最大 size + * @return 返回text的size + */ ++ (CGSize)sizeFromText:(NSString *)text textFont:(UIFont *)font maxSize:(CGSize)maxSize; + +/** + 动态计算属性字符串的宽高 + + @param attributedText 属性字符串 + @param maxSize 最大 size + @return 返回属性字符串的size + */ ++ (CGSize)sizeFromAttributedText:(NSAttributedString *)attributedText maxSize:(CGSize)maxSize; + +/// 切四个不同半径圆角的函数 +/// @param bounds 区域 +/// @param cornerRadii 四个圆角的半径 ++ (CGPathRef)cornerPathCreateWithRoundedRect:(CGRect)bounds cornerRadii:(SLCornerRadii)cornerRadii; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/General/Tool/SLMethod.m b/iOS_Tips/DarkMode/General/Tool/SLMethod.m new file mode 100644 index 00000000..bf5a669d --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLMethod.m @@ -0,0 +1,78 @@ +// +// SLMethod.m +// DarkMode +// +// Created by wsl on 2020/4/24. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLMethod.h" + +@implementation SLMethod + ++ (void)userDefaultsSetObject:(nullable id)value forKey:(NSString *)key { + NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults]; + NSDictionary *dict = @{key:value}; + [userDefault setObject:dict forKey:SLUserDefaultsKey]; + [userDefault synchronize]; +} ++ (id)userDefaultsObjectForKey:(NSString *)key { + NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults]; + NSDictionary *dict = [userDefault objectForKey:SLUserDefaultsKey]; + return dict[key]; +} + +/// 动态计算文字的宽高 ++ (CGSize)sizeFromText:(NSString *)text textFont:(UIFont *)font maxSize:(CGSize)maxSize { + if(text == nil || text == NULL || [text isKindOfClass:[NSNull class]] || [[text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0){ + return CGSizeZero; + } + NSDictionary *attrs = @{NSFontAttributeName:font}; + CGSize size = [text boundingRectWithSize:CGSizeMake(maxSize.width, maxSize.height) options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size; + return CGSizeMake(ceil(size.width), ceil(size.height)); +} ++ (CGSize)sizeFromAttributedText:(NSAttributedString *)attributedText maxSize:(CGSize)maxSize +{ + if (attributedText == nil) { + return CGSizeZero; + } + CGSize size = [attributedText boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil].size; + return CGSizeMake(ceil(size.width), (ceil(size.height)+1)); +} + +/// 切四个不同半径圆角的函数 +/// @param bounds 区域 +/// @param cornerRadii 四个圆角的半径 ++ (CGPathRef)cornerPathCreateWithRoundedRect:(CGRect)bounds cornerRadii:(SLCornerRadii)cornerRadii { + const CGFloat minX = CGRectGetMinX(bounds); + const CGFloat minY = CGRectGetMinY(bounds); + const CGFloat maxX = CGRectGetMaxX(bounds); + const CGFloat maxY = CGRectGetMaxY(bounds); + + const CGFloat topLeftCenterX = minX + cornerRadii.topLeft; + const CGFloat topLeftCenterY = minY + cornerRadii.topLeft; + + const CGFloat topRightCenterX = maxX - cornerRadii.topRight; + const CGFloat topRightCenterY = minY + cornerRadii.topRight; + + const CGFloat bottomLeftCenterX = minX + cornerRadii.bottomLeft; + const CGFloat bottomLeftCenterY = maxY - cornerRadii.bottomLeft; + + const CGFloat bottomRightCenterX = maxX - cornerRadii.bottomRight; + const CGFloat bottomRightCenterY = maxY - cornerRadii.bottomRight; + //虽然顺时针参数是YES,在iOS中的UIView中,这里实际是逆时针 + + CGMutablePathRef path = CGPathCreateMutable(); + //顶 左 + CGPathAddArc(path, NULL, topLeftCenterX, topLeftCenterY,cornerRadii.topLeft, M_PI, 3 * M_PI_2, NO); + //顶 右 + CGPathAddArc(path, NULL, topRightCenterX , topRightCenterY, cornerRadii.topRight, 3 * M_PI_2, 0, NO); + //底 右 + CGPathAddArc(path, NULL, bottomRightCenterX, bottomRightCenterY, cornerRadii.bottomRight,0, M_PI_2, NO); + //底 左 + CGPathAddArc(path, NULL, bottomLeftCenterX, bottomLeftCenterY, cornerRadii.bottomLeft, M_PI_2,M_PI, NO); + CGPathCloseSubpath(path); + return path; +} + +@end diff --git a/iOS_Tips/DarkMode/General/Tool/SLNavigationController.h b/iOS_Tips/DarkMode/General/Tool/SLNavigationController.h new file mode 100644 index 00000000..74efda5e --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLNavigationController.h @@ -0,0 +1,17 @@ +// +// SLNavigationController.h +// DarkMode +// +// Created by wsl on 2020/6/11. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SLNavigationController : UINavigationController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/General/Tool/SLNavigationController.m b/iOS_Tips/DarkMode/General/Tool/SLNavigationController.m new file mode 100644 index 00000000..a4a68602 --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLNavigationController.m @@ -0,0 +1,56 @@ +// +// SLNavigationController.m +// DarkMode +// +// Created by wsl on 2020/6/11. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLNavigationController.h" + +@interface SLNavigationController () + +@end + +@implementation SLNavigationController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor =[UIColor clearColor]; + + // 获取系统自带滑动手势的target对象 + id target = self.interactivePopGestureRecognizer.delegate; + // 创建全屏滑动手势,调用系统自带滑动手势的target的action方法 + UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)]; + // 设置手势代理,拦截手势触发 + pan.delegate = self; + // 给导航控制器的view添加全屏滑动手势 + [self.view addGestureRecognizer:pan]; + // 禁止使用系统自带的pop滑动手势 + self.interactivePopGestureRecognizer.enabled = NO; +} + +#pragma mark -- UIGestureRecognizerDelegate + +// 什么时候调用:每次触发手势之前都会询问下代理,是否触发。 +// 作用:拦截手势触发 +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + // 注意:只有非根控制器才有滑动返回功能,根控制器没有。 + // 判断导航控制器是否只有一个子控制器,如果只有一个子控制器,肯定是根控制器 + if (self.childViewControllers.count == 1 ) { + // 表示用户在根控制器界面,就不需要触发滑动手势, + return NO; + } + return YES; +} +//触发之后是否响应手势事件 +//处理侧滑返回与UISlider的拖动手势冲突 +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { + //如果手势是触摸的UISlider滑块触发的,侧滑返回手势就不响应 + if ([touch.view isKindOfClass:[UISlider class]]) { + return NO; + } + return YES; +} + +@end diff --git a/iOS_Tips/DarkMode/General/Tool/SLProxy.h b/iOS_Tips/DarkMode/General/Tool/SLProxy.h new file mode 100644 index 00000000..860ace99 --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLProxy.h @@ -0,0 +1,19 @@ +// +// SLProxy.h +// DarkMode +// +// Created by wsl on 2020/7/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///消息转发中介 主要解决NSTimer、CADisplayLink等循环引用问题 +@interface SLProxy : NSProxy +///初始化方法 ++ (instancetype)proxyWithTarget:(id)target; +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/General/Tool/SLProxy.m b/iOS_Tips/DarkMode/General/Tool/SLProxy.m new file mode 100644 index 00000000..51e76a17 --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLProxy.m @@ -0,0 +1,68 @@ +// +// SLProxy.m +// DarkMode +// +// Created by wsl on 2020/7/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLProxy.h" + +@interface SLProxy () +///转发对象目标 +@property (nullable, nonatomic, weak, readonly) id target; +@end +@implementation SLProxy ++ (instancetype)proxyWithTarget:(id)target { + return [[SLProxy alloc] initWithTarget:target]; +} +- (instancetype)initWithTarget:(id)target { + _target = target; + return self; +} +//将消息接收对象改为 _target +- (id)forwardingTargetForSelector:(SEL)selector { + return _target; +} +//self 对 target 是弱引用,一旦 target 被释放将调用下面两个方法,如果不实现的话会 crash +- (void)forwardInvocation:(NSInvocation *)invocation { + void *null = NULL; + [invocation setReturnValue:&null]; +} +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { + return [NSObject instanceMethodSignatureForSelector:@selector(init)]; +} +- (BOOL)respondsToSelector:(SEL)aSelector { + return [_target respondsToSelector:aSelector]; +} +- (BOOL)isEqual:(id)object { + return [_target isEqual:object]; +} +- (NSUInteger)hash { + return [_target hash]; +} +- (Class)superclass { + return [_target superclass]; +} +- (Class)class { + return [_target class]; +} +- (BOOL)isKindOfClass:(Class)aClass { + return [_target isKindOfClass:aClass]; +} +- (BOOL)isMemberOfClass:(Class)aClass { + return [_target isMemberOfClass:aClass]; +} +- (BOOL)conformsToProtocol:(Protocol *)aProtocol { + return [_target conformsToProtocol:aProtocol]; +} +- (BOOL)isProxy { + return YES; +} +- (NSString *)description { + return [_target description]; +} +- (NSString *)debugDescription { + return [_target debugDescription]; +} +@end diff --git a/iOS_Tips/DarkMode/General/Tool/SLTimer.h b/iOS_Tips/DarkMode/General/Tool/SLTimer.h new file mode 100644 index 00000000..3026c295 --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLTimer.h @@ -0,0 +1,44 @@ +// +// SLTimer.h +// +// Created by wsl on 2020/6/15. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + + +#import + +///计时器 比NSTimer和CADisplayLink计时准确 +@interface SLTimer : NSObject + +/// 执行任务 返回任务名称 +/// @param task 任务Block +/// @param start 开始时间 +/// @param interval 时间间隔 +/// @param repeats 是否重复 +/// @param async 是否异步 ++ (NSString *)execTask:(void(^)(void))task + start:(NSTimeInterval)start + interval:(NSTimeInterval)interval + repeats:(BOOL)repeats + async:(BOOL)async; + +/// 执行任务 返回任务名称 +/// @param target 选择器执行者 +/// @param selector 选择器 +/// @param start 开始时间 +/// @param interval 时间间隔 +/// @param repeats 是否重复 +/// @param async 是否异步 ++ (NSString *)execTask:(id)target + selector:(SEL)selector + start:(NSTimeInterval)start + interval:(NSTimeInterval)interval + repeats:(BOOL)repeats + async:(BOOL)async; + +/// 取消任务 +/// @param taskName 任务名称 ++ (void)cancelTask:(NSString *)taskName; + +@end diff --git a/iOS_Tips/DarkMode/General/Tool/SLTimer.m b/iOS_Tips/DarkMode/General/Tool/SLTimer.m new file mode 100644 index 00000000..449dfeaa --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLTimer.m @@ -0,0 +1,90 @@ +// +// SLTimer.m +// +// Created by wsl on 2020/6/15. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLTimer.h" + +@implementation SLTimer + +static NSMutableDictionary *timers_; +dispatch_semaphore_t semaphore_; ++ (void)initialize +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + timers_ = [NSMutableDictionary dictionary]; + semaphore_ = dispatch_semaphore_create(1); + }); +} + ++ (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async +{ + if (!task || start < 0 || (interval <= 0 && repeats)) return nil; + + // 队列 + dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue(); + + // 创建定时器 + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + + // 设置时间 + dispatch_source_set_timer(timer, + dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), + interval * NSEC_PER_SEC, 0); + + + dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER); + // 定时器的唯一标识 + NSString *name = [NSString stringWithFormat:@"%zd", timers_.count]; + // 存放到字典中 + timers_[name] = timer; + dispatch_semaphore_signal(semaphore_); + + // 设置回调 + dispatch_source_set_event_handler(timer, ^{ + task(); + + if (!repeats) { // 不重复的任务 + [self cancelTask:name]; + } + }); + + // 启动定时器 + dispatch_resume(timer); + + return name; +} + ++ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async +{ + if (!target || !selector) return nil; + + return [self execTask:^{ + if ([target respondsToSelector:selector]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [target performSelector:selector]; +#pragma clang diagnostic pop + } + } start:start interval:interval repeats:repeats async:async]; +} + ++ (void)cancelTask:(NSString *)taskName; +{ + if (taskName.length == 0) return; + + dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER); + + dispatch_source_t timer = timers_[taskName]; + if (timer) { + dispatch_source_cancel(timer); + [timers_ removeObjectForKey:taskName]; + } + + dispatch_semaphore_signal(semaphore_); +} + +@end diff --git a/iOS_Tips/DarkMode/General/Tool/SLToolMacro.h b/iOS_Tips/DarkMode/General/Tool/SLToolMacro.h new file mode 100644 index 00000000..aea11776 --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLToolMacro.h @@ -0,0 +1,149 @@ +// +// SLToolMacro.h +// DarkMode +// +// Created by wsl on 2020/8/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#ifndef SLToolMacro_h +#define SLToolMacro_h + +#pragma mark - Call Me +///我的联系方式 +#define SL_JianShuUrl @"https://www.jianshu.com/u/e15d1f644bea" +#define SL_GithubUrl @"https://github.com/wsl2ls/iOS_Tips.git" +#define SL_WeChat @"iOS2679114653" +#define SL_QQGroup @"835303405" +#define SL_WeiBo @"https://weibo.com/5732733120/profile?rightmod=1&wvr=6&mod=personinfo&is_all=1" +#define SL_CSDN @"https://blog.csdn.net/wsl2ls" +#define SL_JueJin @"https://juejin.im/user/5c00d97b6fb9a049fb436288" +#define SL_Blog @"https://wsl2ls.github.io" +//【腾讯文档】2020_慕课网/极客/腾讯课堂等课程资源:https://docs.qq.com/doc/DS1lhWkhPc2xEamx5 + +#pragma mark - About UI/Device +//---------------------- About UI/Device ---------------------------- +#define iPhone4 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO) +#define iPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO) +#define iPhone6 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(750, 1334), [[UIScreen mainScreen] currentMode].size) : NO) +#define iPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242, 2208), [[UIScreen mainScreen] currentMode].size) : NO) +#define iPhone6PlusScale ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2001), [[UIScreen mainScreen] currentMode].size) : NO) +#define iPhoneX ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO) +#define iPhoneXR ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(828, 1792), [[UIScreen mainScreen] currentMode].size) : NO) +#define iPhoneXM ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242, 2688), [[UIScreen mainScreen] currentMode].size) : NO) + +#define SL_SafeAreaEnable ((iPhoneX || iPhoneXR || iPhoneXM) ? YES : NO) + +#define SL_TopSafeAreaHeight (SL_SafeAreaEnable ? 44.f : 20.f) +#define SL_TopNavigationBarHeight (SL_SafeAreaEnable ? 88.f : 64.f) +#define SL_BottomTabbarHeight (SL_SafeAreaEnable ? (49.f + 34.f) : (49.f)) +#define SL_BottomSafeAreaHeight (SL_SafeAreaEnable ? (34.f) : (0.f)) + +/// 屏幕宽高 +#define SL_kScreenWidth [UIScreen mainScreen].bounds.size.width +#define SL_kScreenHeight [UIScreen mainScreen].bounds.size.height + +/** 判断是否为iPhone */ +#define isiPhone (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) +/** 判断是否是iPad */ +#define isiPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) +/** 判断是否为iPod */ +#define isiPod ([[[UIDevice currentDevice] model] isEqualToString:@"iPod touch"]) + +//Get the OS version. 判断操作系统版本 +#define SL_IOSVERSION [[[UIDevice currentDevice] systemVersion] floatValue] +#define SL_CurrentSystemVersion ([[UIDevice currentDevice] systemVersion]) +#define SL_CurrentLanguage ([[NSLocale preferredLanguages] objectAtIndex:0]) + +//judge the simulator or hardware device 判断是真机还是模拟器 +#if TARGET_OS_IPHONE +//iPhone Device +#endif +#if TARGET_IPHONE_SIMULATOR +//iPhone Simulator +#endif + +#pragma mark - About Helper 辅助方法 +//---------------------- About Helper 辅助方法 ---------------------------- +/// 弱引用对象 +#define SL_WeakSelf __weak typeof(self) weakSelf = self; + +///主线程操作 +#define SL_DISPATCH_ON_MAIN_THREAD(mainQueueBlock) dispatch_async(dispatch_get_main_queue(),mainQueueBlock); +#define SL_GCDWithGlobal(block) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block) +#define sl_GCDWithMain(block) dispatch_async(dispatch_get_main_queue(),block) + +///NSUserDefaults 存储 +#define SL_UserDefaultSetObjectForKey(__VALUE__,__KEY__) \ +{\ +[[NSUserDefaults standardUserDefaults] setObject:__VALUE__ forKey:__KEY__];\ +[[NSUserDefaults standardUserDefaults] synchronize];\ +} +///NSUserDefaults 获得存储的对象 +#define SL_UserDefaultObjectForKey(__KEY__) [[NSUserDefaults standardUserDefaults] objectForKey:__KEY__] +///NSUserDefaults 删除对象 +#define SL_UserDefaultRemoveObjectForKey(__KEY__) \ +{\ +[[NSUserDefaults standardUserDefaults] removeObjectForKey:__KEY__];\ +[[NSUserDefaults standardUserDefaults] synchronize];\ +} + +/** 快速查询一段代码的执行时间 */ +/** 用法 + SL_StartTime + do your work here + SL_EndDuration + */ +#define SL_StartTime NSDate *startTime = [NSDate date] +#define SL_EndDuration -[startTime timeIntervalSinceNow] + +// STRING容错机制 +#define SL_IS_NULL(x) (!x || [x isKindOfClass:[NSNull class]]) +#define SL_IS_EMPTY_STRING(x) (SL_IS_NULL(x) || [x isEqual:@""] || [x isEqual:@"(null)"]) +#define SL_DEFUSE_EMPTY_STRING(x) (!SL_IS_EMPTY_STRING(x) ? x : @"") + + +//沙河目录 +///获取沙盒主目录路径 +#define SL_HomeDir NSHomeDirectory(); +/// 获取Documents目录路径 +#define SL_DocumentDir [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] +/// 获取Library的目录路径 +#define SL_LibraryDir [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject] +/// 获取Caches目录路径 +#define SL_CachesDir [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] +/// 获取tmp目录路径 +#define SL_TmpDir NSTemporaryDirectory() + +#pragma mark - About Color 颜色 +//---------------------- About Color 颜色 ---------------------------- +/// 随机颜色 +#define SL_UIColorFromRandomColor [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1.0] +/// rgb颜色 +#define SL_UIColorFromRGB(r,g,b,a) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:(a)] +/// 16进制 颜色 +#define SL_UIColorFromHex(rgbValue, a) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:(a)] + +#pragma mark - About Log 打印日志 +//---------------------- About Log 打印日志 ---------------------------- +/// 打印 +#ifdef DEBUG +# define NSLog(fmt, ...) NSLog((fmt), ##__VA_ARGS__); +# define SL_Log(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); +#define SL_NSLog(...) printf("%f %s %ld :%s\n",[[NSDate date]timeIntervalSince1970],strrchr(__FILE__,'/'),[[NSNumber numberWithInt:__LINE__] integerValue],[[NSString stringWithFormat:__VA_ARGS__]UTF8String]); +#else +# define NSLog(fmt, ...) +# define SL_Log(...) +# define SL_NSLog(...) +#endif + +//---------------------- About Shader 着色器 ---------------------------- +//#x 将参数x字符串化 +#define STRINGIZE(x) #x +#define STRINGIZE2(x) STRINGIZE(x) +#define Shader_String(text) @ STRINGIZE2(text) + + + + +#endif /* SLToolMacro_h */ diff --git a/iOS_Tips/DarkMode/General/Tool/SLViewController.h b/iOS_Tips/DarkMode/General/Tool/SLViewController.h new file mode 100644 index 00000000..a50b2d0e --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLViewController.h @@ -0,0 +1,17 @@ +// +// SLViewController.h +// DarkMode +// +// Created by wsl on 2020/6/12. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SLViewController : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/General/Tool/SLViewController.m b/iOS_Tips/DarkMode/General/Tool/SLViewController.m new file mode 100644 index 00000000..a5a63efe --- /dev/null +++ b/iOS_Tips/DarkMode/General/Tool/SLViewController.m @@ -0,0 +1,19 @@ +// +// SLViewController.m +// DarkMode +// +// Created by wsl on 2020/6/12. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLViewController.h" + +@interface SLViewController () + +@end + +@implementation SLViewController +- (void)dealloc { + NSLog(@"%@释放了",NSStringFromClass(self.class)); +} +@end diff --git a/iOS_Tips/DarkMode/General/UI/SLAlertView.h b/iOS_Tips/DarkMode/General/UI/SLAlertView.h new file mode 100644 index 00000000..8df85272 --- /dev/null +++ b/iOS_Tips/DarkMode/General/UI/SLAlertView.h @@ -0,0 +1,21 @@ +// +// SLAlertView.h +// DarkMode +// +// Created by wsl on 2020/3/11. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SLAlertView : NSObject +/// 展示几秒后自动隐藏 +/// @param text 文本 +/// @param delay 展示时长 ++ (void)showAlertViewWithText:(NSString *)text delayHid:(NSTimeInterval)delay; +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/General/UI/SLAlertView.m b/iOS_Tips/DarkMode/General/UI/SLAlertView.m new file mode 100644 index 00000000..514b8045 --- /dev/null +++ b/iOS_Tips/DarkMode/General/UI/SLAlertView.m @@ -0,0 +1,28 @@ +// +// SLAlertView.m +// DarkMode +// +// Created by wsl on 2020/3/11. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLAlertView.h" + +@interface SLAlertView () +@end + +@implementation SLAlertView +/// 展示几秒后自动隐藏 +/// @param text 文本 +/// @param delay 展示时长 ++ (void)showAlertViewWithText:(NSString *)text delayHid:(NSTimeInterval)delay { + MBProgressHUD *progressHUD = [[MBProgressHUD alloc] initWithView:[UIApplication sharedApplication].keyWindow]; + progressHUD.animationType = MBProgressHUDAnimationFade; + progressHUD.mode = MBProgressHUDModeText; + progressHUD.label.text = text; + progressHUD.label.numberOfLines = 0; + [[UIApplication sharedApplication].keyWindow addSubview:progressHUD]; + [progressHUD showAnimated:YES]; + [progressHUD hideAnimated:YES afterDelay:delay]; +} +@end diff --git a/iOS_Tips/DarkMode/General/UI/SLButton.h b/iOS_Tips/DarkMode/General/UI/SLButton.h new file mode 100644 index 00000000..0c5e4531 --- /dev/null +++ b/iOS_Tips/DarkMode/General/UI/SLButton.h @@ -0,0 +1,27 @@ +// +// SLButton.h +// +// Created by wsl on 2020/4/23. +// Copyright © 2020 wsl. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, SLButtonStyle) { + SLButtonStyleImageLeft = 0,// 左图右文 + SLButtonStyleImageRight, //右图左文 + SLButtonStyleImageTop, //上图下文 + SLButtonStyleImageBottom //下图上文 +}; + +/// 自定义Button 自定义文本和图片布局样式 +@interface SLButton : UIControl +@property (nonatomic, strong) UIImageView *imageView; +@property (nonatomic, strong) UILabel *titleLabel; +///设置文本图片布局方式 +- (void)setTitleImageLayoutStyle:(SLButtonStyle)titleImageStyle space:(CGFloat)space; +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/General/UI/SLButton.m b/iOS_Tips/DarkMode/General/UI/SLButton.m new file mode 100644 index 00000000..1565dbd4 --- /dev/null +++ b/iOS_Tips/DarkMode/General/UI/SLButton.m @@ -0,0 +1,132 @@ +// +// EBButton.m +// ZGEBook +// +// Created by wsl on 2020/4/23. +// Copyright © 2020 ZGEBook. All rights reserved. +// + +#import "SLButton.h" + +@interface SLButton () + +@end + +@implementation SLButton + +#pragma mark - Override +- (instancetype)init { + self = [super init]; + if (self) { + } + return self; +} +- (void)didMoveToSuperview { + if (self.superview) { + [self addSubview:self.imageView]; + [self addSubview:self.titleLabel]; + } +} +- (void)didMoveToWindow { + if (self.superview) { + [self addSubview:self.imageView]; + [self addSubview:self.titleLabel]; + } +} + +#pragma mark - Getter +- (UIImageView *)imageView { + if (!_imageView) { + _imageView = [[UIImageView alloc] init]; + // _imageView.backgroundColor = [UIColor greenColor]; + } + return _imageView; +} +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [[UILabel alloc] init]; + // _titleLabel.backgroundColor = [UIColor greenColor]; + } + return _titleLabel; +} + +#pragma mark - Public Methods +/// 设置文本和图片的位置 +- (void)setTitleImageLayoutStyle:(SLButtonStyle)titleImageStyle space:(CGFloat)space { + switch (titleImageStyle) { + case SLButtonStyleImageLeft: + [self imageLeft:space]; + break; + case SLButtonStyleImageRight: + [self imageRight:space]; + break; + case SLButtonStyleImageTop: + [self imageTop:space]; + break; + case SLButtonStyleImageBottom: + [self imageBottom:space]; + break; + default: + break; + } +} + +- (void)imageLeft:(CGFloat)space { + CGSize imageSize = self.imageView.image.size; + CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeZero]; + [self.imageView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerY.mas_equalTo(self.mas_centerY); + make.size.mas_equalTo(imageSize); + make.centerX.mas_equalTo(self.mas_centerX).offset(-imageSize.width/2.0-space/2.0); + }]; + [self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerY.mas_equalTo(self.mas_centerY); + make.size.mas_equalTo(titleSize); + make.centerX.mas_equalTo(self.mas_centerX).offset(titleSize.width/2.0+space/2.0); + }]; +} +- (void)imageRight:(CGFloat)space { + CGSize imageSize = self.imageView.image.size; + CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeZero]; + [self.imageView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerY.mas_equalTo(self.mas_centerY); + make.size.mas_equalTo(imageSize); + make.centerX.mas_equalTo(self.mas_centerX).offset(imageSize.width/2.0+space/2.0); + }]; + [self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerY.mas_equalTo(self.mas_centerY); + make.size.mas_equalTo(titleSize); + make.centerX.mas_equalTo(self.mas_centerX).offset(-titleSize.width/2.0-space/2.0); + }]; +} +- (void)imageTop:(CGFloat)space { + CGSize imageSize = self.imageView.image.size; + CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeZero]; + CGFloat heightGap = imageSize.height - titleSize.height; //高度差距 + [self.imageView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self.mas_centerX); + make.size.mas_equalTo(imageSize); + make.centerY.mas_equalTo(self.mas_centerY).offset(-imageSize.height/2.0-space/2.0 + heightGap/2.0); + }]; + [self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self.mas_centerX); + make.size.mas_equalTo(titleSize); + make.centerY.mas_equalTo(self.mas_centerY).offset(titleSize.height/2.0+space/2.0 + heightGap/2.0); + }]; +} +- (void)imageBottom:(CGFloat)space { + CGSize imageSize = self.imageView.image.size; + CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeZero]; + [self.imageView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self.mas_centerX); + make.size.mas_equalTo(imageSize); + make.centerY.mas_equalTo(self.mas_centerY).offset(imageSize.height/2.0+space/2.0); + }]; + [self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self.mas_centerX); + make.size.mas_equalTo(titleSize); + make.centerY.mas_equalTo(self.mas_centerY).offset(-titleSize.height/2.0-space/2.0); + }]; +} + +@end diff --git a/iOS_Tips/DarkMode/Info.plist b/iOS_Tips/DarkMode/Info.plist index 28c8281f..02e052e3 100644 --- a/iOS_Tips/DarkMode/Info.plist +++ b/iOS_Tips/DarkMode/Info.plist @@ -16,6 +16,19 @@ $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + GrowingIO + CFBundleURLSchemes + + growing.6748563d36f51275 + + + CFBundleVersion 1 LSApplicationQueriesSchemes @@ -25,14 +38,51 @@ LSRequiresIPhoneOS + NFCReaderUsageDescription + 访问NFC + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSAppleMusicUsageDescription + 访问媒体资料库 + NSBluetoothPeripheralUsageDescription + 访问蓝牙 + NSCalendarsUsageDescription + 访问日历 NSCameraUsageDescription - 此功能需要获取你的相机权限 + 获取相机权限 + NSContactsUsageDescription + 访问通讯录 + NSFaceIDUsageDescription + 访问Face ID + NSHealthShareUsageDescription + 访问健康分享 + NSHealthUpdateUsageDescription + 访问健康更新 + NSHomeKitUsageDescription + 访问住宅配件 + NSLocationAlwaysUsageDescription + 始终访问位置 + NSLocationWhenInUseUsageDescription + 在使用期间访问位置 NSMicrophoneUsageDescription - 此功能需要你的麦克风权限 + 获取你的麦克风权限 + NSMotionUsageDescription + 访问运动与健身 NSPhotoLibraryAddUsageDescription - 此功能需要获取你的相册权限 + 保存到相册需要获取你的相册权限 NSPhotoLibraryUsageDescription - 此功能需要获取你的相册权限 + 相册浏览需要获取你的相册权限 + NSRemindersUsageDescription + 访问提醒事项 + NSSiriUsageDescription + 使用Siri功能 + NSSpeechRecognitionUsageDescription + 使用语音识别功能 + NSVideoSubscriberAccountUsageDescription + 访问电视提供商 UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/iOS_Tips/DarkMode/OpenGL/.DS_Store b/iOS_Tips/DarkMode/OpenGL/.DS_Store index 66924419..7d1ef824 100644 Binary files a/iOS_Tips/DarkMode/OpenGL/.DS_Store and b/iOS_Tips/DarkMode/OpenGL/.DS_Store differ diff --git a/iOS_Tips/DarkMode/OpenGL/Controller/SLOpenGLController.m b/iOS_Tips/DarkMode/OpenGL/Controller/SLOpenGLController.m index bc266f93..52886a3a 100644 --- a/iOS_Tips/DarkMode/OpenGL/Controller/SLOpenGLController.m +++ b/iOS_Tips/DarkMode/OpenGL/Controller/SLOpenGLController.m @@ -47,8 +47,23 @@ - (void)setupUI { #pragma mark - Data - (void)getData { //tableView、UIAlertView等系统控件,在不自定义颜色的情况下,默认颜色都是动态的,支持暗黑模式 - [self.dataSource addObjectsFromArray:@[@" GLKit 加载图片", @" GLKit 绘制正方体", @" GLKit 颜色和纹理混合金字塔",@" OpenGL ShaderLanguage(GLSL) 加载图片", @" GLSL 绘制金字塔", @" GLSL 颜色和纹理混合", @" GLSL 分屏特效", @" GLSL 滤镜集合", @"GLSL 抖音部分特效集合"]]; - [self.classArray addObjectsFromArray:@[[SLLoadImageVC class], [SLCubeViewController class], [SLGLKPyramidVC class], [SLShaderLanguageViewController class], [SLShaderCubeViewController class], [SLMixColorTextureVC class], [SLSplitScreenViewController class], [SLShaderFilterViewController class], [SLSpecialEffectsViewController class]]]; + [self.dataSource addObjectsFromArray:@[@" GLKit 加载图片", + @" GLKit 绘制正方体", + @" GLKit 颜色和纹理混合金字塔", + @" OpenGL ShaderLanguage(GLSL) 加载图片", + @" GLSL 绘制金字塔", @" GLSL 颜色和纹理混合", + @" GLSL 分屏特效", + @" GLSL 滤镜集合", + @"GLSL 抖音部分特效集合"]]; + [self.classArray addObjectsFromArray:@[[SLLoadImageVC class], + [SLCubeViewController class], + [SLGLKPyramidVC class], + [SLShaderLanguageViewController class], + [SLShaderCubeViewController class], + [SLMixColorTextureVC class], + [SLSplitScreenViewController class], + [SLShaderFilterViewController class], + [SLSpecialEffectsViewController class]]]; [self.tableView reloadData]; } #pragma mark - Getter diff --git a/iOS_Tips/DarkMode/OpenGL/Controller/SLShaderLanguageViewController.m b/iOS_Tips/DarkMode/OpenGL/Controller/SLShaderLanguageViewController.m index 3f46c8b4..7e8f5769 100644 --- a/iOS_Tips/DarkMode/OpenGL/Controller/SLShaderLanguageViewController.m +++ b/iOS_Tips/DarkMode/OpenGL/Controller/SLShaderLanguageViewController.m @@ -21,8 +21,8 @@ OpenGL ES 3种变量修饰符(varying, attribute, uniform) https://blog.csdn.net/hgl868/article/details/7846269 uniform: 由外部客户端传入,由函数glUniform** 提供赋值功能,类似于const, 被uniform 修饰变量在顶点/片元着色器中 只能用,不能修改 一般用来修饰矩阵 - attribute:只能在顶点着色器出现 - varying:中间传递,顶点和片元着色器之间传递数据 + attribute:只能在顶点着色器出现, + varying:中间传递,由顶点着色器传向片元着色器的数据变量 lowp, mediump和highp:精度修饰符声明了底层实现存储这些变量必须要使用的最小范围和精度。实现可能会使用比要求更大的范围和精度,但绝对不会比要求少。 @@ -325,7 +325,7 @@ -(void)renderLayer { //10.加载纹理 [self setupTexture]; - //11. 设置纹理采样器 sampler2D 纹理单元GL_TEXTURE0 - GL_TEXTURE15 总共有16个纹理单元 + //11. 设置纹理采样器sampler2D 纹理单元GL_TEXTURE0 - GL_TEXTURE15 总共有16个纹理单元 glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0); //12.不使用索引数组 绘图 从第0个顶点开始,共六个顶点 @@ -424,7 +424,7 @@ - (GLuint)setupTexture{ //7、画图完毕就释放上下文 CGContextRelease(spriteContext); - //8、绑定纹理到默认的纹理ID( + //8、绑定纹理到默认的纹理ID: 0( glBindTexture(GL_TEXTURE_2D, 0); //9.设置纹理属性 diff --git a/iOS_Tips/DarkMode/OpenGL/Controller/SLSplitScreenViewController.m b/iOS_Tips/DarkMode/OpenGL/Controller/SLSplitScreenViewController.m index 5d2f1d76..8f67a3ff 100644 --- a/iOS_Tips/DarkMode/OpenGL/Controller/SLSplitScreenViewController.m +++ b/iOS_Tips/DarkMode/OpenGL/Controller/SLSplitScreenViewController.m @@ -59,6 +59,7 @@ - (void)dealloc { - (void)setupUI { self.view.backgroundColor = [UIColor whiteColor]; [self.view addSubview:self.collectionView]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"切换图片" style:UIBarButtonItemStyleDone target:self action:@selector(changeImage:)]; } #pragma mark - FilterInit @@ -90,14 +91,24 @@ - (void)filterInit { //5.绑定渲染缓存区 [self bindRenderLayer:layer]; - //6.获取处理的图片路径 + //6.获取纹理 并载入图像纹理数据 + GLuint textureID; + //获取纹理ID + glGenTextures(1, &textureID); + //绑定纹理 + /* + 参数1:纹理维度 + 参数2:纹理ID,因为只有一个纹理,给0就可以了。 + */ + glBindTexture(GL_TEXTURE_2D, textureID); + //获取处理的图片路径 NSString *myBundlePath = [[NSBundle mainBundle] pathForResource:@"Resources" ofType:@"bundle"]; NSString *imagePath = [[NSBundle bundleWithPath:myBundlePath] pathForResource:@"素材1" ofType:@"png" inDirectory:@"Images"]; //读取图片 UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; - //将图片转换成纹理图片 - GLuint textureID = [self createTextureWithImage:image]; - //设置纹理ID + //将图片转换成纹理数据 并载入纹理数据 + [self createTextureWithImage:image]; + //保存纹理ID self.textureID = textureID; // 将纹理 ID 保存,方便后面切换滤镜的时候重用 //7.设置视口 @@ -153,7 +164,7 @@ - (void)bindRenderLayer:(CALayer *)layer { renderBuffer); } //从图片中加载纹理 -- (GLuint)createTextureWithImage:(UIImage *)image { +- (void)createTextureWithImage:(UIImage *)image { //1、将 UIImage 转换为 CGImageRef CGImageRef cgImageRef = [image CGImage]; @@ -193,12 +204,7 @@ - (GLuint)createTextureWithImage:(UIImage *)image { CGContextDrawImage(context, rect, cgImageRef); //设置图片纹理属性 - //5. 获取纹理ID - GLuint textureID; - glGenTextures(1, &textureID); - glBindTexture(GL_TEXTURE_2D, textureID); - - //6.载入纹理2D数据 + //5.载入纹理2D数据 /* 参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 参数2:加载的层次,一般设置为0 @@ -212,25 +218,16 @@ - (GLuint)createTextureWithImage:(UIImage *)image { */ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); - //7.设置纹理属性 + //6.设置纹理属性 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - //8.绑定纹理 - /* - 参数1:纹理维度 - 参数2:纹理ID,因为只有一个纹理,给0就可以了。 - */ - glBindTexture(GL_TEXTURE_2D, 0); - - //9.释放context,imageData + //7.释放context,imageData CGContextRelease(context); free(imageData); - - //10.返回纹理ID - return textureID; + } //获取渲染缓存区的宽 - (GLint)drawableWidth { @@ -264,6 +261,11 @@ - (void)setupShaderProgramWithName:(NSString *)name { //4.激活纹理,绑定纹理ID glActiveTexture(GL_TEXTURE0); + //绑定纹理 + /* + 参数1:纹理维度 + 参数2:纹理ID,因为只有一个纹理,给0就可以了。 + */ glBindTexture(GL_TEXTURE_2D, self.textureID); //5.纹理sample @@ -374,6 +376,19 @@ - (NSMutableArray *)dataSource { return _dataSource; } +#pragma mark - Events Handle +//切换图片 +- (void)changeImage:(id)sender { + //更换图片 + NSString *myBundlePath = [[NSBundle mainBundle] pathForResource:@"Resources" ofType:@"bundle"]; + NSString *imagePath = [[NSBundle bundleWithPath:myBundlePath] pathForResource:@"素材2" ofType:@"png" inDirectory:@"Images"]; + UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; + //载入纹理数据 + [self createTextureWithImage:image]; + //重新渲染到屏幕 + [self presentRenderbuffer]; +} + #pragma mark - UICollectionViewDelegate, UICollectionViewDataSource - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 1; diff --git a/iOS_Tips/DarkMode/OpenGL/ShaderLanguage/shaderf.fsh b/iOS_Tips/DarkMode/OpenGL/ShaderLanguage/shaderf.fsh index 636fc5d6..b411d604 100644 --- a/iOS_Tips/DarkMode/OpenGL/ShaderLanguage/shaderf.fsh +++ b/iOS_Tips/DarkMode/OpenGL/ShaderLanguage/shaderf.fsh @@ -7,4 +7,6 @@ void main() } -// 后缀.vsh和.fsh 是可以自定义的,只是为了区分和管理顶点着色器和片元着色器,也可以用字符串存储 +// 后缀.vsh(顶点着色器)和.fsh(片元着色器) 是可以自定义的,只是为了区分和管理顶点着色器和片元着色器,也可以用字符串存储 +// colorMap 采样器 +// varyTextCoord 纹理坐标 由顶点着色器传入,必须和.vsh(顶点着色器)的变量保持一致 diff --git a/iOS_Tips/DarkMode/OpenGL/Controller/SLSplitScreenCell.h b/iOS_Tips/DarkMode/OpenGL/View/SLSplitScreenCell.h similarity index 100% rename from iOS_Tips/DarkMode/OpenGL/Controller/SLSplitScreenCell.h rename to iOS_Tips/DarkMode/OpenGL/View/SLSplitScreenCell.h diff --git a/iOS_Tips/DarkMode/OpenGL/Controller/SLSplitScreenCell.m b/iOS_Tips/DarkMode/OpenGL/View/SLSplitScreenCell.m similarity index 100% rename from iOS_Tips/DarkMode/OpenGL/Controller/SLSplitScreenCell.m rename to iOS_Tips/DarkMode/OpenGL/View/SLSplitScreenCell.m diff --git a/iOS_Tips/DarkMode/Recource/.DS_Store b/iOS_Tips/DarkMode/Recource/.DS_Store index be394d57..516c956c 100644 Binary files a/iOS_Tips/DarkMode/Recource/.DS_Store and b/iOS_Tips/DarkMode/Recource/.DS_Store differ diff --git a/iOS_Tips/DarkMode/SLCategory/NSDictionary+SLExtension.h b/iOS_Tips/DarkMode/SLCategory/NSDictionary+SLExtension.h new file mode 100644 index 00000000..c434d8e3 --- /dev/null +++ b/iOS_Tips/DarkMode/SLCategory/NSDictionary+SLExtension.h @@ -0,0 +1,24 @@ +// +// NSDictionary+SLExtension.h +// +// +// Created by wsl on 2020/6/18. +// Copyright © 2020 wsl. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSDictionary (SLExtension) + +///容错处理 value 应为NSString +- (NSString *)sl_decodeStringFormDictWithKey:(NSString *)key; +///容错处理 value 为NSArray +- (NSArray *)sl_decodeArrayFormDictWithKey:(NSString *)key; +///容错处理 value 为NSDictionary +- (NSDictionary *)sl_decodeDictionaryFormDictWithKey:(NSString *)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/SLCategory/NSDictionary+SLExtension.m b/iOS_Tips/DarkMode/SLCategory/NSDictionary+SLExtension.m new file mode 100644 index 00000000..2f85564d --- /dev/null +++ b/iOS_Tips/DarkMode/SLCategory/NSDictionary+SLExtension.m @@ -0,0 +1,50 @@ +// +// NSDictionary+SLExtension.m +// +// +// Created by wsl on 2020/6/18. +// Copyright © 2020 wsl. All rights reserved. +// + +#import "NSDictionary+SLExtension.h" + +@implementation NSDictionary (SLExtension) + +///value 为NSString +- (NSString *)sl_decodeStringFormDictWithKey:(NSString *)key { + NSString *string = @""; + if (self && [self isKindOfClass:[NSDictionary class]]) { + NSDictionary *dict = (NSDictionary *)self; + if ([dict[key] isKindOfClass:[NSString class]]) { + string = dict[key]; + } + else if ([dict[key] isKindOfClass:[NSNumber class]]) { + string = [dict[key] stringValue]; + } + } + return string; +} +///value 为NSArray +- (NSArray *)sl_decodeArrayFormDictWithKey:(NSString *)key { + + NSArray *array = [NSArray array]; + if (self && [self isKindOfClass:[NSDictionary class]]) { + NSDictionary *dict = (NSDictionary *)self; + if ([dict[key] isKindOfClass:[NSArray class]]) { + array = dict[key]; + } + } + return array; +} +///容错处理 value 为NSDictionary +- (NSDictionary *)sl_decodeDictionaryFormDictWithKey:(NSString *)key { + NSDictionary *dictionary = [NSDictionary dictionary]; + if (self && [self isKindOfClass:[NSDictionary class]]) { + NSDictionary *dict = (NSDictionary *)self; + if ([dict[key] isKindOfClass:[NSDictionary class]]) { + dictionary = dict[key]; + } + } + return dictionary; +} +@end diff --git a/iOS_Tips/DarkMode/SLCategory/UIButton+SLTitleImage.h b/iOS_Tips/DarkMode/SLCategory/UIButton+SLTitleImage.h new file mode 100644 index 00000000..ed0dbb8d --- /dev/null +++ b/iOS_Tips/DarkMode/SLCategory/UIButton+SLTitleImage.h @@ -0,0 +1,27 @@ +// +// UIButton+SLTitleImage.h +// ZGEBook +// +// Created by wsl on 2020/4/22. +// Copyright © 2020 ZGEBook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, SLTitleImageStyle) { + SLTitleImageStyleImageLeft = 0,// 左图右文 + SLTitleImageStyleImageRight, //右图左文 + SLTitleImageStyleImageTop, //上图下文 + SLTitleImageStyleImageBottom //下图上文 +}; + +/// 设置文本和图片的位置 +@interface UIButton (SLTitleImage) +///设置文本图片布局方式 +- (void)sl_setTitleImageLayoutStyle:(SLTitleImageStyle)titleImageStyle space:(CGFloat)space; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/SLCategory/UIButton+SLTitleImage.m b/iOS_Tips/DarkMode/SLCategory/UIButton+SLTitleImage.m new file mode 100644 index 00000000..81a5a84d --- /dev/null +++ b/iOS_Tips/DarkMode/SLCategory/UIButton+SLTitleImage.m @@ -0,0 +1,67 @@ +// +// UIButton+SLTitleImage.m +// +// +// Created by wsl on 2020/4/22. +// Copyright © 2020 ZGEBook. All rights reserved. +// + +#import "UIButton+SLTitleImage.h" + +@implementation UIButton (SLTitleImage) + +/// 设置文本和图片的位置 +- (void)sl_setTitleImageLayoutStyle:(SLTitleImageStyle)titleImageStyle space:(CGFloat)space { + self.titleLabel.textAlignment = NSTextAlignmentCenter; + switch (titleImageStyle) { + case SLTitleImageStyleImageLeft: + [self imageLeft:space]; + break; + case SLTitleImageStyleImageRight: + [self imageRight:space]; + break; + case SLTitleImageStyleImageTop: + [self imageTop:space]; + break; + case SLTitleImageStyleImageBottom: + [self imageBottom:space]; + break; + default: + break; + } +} + +- (void)imageLeft:(CGFloat)space { + CGSize imageSize = self.imageView.image.size; + CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeZero]; + //偏移是相对于图片/文本的原位置 靠近各边偏移是-,远离各边偏移是+ + [self setImageEdgeInsets:UIEdgeInsetsMake(0, -space/2.0, 0, space/2.0)]; + [self setTitleEdgeInsets:UIEdgeInsetsMake(0, space/2.0, 0, -space/2.0)]; +} + +- (void)imageRight:(CGFloat)space { + CGSize imageSize = self.imageView.image.size; + CGSize titleSize = self.titleLabel.frame.size; + //偏移是相对于图片/文本的原位置 靠近各边偏移是-,远离各边偏移是+ + [self setImageEdgeInsets:UIEdgeInsetsMake(0, space/2.0 + titleSize.width, 0, -(space/2.0 + titleSize.width))]; + [self setTitleEdgeInsets:UIEdgeInsetsMake(0, -(space/2.0 + imageSize.width), 0, space/2.0 + imageSize.width)]; +} + +- (void)imageTop:(CGFloat)space { + CGSize imageSize = self.imageView.image.size; + CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeZero]; + //偏移是相对于图片/文本的原位置 靠近各边偏移是-,远离各边偏移是+ + [self setImageEdgeInsets:UIEdgeInsetsMake(-(imageSize.height*0.5 + space*0.5), self.bounds.size.width/2.0- self.imageView.center.x, imageSize.height*0.5 + space*0.5, -(self.bounds.size.width/2.0- self.imageView.center.x))]; + [self setTitleEdgeInsets:UIEdgeInsetsMake(titleSize.height*0.5 + space*0.5, -imageSize.width*0.5, -(titleSize.height*0.5 + space*0.5), imageSize.width*0.5)]; +} + +- (void)imageBottom:(CGFloat)space { + CGSize imageSize = self.imageView.image.size; + CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeZero]; + //偏移是相对于图片/文本的原位置 靠近各边偏移是-,远离各边偏移是+ + [self setImageEdgeInsets:UIEdgeInsetsMake((imageSize.height*0.5 + space*0.5), self.bounds.size.width/2.0 - self.imageView.center.x, -(imageSize.height*0.5 + space*0.5), -(self.bounds.size.width/2.0 - self.imageView.center.x))]; + [self setTitleEdgeInsets:UIEdgeInsetsMake(-(titleSize.height*0.5 + space*0.5), -imageSize.width*0.5, (titleSize.height*0.5 + space*0.5), imageSize.width*0.5)]; +} + + +@end diff --git a/iOS_Tips/DarkMode/SLCategory/UIColor+SLCommon.h b/iOS_Tips/DarkMode/SLCategory/UIColor+SLCommon.h new file mode 100644 index 00000000..f6de63d8 --- /dev/null +++ b/iOS_Tips/DarkMode/SLCategory/UIColor+SLCommon.h @@ -0,0 +1,30 @@ +// +// UIColor+SLCommon.h +// DarkMode +// +// Created by wsl on 2020/6/10. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIColor (SLCommon) + +/// 根据16进制颜色值返回UIColor +/// @param hexValue 16进制值 +/// @param alpha 透明度 ++ (UIColor *)sl_colorWithHex:(int)hexValue alpha:(CGFloat)alpha; + +/// 根据UIColor实例获得RGBA的值 +/// @param color UIColor实例 ++ (NSArray *)sl_rgbaValueWithColor:(UIColor *)color; + +/// 根据UIColor实例返回16进制颜色值 +/// @param color UIColor实例 ++ (int)sl_hexValueWithColor:(UIColor *)color; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/SLCategory/UIColor+SLCommon.m b/iOS_Tips/DarkMode/SLCategory/UIColor+SLCommon.m new file mode 100644 index 00000000..65c1b0b9 --- /dev/null +++ b/iOS_Tips/DarkMode/SLCategory/UIColor+SLCommon.m @@ -0,0 +1,45 @@ +// +// UIColor+SLCommon.m +// DarkMode +// +// Created by wsl on 2020/6/10. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "UIColor+SLCommon.h" + +@implementation UIColor (SLCommon) + +/// 根据16进制颜色值返回UIColor +/// @param hexValue 16进制值 +/// @param alpha 透明度 ++ (UIColor *)sl_colorWithHex:(int)hexValue alpha:(CGFloat)alpha{ + return [UIColor colorWithRed:((float)((hexValue & 0xFF0000) >> 16))/255.0 green:((float)((hexValue & 0xFF00) >> 8))/255.0 blue:((float)(hexValue & 0xFF))/255.0 alpha:alpha]; +} + +/// 根据UIColor实例获得RGBA的值 +/// @param color UIColor实例 ++ (NSArray *)sl_rgbaValueWithColor:(UIColor *)color { + NSInteger numComponents = CGColorGetNumberOfComponents(color.CGColor); + NSArray *array = nil; + if (numComponents == 4) { + const CGFloat *components = CGColorGetComponents(color.CGColor); + array = @[@((int)(components[0] * 255)), + @((int)(components[1] * 255)), + @((int)(components[2] * 255)), + @(components[3])]; + } + return array; +} + +/// 根据UIColor实例返回16进制颜色值 +/// @param color UIColor实例 ++ (int)sl_hexValueWithColor:(UIColor *)color { + NSArray *rgba = [UIColor sl_rgbaValueWithColor:color]; + if (rgba.count == 4) { + return (([rgba[0] intValue] << 16) | ([rgba[1] intValue] << 8) | [rgba[2] intValue]); + } + return 0x000000; +} + +@end diff --git a/iOS_Tips/DarkMode/SLCategory/UIImage+SLCommon.h b/iOS_Tips/DarkMode/SLCategory/UIImage+SLCommon.h index 74889a70..c4c0fd6f 100644 --- a/iOS_Tips/DarkMode/SLCategory/UIImage+SLCommon.h +++ b/iOS_Tips/DarkMode/SLCategory/UIImage+SLCommon.h @@ -15,9 +15,13 @@ NS_ASSUME_NONNULL_BEGIN /// 将图片旋转弧度radians - (UIImage *)sl_imageRotatedByRadians:(CGFloat)radians; + /// 提取图片上某位置像素的颜色 - (UIColor *)sl_colorAtPixel:(CGPoint)point; +/// 图片缩放,针对大图片处理 ++ (UIImage *)sl_scaledImageWithData:(NSData *)data withSize:(CGSize)size scale:(CGFloat)scale orientation:(UIImageOrientation)orientation; + @end NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/SLCategory/UIImage+SLCommon.m b/iOS_Tips/DarkMode/SLCategory/UIImage+SLCommon.m index bd351721..b64d4ba0 100644 --- a/iOS_Tips/DarkMode/SLCategory/UIImage+SLCommon.m +++ b/iOS_Tips/DarkMode/SLCategory/UIImage+SLCommon.m @@ -78,4 +78,16 @@ - (UIColor *)sl_colorAtPixel:(CGPoint)point { return [UIColor colorWithRed:red green:green blue:blue alpha:alpha]; } +/// 图片缩放,针对大图片处理 ++ (UIImage *)sl_scaledImageWithData:(NSData *)data withSize:(CGSize)size scale:(CGFloat)scale orientation:(UIImageOrientation)orientation { + CGFloat maxPixelSize = MAX(size.width, size.height); + CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data,nil); + NSDictionary *optoins = @{(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways:(__bridge id)kCFBooleanTrue,(__bridge id)kCGImageSourceThumbnailMaxPixelSize:[NSNumber numberWithFloat:maxPixelSize]}; + CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)optoins); + UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale orientation:orientation]; + CGImageRelease(imageRef); + CFRelease(sourceRef); + return image; +} + @end diff --git a/iOS_Tips/DarkMode/SLCategory/UIScrollView+SLCommon.h b/iOS_Tips/DarkMode/SLCategory/UIScrollView+SLCommon.h new file mode 100644 index 00000000..653f1a89 --- /dev/null +++ b/iOS_Tips/DarkMode/SLCategory/UIScrollView+SLCommon.h @@ -0,0 +1,26 @@ +// +// UIScrollView+SLCommon.h +// DarkMode +// +// Created by wsl on 2020/5/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIScrollView (SLCommon) + +/// Y轴方向的最大的偏移量 +- (CGFloat)sl_maxContentOffsetY; +/// 在底部 +- (BOOL)sl_isBottom; +/// 在顶部 +- (BOOL)sl_isTop; +/// 滚动到顶部 +- (void)sl_scrollToTopWithAnimated:(BOOL)animated; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/SLCategory/UIScrollView+SLCommon.m b/iOS_Tips/DarkMode/SLCategory/UIScrollView+SLCommon.m new file mode 100644 index 00000000..8bfb3a1c --- /dev/null +++ b/iOS_Tips/DarkMode/SLCategory/UIScrollView+SLCommon.m @@ -0,0 +1,27 @@ +// +// UIScrollView+SLCommon.m +// DarkMode +// +// Created by wsl on 2020/5/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "UIScrollView+SLCommon.h" + +@implementation UIScrollView (SLCommon) + +- (CGFloat)sl_maxContentOffsetY { + return MAX(0, self.contentSize.height - self.frame.size.height); +} +- (BOOL)sl_isBottom { + return self.contentOffset.y + 0.5 >= [self sl_maxContentOffsetY] || + fabs(self.contentOffset.y - [self sl_maxContentOffsetY]) < FLT_EPSILON; +} +- (BOOL)sl_isTop { + return self.contentOffset.y <= 0; +} +- (void)sl_scrollToTopWithAnimated:(BOOL)animated { + [self setContentOffset:CGPointZero animated:animated]; +} + +@end diff --git a/iOS_Tips/DarkMode/SLCategory/UIView+SLFrame.h b/iOS_Tips/DarkMode/SLCategory/UIView+SLFrame.h index f47224d9..cb0c45b9 100644 --- a/iOS_Tips/DarkMode/SLCategory/UIView+SLFrame.h +++ b/iOS_Tips/DarkMode/SLCategory/UIView+SLFrame.h @@ -11,14 +11,20 @@ NS_ASSUME_NONNULL_BEGIN @interface UIView (SLFrame) -@property (assign, nonatomic) CGFloat sl_x; -@property (assign, nonatomic) CGFloat sl_y; -@property (assign, nonatomic) CGFloat sl_w; -@property (assign, nonatomic) CGFloat sl_h; -@property (assign, nonatomic) CGSize sl_size; -@property (nonatomic, assign) CGFloat sl_centerX; -@property (nonatomic, assign) CGFloat sl_centerY; -@property (assign, nonatomic) CGPoint sl_origin; +@property (nonatomic, assign ) CGFloat sl_x; +@property (nonatomic, assign ) CGFloat sl_y; +@property (nonatomic, assign ) CGFloat sl_width; +@property (nonatomic, assign ) CGFloat sl_height; +@property (nonatomic, assign ) CGFloat sl_centerX; +@property (nonatomic, assign ) CGFloat sl_centerY; + +@property (nonatomic, assign ) CGSize sl_size; +@property (nonatomic, assign ) CGPoint sl_origin; + +@property (nonatomic, assign) CGFloat sl_left; +@property (nonatomic, assign) CGFloat sl_right; +@property (nonatomic, assign) CGFloat sl_top; +@property (nonatomic, assign) CGFloat sl_bottom; @end diff --git a/iOS_Tips/DarkMode/SLCategory/UIView+SLFrame.m b/iOS_Tips/DarkMode/SLCategory/UIView+SLFrame.m index 87d6509d..f490b99d 100644 --- a/iOS_Tips/DarkMode/SLCategory/UIView+SLFrame.m +++ b/iOS_Tips/DarkMode/SLCategory/UIView+SLFrame.m @@ -28,21 +28,21 @@ - (CGFloat)sl_y { return self.frame.origin.y; } -- (void)setSl_w:(CGFloat)sl_w { +- (void)setSl_width:(CGFloat)sl_w { CGRect frame = self.frame; frame.size.width = sl_w; self.frame = frame; } -- (CGFloat)sl_w { +- (CGFloat)sl_width { return self.frame.size.width; } -- (void)setSl_h:(CGFloat)sl_h { +- (void)setSl_height:(CGFloat)sl_h { CGRect frame = self.frame; frame.size.height = sl_h; self.frame = frame; } -- (CGFloat)sl_h { +- (CGFloat)sl_height { return self.frame.size.height; } @@ -82,4 +82,43 @@ - (CGPoint)sl_origin { return self.frame.origin; } +- (CGFloat)sl_left{ + return self.frame.origin.x; +} +- (void)setSl_left:(CGFloat)left{ + CGRect frame = self.frame; + frame.origin.x = left; + self.frame = frame; +} + +- (CGFloat)sl_right{ + return CGRectGetMaxX(self.frame); +} + +-(void)setSl_right:(CGFloat)right{ + CGRect frame = self.frame; + frame.origin.x = right - frame.size.width; + self.frame = frame; +} + +- (CGFloat)sl_top{ + return self.frame.origin.y; +} + +- (void)setSl_top:(CGFloat)top{ + CGRect frame = self.frame; + frame.origin.y = top; + self.frame = frame; +} + +- (CGFloat)sl_bottom{ + return CGRectGetMaxY(self.frame); +} +- (void)setSl_bottom:(CGFloat)bottom{ + CGRect frame = self.frame; + frame.origin.y = bottom - frame.size.height; + self.frame = frame; +} + + @end diff --git a/iOS_Tips/DarkMode/SLCategory/WKWebView+SLExtension.h b/iOS_Tips/DarkMode/SLCategory/WKWebView+SLExtension.h new file mode 100644 index 00000000..7cf6cd9f --- /dev/null +++ b/iOS_Tips/DarkMode/SLCategory/WKWebView+SLExtension.h @@ -0,0 +1,44 @@ +// +// WKWebView+SLExtension.h +// DarkMode +// +// Created by wsl on 2020/5/30. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///设置UserAgent的方式 +typedef NS_ENUM (NSInteger, SLSetUAType){ + ///替换默认UA + SLSetUATypeReplace, + ///拼接默认UA + SLSetUATypeAppend, +}; + +@interface WKWebView (SLExtension) + +///注册http/https 以支持NSURLProtocol拦截WKWebView的网络请求 ++ (void)sl_registerSchemeForSupportHttpProtocol; +///取消注册http/https ++ (void)sl_unregisterSchemeForSupportHttpProtocol; + +/// 获取UA ++ (NSString *)sl_getUserAgent; +/// 设置UA 在WKWebView初始化之前设置,才能实时生效 ++ (void)sl_setCustomUserAgentWithType:(SLSetUAType)type UAString:(NSString *)customUserAgent; + +///设置自定义Cookie ++ (void)sl_setCustomCookieWithName:(NSString *)name + value:(NSString *)value + domain:(NSString *)domain + path:(NSString *)path + expiresDate:(NSDate *)expiresDate; +///查询获取所有Cookies ++ (void)sl_getAllCookies; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/SLCategory/WKWebView+SLExtension.m b/iOS_Tips/DarkMode/SLCategory/WKWebView+SLExtension.m new file mode 100644 index 00000000..fbb0f24a --- /dev/null +++ b/iOS_Tips/DarkMode/SLCategory/WKWebView+SLExtension.m @@ -0,0 +1,119 @@ +// +// WKWebView+SLExtension.m +// DarkMode +// +// Created by wsl on 2020/5/30. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "WKWebView+SLExtension.h" +#import + +///资料: https://github.com/dequan1331/WKWebViewExtension、(WKWebView那些坑)https://mp.weixin.qq.com/s/rhYKLIbXOsUJC_n6dt9UfA? +@interface WKWebView() + +@end + +@implementation WKWebView (SLExtension) + +#pragma mark - NSURLProtocol +///注册http/https 以支持NSURLProtocol对WKWebView的网络请求 ++ (void)sl_registerSchemeForSupportHttpProtocol{ + Class cls = NSClassFromString([NSString stringWithFormat:@"%@%@%@%@%@", @"W", @"K", @"Browsing", @"Context", @"Controller"]); + SEL sel = NSSelectorFromString([NSString stringWithFormat:@"%@%@%@%@%@", @"register", @"SchemeFor", @"Custom", @"Protocol", @":"]); + if ([cls respondsToSelector:sel]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + + [cls performSelector:sel withObject:@"http"]; + [cls performSelector:sel withObject:@"https"]; +#pragma clang diagnostic pop + } +} +///取消注册http/https ++ (void)sl_unregisterSchemeForSupportHttpProtocol{ + Class cls = NSClassFromString([NSString stringWithFormat:@"%@%@%@%@%@", @"W", @"K", @"Browsing", @"Context", @"Controller"]); + SEL sel = NSSelectorFromString([NSString stringWithFormat:@"%@%@%@%@%@", @"unregister", @"SchemeFor", @"Custom", @"Protocol", @":"]); + if ([cls respondsToSelector:sel]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + + [cls performSelector:sel withObject:@"http"]; + [cls performSelector:sel withObject:@"https"]; +#pragma clang diagnostic pop + } +} + +#pragma mark - UserAgent +/// 获取UA ++ (NSString *)sl_getUserAgent { + //获取UserAgent 这个是异步的 + // [self evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable response, NSError * _Nullable error) { + // NSString *userAgent = response; + // }]; + UIWebView *webView = [[UIWebView alloc] init]; + NSString *userAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"]; + return userAgent; +} +/// 设置UA 在WKWebView初始化之前设置 ,才能实时生效 ++ (void)sl_setCustomUserAgentWithType:(SLSetUAType)type UAString:(NSString *)customUserAgent { + if (!customUserAgent || customUserAgent.length <= 0) { + return; + } + // 这种设置方式仅对当前webView对象有效 + // self.customUserAgent = customUserAgent; + if (type == SLSetUATypeReplace) { + NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:customUserAgent, @"UserAgent", nil]; + //iOS8.0之前 是通过这种方式设置的,设置之后是全局 + [[NSUserDefaults standardUserDefaults] registerDefaults:dictionary]; + }else { + NSString *originalUserAgent = [WKWebView sl_getUserAgent]; + NSString *appUserAgent = [NSString stringWithFormat:@"%@-%@", originalUserAgent, customUserAgent]; + NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:appUserAgent, @"UserAgent", nil]; + [[NSUserDefaults standardUserDefaults] registerDefaults:dictionary]; + } +} + +#pragma mark - Cookie +///设置自定义Cookie ++ (void)sl_setCustomCookieWithName:(NSString *)name + value:(NSString *)value + domain:(NSString *)domain + path:(NSString *)path + expiresDate:(NSDate *)expiresDate{ + NSDictionary *mCookProperties = @{ + NSHTTPCookieDomain: domain, + NSHTTPCookiePath: path, + NSHTTPCookieName: name, + NSHTTPCookieValue: value, + NSHTTPCookieExpires : expiresDate + }; + NSHTTPCookie *myCookie = [NSHTTPCookie cookieWithProperties:mCookProperties]; + if (@available(iOS 11.0, *)) { + WKWebsiteDataStore *store = [WKWebsiteDataStore defaultDataStore]; + [store.httpCookieStore setCookie:myCookie completionHandler:^{ + }]; + } else { + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:myCookie]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } +} +///查询获取所有Cookies ++ (void)sl_getAllCookies { + if (@available(iOS 11.0, *)) { + WKWebsiteDataStore *store = [WKWebsiteDataStore defaultDataStore]; + [store.httpCookieStore getAllCookies:^(NSArray * cookies) { + [cookies enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + // NSLog(@" name:%@; value:%@ domain:%@ ", obj.name, obj.value, obj.domain); + }]; + }]; + } + else { + NSArray *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies; + for (NSHTTPCookie *cookie in cookieJar) { + // NSLog(@" name:%@; value:%@ domain:%@ ", cookie.name, cookie.value, cookie.domain); + } + } +} + +@end diff --git a/iOS_Tips/DarkMode/ThirdLibary/BSBacktraceLogger/BSBacktraceLogger.h b/iOS_Tips/DarkMode/ThirdLibary/BSBacktraceLogger/BSBacktraceLogger.h new file mode 100644 index 00000000..57717f90 --- /dev/null +++ b/iOS_Tips/DarkMode/ThirdLibary/BSBacktraceLogger/BSBacktraceLogger.h @@ -0,0 +1,24 @@ +// +// BSBacktraceLogger.h +// BSBacktraceLogger +// +// Created by 张星宇 on 16/8/27. +// Copyright © 2016年 bestswifter. All rights reserved. +// + +#import + +#define BSLOG NSLog(@"%@",[BSBacktraceLogger bs_backtraceOfCurrentThread]); +#define BSLOG_MAIN NSLog(@"%@",[BSBacktraceLogger bs_backtraceOfMainThread]); +#define BSLOG_ALL NSLog(@"%@",[BSBacktraceLogger bs_backtraceOfAllThread]); + +///记录线程的函数调用栈 https://toutiao.io/posts/aveig6/preview +@interface BSBacktraceLogger : NSObject + +///返回调用栈字符串 上传或其他处理 ++ (NSString *)bs_backtraceOfAllThread; ++ (NSString *)bs_backtraceOfCurrentThread; ++ (NSString *)bs_backtraceOfMainThread; ++ (NSString *)bs_backtraceOfNSThread:(NSThread *)thread; + +@end diff --git a/iOS_Tips/DarkMode/ThirdLibary/BSBacktraceLogger/BSBacktraceLogger.m b/iOS_Tips/DarkMode/ThirdLibary/BSBacktraceLogger/BSBacktraceLogger.m new file mode 100644 index 00000000..96240bdd --- /dev/null +++ b/iOS_Tips/DarkMode/ThirdLibary/BSBacktraceLogger/BSBacktraceLogger.m @@ -0,0 +1,420 @@ +// +// BSBacktraceLogger.m +// BSBacktraceLogger +// +// Created by 张星宇 on 16/8/27. +// Copyright © 2016年 bestswifter. All rights reserved. +// + +#import "BSBacktraceLogger.h" +#import +#include +#include +#include +#include +#include +#include +#include + +#pragma -mark DEFINE MACRO FOR DIFFERENT CPU ARCHITECTURE +#if defined(__arm64__) +#define DETAG_INSTRUCTION_ADDRESS(A) ((A) & ~(3UL)) +#define BS_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT +#define BS_THREAD_STATE ARM_THREAD_STATE64 +#define BS_FRAME_POINTER __fp +#define BS_STACK_POINTER __sp +#define BS_INSTRUCTION_ADDRESS __pc + +#elif defined(__arm__) +#define DETAG_INSTRUCTION_ADDRESS(A) ((A) & ~(1UL)) +#define BS_THREAD_STATE_COUNT ARM_THREAD_STATE_COUNT +#define BS_THREAD_STATE ARM_THREAD_STATE +#define BS_FRAME_POINTER __r[7] +#define BS_STACK_POINTER __sp +#define BS_INSTRUCTION_ADDRESS __pc + +#elif defined(__x86_64__) +#define DETAG_INSTRUCTION_ADDRESS(A) (A) +#define BS_THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT +#define BS_THREAD_STATE x86_THREAD_STATE64 +#define BS_FRAME_POINTER __rbp +#define BS_STACK_POINTER __rsp +#define BS_INSTRUCTION_ADDRESS __rip + +#elif defined(__i386__) +#define DETAG_INSTRUCTION_ADDRESS(A) (A) +#define BS_THREAD_STATE_COUNT x86_THREAD_STATE32_COUNT +#define BS_THREAD_STATE x86_THREAD_STATE32 +#define BS_FRAME_POINTER __ebp +#define BS_STACK_POINTER __esp +#define BS_INSTRUCTION_ADDRESS __eip + +#endif + +#define CALL_INSTRUCTION_FROM_RETURN_ADDRESS(A) (DETAG_INSTRUCTION_ADDRESS((A)) - 1) + +#if defined(__LP64__) +#define TRACE_FMT "%-4d%-31s 0x%016lx %s + %lu" +#define POINTER_FMT "0x%016lx" +#define POINTER_SHORT_FMT "0x%lx" +#define BS_NLIST struct nlist_64 +#else +#define TRACE_FMT "%-4d%-31s 0x%08lx %s + %lu" +#define POINTER_FMT "0x%08lx" +#define POINTER_SHORT_FMT "0x%lx" +#define BS_NLIST struct nlist +#endif + +typedef struct BSStackFrameEntry{ + const struct BSStackFrameEntry *const previous; + const uintptr_t return_address; +} BSStackFrameEntry; + +static mach_port_t main_thread_id; + +@implementation BSBacktraceLogger + ++ (void)load { + main_thread_id = mach_thread_self(); +} + +#pragma -mark Implementation of interface ++ (NSString *)bs_backtraceOfNSThread:(NSThread *)thread { + return _bs_backtraceOfThread(bs_machThreadFromNSThread(thread)); +} + ++ (NSString *)bs_backtraceOfCurrentThread { + return [self bs_backtraceOfNSThread:[NSThread currentThread]]; +} + ++ (NSString *)bs_backtraceOfMainThread { + return [self bs_backtraceOfNSThread:[NSThread mainThread]]; +} + ++ (NSString *)bs_backtraceOfAllThread { + thread_act_array_t threads; + mach_msg_type_number_t thread_count = 0; + const task_t this_task = mach_task_self(); + + kern_return_t kr = task_threads(this_task, &threads, &thread_count); + if(kr != KERN_SUCCESS) { + return @"Fail to get information of all threads"; + } + + NSMutableString *resultString = [NSMutableString stringWithFormat:@"Call Backtrace of %u threads:\n", thread_count]; + for(int i = 0; i < thread_count; i++) { + [resultString appendString:_bs_backtraceOfThread(threads[i])]; + } + return [resultString copy]; +} + +#pragma -mark Get call backtrace of a mach_thread +NSString *_bs_backtraceOfThread(thread_t thread) { + uintptr_t backtraceBuffer[50]; + int i = 0; + NSMutableString *resultString = [[NSMutableString alloc] initWithFormat:@"Backtrace of Thread %u:\n", thread]; + + _STRUCT_MCONTEXT machineContext; + if(!bs_fillThreadStateIntoMachineContext(thread, &machineContext)) { + return [NSString stringWithFormat:@"Fail to get information about thread: %u", thread]; + } + + const uintptr_t instructionAddress = bs_mach_instructionAddress(&machineContext); + backtraceBuffer[i] = instructionAddress; + ++i; + + uintptr_t linkRegister = bs_mach_linkRegister(&machineContext); + if (linkRegister) { + backtraceBuffer[i] = linkRegister; + i++; + } + + if(instructionAddress == 0) { + return @"Fail to get instruction address"; + } + + BSStackFrameEntry frame = {0}; + const uintptr_t framePtr = bs_mach_framePointer(&machineContext); + if(framePtr == 0 || + bs_mach_copyMem((void *)framePtr, &frame, sizeof(frame)) != KERN_SUCCESS) { + return @"Fail to get frame pointer"; + } + + for(; i < 50; i++) { + backtraceBuffer[i] = frame.return_address; + if(backtraceBuffer[i] == 0 || + frame.previous == 0 || + bs_mach_copyMem(frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) { + break; + } + } + + int backtraceLength = i; + Dl_info symbolicated[backtraceLength]; + bs_symbolicate(backtraceBuffer, symbolicated, backtraceLength, 0); + for (int i = 0; i < backtraceLength; ++i) { + [resultString appendFormat:@"%@", bs_logBacktraceEntry(i, backtraceBuffer[i], &symbolicated[i])]; + } + [resultString appendFormat:@"\n"]; + return [resultString copy]; +} + +#pragma -mark Convert NSThread to Mach thread +thread_t bs_machThreadFromNSThread(NSThread *nsthread) { + char name[256]; + mach_msg_type_number_t count; + thread_act_array_t list; + task_threads(mach_task_self(), &list, &count); + + NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970]; + NSString *originName = [nsthread name]; + [nsthread setName:[NSString stringWithFormat:@"%f", currentTimestamp]]; + + if ([nsthread isMainThread]) { + return (thread_t)main_thread_id; + } + + for (int i = 0; i < count; ++i) { + pthread_t pt = pthread_from_mach_thread_np(list[i]); + if ([nsthread isMainThread]) { + if (list[i] == main_thread_id) { + return list[i]; + } + } + if (pt) { + name[0] = '\0'; + pthread_getname_np(pt, name, sizeof name); + if (!strcmp(name, [nsthread name].UTF8String)) { + [nsthread setName:originName]; + return list[i]; + } + } + } + + [nsthread setName:originName]; + return mach_thread_self(); +} + +#pragma -mark GenerateBacbsrackEnrty +NSString* bs_logBacktraceEntry(const int entryNum, + const uintptr_t address, + const Dl_info* const dlInfo) { + char faddrBuff[20]; + char saddrBuff[20]; + + const char* fname = bs_lastPathEntry(dlInfo->dli_fname); + if(fname == NULL) { + sprintf(faddrBuff, POINTER_FMT, (uintptr_t)dlInfo->dli_fbase); + fname = faddrBuff; + } + + uintptr_t offset = address - (uintptr_t)dlInfo->dli_saddr; + const char* sname = dlInfo->dli_sname; + if(sname == NULL) { + sprintf(saddrBuff, POINTER_SHORT_FMT, (uintptr_t)dlInfo->dli_fbase); + sname = saddrBuff; + offset = address - (uintptr_t)dlInfo->dli_fbase; + } + return [NSString stringWithFormat:@"%-30s 0x%08" PRIxPTR " %s + %lu\n" ,fname, (uintptr_t)address, sname, offset]; +} + +const char* bs_lastPathEntry(const char* const path) { + if(path == NULL) { + return NULL; + } + + char* lastFile = strrchr(path, '/'); + return lastFile == NULL ? path : lastFile + 1; +} + +#pragma -mark HandleMachineContext +bool bs_fillThreadStateIntoMachineContext(thread_t thread, _STRUCT_MCONTEXT *machineContext) { + mach_msg_type_number_t state_count = BS_THREAD_STATE_COUNT; + kern_return_t kr = thread_get_state(thread, BS_THREAD_STATE, (thread_state_t)&machineContext->__ss, &state_count); + return (kr == KERN_SUCCESS); +} + +uintptr_t bs_mach_framePointer(mcontext_t const machineContext){ + return machineContext->__ss.BS_FRAME_POINTER; +} + +uintptr_t bs_mach_stackPointer(mcontext_t const machineContext){ + return machineContext->__ss.BS_STACK_POINTER; +} + +uintptr_t bs_mach_instructionAddress(mcontext_t const machineContext){ + return machineContext->__ss.BS_INSTRUCTION_ADDRESS; +} + +uintptr_t bs_mach_linkRegister(mcontext_t const machineContext){ +#if defined(__i386__) || defined(__x86_64__) + return 0; +#else + return machineContext->__ss.__lr; +#endif +} + +kern_return_t bs_mach_copyMem(const void *const src, void *const dst, const size_t numBytes){ + vm_size_t bytesCopied = 0; + return vm_read_overwrite(mach_task_self(), (vm_address_t)src, (vm_size_t)numBytes, (vm_address_t)dst, &bytesCopied); +} + +#pragma -mark Symbolicate +void bs_symbolicate(const uintptr_t* const backtraceBuffer, + Dl_info* const symbolsBuffer, + const int numEntries, + const int skippedEntries){ + int i = 0; + + if(!skippedEntries && i < numEntries) { + bs_dladdr(backtraceBuffer[i], &symbolsBuffer[i]); + i++; + } + + for(; i < numEntries; i++) { + bs_dladdr(CALL_INSTRUCTION_FROM_RETURN_ADDRESS(backtraceBuffer[i]), &symbolsBuffer[i]); + } +} + +bool bs_dladdr(const uintptr_t address, Dl_info* const info) { + info->dli_fname = NULL; + info->dli_fbase = NULL; + info->dli_sname = NULL; + info->dli_saddr = NULL; + + const uint32_t idx = bs_imageIndexContainingAddress(address); + if(idx == UINT_MAX) { + return false; + } + const struct mach_header* header = _dyld_get_image_header(idx); + const uintptr_t imageVMAddrSlide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx); + const uintptr_t addressWithSlide = address - imageVMAddrSlide; + const uintptr_t segmentBase = bs_segmentBaseOfImageIndex(idx) + imageVMAddrSlide; + if(segmentBase == 0) { + return false; + } + + info->dli_fname = _dyld_get_image_name(idx); + info->dli_fbase = (void*)header; + + // Find symbol tables and get whichever symbol is closest to the address. + const BS_NLIST* bestMatch = NULL; + uintptr_t bestDistance = ULONG_MAX; + uintptr_t cmdPtr = bs_firstCmdAfterHeader(header); + if(cmdPtr == 0) { + return false; + } + for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++) { + const struct load_command* loadCmd = (struct load_command*)cmdPtr; + if(loadCmd->cmd == LC_SYMTAB) { + const struct symtab_command* symtabCmd = (struct symtab_command*)cmdPtr; + const BS_NLIST* symbolTable = (BS_NLIST*)(segmentBase + symtabCmd->symoff); + const uintptr_t stringTable = segmentBase + symtabCmd->stroff; + + for(uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++) { + // If n_value is 0, the symbol refers to an external object. + if(symbolTable[iSym].n_value != 0) { + uintptr_t symbolBase = symbolTable[iSym].n_value; + uintptr_t currentDistance = addressWithSlide - symbolBase; + if((addressWithSlide >= symbolBase) && + (currentDistance <= bestDistance)) { + bestMatch = symbolTable + iSym; + bestDistance = currentDistance; + } + } + } + if(bestMatch != NULL) { + info->dli_saddr = (void*)(bestMatch->n_value + imageVMAddrSlide); + info->dli_sname = (char*)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx); + if(*info->dli_sname == '_') { + info->dli_sname++; + } + // This happens if all symbols have been stripped. + if(info->dli_saddr == info->dli_fbase && bestMatch->n_type == 3) { + info->dli_sname = NULL; + } + break; + } + } + cmdPtr += loadCmd->cmdsize; + } + return true; +} + +uintptr_t bs_firstCmdAfterHeader(const struct mach_header* const header) { + switch(header->magic) { + case MH_MAGIC: + case MH_CIGAM: + return (uintptr_t)(header + 1); + case MH_MAGIC_64: + case MH_CIGAM_64: + return (uintptr_t)(((struct mach_header_64*)header) + 1); + default: + return 0; // Header is corrupt + } +} + +uint32_t bs_imageIndexContainingAddress(const uintptr_t address) { + const uint32_t imageCount = _dyld_image_count(); + const struct mach_header* header = 0; + + for(uint32_t iImg = 0; iImg < imageCount; iImg++) { + header = _dyld_get_image_header(iImg); + if(header != NULL) { + // Look for a segment command with this address within its range. + uintptr_t addressWSlide = address - (uintptr_t)_dyld_get_image_vmaddr_slide(iImg); + uintptr_t cmdPtr = bs_firstCmdAfterHeader(header); + if(cmdPtr == 0) { + continue; + } + for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++) { + const struct load_command* loadCmd = (struct load_command*)cmdPtr; + if(loadCmd->cmd == LC_SEGMENT) { + const struct segment_command* segCmd = (struct segment_command*)cmdPtr; + if(addressWSlide >= segCmd->vmaddr && + addressWSlide < segCmd->vmaddr + segCmd->vmsize) { + return iImg; + } + } + else if(loadCmd->cmd == LC_SEGMENT_64) { + const struct segment_command_64* segCmd = (struct segment_command_64*)cmdPtr; + if(addressWSlide >= segCmd->vmaddr && + addressWSlide < segCmd->vmaddr + segCmd->vmsize) { + return iImg; + } + } + cmdPtr += loadCmd->cmdsize; + } + } + } + return UINT_MAX; +} + +uintptr_t bs_segmentBaseOfImageIndex(const uint32_t idx) { + const struct mach_header* header = _dyld_get_image_header(idx); + + // Look for a segment command and return the file image address. + uintptr_t cmdPtr = bs_firstCmdAfterHeader(header); + if(cmdPtr == 0) { + return 0; + } + for(uint32_t i = 0;i < header->ncmds; i++) { + const struct load_command* loadCmd = (struct load_command*)cmdPtr; + if(loadCmd->cmd == LC_SEGMENT) { + const struct segment_command* segmentCmd = (struct segment_command*)cmdPtr; + if(strcmp(segmentCmd->segname, SEG_LINKEDIT) == 0) { + return segmentCmd->vmaddr - segmentCmd->fileoff; + } + } + else if(loadCmd->cmd == LC_SEGMENT_64) { + const struct segment_command_64* segmentCmd = (struct segment_command_64*)cmdPtr; + if(strcmp(segmentCmd->segname, SEG_LINKEDIT) == 0) { + return (uintptr_t)(segmentCmd->vmaddr - segmentCmd->fileoff); + } + } + cmdPtr += loadCmd->cmdsize; + } + return 0; +} + +@end diff --git a/iOS_Tips/DarkMode/ThirdLibary/Fishhook/fishhook.c b/iOS_Tips/DarkMode/ThirdLibary/Fishhook/fishhook.c new file mode 100644 index 00000000..205ee82b --- /dev/null +++ b/iOS_Tips/DarkMode/ThirdLibary/Fishhook/fishhook.c @@ -0,0 +1,210 @@ +// Copyright (c) 2013, Facebook, Inc. +// All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "fishhook.h" + +#import +#import +#import +#import +#import +#import +#import + +#ifdef __LP64__ +typedef struct mach_header_64 mach_header_t; +typedef struct segment_command_64 segment_command_t; +typedef struct section_64 section_t; +typedef struct nlist_64 nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 +#else +typedef struct mach_header mach_header_t; +typedef struct segment_command segment_command_t; +typedef struct section section_t; +typedef struct nlist nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT +#endif + +#ifndef SEG_DATA_CONST +#define SEG_DATA_CONST "__DATA_CONST" +#endif + +struct rebindings_entry { + struct rebinding *rebindings; + size_t rebindings_nel; + struct rebindings_entry *next; +}; + +static struct rebindings_entry *_rebindings_head; + +static int prepend_rebindings(struct rebindings_entry **rebindings_head, + struct rebinding rebindings[], + size_t nel) { + struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry)); + if (!new_entry) { + return -1; + } + new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel); + if (!new_entry->rebindings) { + free(new_entry); + return -1; + } + memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel); + new_entry->rebindings_nel = nel; + new_entry->next = *rebindings_head; + *rebindings_head = new_entry; + return 0; +} + +static void perform_rebinding_with_section(struct rebindings_entry *rebindings, + section_t *section, + intptr_t slide, + nlist_t *symtab, + char *strtab, + uint32_t *indirect_symtab) { + uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; + void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); + for (uint i = 0; i < section->size / sizeof(void *); i++) { + uint32_t symtab_index = indirect_symbol_indices[i]; + if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || + symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) { + continue; + } + uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; + char *symbol_name = strtab + strtab_offset; + if (strnlen(symbol_name, 2) < 2) { + continue; + } + struct rebindings_entry *cur = rebindings; + while (cur) { + for (uint j = 0; j < cur->rebindings_nel; j++) { + if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { + if (cur->rebindings[j].replaced != NULL && + indirect_symbol_bindings[i] != cur->rebindings[j].replacement) { + *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; + } + indirect_symbol_bindings[i] = cur->rebindings[j].replacement; + goto symbol_loop; + } + } + cur = cur->next; + } + symbol_loop:; + } +} + +static void rebind_symbols_for_image(struct rebindings_entry *rebindings, + const struct mach_header *header, + intptr_t slide) { + Dl_info info; + if (dladdr(header, &info) == 0) { + return; + } + + segment_command_t *cur_seg_cmd; + segment_command_t *linkedit_segment = NULL; + struct symtab_command* symtab_cmd = NULL; + struct dysymtab_command* dysymtab_cmd = NULL; + + uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { + linkedit_segment = cur_seg_cmd; + } + } else if (cur_seg_cmd->cmd == LC_SYMTAB) { + symtab_cmd = (struct symtab_command*)cur_seg_cmd; + } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { + dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; + } + } + + if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || + !dysymtab_cmd->nindirectsyms) { + return; + } + + // Find base symbol/string table addresses + uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; + nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); + char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); + + // Get indirect symbol table (array of uint32_t indices into symbol table) + uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); + + cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 && + strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) { + continue; + } + for (uint j = 0; j < cur_seg_cmd->nsects; j++) { + section_t *sect = + (section_t *)(cur + sizeof(segment_command_t)) + j; + if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { + perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); + } + if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { + perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); + } + } + } + } +} + +static void _rebind_symbols_for_image(const struct mach_header *header, + intptr_t slide) { + rebind_symbols_for_image(_rebindings_head, header, slide); +} + +int rebind_symbols_image(void *header, + intptr_t slide, + struct rebinding rebindings[], + size_t rebindings_nel) { + struct rebindings_entry *rebindings_head = NULL; + int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel); + rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide); + free(rebindings_head); + return retval; +} + +int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) { + int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel); + if (retval < 0) { + return retval; + } + // If this was the first call, register callback for image additions (which is also invoked for + // existing images, otherwise, just run on existing images + if (!_rebindings_head->next) { + _dyld_register_func_for_add_image(_rebind_symbols_for_image); + } else { + uint32_t c = _dyld_image_count(); + for (uint32_t i = 0; i < c; i++) { + _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); + } + } + return retval; +} diff --git a/iOS_Tips/DarkMode/ThirdLibary/Fishhook/fishhook.h b/iOS_Tips/DarkMode/ThirdLibary/Fishhook/fishhook.h new file mode 100644 index 00000000..c8edadfe --- /dev/null +++ b/iOS_Tips/DarkMode/ThirdLibary/Fishhook/fishhook.h @@ -0,0 +1,76 @@ +// Copyright (c) 2013, Facebook, Inc. +// All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef fishhook_h +#define fishhook_h + +#include +#include + +#if !defined(FISHHOOK_EXPORT) +#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden"))) +#else +#define FISHHOOK_VISIBILITY __attribute__((visibility("default"))) +#endif + +#ifdef __cplusplus +extern "C" { +#endif //__cplusplus + +/* + * A structure representing a particular intended rebinding from a symbol + * name to its replacement + */ +struct rebinding { + const char *name; //需要Hook的函数名称,c字符串 + void *replacement; //新函数的地址 + void **replaced; //原始函数地址的指针 +}; + +/* + * For each rebinding in rebindings, rebinds references to external, indirect + * symbols with the specified name to instead point at replacement for each + * image in the calling process as well as for all future images that are loaded + * by the process. If rebind_functions is called more than once, the symbols to + * rebind are added to the existing list of rebindings, and if a given symbol + * is rebound more than once, the later rebinding will take precedence. + */ +FISHHOOK_VISIBILITY +int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); + +/* + * Rebinds as above, but only in the specified image. The header should point + * to the mach-o header, the slide should be the slide offset. Others as above. + */ +FISHHOOK_VISIBILITY +int rebind_symbols_image(void *header, + intptr_t slide, + struct rebinding rebindings[], + size_t rebindings_nel); + +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif //fishhook_h + diff --git a/iOS_Tips/DarkMode/ThirdLibary/Fishhook/queue.c b/iOS_Tips/DarkMode/ThirdLibary/Fishhook/queue.c new file mode 100644 index 00000000..fc155269 --- /dev/null +++ b/iOS_Tips/DarkMode/ThirdLibary/Fishhook/queue.c @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include + +#include "queue.h" + + +struct DSQueue { + /* An array of elements in the queue. */ + void **buf; + + /* The position of the first element in the queue. */ + uint32_t pos; + + /* The number of items currently in the queue. + * When `length` = 0, ds_queue_get will block. + * When `length` = `capacity`, ds_queue_put will block. */ + uint32_t length; + + /* The total number of allowable items in the queue */ + uint32_t capacity; + + /* When true, the queue has been closed. A run-time error will occur + * if a value is sent to a closed queue. */ + bool closed; + + /* Guards the modification of `length` (a condition variable) and `pos`. */ + pthread_mutex_t mutate; + + /* A condition variable that is pinged whenever `length` has changed or + * when the queue has been closed. */ + pthread_cond_t cond_length; +}; + + +struct DSQueue * +ds_queue_create(uint32_t buffer_capacity) +{ + struct DSQueue *queue; + int errno; + + assert(buffer_capacity > 0); + + queue = malloc(sizeof(*queue)); + assert(queue); + + queue->pos = 0; + queue->length = 0; + queue->capacity = buffer_capacity; + queue->closed = false; + + queue->buf = malloc(buffer_capacity * sizeof(*queue->buf)); + assert(queue->buf); + + if (0 != (errno = pthread_mutex_init(&queue->mutate, NULL))) { + fprintf(stderr, "Could not create mutex. Errno: %d\n", errno); + exit(1); + } + if (0 != (errno = pthread_cond_init(&queue->cond_length, NULL))) { + fprintf(stderr, "Could not create cond var. Errno: %d\n", errno); + exit(1); + } + + return queue; +} + +void +ds_queue_free(struct DSQueue *queue) +{ + int errno; + + if (0 != (errno = pthread_mutex_destroy(&queue->mutate))) { + fprintf(stderr, "Could not destroy mutex. Errno: %d\n", errno); + exit(1); + } + if (0 != (errno = pthread_cond_destroy(&queue->cond_length))) { + fprintf(stderr, "Could not destroy cond var. Errno: %d\n", errno); + exit(1); + } + free(queue->buf); + free(queue); +} + +int +ds_queue_length(struct DSQueue *queue) +{ + int len; + pthread_mutex_lock(&queue->mutate); + len = queue->length; + pthread_mutex_unlock(&queue->mutate); + return len; +} + +int +ds_queue_capacity(struct DSQueue *queue) +{ + return queue->capacity; +} + +void +ds_queue_close(struct DSQueue *queue) +{ + pthread_mutex_lock(&queue->mutate); + queue->closed = true; + pthread_cond_broadcast(&queue->cond_length); + pthread_mutex_unlock(&queue->mutate); +} + +void +ds_queue_put(struct DSQueue *queue, void *item) +{ + pthread_mutex_lock(&queue->mutate); + assert(!queue->closed); + + while (queue->length == queue->capacity) + pthread_cond_wait(&queue->cond_length, &queue->mutate); + + assert(!queue->closed); + assert(queue->length < queue->capacity); + + queue->buf[(queue->pos + queue->length) % queue->capacity] = item; + queue->length++; + pthread_cond_broadcast(&queue->cond_length); + + pthread_mutex_unlock(&queue->mutate); +} + +void * +ds_queue_get(struct DSQueue *queue) +{ + void *item; + + pthread_mutex_lock(&queue->mutate); + + while (queue->length == 0) { + /* This is a bit tricky. It is possible that the queue has been closed + * *and* has become empty while `pthread_cond_wait` is blocking. + * Therefore, it is necessary to always check if the queue has been + * closed when the queue is empty, otherwise we will deadlock. */ + if (queue->closed) { + pthread_mutex_unlock(&queue->mutate); + return NULL; + } + pthread_cond_wait(&queue->cond_length, &queue->mutate); + } + + assert(queue->length <= queue->capacity); + assert(queue->length > 0); + + item = queue->buf[queue->pos]; + queue->buf[queue->pos] = NULL; + queue->pos = (queue->pos + 1) % queue->capacity; + + queue->length--; + pthread_cond_broadcast(&queue->cond_length); + + pthread_mutex_unlock(&queue->mutate); + + return item; +} diff --git a/iOS_Tips/DarkMode/ThirdLibary/Fishhook/queue.h b/iOS_Tips/DarkMode/ThirdLibary/Fishhook/queue.h new file mode 100644 index 00000000..e9796e16 --- /dev/null +++ b/iOS_Tips/DarkMode/ThirdLibary/Fishhook/queue.h @@ -0,0 +1,70 @@ +#ifndef __LIBDS_QUEUE_H__ +#define __LIBDS_QUEUE_H__ + +#include + +/* + * DSQueue is a thread-safe queue that has no limitation on the number of + * threads that can call ds_queue_put and ds_queue_get simultaneously. + * That is, it supports a multiple producer and multiple consumer model. + */ + +/* DSQueue implements a thread-safe queue using a fairly standard + * circular buffer. */ +struct DSQueue; + +/* Allocates a new DSQueue with a buffer size of the capacity given. */ +struct DSQueue * +ds_queue_create(uint32_t buffer_capacity); + +/* Frees all data used to create a DSQueue. It should only be called after + * a call to ds_queue_close to make sure all 'gets' are terminated before + * destroying mutexes/condition variables. + * + * Note that the data inside the buffer is not freed. */ +void +ds_queue_free(struct DSQueue *queue); + +/* Returns the current length (number of items) in the queue. */ +int +ds_queue_length(struct DSQueue *queue); + +/* Returns the capacity of the queue. This is always equivalent to the + * size of the initial buffer capacity. */ +int +ds_queue_capacity(struct DSQueue *queue); + +/* Closes a queue. A closed queue cannot add any new values. + * + * When a queue is closed, an empty queue will always be empty. + * Therefore, `ds_queue_get` will return NULL and not block when + * the queue is empty. Therefore, one can traverse the items in a queue + * in a thread-safe manner with something like: + * + * void *queue_item; + * while (NULL != (queue_item = ds_queue_get(queue))) + * do_something_with(queue_item); + */ +void +ds_queue_close(struct DSQueue *queue); + +/* Adds new values to a queue (or "sends values to a consumer"). + * `ds_queue_put` cannot be called with a queue that has been closed. If + * it is, an assertion error will occur. + * If the queue is full, `ds_queue_put` will block until it is not full, + * in which case the value will be added to the queue. */ +void +ds_queue_put(struct DSQueue *queue, void *item); + +/* Reads new values from a queue (or "receives values from a producer"). + * `ds_queue_get` will block if the queue is empty until a new value has been + * added to the queue with `ds_queue_put`. In which case, `ds_queue_get` will + * return the next item in the queue. + * `ds_queue_get` can be safely called on a queue that has been closed (indeed, + * this is probably necessary). If the queue is closed and not empty, the next + * item in the queue is returned. If the queue is closed and empty, it will + * always be empty, and therefore NULL will be returned immediately. */ +void * +ds_queue_get(struct DSQueue *queue); + +#endif diff --git a/iOS_Tips/DarkMode/General/Tool/OpenGLUtils/GLESMath.c b/iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/GLESMath.c similarity index 100% rename from iOS_Tips/DarkMode/General/Tool/OpenGLUtils/GLESMath.c rename to iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/GLESMath.c diff --git a/iOS_Tips/DarkMode/General/Tool/OpenGLUtils/GLESMath.h b/iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/GLESMath.h similarity index 100% rename from iOS_Tips/DarkMode/General/Tool/OpenGLUtils/GLESMath.h rename to iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/GLESMath.h diff --git a/iOS_Tips/DarkMode/General/Tool/OpenGLUtils/GLESUtils.h b/iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/GLESUtils.h similarity index 100% rename from iOS_Tips/DarkMode/General/Tool/OpenGLUtils/GLESUtils.h rename to iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/GLESUtils.h diff --git a/iOS_Tips/DarkMode/General/Tool/OpenGLUtils/GLESUtils.m b/iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/GLESUtils.m similarity index 100% rename from iOS_Tips/DarkMode/General/Tool/OpenGLUtils/GLESUtils.m rename to iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/GLESUtils.m diff --git a/iOS_Tips/DarkMode/General/Tool/OpenGLUtils/Quaternion.h b/iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/Quaternion.h similarity index 100% rename from iOS_Tips/DarkMode/General/Tool/OpenGLUtils/Quaternion.h rename to iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/Quaternion.h diff --git a/iOS_Tips/DarkMode/General/Tool/OpenGLUtils/Vector.h b/iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/Vector.h similarity index 100% rename from iOS_Tips/DarkMode/General/Tool/OpenGLUtils/Vector.h rename to iOS_Tips/DarkMode/ThirdLibary/OpenGLUtils/Vector.h diff --git a/iOS_Tips/DarkMode/ThirdLibary/Reachability/Reachability.h b/iOS_Tips/DarkMode/ThirdLibary/Reachability/Reachability.h new file mode 100644 index 00000000..50c048c1 --- /dev/null +++ b/iOS_Tips/DarkMode/ThirdLibary/Reachability/Reachability.h @@ -0,0 +1,64 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + */ + +#import +#import +#import + + +typedef enum : NSInteger { + NotReachable = 0, + ReachableViaWiFi, + ReachableViaWWAN +} NetworkStatus; + +#pragma mark IPv6 Support +//Reachability fully support IPv6. For full details, see ReadMe.md. + + +extern NSString *kReachabilityChangedNotification; + + +@interface Reachability : NSObject + +/*! + * Use to check the reachability of a given host name. + */ ++ (instancetype)reachabilityWithHostName:(NSString *)hostName; + +/*! + * Use to check the reachability of a given IP address. + */ ++ (instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress; + +/*! + * Checks whether the default route is available. Should be used by applications that do not connect to a particular host. + */ ++ (instancetype)reachabilityForInternetConnection; + + +#pragma mark reachabilityForLocalWiFi +//reachabilityForLocalWiFi has been removed from the sample. See ReadMe.md for more information. +//+ (instancetype)reachabilityForLocalWiFi; + +/*! + * Start listening for reachability notifications on the current run loop. + */ +- (BOOL)startNotifier; +- (void)stopNotifier; + +- (NetworkStatus)currentReachabilityStatus; + +/*! + * WWAN may be available, but not active until a connection has been established. WiFi may require a connection for VPN on Demand. + */ +- (BOOL)connectionRequired; + +@end + + diff --git a/iOS_Tips/DarkMode/ThirdLibary/Reachability/Reachability.m b/iOS_Tips/DarkMode/ThirdLibary/Reachability/Reachability.m new file mode 100644 index 00000000..f081a16a --- /dev/null +++ b/iOS_Tips/DarkMode/ThirdLibary/Reachability/Reachability.m @@ -0,0 +1,242 @@ +/* + Copyright (C) 2016 Apple Inc. All Rights Reserved. + See LICENSE.txt for this sample’s licensing information + + Abstract: + Basic demonstration of how to use the SystemConfiguration Reachablity APIs. + */ + +#import +#import +#import +#import +#import + +#import + +#import "Reachability.h" + +#pragma mark IPv6 Support +//Reachability fully support IPv6. For full details, see ReadMe.md. + + +NSString *kReachabilityChangedNotification = @"kNetworkReachabilityChangedNotification"; + + +#pragma mark - Supporting functions + +#define kShouldPrintReachabilityFlags 1 + +static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment) +{ +#if kShouldPrintReachabilityFlags + + NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n", + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', + comment + ); +#endif +} + + +static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target, flags) + NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); + NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); + + Reachability* noteObject = (__bridge Reachability *)info; + // Post a notification to notify the client that the network reachability changed. + [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject]; +} + + +#pragma mark - Reachability implementation + +@implementation Reachability +{ + SCNetworkReachabilityRef _reachabilityRef; +} + ++ (instancetype)reachabilityWithHostName:(NSString *)hostName +{ + Reachability* returnValue = NULL; + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); + if (reachability != NULL) + { + returnValue= [[self alloc] init]; + if (returnValue != NULL) + { + returnValue->_reachabilityRef = reachability; + } + else { + CFRelease(reachability); + } + } + return returnValue; +} + + ++ (instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress +{ + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, hostAddress); + + Reachability* returnValue = NULL; + + if (reachability != NULL) + { + returnValue = [[self alloc] init]; + if (returnValue != NULL) + { + returnValue->_reachabilityRef = reachability; + } + else { + CFRelease(reachability); + } + } + return returnValue; +} + + ++ (instancetype)reachabilityForInternetConnection +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + return [self reachabilityWithAddress: (const struct sockaddr *) &zeroAddress]; +} + +#pragma mark reachabilityForLocalWiFi +//reachabilityForLocalWiFi has been removed from the sample. See ReadMe.md for more information. +//+ (instancetype)reachabilityForLocalWiFi + + + +#pragma mark - Start and stop notifier + +- (BOOL)startNotifier +{ + BOOL returnValue = NO; + SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; + + if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context)) + { + if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) + { + returnValue = YES; + } + } + + return returnValue; +} + + +- (void)stopNotifier +{ + if (_reachabilityRef != NULL) + { + SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + } +} + + +- (void)dealloc +{ + [self stopNotifier]; + if (_reachabilityRef != NULL) + { + CFRelease(_reachabilityRef); + } +} + + +#pragma mark - Network Flag Handling + +- (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags +{ + PrintReachabilityFlags(flags, "networkStatusForFlags"); + if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) + { + // The target host is not reachable. + return NotReachable; + } + + NetworkStatus returnValue = NotReachable; + + if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) + { + /* + If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi... + */ + returnValue = ReachableViaWiFi; + } + + if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) + { + /* + ... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs... + */ + + if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) + { + /* + ... and no [user] intervention is needed... + */ + returnValue = ReachableViaWiFi; + } + } + + if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) + { + /* + ... but WWAN connections are OK if the calling application is using the CFNetwork APIs. + */ + returnValue = ReachableViaWWAN; + } + + return returnValue; +} + + +- (BOOL)connectionRequired +{ + NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + + return NO; +} + + +- (NetworkStatus)currentReachabilityStatus +{ + NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL SCNetworkReachabilityRef"); + NetworkStatus returnValue = NotReachable; + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) + { + returnValue = [self networkStatusForFlags:flags]; + } + + return returnValue; +} + + +@end diff --git a/iOS_Tips/DarkMode/ThirdLibary/YYAnimatedImageViewCategory/YYAnimatedImageView+iOS14.h b/iOS_Tips/DarkMode/ThirdLibary/YYAnimatedImageViewCategory/YYAnimatedImageView+iOS14.h new file mode 100644 index 00000000..f6930df8 --- /dev/null +++ b/iOS_Tips/DarkMode/ThirdLibary/YYAnimatedImageViewCategory/YYAnimatedImageView+iOS14.h @@ -0,0 +1,18 @@ +// +// YYAnimatedImageView+iOS14.h +// DarkMode +// +// Created by 王先生 on 2020/12/5. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "YYAnimatedImageView.h" + +NS_ASSUME_NONNULL_BEGIN + +///主要解决 ios14YYAnimatedImageView图片显示问题 #573 https://github.com/ibireme/YYKit/issues/573 +@interface YYAnimatedImageView (iOS14) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/ThirdLibary/YYAnimatedImageViewCategory/YYAnimatedImageView+iOS14.m b/iOS_Tips/DarkMode/ThirdLibary/YYAnimatedImageViewCategory/YYAnimatedImageView+iOS14.m new file mode 100644 index 00000000..2e3b766b --- /dev/null +++ b/iOS_Tips/DarkMode/ThirdLibary/YYAnimatedImageViewCategory/YYAnimatedImageView+iOS14.m @@ -0,0 +1,33 @@ +// +// YYAnimatedImageView+iOS14.m +// DarkMode +// +// Created by 王先生 on 2020/12/5. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "YYAnimatedImageView+iOS14.h" +#import + +@implementation YYAnimatedImageView (iOS14) ++(void)load { + // 获取系统的方法 + Method displayLayerMethod = class_getInstanceMethod(self, @selector(displayLayer:)); + // 获取更新的方法 + Method displayLayerNewMethod = class_getInstanceMethod(self, @selector(displayLayerNew:)); + // 方法交换 + method_exchangeImplementations(displayLayerMethod, displayLayerNewMethod); +} +-(void)displayLayerNew:(CALayer *)layer { + Ivar imageIvar = class_getInstanceVariable([self class], "_curFrame"); + UIImage *image = object_getIvar(self, imageIvar); + if (image) { + layer.contents = (__bridge id)image.CGImage; + } + else { + if (@available(iOS 14.0, *)) { + [super displayLayer:layer]; + } + } +} +@end diff --git a/iOS_Tips/DarkMode/ViewController.m b/iOS_Tips/DarkMode/ViewController.m index ee4ac92a..cc8af04d 100644 --- a/iOS_Tips/DarkMode/ViewController.m +++ b/iOS_Tips/DarkMode/ViewController.m @@ -8,14 +8,15 @@ #import "ViewController.h" #import "SLDarkModeViewController.h" -#import "SLShotViewController.h" -#import "SLFaceDetectController.h" -#import "SLFilterViewController.h" -#import "SLGPUImageController.h" +#import "SLAVListViewController.h" #import "SLOpenGLController.h" +#import "SLWorkIssuesViewController.h" +#import "SLCrashViewController.h" +#import "SLWebViewListController.h" @interface ViewController () @property (nonatomic, strong) NSMutableArray *dataSource; +@property (nonatomic, strong) NSMutableArray *classArray; @end @implementation ViewController @@ -44,7 +45,22 @@ - (void)setupUI { #pragma mark - Data - (void)getData { //tableView、UIAlertView等系统控件,在不自定义颜色的情况下,默认颜色都是动态的,支持暗黑模式 - [self.dataSource addObjectsFromArray:@[@"暗黑/光亮模式", @"AppleId三方登录应用", @"AVFoundation 高仿微信相机拍摄和编辑功能", @"AVFoundation 人脸检测", @"AVFoundation 实时滤镜拍摄和导出", @"GPUImage框架的使用", @"VideoToolBox和AudioToolBox音视频编解码", @"OpenGL-ES学习", @"LeetCode算法练习集合"]]; + [self.dataSource addObjectsFromArray:@[@"暗黑/光亮模式", + @"AppleId三方登录应用", + @"AVFoundation音视频相关", + @"OpenGL-ES学习", + @"LeetCode算法练习集合", + @"工作中踩过的坑", + @"iOS Crash防护", + @"WKWebView相关"]]; + [self.classArray addObjectsFromArray:@[[SLDarkModeViewController class], + [UIViewController class], + [SLAVListViewController class], + [SLOpenGLController class], + [UIViewController class], + [SLWorkIssuesViewController class], + [SLCrashViewController class], + [SLWebViewListController class]]]; [self.tableView reloadData]; } @@ -55,10 +71,12 @@ - (NSMutableArray *)dataSource { } return _dataSource; } - -#pragma mark - HelpMethods - -#pragma mark - EventsHandle +- (NSMutableArray *)classArray { + if (_classArray == nil) { + _classArray = [NSMutableArray array]; + } + return _classArray; +} #pragma mark - UITableViewDelegate, UITableViewDataSource - (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { @@ -72,53 +90,18 @@ - (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForR } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; + UIViewController *nextVc = [[self.classArray[indexPath.row] alloc] init]; switch (indexPath.row) { - case 0: { - SLDarkModeViewController * darkModeViewController = [[SLDarkModeViewController alloc] init]; - [self.navigationController pushViewController:darkModeViewController animated:YES]; - } - break; case 1: { - NSLog(@"查看本仓库下的AddingTheSignInWithAppleFlowToYourApp"); - } - break; - case 2: { - SLShotViewController * shotViewController = [[SLShotViewController alloc] init]; - shotViewController.modalPresentationStyle = UIModalPresentationFullScreen; - [self presentViewController:shotViewController animated:YES completion:nil]; - } - break; - case 3: { - SLFaceDetectController * faceDetectController = [[SLFaceDetectController alloc] init]; - faceDetectController.modalPresentationStyle = UIModalPresentationFullScreen; - [self presentViewController:faceDetectController animated:YES completion:nil]; + [SLAlertView showAlertViewWithText:@"查看本仓库下的AddingTheSignInWithAppleFlowToYourApp" delayHid:2]; } break; case 4: { - SLFilterViewController * filterViewController = [[SLFilterViewController alloc] init]; - filterViewController.modalPresentationStyle = UIModalPresentationFullScreen; - [self presentViewController:filterViewController animated:YES completion:nil]; - } - break; - case 5: { - SLGPUImageController * gpuImageController = [[SLGPUImageController alloc] init]; - gpuImageController.modalPresentationStyle = UIModalPresentationFullScreen; - [self presentViewController:gpuImageController animated:YES completion:nil]; - } - break; - case 6: - NSLog(@"查看本仓库下的VideoEncoder&Decoder"); - break; - case 7: { - SLOpenGLController * openGLController = [[SLOpenGLController alloc] init]; - [self.navigationController pushViewController:openGLController animated:YES]; - } - break; - case 8: { - NSLog(@"LeetCode算法练习集合地址:https://github.com/wsl2ls/AlgorithmSet.git"); - } + [SLAlertView showAlertViewWithText:@"LeetCode算法练习集合: https://github.com/wsl2ls/AlgorithmSet.git" delayHid:2]; + } break; default: + [self.navigationController pushViewController:nextVc animated:YES]; break; } } diff --git a/iOS_Tips/DarkMode/WKWebView/SLTableViewController.h b/iOS_Tips/DarkMode/WKWebView/SLTableViewController.h new file mode 100644 index 00000000..242444ef --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/SLTableViewController.h @@ -0,0 +1,18 @@ +// +// SLTableViewController.h +// DarkMode +// +// Created by wsl on 2020/6/9. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///UITableView原理, 用UIScrollView实现 类似UITableView的复用功能 +@interface SLTableViewController : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/SLTableViewController.m b/iOS_Tips/DarkMode/WKWebView/SLTableViewController.m new file mode 100644 index 00000000..1c0d5d11 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/SLTableViewController.m @@ -0,0 +1,314 @@ +// +// SLTableViewController.m +// DarkMode +// +// Created by wsl on 2020/6/9. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLTableViewController.h" + +@interface SLTableViewCell : UIButton +@property (nonatomic, copy) NSString *cellID; +@property (nonatomic, assign) NSInteger index; +@end +@implementation SLTableViewCell +@end + +@class SLTableView; +@protocol SLTableViewDataSource +@required +///行数 +- (NSInteger)numberOfRowsInTableView:(SLTableView *)tableView; +///行高 +- (CGFloat)tableView:(SLTableView *)tableView heightForRowAtIndex:(NSInteger)index; +///行内容 +- (SLTableViewCell *)tableView:(SLTableView *)tableView cellForRowAtIndex:(NSInteger)index; +@end +@protocol SLTableViewDelegate +///选中行 +- (void)tableView:(SLTableView *)tableView didSelectRowAtIndex:(NSInteger)index; +@end + +@interface SLTableView : UIScrollView +///复用池 +@property (nonatomic, strong) NSMutableDictionary *> *reusablePool; +///注册的类 +@property (nonatomic, strong) NSMutableDictionary *registerClasses; +/// 每一行的坐标位置 +@property (nonatomic, strong) NSMutableArray *frameArray; +/// 当前可见的cells +@property (nonatomic, strong) NSMutableArray *visibleCells; +///记录最后一次的偏移量,用来判断滑动方向 +@property (nonatomic, assign) CGFloat lastContentOffsetY; +///顶部即将展示的索引 +@property (nonatomic, assign) NSInteger willDisplayIndexTop; +///底部即将展示的索引 +@property (nonatomic, assign) NSInteger willDisplayIndexBottom; +///数据源代理 +@property (nonatomic, weak) iddelegate; +///数据源代理 +@property (nonatomic, weak) iddataSource; +@end + +@implementation SLTableView +@dynamic delegate; +#pragma mark - Override +- (void)willMoveToSuperview:(UIView *)newSuperview { + if (newSuperview) { + [self addKVO]; + } +} +- (void)dealloc { + [self removeKVO]; +} + +#pragma mark - Setter +- (void)setDelegate:(id)delegate{ + [super setDelegate:delegate]; +} +#pragma mark - Getter +- (NSMutableDictionary *)reusablePool { + if (!_reusablePool) { + _reusablePool = [NSMutableDictionary dictionary]; + } + return _reusablePool;; +} +- (NSMutableDictionary *)registerClasses { + if (!_registerClasses) { + _registerClasses = [NSMutableDictionary dictionary]; + } + return _registerClasses; +} +- (NSMutableArray *)frameArray { + if (!_frameArray) { + _frameArray = [NSMutableArray array]; + } + return _frameArray; +} +- (NSMutableArray *)visibleCells { + if (!_visibleCells) { + _visibleCells = [NSMutableArray array]; + } + return _visibleCells;; +} +- (id)delegate{ + id curDelegate = [super delegate]; + return curDelegate; +} + +#pragma mark - KVO +- (void)addKVO { + [self addObserver:self + forKeyPath:@"contentOffset" + options:NSKeyValueObservingOptionNew + context:nil]; +} +- (void)removeKVO{ + [self removeObserver:self forKeyPath:@"contentOffset"]; +} +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ + if(object == self && [keyPath isEqualToString:@"contentOffset"]) { + if(self.contentOffset.y > self.lastContentOffsetY) { + [self willDisplayCellWithDirection:NO]; + [self willDisappearCellWithDirection:YES]; + }else { + [self willDisplayCellWithDirection:YES]; + [self willDisappearCellWithDirection:NO]; + } + self.lastContentOffsetY = self.contentOffset.y; + } +} + +#pragma mark - Help Methods +///刷新数据 +- (void)reloadData { + //清空布局信息 + [self.frameArray removeAllObjects]; + self.willDisplayIndexTop = -1; + //数据源个数 + NSInteger count = [self.dataSource numberOfRowsInTableView:self]; + self.willDisplayIndexBottom = count; + + CGFloat y = 0; + //获取每一行的布局信息 + for (int i = 0; i < count; i++) { + CGFloat cellHeight = [self.dataSource tableView:self heightForRowAtIndex:i]; + CGRect rect = CGRectMake(0, y, self.sl_width, cellHeight); + [self.frameArray addObject:[NSValue valueWithCGRect:rect]]; + + if (rect.origin.y + rect.size.height < self.contentOffset.y) { + self.willDisplayIndexTop = i; + } + + //按需加载 只加载坐标位置是在当前窗口显示的视图 + if (rect.origin.y + rect.size.height >= self.contentOffset.y && rect.origin.y <= self.contentOffset.y + self.sl_height) { + SLTableViewCell *cell = [self.dataSource tableView:self cellForRowAtIndex:i]; + cell.frame = rect; + [self addSubview:cell]; + [self.visibleCells addObject:cell]; + } + + if (rect.origin.y > self.contentOffset.y + self.sl_height && self.willDisplayIndexBottom == count) { + self.willDisplayIndexBottom = i; + } + + //下一行的起始纵坐标 + y += cellHeight; + + //最后 确定了内容大小contentSize + if (i == count - 1) { + self.contentSize = CGSizeMake(self.sl_width, y); + } + } +} +///根据cellID从复用池reusablePool取可重用的view,如果没有,重新创建一个新对象返回 +- (SLTableViewCell *)dequeueReusableCellWithIdentifier:(nonnull NSString *)cellID index:(NSInteger)index{ + NSHashTable *hashTable = self.reusablePool[cellID]; + SLTableViewCell *cell = hashTable.allObjects.firstObject; + if (cell == nil) { + //复用池reusablePool没有可重用的,就重新创建一个新对象返回 + cell = [[self.registerClasses[cellID] alloc] init]; + [cell addTarget:self action:@selector(didSelectedAction:) forControlEvents:UIControlEventTouchUpInside]; + cell.cellID = cellID; + }else { + //从缓冲池中取出可重用的cell + [hashTable removeObject:cell]; + } + cell.index = index; + return cell; +} +///注册样式 +- (void)registerClass:(Class)class forCellReuseIdentifier:(NSString *)cellID { + self.reusablePool[cellID] = [NSHashTable weakObjectsHashTable]; + self.registerClasses[cellID] = class; +} +///当前可见cell的索引 其实绘制cell的时候就可以先保存可见的索引,不用每次遍历查询 +- (NSArray *)indexForVisibleRows { + NSMutableArray *indexs = [NSMutableArray array]; + for (NSInteger i = self.willDisplayIndexTop+1; i < self.willDisplayIndexBottom; i++) { + [indexs addObject:@(i)]; + } + return indexs; +} +///即将显示的cell,显示时创建或从缓存池中取出调整坐标位置 top:YES上/NO下 +- (void)willDisplayCellWithDirection:(BOOL)top { + if(top) { + if (_willDisplayIndexTop < 0) return; + CGRect rect = [self.frameArray[self.willDisplayIndexTop] CGRectValue]; + //按需加载 只加载坐标位置是在当前窗口显示的视图 + if (rect.origin.y + rect.size.height >= self.contentOffset.y && rect.origin.y <= self.contentOffset.y + self.sl_height) { + NSLog(@"上 第 %ld 个cell显示",self.willDisplayIndexTop); + SLTableViewCell *cell = [self.dataSource tableView:self cellForRowAtIndex:self.willDisplayIndexTop]; + cell.frame = rect; + [self addSubview:cell]; + self.willDisplayIndexTop -=1; + [self.visibleCells insertObject:cell atIndex:0]; + } + }else { + NSInteger count = [self.dataSource numberOfRowsInTableView:self]; + if (_willDisplayIndexBottom >= count) return; + CGRect rect = [self.frameArray[self.willDisplayIndexBottom] CGRectValue]; + //按需加载 只加载坐标位置是在当前窗口显示的视图 + if (rect.origin.y + rect.size.height >= self.contentOffset.y && rect.origin.y <= self.contentOffset.y + self.sl_height) { + NSLog(@"下 第 %ld 个cell显示",self.willDisplayIndexBottom); + SLTableViewCell *cell = [self.dataSource tableView:self cellForRowAtIndex:self.willDisplayIndexBottom]; + cell.frame = rect; + [self addSubview:cell]; + self.willDisplayIndexBottom +=1; + [self.visibleCells addObject:cell]; + } + } +} +//即将消失的cell,在消失时放入缓冲池里 top:YES上/NO下 +- (void)willDisappearCellWithDirection:(BOOL)top { + if(top) { + if (self.willDisplayIndexTop+1 >= self.frameArray.count) return; + CGRect rect = [self.frameArray[self.willDisplayIndexTop+1] CGRectValue]; + if (rect.origin.y + rect.size.height < self.contentOffset.y) { + self.willDisplayIndexTop = self.willDisplayIndexTop+1; + NSLog(@"上 第 %ld 个cell消失",self.willDisplayIndexTop); + SLTableViewCell *cell = self.visibleCells.firstObject; + NSHashTable * hashTable= self.reusablePool[cell.cellID]; + [hashTable addObject:cell]; + [self.visibleCells removeObjectAtIndex:0]; + } + }else { + if (self.willDisplayIndexBottom-1 < 0) return; + CGRect rect = [self.frameArray[self.willDisplayIndexBottom-1] CGRectValue]; + if (rect.origin.y > self.contentOffset.y + self.sl_height) { + self.willDisplayIndexBottom = self.willDisplayIndexBottom-1; + NSLog(@"下 第 %ld 个cell消失",self.willDisplayIndexBottom); + SLTableViewCell *cell = self.visibleCells.lastObject; + NSHashTable * hashTable= self.reusablePool[cell.cellID]; + [hashTable addObject:cell]; + [self.visibleCells removeLastObject]; + } + } +} + +#pragma mark - Events Handle +- (void)didSelectedAction:(SLTableViewCell *)cell { + [self.delegate tableView:self didSelectRowAtIndex:cell.index]; +} +@end + +@interface SLTableViewController () +@property (nonatomic, strong) SLTableView *tableView; +@end + +@implementation SLTableViewController +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUI]; +} + +#pragma mark - UI +- (void)setupUI { + self.view.backgroundColor = [UIColor whiteColor]; + [self.view addSubview:self.tableView]; + [self.tableView reloadData]; +} + +#pragma mark - Getter +- (SLTableView *)tableView { + if (!_tableView) { + _tableView = [[SLTableView alloc] initWithFrame:self.view.bounds]; + _tableView.dataSource = self; + _tableView.delegate = self; + if (@available(iOS 11.0, *)) { + _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + self.automaticallyAdjustsScrollViewInsets = NO; + } + [_tableView registerClass:[SLTableViewCell class] forCellReuseIdentifier:@"cellID"]; + } + return _tableView; +} + +#pragma mark - SLTableViewDataSource +///行数 +- (NSInteger)numberOfRowsInTableView:(SLTableView *)tableView { + return 40; +} +///行高 +- (CGFloat)tableView:(SLTableView *)tableView heightForRowAtIndex:(NSInteger)index { + return 100; +} +///行内容 +- (SLTableViewCell *)tableView:(SLTableView *)tableView cellForRowAtIndex:(NSInteger)index { + SLTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID" index:index]; + cell.layer.borderWidth = 3; + [cell setTitle:[NSString stringWithFormat:@"第 %ld 个",(long)index] forState:UIControlStateNormal]; + [cell setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + cell.titleLabel.textAlignment = NSTextAlignmentCenter; + return cell; +} + +#pragma mark - SLTableViewDelegate +///选中行 +- (void)tableView:(SLTableView *)tableView didSelectRowAtIndex:(NSInteger)index { + NSLog(@"选中 %ld",(long)index); +} +@end diff --git a/iOS_Tips/DarkMode/WKWebView/SLUrlProtocolAddCookie.h b/iOS_Tips/DarkMode/WKWebView/SLUrlProtocolAddCookie.h new file mode 100644 index 00000000..93ade66a --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/SLUrlProtocolAddCookie.h @@ -0,0 +1,18 @@ +// +// SLUrlProtocolAddCookie.h +// DarkMode +// +// Created by wsl on 2020/6/5. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///解决WKWebView上请求不会自动携带Cookie的问题:通过NSURLProtocol,拦截request,然后在请求头里添加Cookie的方式 +@interface SLUrlProtocolAddCookie : NSURLProtocol + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/SLUrlProtocolAddCookie.m b/iOS_Tips/DarkMode/WKWebView/SLUrlProtocolAddCookie.m new file mode 100644 index 00000000..ce7d922d --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/SLUrlProtocolAddCookie.m @@ -0,0 +1,94 @@ +// +// SLUrlProtocolAddCookie.m +// DarkMode +// +// Created by wsl on 2020/6/5. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLUrlProtocolAddCookie.h" + +@interface SLUrlProtocolAddCookie () +@property (nonatomic, strong) NSURLSession *session; //会话 +@end + +@implementation SLUrlProtocolAddCookie + +//所有注册此Protocol的请求都会经过这个方法,根据request判断是否进行需要拦截 ++ (BOOL)canInitWithRequest:(NSURLRequest *)request { + //只允许GET方法通过,因为post请求body数据被清空 + if ([request.HTTPMethod compare:@"GET"] != NSOrderedSame) { + return NO; + } + //判断该request是否已经处理过了,防止无限循环 + if ([NSURLProtocol propertyForKey:@"SLUrlProtocolHandled" inRequest:request]) { + return NO; + } + return YES; +} +//可选方法,这个方法用来统一处理请求的request对象,可以修改头信息,或者重定向。没有特殊需要,则直接return request。 ++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { + return request; +} +//主要判断两个request是否相同,如果相同的话可以使用缓存数据,通常只需要调用父类的实现 ++ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { + return [super requestIsCacheEquivalent:a toRequest:b]; +} +//初始化protocol实例,所有来源的请求都以NSURLRequest形式接收 +- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id )client { + return [super initWithRequest:request cachedResponse:cachedResponse client:client]; +} +/** + 开始请求: + 在这里需要我们手动的把请求发出去,可以使用原生的NSURLSessionDataTask,也可以使用的第三方网络库 + 同时设置"NSURLSessionDataDelegate"协议,接收Server端的响应 + */ +- (void)startLoading { + NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy]; + //标示该request已经处理过了,防止无限循环 + [NSURLProtocol setProperty:@(YES) forKey:@"SLUrlProtocolHandled" inRequest:mutableReqeust]; + + NSArray *cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies; + NSDictionary *requestHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; + mutableReqeust.allHTTPHeaderFields = requestHeaderFields; + + //使用NSURLSession继续把request发送出去 + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; + self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:mainQueue]; + NSURLSessionDataTask *task = [self.session dataTaskWithRequest:mutableReqeust]; + [task resume]; + +} +//停止请求加载 +- (void)stopLoading { + [self.session invalidateAndCancel]; + self.session = nil; +} +#pragma mark - NSURLSessionDelegate +//接收到返回的响应信息时(还未开始下载), 执行的代理方法 +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { + [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + completionHandler(NSURLSessionResponseAllow); + //请求头信息 + NSDictionary *allHTTPHeaderFields = [dataTask.currentRequest allHTTPHeaderFields]; + NSLog(@" Cookie: %@",allHTTPHeaderFields[@"Cookie"]); +} +//接收到服务器返回的数据 调用多次,数据是分批返回的 +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + [self.client URLProtocol:self didLoadData:data]; +} +//请求结束或者是失败的时候调用 +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task +didCompleteWithError:(nullable NSError *)error { + if (error) { + [self.client URLProtocol:self didFailWithError:error]; + } else { + [self.client URLProtocolDidFinishLoading:self]; + } +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/SLWebViewController.h b/iOS_Tips/DarkMode/WKWebView/SLWebViewController.h new file mode 100644 index 00000000..cebe5dda --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/SLWebViewController.h @@ -0,0 +1,24 @@ +// +// SLWebViewController.h +// DarkMode +// +// Created by wsl on 2020/5/21. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///关于WKWebView的那些坑: https://mp.weixin.qq.com/s/rhYKLIbXOsUJC_n6dt9UfA? +/// https://github.com/ChenYilong/ParseSourceCodeStudy/blob/master/02_Parse%E7%9A%84%E7%BD%91%E7%BB%9C%E7%BC%93%E5%AD%98%E4%B8%8E%E7%A6%BB%E7%BA%BF%E5%AD%98%E5%82%A8/iOS%E7%BD%91%E7%BB%9C%E7%BC%93%E5%AD%98%E6%89%AB%E7%9B%B2%E7%AF%87.md +/// https://dequan1331.github.io/index.html +///关于WKWebView的使用可以看我之前的总结: https://github.com/wsl2ls/WKWebView +@interface SLWebViewController : SLViewController + +///打开的web地址 默认:https://www.jianshu.com/p/5cf0d241ae12 +@property (nonatomic, strong) NSString *urlString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/SLWebViewController.m b/iOS_Tips/DarkMode/WKWebView/SLWebViewController.m new file mode 100644 index 00000000..a8499e5c --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/SLWebViewController.m @@ -0,0 +1,211 @@ +// +// SLWebViewController.m +// DarkMode +// +// Created by wsl on 2020/5/21. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLWebViewController.h" +#import +#import "WKWebView+SLExtension.h" +#import "SLUrlProtocolAddCookie.h" + +///关于WKWebView的其他更多使用可以看我之前的总结: https://github.com/wsl2ls/WKWebView +@interface SLWebViewController () + +@property (nonatomic, strong) WKWebView * webView; +///网页加载进度视图 +@property (nonatomic, strong) UIProgressView * progressView; +/// WKWebView 内容的高度 +@property (nonatomic, assign) CGFloat webContentHeight; + +@end + +@implementation SLWebViewController +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUI]; + [self addKVO]; +} +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self.progressView removeFromSuperview]; +} +- (void)dealloc { + [self removeKVO]; + [WKWebView sl_unregisterSchemeForSupportHttpProtocol]; + [NSURLProtocol registerClass:[NSURLProtocol class]]; +} + +#pragma mark - UI +- (void)setupUI { + self.view.backgroundColor = UIColor.whiteColor; + UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"上一步" style:UIBarButtonItemStyleDone target:self action:@selector(goBackAction:)]; + UIBarButtonItem *forwardItem = [[UIBarButtonItem alloc] initWithTitle:@"下一步" style:UIBarButtonItemStyleDone target:self action:@selector(goForwardAction:)]; + self.navigationItem.rightBarButtonItems = @[forwardItem,backItem]; + + //UA 设置在WKWebView初始化之前 + [self aboutUserAgent]; + //设置自定义Cookie在WKWebView初始化之前 + [self aboutCookie]; + + [self.view addSubview:self.webView]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.urlString]]; + //设置上一次的请求时间 + [request setValue:[SLMethod userDefaultsObjectForKey:@"localLastModified"] forHTTPHeaderField:@"If-Modified-Since"]; + [_webView loadRequest:request]; +} + +#pragma mark - Getter +- (WKWebView *)webView { + if(_webView == nil){ + //创建网页配置 + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, SL_kScreenHeight) configuration:config]; + _webView.navigationDelegate = self; + } + return _webView; +} +- (UIProgressView *)progressView { + if (!_progressView){ + _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, 2)]; + _progressView.tintColor = [UIColor blueColor]; + _progressView.trackTintColor = [UIColor clearColor]; + } + if (_progressView.superview == nil) { + [self.navigationController.navigationBar addSubview:_progressView]; + } + return _progressView; +} +- (NSString *)urlString { + if (!_urlString) { + _urlString = @"https://www.jianshu.com/p/5cf0d241ae12"; + } + return _urlString; +} + +#pragma mark - Look Here +///关于WKWebView的那些坑: https://mp.weixin.qq.com/s/rhYKLIbXOsUJC_n6dt9UfA? +- (void)aboutJsToOC { + ///关于JS和OC的交互以及API基本的使用可以看我之前的总结: https://github.com/wsl2ls/WKWebView +} +///关于UserAgent +- (void)aboutUserAgent { + //设置UA 在WKWebView初始化之前设置,才能实时生效 + [WKWebView sl_setCustomUserAgentWithType:SLSetUATypeAppend UAString:@"wsl2ls"]; +} +///关于Cookie https://www.jianshu.com/p/cd0d819b9851 +- (void)aboutCookie { + ///添加自定义Cookie到WebView的Cookie管理器 + [WKWebView sl_setCustomCookieWithName:@" 且行且珍惜_iOS " value:@" wsl❤ls " domain:@".jianshu.com" path:@"/" expiresDate:[NSDate dateWithTimeIntervalSince1970:1591440726]]; + + //解决WKWebView上请求不会自动携带Cookie的问题 + /* + 方案1、①. [webView loadRequest]前,在request header中设置Cookie, 手动设置cookies,可以解决首个请求Cookie带不上的问题; + NSArray *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies; + NSDictionary *requestHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookieJar]; + request.allHTTPHeaderFields = requestHeaderFields; + ②. 通过注入JS方法,document.cookie设置Cookie 解决后续页面(同域)Ajax、iframe 请求的 Cookie 问题; + WKUserContentController* userContentController = [WKUserContentController new]; + WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"document.cookie = ' 且行且珍惜_iOS = wsl❤ls ';" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; + [userContentController addUserScript:cookieScript]; + config.userContentController = userContentController; + 方案2、通过NSURLProtocol,拦截request,然后在请求头里添加Cookie的方式 + */ + + //这里采用方案2 + //用完记得取消注册sl_unregisterSchemeForSupportHttpProtocol + [WKWebView sl_registerSchemeForSupportHttpProtocol]; + [NSURLProtocol registerClass:[SLUrlProtocolAddCookie class]]; +} + +#pragma mark - KVO +///添加键值对监听 +- (void)addKVO { + //监听网页加载进度 + [self.webView addObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) + options:NSKeyValueObservingOptionNew + context:nil]; + //监听网页内容高度 + [self.webView.scrollView addObserver:self + forKeyPath:@"contentSize" + options:NSKeyValueObservingOptionNew + context:nil]; +} +///移除监听 +- (void)removeKVO { + //移除观察者 + [_webView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress))]; + [_webView.scrollView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(contentSize))]; +} +//kvo监听 必须实现此方法 +-(void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context{ + + if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] + && object == _webView) { + // NSLog(@"网页加载进度 = %f",_webView.estimatedProgress); + self.progressView.progress = _webView.estimatedProgress; + if (_webView.estimatedProgress >= 1.0f) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.progressView.progress = 0; + }); + } + }else if ([keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))] + && object == _webView.scrollView && _webContentHeight != _webView.scrollView.contentSize.height) { + _webContentHeight = _webView.scrollView.contentSize.height; + } +} + +#pragma mark - Events Handle +//返回上一步 +- (void)goBackAction:(id)sender{ + [_webView goBack]; +} +//前往下一步 +- (void)goForwardAction:(id)sender{ + [_webView goForward]; +} + +#pragma mark - WKNavigationDelegate +// 根据WebView对于即将跳转的HTTP请求头信息和相关信息来决定是否跳转 +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { + //请求头信息 + NSDictionary *allHTTPHeaderFields = [navigationAction.request allHTTPHeaderFields]; + NSLog(@" UserAgent: %@",allHTTPHeaderFields[@"User-Agent"]); + decisionHandler(WKNavigationActionPolicyAllow); +} +// 根据客户端受到的服务器响应头以及response相关信息来决定是否可以跳转 +- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{ + if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]] && [navigationResponse.response.URL.absoluteString isEqualToString:self.urlString]) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)navigationResponse.response; + if (httpResponse.statusCode == 304) { + //自上次请求后,文件还没有修改变化 + //可以加载本地缓存 + }else if (httpResponse.statusCode == 200){ + [SLMethod userDefaultsSetObject:httpResponse.allHeaderFields[@"Last-Modified"] forKey:@"localLastModified"]; + NSLog(@" httpResponse:%@",httpResponse); + } + } + //允许跳转 + decisionHandler(WKNavigationResponsePolicyAllow); +} +// 页面加载完成之后调用 +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + +} +//进程被终止时调用 +- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{ + //当 WKWebView 总体内存占用过大,页面即将白屏的时候,系统会调用此7u776回调函数,我们在该函数里执行[webView reload](这个时候 webView.URL 取值尚不为 nil)解决白屏问题。在一些高内存消耗的页面可能会频繁刷新当前页面,H5侧也要做相应的适配操作。 + [webView reload]; +} +@end + diff --git a/iOS_Tips/DarkMode/WKWebView/SLWebViewListController.h b/iOS_Tips/DarkMode/WKWebView/SLWebViewListController.h new file mode 100644 index 00000000..afdeb711 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/SLWebViewListController.h @@ -0,0 +1,18 @@ +// +// SLWebViewListController.h +// DarkMode +// +// Created by wsl on 2020/5/27. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 关于WKWebView的知识点列表 +@interface SLWebViewListController : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/SLWebViewListController.m b/iOS_Tips/DarkMode/WKWebView/SLWebViewListController.m new file mode 100644 index 00000000..4a4b7b90 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/SLWebViewListController.m @@ -0,0 +1,131 @@ +// +// SLWebViewListController.m +// DarkMode +// +// Created by wsl on 2020/5/27. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLWebViewListController.h" +#import "SLWebViewController.h" +#import "SLWebTableViewController.h" +#import "SLWebTableViewController2.h" +#import "SLWebTableViewController3.h" +#import "SLWebTableViewController4.h" +#import "SLScrollViewController.h" +#import "SLWebCacheViewController.h" +#import "SLWebNativeViewController.h" +#import "SLTableViewController.h" + +@interface SLWebViewListController () +@property (nonatomic, strong) UITableView *tableView; +@property (nonatomic, strong) NSMutableArray *titles; +@property (nonatomic, strong) NSMutableArray *subTitles; +@property (nonatomic, strong) NSMutableArray *classArray; +@end + +@implementation SLWebViewListController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self getData]; + [self setupUI]; +} +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.navigationController.navigationBar.hidden = NO; +} +- (BOOL)prefersStatusBarHidden { + return NO; +} +#pragma mark - UI +- (void)setupUI { + self.navigationItem.title = @"WKWebView"; + [self.view addSubview:self.tableView]; +} + +#pragma mark - Data +- (void)getData { + //tableView、UIAlertView等系统控件,在不自定义颜色的情况下,默认颜色都是动态的,支持暗黑模式 + [self.titles addObjectsFromArray:@[@" WKWebView的使用 ", + @" WKWebView + UITableView(方案1 不推荐)", + @" WKWebView + UITableView(方案2)", + @" WKWebView + UITableView(方案3)(推荐)", + @" WKWebView + UITableView(方案4)(推荐) ", + @" WKWebView离线缓存", + @" WKWebView渲染的部分HTML元素替换为用原生组件显示", + @" UIScrollView的实现原理", + @" UITableView的原理"]]; + [self.subTitles addObjectsFromArray:@[@" WKWebView的使用、JS和OC的交互、网页内容加载进度条的实现、NSNSURLProtocol拦截、Cookies丢失、设置UserAgent", + @" tableView.tableHeaderView = webView 撑开webView ", + @" [webView.scrollView addSubview:tableView] + 占位Div ", + @" tableView.tableHeaderView = webView 不撑开webView ", + @" [UIScrollView addSubView: WKWebView & UITableView]", + @" NSURLProtocol 和 NSURLCache 两种缓存方案", + @" 图片、视频、音频等元素用原生组件显示", + @"SLScrollView继承于UIView,自定义实现UIScrollView的效果",@" 用UIScrollView实现 类似UITableView的复用功能"]]; + [self.classArray addObjectsFromArray:@[[SLWebViewController class], + [SLWebTableViewController class], + [SLWebTableViewController2 class], + [SLWebTableViewController3 class], + [SLWebTableViewController4 class], + [SLWebCacheViewController class], + [SLWebNativeViewController class], + [SLScrollViewController class], + [SLTableViewController class]]]; + [self.tableView reloadData]; +} + +#pragma mark - Getter +- (UITableView *)tableView { + if (_tableView == nil) { + _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain]; + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.estimatedRowHeight = 1; + } + return _tableView; +} +- (NSMutableArray *)titles { + if (_titles == nil) { + _titles = [NSMutableArray array]; + } + return _titles; +} +- (NSMutableArray *)subTitles { + if (_subTitles == nil) { + _subTitles = [NSMutableArray array]; + } + return _subTitles; +} +- (NSMutableArray *)classArray { + if (!_classArray) { + _classArray = [NSMutableArray array]; + } + return _classArray; +} + +#pragma mark - UITableViewDelegate, UITableViewtitles +- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.titles.count; +} +- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellId"]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellId"]; + } + cell.textLabel.numberOfLines = 0; + cell.textLabel.text = [NSString stringWithFormat:@"%ld、%@",(long)indexPath.row,self.titles[indexPath.row]]; + cell.detailTextLabel.numberOfLines = 0; + cell.detailTextLabel.text = self.subTitles[indexPath.row]; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:NO]; + UIViewController *nextVc = [[self.classArray[indexPath.row] alloc] init]; + nextVc.title = self.titles[indexPath.row]; + [self.navigationController pushViewController:nextVc animated:YES]; +} + + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlCache.h b/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlCache.h new file mode 100644 index 00000000..6f7d9064 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlCache.h @@ -0,0 +1,18 @@ +// +// SLUrlCache.h +// DarkMode +// +// Created by wsl on 2020/6/1. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///缓存方案2: NSURLCache +@interface SLUrlCache : NSURLCache + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlCache.m b/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlCache.m new file mode 100644 index 00000000..c3fbdc39 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlCache.m @@ -0,0 +1,44 @@ +// +// SLUrlCache.m +// DarkMode +// +// Created by wsl on 2020/6/1. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLUrlCache.h" +#import "SLWebCacheManager.h" + +@interface SLUrlCache () + +@end + +@implementation SLUrlCache + +#pragma mark - Override +///开始缓存 +- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request { + if (![[SLWebCacheManager shareInstance] canCacheRequest:request]) { + return nil; + } + ///本地缓存的数据 + NSCachedURLResponse *cachedResponse = [[SLWebCacheManager shareInstance] loadCachedResponeWithRequest:request]; + if(!cachedResponse) { + //没有缓存,请求网络数据 + cachedResponse = [[SLWebCacheManager shareInstance] requestNetworkData:request]; + } + //调用系统的缓存方法,当然这里也可以不用调 + [self storeCachedResponse:cachedResponse forRequest:request]; + return cachedResponse;; +} +///移除缓存 +- (void)removeCachedResponseForRequest:(NSURLRequest *)request { + [super removeCachedResponseForRequest:request]; + [[SLWebCacheManager shareInstance] removeCacheFileWithRequest:request]; +} +///移除所有缓存 +- (void)removeAllCachedResponses { + [super removeAllCachedResponses]; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlProtocol.h b/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlProtocol.h new file mode 100644 index 00000000..876d7624 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlProtocol.h @@ -0,0 +1,21 @@ +// +// SLUrlProtocol.h +// DarkMode +// +// Created by wsl on 2020/5/30. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +/* + NSURLProtocol的简介:https://www.jianshu.com/p/ae5e8f9988d8 + */ + +///缓存方案1: NSURLProtocol 拦截HTTP/https请求 实现缓存 +@interface SLUrlProtocol : NSURLProtocol + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlProtocol.m b/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlProtocol.m new file mode 100644 index 00000000..bd0c0899 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebCache/SLUrlProtocol.m @@ -0,0 +1,127 @@ +// +// SLUrlProtocol.m +// DarkMode +// +// Created by wsl on 2020/5/30. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLUrlProtocol.h" +#import "SLWebCacheManager.h" + +static NSString *SLUrlProtocolHandled = @"SLUrlProtocolHandled"; + +@interface SLUrlProtocol () + +@property (nonatomic, strong) NSURLSession *session; //会话 +@property (nonatomic, readwrite, strong) NSMutableData *data; //请求到的数据 + +@end + +@implementation SLUrlProtocol + +//所有注册此Protocol的请求都会经过这个方法,根据request判断是否进行需要拦截 ++ (BOOL)canInitWithRequest:(NSURLRequest *)request { + if (![[SLWebCacheManager shareInstance] canCacheRequest:request]) { + return NO; + } + //判断该request是否已经处理过了,防止无限循环 + if ([NSURLProtocol propertyForKey:SLUrlProtocolHandled inRequest:request]) { + return NO; + } + return YES; +} +//可选方法,这个方法用来统一处理请求的request对象,可以修改头信息,或者重定向。没有特殊需要,则直接return request。 ++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { + return request; +} +//主要判断两个request是否相同,如果相同的话可以使用缓存数据,通常只需要调用父类的实现 ++ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { + return [super requestIsCacheEquivalent:a toRequest:b]; +} +//初始化protocol实例,所有来源的请求都以NSURLRequest形式接收 +- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id )client { + return [super initWithRequest:request cachedResponse:cachedResponse client:client]; +} +/** + 开始请求: + 在这里需要我们手动的把请求发出去,可以使用原生的NSURLSessionDataTask,也可以使用的第三方网络库 + 同时设置"NSURLSessionDataDelegate"协议,接收Server端的响应 + */ +- (void)startLoading { + NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy]; + //标示该request已经处理过了,防止无限循环 + [NSURLProtocol setProperty:@(YES) forKey:SLUrlProtocolHandled inRequest:mutableReqeust]; + + SLWebCacheManager *webCacheManager = [SLWebCacheManager shareInstance]; + ///加载本地缓存数据 + NSCachedURLResponse *cachedURLResponse = [webCacheManager loadCachedResponeWithRequest:mutableReqeust]; + if (cachedURLResponse) { + [self.client URLProtocol:self didReceiveResponse:cachedURLResponse.response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + [self.client URLProtocol:self didLoadData:cachedURLResponse.data]; + [self.client URLProtocolDidFinishLoading:self]; + }else { + //没有缓存数据 + //使用NSURLSession继续把request发送出去 + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; + self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:mainQueue]; + NSURLSessionDataTask *task = [self.session dataTaskWithRequest:mutableReqeust]; + [task resume]; + } +} +//停止请求加载 +- (void)stopLoading { + [self.session invalidateAndCancel]; + self.session = nil; +} + +#pragma mark - Help Methods +- (void)clear { + self.data = nil; + self.session = nil; +} +///合并数据 +- (void)appendData:(NSData *)newData { + if (self.data == nil) { + self.data = [newData mutableCopy]; + } else { + [self.data appendData:newData]; + } +} + +#pragma mark - NSURLSessionDelegate +//接收到返回的响应信息时(还未开始下载), 执行的代理方法 +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { + [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + completionHandler(NSURLSessionResponseAllow); +} +//接收到服务器返回的数据 调用多次,数据是分批返回的 +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + // 打印返回的数据 + // NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + // if (dataStr) { + // NSLog(@"收到数据: %@", dataStr); + // } + //拼接数据 + [self appendData:data]; + [self.client URLProtocol:self didLoadData:data]; +} +//请求结束或者是失败的时候调用 +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task +didCompleteWithError:(nullable NSError *)error { + if (error) { + [self.client URLProtocol:self didFailWithError:error]; + } else { + [self.client URLProtocolDidFinishLoading:self]; + //开始写入缓存数据 + NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:task.response data:[self.data mutableCopy]]; + [[SLWebCacheManager shareInstance] writeCacheData:cachedResponse withRequest:[self.request mutableCopy]]; + } + [self clear]; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheManager.h b/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheManager.h new file mode 100644 index 00000000..e5db1297 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheManager.h @@ -0,0 +1,63 @@ +// +// SLWebCacheManager.h +// DarkMode +// +// Created by wsl on 2020/5/31. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///缓存管理者 +@interface SLWebCacheManager : NSObject + +/// 缓存方案:SLUrlCache 和 SLUrlProtocol,默认NO 即使用SLUrlCache进行缓存 +@property (nonatomic, assign) BOOL isUsingURLProtocol; +/// 内存缓存容量 默认20M 注意0表示无限制 +@property (nonatomic, assign) NSUInteger memoryCapacity; +/// 磁盘缓存容量 默认50M +@property (nonatomic, assign) NSUInteger diskCapacity; +/// 缓存的有效时长(s) 默认 24 * 60 * 60 一天 ,如果为0,表示永久有效,除非手动强制删除,否则有缓存之后,只从缓存读取 +@property (nonatomic, assign) NSUInteger cacheTime; +/// 磁盘路径 默认 NSCachesDirectory +@property (nonatomic, copy) NSString *diskPath; +/// 缓存文件夹 默认 @"com.wsl2ls.webCache" +@property (nonatomic, copy) NSString *cacheFolder; +/// 子路径 默认 @"UrlCacheDownload" +@property (nonatomic, copy) NSString *subDirectory; + +/// 以下3类都可以作为过滤不必要请求缓存的接口:若都为空,表示不设置过滤,所有的请求都缓存,设置了之后,仅对在白名单里或符合UA的请求进行缓存 +@property (nonatomic, strong) NSArray *whiteListsHost; //域名白名单 +@property (nonatomic, strong) NSArray *whiteListsRequestUrl; //请求地址白名单 +@property (nonatomic, strong) NSString *whiteUserAgent; //WebView的user-agent白名单 + ++ (SLWebCacheManager *)shareInstance; + +///启用缓存功能 +- (void)openCache; +///关闭缓存功能 +- (void)closeCache; +///是否可以缓存该请求,该请求是否在白名单里或合法 +- (BOOL)canCacheRequest:(NSURLRequest *)request; + +///对应请求的缓存文件/信息路径 +- (NSString *)filePathFromRequest:(NSURLRequest *)request isInfo:(BOOL)info; +///写入缓存数据 +- (BOOL)writeCacheData:(NSCachedURLResponse *)cachedURLResponse withRequest:(NSURLRequest *)request; +///加载请求的缓存数据,如果没有缓存就返回nil +- (NSCachedURLResponse *)loadCachedResponeWithRequest:(NSURLRequest *)request; +///请求网络数据并写入本地 +- (NSCachedURLResponse *)requestNetworkData:(NSURLRequest *)request; + +///清除对应请求的缓存 +- (void)removeCacheFileWithRequest:(NSURLRequest *)request; +/// 检测缓存容量,超出限制就清除 +- (void)checkCapacity; +///强制清除缓存 +- (void)clearCache; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheManager.m b/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheManager.m new file mode 100644 index 00000000..91262fd5 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheManager.m @@ -0,0 +1,367 @@ +// +// SLWebCacheManager.m +// DarkMode +// +// Created by wsl on 2020/5/31. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLWebCacheManager.h" +#import +#import "SLUrlCache.h" +#import "SLUrlProtocol.h" +#import "WKWebView+SLExtension.h" + +//实现原理参考 戴明大神:https://github.com/ming1016/STMURLCache +@interface SLWebCacheManager () +@property (nonatomic, strong) NSMutableDictionary *responseDic; //记录正在下载的任务、防止下载请求的循环调用 +///内存缓存空间 +@property (nonatomic, strong) NSCache *memoryCache; +@end + +@implementation SLWebCacheManager + +#pragma mark - Public ++ (SLWebCacheManager *)shareInstance { + static SLWebCacheManager *instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[SLWebCacheManager alloc] init]; + }); + return instance; +} +///启用缓存功能 +- (void)openCache { + //注册协议类, 然后URL加载系统就会在请求发出时使用我们创建的协议对象对该请求进行拦截处理,不需要拦截的时候,要进行注销unregisterClass + [WKWebView sl_registerSchemeForSupportHttpProtocol]; + if (self.isUsingURLProtocol) { + [NSURLProtocol registerClass:[SLUrlProtocol class]]; + }else { + SLUrlCache * urlCache = [[SLUrlCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:0]; + [NSURLCache setSharedURLCache:urlCache]; + } + //添加内存警告监听 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; +} +///关闭缓存功能 +- (void)closeCache { + [WKWebView sl_unregisterSchemeForSupportHttpProtocol]; + if (self.isUsingURLProtocol) { + [NSURLProtocol registerClass:[NSURLProtocol class]]; + }else { + NSURLCache* urlCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:0]; + [NSURLCache setSharedURLCache:urlCache]; + } + //移除观察者 + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} +///是否缓存该请求,该请求是否在白名单里或合法 +- (BOOL)canCacheRequest:(NSURLRequest *)request { + //User-Agent来过滤 + if (self.whiteUserAgent.length > 0) { + NSString *uAgent = [request.allHTTPHeaderFields objectForKey:@"User-Agent"]; + if (uAgent) { + if (![uAgent hasSuffix:self.whiteUserAgent]) { + return NO; + } + } else { + return NO; + } + } + //只允许GET方法通过,因为post请求body数据被清空 + //如果通过 registerSchemeForCustomProtocol 注册了 http(s) scheme, 那么由WKWebView发起的所有 http(s)请求都会通过 IPC 传给主进程NSURLProtocol处理,导致post请求body被清空 + if ([request.HTTPMethod compare:@"GET"] != NSOrderedSame) { + return NO; + } + + //对于域名白名单的过滤 + if (self.whiteListsHost.count > 0) { + BOOL isExist = [self.whiteListsHost containsObject:request.URL.host]; + if (!isExist) { + return NO; + } + } + //请求地址白名单 + if (self.whiteListsRequestUrl.count > 0) { + BOOL isExist = [self.whiteListsRequestUrl containsObject:request.URL.host]; + if (!isExist) { + return NO; + } + } + NSString *scheme = [[request.URL scheme] lowercaseString]; + if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]) { + // + } else { + return NO; + } + return YES; +} + +#pragma mark - Cache Path +/// 对应请求的缓存文件/信息路径 +- (NSString *)filePathFromRequest:(NSURLRequest *)request isInfo:(BOOL)info { + NSString *url = request.URL.absoluteString; + NSString *fileName = [self cacheRequestFileName:url]; + NSString *otherInfoFileName = [self cacheRequestOtherInfoFileName:url]; + NSString *filePath = [self cacheFilePath:fileName]; + NSString *fileInfoPath = [self cacheFilePath:otherInfoFileName]; + if (info) { + return fileInfoPath; + } + return filePath; +} +///缓存数据文件名 +- (NSString *)cacheRequestFileName:(NSString *)requestUrl { + return [SLWebCacheManager md5Hash:[NSString stringWithFormat:@"%@",requestUrl]]; +} +///缓存其他信息的文件名 +- (NSString *)cacheRequestOtherInfoFileName:(NSString *)requestUrl { + return [SLWebCacheManager md5Hash:[NSString stringWithFormat:@"%@-otherInfo",requestUrl]]; +} +///缓存的文件路径 +- (NSString *)cacheFilePath:(NSString *)file { + NSString *path = [NSString stringWithFormat:@"%@/%@",self.diskPath,self.cacheFolder]; + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL isDir; + if ([fm fileExistsAtPath:path isDirectory:&isDir] && isDir) { + // + } else { + [fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; + } + NSString *subDirPath = [NSString stringWithFormat:@"%@/%@/%@",self.diskPath,self.cacheFolder,self.subDirectory]; + if ([fm fileExistsAtPath:subDirPath isDirectory:&isDir] && isDir) { + // + } else { + [fm createDirectoryAtPath:subDirPath withIntermediateDirectories:YES attributes:nil error:nil]; + } + NSString *cFilePath = [NSString stringWithFormat:@"%@/%@",subDirPath,file]; + // NSLog(@"%@",cFilePath); + return cFilePath; +} +//url加密 ++ (NSString *)md5Hash:(NSString *)str { + const char *cStr = [str UTF8String]; + unsigned char result[16]; + CC_MD5( cStr, (CC_LONG)strlen(cStr), result ); + NSString *md5Result = [NSString stringWithFormat: + @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + result[0], result[1], result[2], result[3], + result[4], result[5], result[6], result[7], + result[8], result[9], result[10], result[11], + result[12], result[13], result[14], result[15] + ]; + return md5Result; +} +//缓存文件所在目录 +- (NSString *)cacheFolderPath { + return [NSString stringWithFormat:@"%@/%@/%@",self.diskPath,self.cacheFolder,self.subDirectory]; +} + +#pragma mark - Cache Manage +///写入缓存数据 内存和磁盘 +- (BOOL)writeCacheData:(NSCachedURLResponse *)cachedURLResponse withRequest:(NSURLRequest *)request { + NSDate *date = [NSDate date]; + NSDictionary *info = @{@"time" : [NSString stringWithFormat:@"%f",[date timeIntervalSince1970]], + @"MIMEType" : cachedURLResponse.response.MIMEType, + @"textEncodingName" : cachedURLResponse.response.textEncodingName == nil ? @"": cachedURLResponse.response.textEncodingName}; + + //写入磁盘 + BOOL result1 = [info writeToFile:[self filePathFromRequest:request isInfo:YES] atomically:YES]; + BOOL result2 = [cachedURLResponse.data writeToFile:[self filePathFromRequest:request isInfo:NO] atomically:YES]; + //写入内存 + [self.memoryCache setObject:cachedURLResponse.data forKey:[self cacheRequestFileName:request.URL.absoluteString]]; + [self.memoryCache setObject:info forKey:[self cacheRequestOtherInfoFileName:request.URL.absoluteString]]; + + return result1 & result2; +} +///加载缓存数据 内存 -> 磁盘 ->网络 +- (NSCachedURLResponse *)loadCachedResponeWithRequest:(NSURLRequest *)request { + + //加载内存cache + BOOL isMemory = NO; //是否在内存中 + NSData *data = [self.memoryCache objectForKey:[self cacheRequestFileName:request.URL.absoluteString]]; + NSDictionary *otherInfo = [self.memoryCache objectForKey:[self cacheRequestOtherInfoFileName:request.URL.absoluteString]]; + if (data != nil) isMemory = YES; + + NSDate *date = [NSDate date]; + if (!isMemory) { + //如果不在内存中 + NSString *filePath = [self filePathFromRequest:request isInfo:NO]; + NSString *otherInfoPath = [self filePathFromRequest:request isInfo:YES]; + NSFileManager *fm = [NSFileManager defaultManager]; + if ([fm fileExistsAtPath:filePath]) { + //加载磁盘cache + otherInfo = [NSDictionary dictionaryWithContentsOfFile:otherInfoPath]; + data = [NSData dataWithContentsOfFile:filePath]; + //写入内存 + [self.memoryCache setObject:data forKey:[self cacheRequestFileName:request.URL.absoluteString]]; + [self.memoryCache setObject:otherInfo forKey:[self cacheRequestOtherInfoFileName:request.URL.absoluteString]]; + }else { + //磁盘里也没有cache + return nil; + } + } + + //cache是否过期 + BOOL expire = false; + if (self.cacheTime > 0) { + NSInteger createTime = [[otherInfo objectForKey:@"time"] integerValue]; + if (createTime + self.cacheTime < [date timeIntervalSince1970]) { + expire = true; + } + } + if (expire == false) { + //cache没过期 + NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:[otherInfo objectForKey:@"MIMEType"] expectedContentLength:data.length textEncodingName:[otherInfo objectForKey:@"textEncodingName"]]; + NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data]; + return cachedResponse; + } else { + //cache失效了,移除缓存文件 + [self removeCacheFileWithRequest:request]; + } + return nil; +} +///从网络读取数据并写入本地 +- (NSCachedURLResponse *)requestNetworkData:(NSURLRequest *)request{ + __block NSCachedURLResponse *cachedResponse = nil; + id isExist = [self.responseDic objectForKey:request.URL.absoluteString]; + if (isExist == nil) { + [self.responseDic setValue:[NSNumber numberWithBool:TRUE] forKey:request.URL.absoluteString]; + NSURLSession *session = [NSURLSession sharedSession]; + NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + } + if (error) { + cachedResponse = nil; + } else { + cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data]; + //写入本地缓存 + [self writeCacheData:cachedResponse withRequest:request]; + } + }]; + [task resume]; + return cachedResponse; + } + return nil; +} +///移除缓存文件 +- (void)removeCacheFileWithRequest:(NSURLRequest *)request { + //清除内存cache + [self.memoryCache removeObjectForKey:[self cacheRequestFileName:request.URL.absoluteString]]; + [self.memoryCache removeObjectForKey:[self cacheRequestOtherInfoFileName:request.URL.absoluteString]]; + //清除磁盘cache + NSString *filePath = [self filePathFromRequest:request isInfo:NO]; + NSString *otherInfoFilePath = [self filePathFromRequest:request isInfo:YES]; + NSFileManager *fm = [NSFileManager defaultManager]; + [fm removeItemAtPath:filePath error:nil]; + [fm removeItemAtPath:otherInfoFilePath error:nil]; +} +/// 检测缓存容量,超出限制就清除 +- (void)checkCapacity { + if ([self folderSize] > self.diskCapacity) { + [self clearCache]; + } +} +///接收到内存警告,清除缓存 +- (void)handleMemoryWarning { + [self clearCache]; +} +///强制清除缓存 +- (void)clearCache { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + [self deleteCacheFolder]; + [self.memoryCache removeAllObjects]; + }); +} +///删除缓存文件夹 +- (void)deleteCacheFolder { + [[NSFileManager defaultManager] removeItemAtPath:[self cacheFolderPath] error:nil]; +} +///缓存文件大小 +- (NSUInteger)folderSize { + NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:[self cacheFolderPath] error:nil]; + NSEnumerator *filesEnumerator = [filesArray objectEnumerator]; + NSString *fileName; + unsigned long long int fileSize = 0; + while (fileName = [filesEnumerator nextObject]) { + NSDictionary *fileDic = [[NSFileManager defaultManager] attributesOfItemAtPath:[[self cacheFolderPath] stringByAppendingPathComponent:fileName] error:nil]; + fileSize += [fileDic fileSize]; + } + return (NSUInteger)fileSize; +} + +#pragma mark - Getter +- (NSString *)diskPath { + if (!_diskPath) { + _diskPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; + } + return _diskPath; +} +- (NSString *)cacheFolder { + if (!_cacheFolder) { + _cacheFolder = @"com.wsl2ls.webCache"; + } + return _cacheFolder; +} +- (NSString *)subDirectory { + if (!_subDirectory) { + _subDirectory = @"UrlCacheDownload"; + } + return _subDirectory; +} +- (NSUInteger)memoryCapacity { + if (!_memoryCapacity) { + _memoryCapacity = 20 * 1024 * 1024; + } + return _memoryCapacity; +} +- (NSUInteger)diskCapacity { + if (!_diskCapacity) { + _diskCapacity = 50 * 1024 * 1024; + } + return _diskCapacity; +} +- (NSUInteger)cacheTime { + if (!_cacheTime) { + _cacheTime = 24 * 60 * 60; + } + return _cacheTime; +} +- (NSArray *)whiteListsHost { + if (!_whiteListsHost) { + _whiteListsHost = [NSArray array]; + } + return _whiteListsHost; +} +- (NSArray *)whiteListsRequestUrl { + if (!_whiteListsRequestUrl) { + _whiteListsRequestUrl = [NSArray array]; + } + return _whiteListsRequestUrl; +} +- (NSString *)whiteUserAgent { + if (!_whiteUserAgent) { + _whiteUserAgent = @""; + } + return _whiteUserAgent; +} + +- (NSMutableDictionary *)responseDic { + if (!_responseDic) { + _responseDic = [NSMutableDictionary dictionaryWithCapacity:0]; + } + return _responseDic; +} +- (NSCache *)memoryCache{ + if (!_memoryCache) { + _memoryCache = [[NSCache alloc] init]; + //缓存空间的最大总成本,超出上限会自动回收对象。默认值为0,表示没有限制 + _memoryCache.totalCostLimit = self.memoryCapacity; + //能够缓存的对象的最大数量。默认值为0,表示没有限制 + _memoryCache.countLimit = 100; + } + return _memoryCache; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheViewController.h b/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheViewController.h new file mode 100644 index 00000000..5542f0d2 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheViewController.h @@ -0,0 +1,18 @@ +// +// SLWebCacheViewController.h +// DarkMode +// +// Created by wsl on 2020/5/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// WKWebView 缓存实现 +@interface SLWebCacheViewController : SLViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheViewController.m b/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheViewController.m new file mode 100644 index 00000000..bc4e58ee --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebCache/SLWebCacheViewController.m @@ -0,0 +1,158 @@ +// +// SLWebCacheViewController.m +// DarkMode +// +// Created by wsl on 2020/5/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLWebCacheViewController.h" +#import +#import "SLWebCacheManager.h" + +/* + https://zhuanlan.zhihu.com/p/148931732 WKWebView离线化方案——实现Service Worker API + https://github.com/ming1016/STMURLCache 戴明大神 + */ +@interface SLWebCacheViewController () + +@property (nonatomic, strong) WKWebView * webView; +///网页加载进度视图 +@property (nonatomic, strong) UIProgressView * progressView; +/// WKWebView 内容的高度 +@property (nonatomic, assign) CGFloat webContentHeight; + +@end + +@implementation SLWebCacheViewController + +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUI]; + [self addKVO]; +} +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self.progressView removeFromSuperview]; +} +- (void)dealloc { + [self removeKVO]; + //关闭缓存 + [[SLWebCacheManager shareInstance] closeCache]; +} + +#pragma mark - UI +- (void)setupUI { + self.navigationItem.title = @"WK缓存方案"; + self.view.backgroundColor = UIColor.whiteColor; + UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"上一步" style:UIBarButtonItemStyleDone target:self action:@selector(goBackAction:)]; + UIBarButtonItem *forwardItem = [[UIBarButtonItem alloc] initWithTitle:@"下一步" style:UIBarButtonItemStyleDone target:self action:@selector(goForwardAction:)]; + UIBarButtonItem *clearItem = [[UIBarButtonItem alloc] initWithTitle:@"清理缓存" style:UIBarButtonItemStyleDone target:self action:@selector(clearCacheAction:)]; + self.navigationItem.rightBarButtonItems = @[forwardItem,backItem,clearItem]; + + SLWebCacheManager *cacheManager = [SLWebCacheManager shareInstance]; + cacheManager.whiteListsHost = @[@"www.baidu.com", @"github.com", @"www.jianshu.com", @"juejin.im"]; + //选择缓存方案 + cacheManager.isUsingURLProtocol = YES; + //开启缓存功能 + [cacheManager openCache]; + + [self.view addSubview:self.webView]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:SL_GithubUrl]]; + [self.webView loadRequest:request]; +} + +#pragma mark - Getter +- (WKWebView *)webView { + if(_webView == nil){ + //创建网页配置 + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, SL_kScreenHeight) configuration:config]; + _webView.navigationDelegate = self; + if (@available(iOS 11.0, *)) { + _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + self.automaticallyAdjustsScrollViewInsets = NO; + } + } + return _webView; +} +- (UIProgressView *)progressView { + if (!_progressView){ + _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, 2)]; + _progressView.tintColor = [UIColor blueColor]; + _progressView.trackTintColor = [UIColor clearColor]; + } + if (_progressView.superview == nil) { + [self.navigationController.navigationBar addSubview:_progressView]; + } + return _progressView; +} + +#pragma mark - KVO +///添加键值对监听 +- (void)addKVO { + //监听网页加载进度 + [self.webView addObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) + options:NSKeyValueObservingOptionNew + context:nil]; + //监听网页内容高度 + [self.webView.scrollView addObserver:self + forKeyPath:@"contentSize" + options:NSKeyValueObservingOptionNew + context:nil]; +} +///移除监听 +- (void)removeKVO { + //移除观察者 + [_webView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress))]; + [_webView.scrollView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(contentSize))]; +} +//kvo监听 必须实现此方法 +-(void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context{ + + if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] + && object == _webView) { + // NSLog(@"网页加载进度 = %f",_webView.estimatedProgress); + self.progressView.progress = _webView.estimatedProgress; + if (_webView.estimatedProgress >= 1.0f) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.progressView.progress = 0; + }); + } + }else if ([keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))] + && object == _webView.scrollView && _webContentHeight != _webView.scrollView.contentSize.height) { + _webContentHeight = _webView.scrollView.contentSize.height; + } +} + +#pragma mark - Events Handle +//返回上一步 +- (void)goBackAction:(id)sender{ + [_webView goBack]; +} +//前往下一步 +- (void)goForwardAction:(id)sender{ + [_webView goForward]; +} +//清理缓存 +- (void)clearCacheAction:(id)sender { + [[SLWebCacheManager shareInstance] clearCache]; +} + +#pragma mark - WKNavigationDelegate +// 根据客户端受到的服务器响应头以及response相关信息来决定是否可以跳转 +- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{ + //允许跳转 + decisionHandler(WKNavigationResponsePolicyAllow); +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureBrowseController.h b/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureBrowseController.h new file mode 100644 index 00000000..c054d022 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureBrowseController.h @@ -0,0 +1,24 @@ +// +// SLPictureBrowseController.h +// TELiveClass +// +// Created by wsl on 2020/2/28. +// Copyright © 2020 offcn_c. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol SLPictureAnimationViewDelegate +//用于转场的动画视图 +- (UIView *)animationViewOfPictureTransition:(NSIndexPath *)indexPath; +@end + +/// 图集浏览控制器 +@interface SLPictureBrowseController : UIViewController +@property (nonatomic, strong) NSMutableArray *imagesArray; +@property (nonatomic, strong) NSIndexPath *indexPath; //数据来源索引 +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureBrowseController.m b/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureBrowseController.m new file mode 100644 index 00000000..1ca831d7 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureBrowseController.m @@ -0,0 +1,494 @@ +// +// SLPictureBrowseController.m +// TELiveClass +// +// Created by wsl on 2020/2/28. +// Copyright © 2020 offcn_c. All rights reserved. +// + +#import "SLPictureBrowseController.h" +#import + +/// 图片缩放视图 +@interface SLPictureZoomView : UIScrollView +@property (nonatomic, strong) YYAnimatedImageView *imageView; +@property (nonatomic, strong) UIActivityIndicatorView *indicatorView; //下载指示器 +@property (nonatomic, assign) CGSize imageNormalSize; //图片原尺寸 +@end +@implementation SLPictureZoomView + +#pragma mark - Override +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setupUI]; + } + return self; +} + +#pragma mark - UI +- (void)setupUI { + self.delegate = self; + if (@available(iOS 11.0, *)) { + self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + self.minimumZoomScale = 1.0; + self.maximumZoomScale = 2.0; + self.clipsToBounds = NO; + [self addSubview:self.imageView]; +} + +#pragma mark - Getter +- (YYAnimatedImageView *)imageView { + if (!_imageView) { + _imageView = [[YYAnimatedImageView alloc] init]; + _imageView.userInteractionEnabled = YES; + } + return _imageView; +} +- (UIActivityIndicatorView *)indicatorView { + if (!_indicatorView) { + _indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + _indicatorView.frame = CGRectMake(0, 0, 30, 30); + _indicatorView.center = CGPointMake([UIScreen mainScreen].bounds.size.width/2.0, [UIScreen mainScreen].bounds.size.height/2.0); + } + return _indicatorView; +} +- (CGSize)imageNormalSize { + if (_imageNormalSize.width == 0) { + _imageNormalSize = CGSizeMake(self.frame.size.width, self.frame.size.height); + } + return _imageNormalSize; +} + +#pragma mark - HelpMethods +- (void)setImageUrl:(NSURL *)url { + + [[YYImageCache sharedCache] getImageForKey:[url absoluteString] withType:YYImageCacheTypeAll withBlock:^(UIImage * _Nullable image, YYImageCacheType type) { + if (!image) { + [self.indicatorView startAnimating]; + [self addSubview:self.indicatorView]; + }else { + [self.indicatorView stopAnimating]; + [self.indicatorView removeFromSuperview]; + } + }]; + + __weak typeof(self) weakSelf = self; + [self.imageView yy_setImageWithURL:url placeholder:nil options:YYWebImageOptionShowNetworkActivity completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) { + [weakSelf.indicatorView stopAnimating]; + [weakSelf.indicatorView removeFromSuperview]; + if (image == nil) { + return ; + } + weakSelf.imageNormalSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width*image.size.height/image.size.width); + weakSelf.imageView.frame = CGRectMake(0, 0, weakSelf.imageNormalSize.width, weakSelf.imageNormalSize.height); + weakSelf.contentSize = weakSelf.imageNormalSize; + if (weakSelf.imageNormalSize.height <= [UIScreen mainScreen].bounds.size.height) { + weakSelf.imageView.center = CGPointMake([UIScreen mainScreen].bounds.size.width/2.0, [UIScreen mainScreen].bounds.size.height/2.0); + } + }]; +} + +#pragma mark - UIScrollViewDelegate +//返回缩放的视图 +- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { + return self.imageView; +} +//缩放过程中 +- (void)scrollViewDidZoom:(UIScrollView *)scrollView { + CGFloat imageSclaeW = scrollView.zoomScale * self.imageNormalSize.width; + CGFloat imageSclaeH = scrollView.zoomScale * self.imageNormalSize.height; + CGFloat imageX = 0; + CGFloat imageY = 0; + if (imageSclaeW < self.frame.size.width) { + imageX = (self.frame.size.width - imageSclaeW)/2.0; + } + if (imageSclaeH < self.frame.size.height) { + imageY = (self.frame.size.height - imageSclaeH)/2.0; + } + self.imageView.frame = CGRectMake(imageX, imageY, imageSclaeW, imageSclaeH); +} + +@end + + +#define KSLPictureBrowseSpace 8 // 浏览的图片间隔1/2 +/// 图片浏览单元 +@interface SLPictureBrowsingCell: UICollectionViewCell +@property (nonatomic, strong) SLPictureZoomView *zoomView; +@end +@implementation SLPictureBrowsingCell + +#pragma mark - Override +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setupUI]; + } + return self; +} + +#pragma mark - UI +- (void)setupUI { + [self.contentView addSubview:self.zoomView]; + [self.zoomView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(self.contentView.mas_left).offset(KSLPictureBrowseSpace); + make.top.bottom.mas_equalTo(self.contentView); + make.right.mas_equalTo(self.contentView).offset(-KSLPictureBrowseSpace); + }]; + //解决 self.pictureZoomView 和UICollectionView 手势冲突 + self.zoomView.userInteractionEnabled = NO; + [self.contentView addGestureRecognizer:self.zoomView.panGestureRecognizer]; + [self.contentView addGestureRecognizer:self.zoomView.pinchGestureRecognizer]; +} + +#pragma mark - Getter +- (SLPictureZoomView *)zoomView { + if (!_zoomView) { + _zoomView = [[SLPictureZoomView alloc] init]; + _zoomView.backgroundColor = [UIColor clearColor]; + } + return _zoomView; +} +@end + +#import +#import +#import "SLPictureTransitionAnimation.h" + +/// 图集浏览控制器 +@interface SLPictureBrowseController (){ + UIViewController *_fromViewController; +} +@property (nonatomic, strong) UICollectionView *collectionView; +@property (nonatomic, strong) UIPageControl *pageControl; +@property (nonatomic, strong) UIButton *saveButton; +@property (nonatomic, strong) SLPictureTransitionAnimation *transitionAnimation; //转场动画 +@property (nonatomic, assign) NSInteger currentPage; //图片当前页码 +@end + +@implementation SLPictureBrowseController + +#pragma mark - Override +- (id)init { + self = [super init]; + if (self) { + self.transitionAnimation = [[SLPictureTransitionAnimation alloc] init]; + self.transitionAnimation.transitionType = SLTransitionTypePresent; + self.transitioningDelegate = self; //设置了这个属性之后,在present转场动画处理时,转场前的视图fromVC的view一直都在管理转场动画视图的容器containerView中,会被转场后,后加入到containerView中视图toVC的View遮住,类似于入栈出栈的原理;如果没有设置的话,present转场时,fromVC.view就会先出栈从containerView移除,然后toVC.View入栈,那之后再进行disMiss转场返回时,需要重新把fromVC.view加入containerView中。 + //在push转场动画处理时,设置这个属性是没有效果的,也就是没用的。 + self.modalPresentationStyle = UIModalPresentationOverFullScreen; + } + return self; +} +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUI]; +} +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; +} +- (void)dealloc { +} +- (void)viewSafeAreaInsetsDidChange { + [super viewSafeAreaInsetsDidChange]; +} + +#pragma mark - UI +- (void)setupUI { + self.view.backgroundColor = [UIColor blackColor]; + self.view.clipsToBounds = YES; + [self.view addSubview:self.collectionView]; + [self.view addSubview:self.pageControl]; + [self.view addSubview:self.saveButton]; + [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.mas_equalTo(0); + make.bottom.mas_equalTo(0); + make.left.mas_equalTo(-KSLPictureBrowseSpace); + make.right.mas_equalTo(KSLPictureBrowseSpace); + }]; + [self.pageControl mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self.view); + make.top.mas_equalTo(self.view).offset(SL_TopSafeAreaHeight); + }]; + if (self.imagesArray.count > 1) { + self.pageControl.numberOfPages = self.imagesArray.count; + self.pageControl.currentPage = self.currentPage; + self.pageControl.hidden = NO; + }else { + self.pageControl.hidden = YES; + } + if(self.imagesArray.count == 1) self.collectionView.scrollEnabled = NO; + self.collectionView.contentSize = CGSizeMake(self.imagesArray.count * (self.view.frame.size.width + 2 * KSLPictureBrowseSpace), self.view.frame.size.height); + self.collectionView.contentOffset = CGPointMake(self.currentPage * (self.view.frame.size.width + 2 * KSLPictureBrowseSpace), 0); + [self.saveButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self.view); + make.bottom.mas_equalTo(-25-SL_BottomSafeAreaHeight); + make.size.mas_equalTo(CGSizeMake(120, 38)); + }]; + + //添加拖拽手势 拖拽图片退出图集浏览界面 + self.view.userInteractionEnabled = YES; + UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panPicture:)]; + [self.view addGestureRecognizer:pan]; + //单击手势 退出浏览 + UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)]; + singleTap.numberOfTouchesRequired = 1; + singleTap.numberOfTapsRequired = 1; + [self.view addGestureRecognizer:singleTap]; + //双击手势放大 + UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)]; + doubleTap.numberOfTouchesRequired = 1; + doubleTap.numberOfTapsRequired = 2; + [self.view addGestureRecognizer:doubleTap]; + [singleTap requireGestureRecognizerToFail:doubleTap]; +} + +#pragma mark - Getter +- (UICollectionView *)collectionView { + if (!_collectionView) { + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; + _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + if (@available(iOS 11.0, *)) { + _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + _collectionView.backgroundColor = [UIColor clearColor]; + _collectionView.delegate = self; + _collectionView.dataSource = self; + _collectionView.pagingEnabled = YES; + _collectionView.allowsSelection = NO; + [_collectionView registerClass:[SLPictureBrowsingCell class] forCellWithReuseIdentifier:@"SLPictureBrowsingCell"]; + } + return _collectionView; +} +- (UIPageControl *)pageControl { + if (!_pageControl) { + _pageControl = [[UIPageControl alloc] init]; + _pageControl.pageIndicatorTintColor = [UIColor grayColor]; + _pageControl.currentPageIndicatorTintColor = [UIColor whiteColor]; + } + return _pageControl; +} +- (UIButton *)saveButton { + if (!_saveButton) { + _saveButton = [[UIButton alloc] init]; + [_saveButton setTitle:@"保存" forState:UIControlStateNormal]; + [_saveButton setTitleColor:SL_UIColorFromHex(0xffffff,1.0) forState:UIControlStateNormal]; + _saveButton.titleLabel.font = [UIFont systemFontOfSize:14]; + [_saveButton setImage:[UIImage imageNamed:@"message_img_download"] forState:UIControlStateNormal]; + _saveButton.backgroundColor = SL_UIColorFromHex(0x393939,1.0); + _saveButton.layer.masksToBounds = YES; + _saveButton.layer.cornerRadius = 4; + [_saveButton addTarget:self action:@selector(saveButtonAction:) forControlEvents:UIControlEventTouchUpInside]; + } + return _saveButton; +} + +#pragma mark - HelpMethods +//返回当前页面用于转场动画的视图 +- (UIView *)currentAnimatonView { + SLPictureBrowsingCell *cell = [self.collectionView visibleCells].firstObject; + if (cell == nil) { + cell = [[SLPictureBrowsingCell alloc] initWithFrame: CGRectMake(0,0, self.view.frame.size.width, self.view.frame.size.height)]; + NSURL *imgUrl = self.imagesArray[self.currentPage]; + [cell.zoomView setImageUrl:imgUrl]; + UIView *tempView = cell.zoomView.imageView; + return tempView; + }else { + UIView *imageView = cell.zoomView.imageView; + UIView *tempView = [imageView snapshotViewAfterScreenUpdates:YES]; + tempView.frame = [imageView convertRect:imageView.bounds toView:self.view]; + return tempView; + } +} + +#pragma mark - EventsHandle +//拖拽即将推出图片浏览模式 +- (void)panPicture:(UIPanGestureRecognizer *)pan { + SLPictureBrowsingCell *cell = [self.collectionView visibleCells].firstObject; + SLPictureZoomView *zoomView = cell.zoomView; + CGPoint translation = [pan translationInView:cell]; + zoomView.center = CGPointMake(zoomView.center.x+translation.x, zoomView.center.y+translation.y); + [pan setTranslation:CGPointZero inView:cell]; + //滑动的距离百分比 + CGFloat percentComplete = 0; + percentComplete = (zoomView.center.y - [UIScreen mainScreen].bounds.size.height/2.0)/([UIScreen mainScreen].bounds.size.height/2.0); + percentComplete = fabs(percentComplete); + switch (pan.state) { + case UIGestureRecognizerStateBegan: + self.saveButton.hidden = YES; + break; + case UIGestureRecognizerStateChanged: + if (zoomView.center.y > [UIScreen mainScreen].bounds.size.height/2.0 && percentComplete > 0.01 && percentComplete < 1.0) { + self.view.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:1 - percentComplete]; + zoomView.transform = CGAffineTransformMakeScale(1 - percentComplete/2.0, 1 - percentComplete/2.0); + } + break; + case UIGestureRecognizerStateEnded: + if (percentComplete >= 0.5 && zoomView.center.y > [UIScreen mainScreen].bounds.size.height/2.0) { + [self dismissViewControllerAnimated:YES completion:nil]; + }else { + self.view.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:1]; + [UIView animateWithDuration:0.3 animations:^{ + zoomView.center = CGPointMake(([UIScreen mainScreen].bounds.size.width+KSLPictureBrowseSpace * 2)/2.0, [UIScreen mainScreen].bounds.size.height/2.0); + zoomView.transform = CGAffineTransformMakeScale(1, 1); + } completion:^(BOOL finished) { + self.saveButton.hidden = NO; + }]; + } + break; + default: + break; + } + +} +//单击退出出图片浏览模式 +- (void)singleTap:(UITapGestureRecognizer *)singleTap { + [self dismissViewControllerAnimated:YES completion:nil]; +} +//双击放大点击点 +- (void)doubleTap:(UITapGestureRecognizer *)doubleTap { + SLPictureBrowsingCell *cell = [self.collectionView visibleCells].firstObject; + SLPictureZoomView *zoomView = cell.zoomView; + //获得触摸点在imageView上的位置 + CGPoint tapPosionOfPicture = [doubleTap locationInView:zoomView.imageView]; + //获得触摸点在zoomView上的位置 + CGPoint tapPosionOfScreen = [doubleTap locationInView:zoomView]; + [UIView animateWithDuration:0.3 animations:^{ + if(zoomView.zoomScale != 1) { + zoomView.zoomScale = 1; + [zoomView scrollViewDidZoom:zoomView]; + zoomView.contentOffset = CGPointZero; + }else { + //获得点击的图片位置放大后的坐标 相对于ImageView + CGPoint newTapPosionOfPicture = CGPointMake(tapPosionOfPicture.x*zoomView.maximumZoomScale, tapPosionOfPicture.y*zoomView.maximumZoomScale); + zoomView.zoomScale = zoomView.maximumZoomScale; + [zoomView scrollViewDidZoom:zoomView]; + + if (newTapPosionOfPicture.y < zoomView.frame.size.height || zoomView.imageView.frame.size.height < zoomView.frame.size.height) { + // 放大后对应的点击点在图片上的位置 处在前一屏当中 + zoomView.contentOffset = CGPointMake(newTapPosionOfPicture.x - tapPosionOfScreen.x, 0); + } else { // 点击点在图片上的位置超过一屏时 + if (newTapPosionOfPicture.y > zoomView.imageView.frame.size.height - zoomView.frame.size.height){ + // 点击点在图片最底部一屏中 + zoomView.contentOffset = CGPointMake(newTapPosionOfPicture.x - tapPosionOfScreen.x, zoomView.imageView.frame.size.height - zoomView.frame.size.height); + }else{ + //点击点在图片中间层 + zoomView.contentOffset = CGPointMake(newTapPosionOfPicture.x - tapPosionOfScreen.x, newTapPosionOfPicture.y - tapPosionOfScreen.y); + } + } + } + } completion:^(BOOL finished) { + + }]; +} +//保存 +- (void)saveButtonAction:(UIButton *)btn { + PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; + if (status == PHAuthorizationStatusNotDetermined) { // 用户还没有做出选择 + // 弹框请求用户授权 + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + if (status == PHAuthorizationStatusAuthorized) { + // 用户第一次同意了访问相册权限 + [self saveImageToPhotosAlbum]; + } + }]; + return; + }else if (status == PHAuthorizationStatusRestricted || status == PHAuthorizationStatusDenied) { + // [self showOnlyTextWithHint:@"App需要经过您的同意,才能保存图片到相册"]; + return; + } + [self saveImageToPhotosAlbum]; +} +//保存图片到相册 +- (void)saveImageToPhotosAlbum { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSURL *url = self.imagesArray[self.currentPage]; + [[YYImageCache sharedCache] getImageDataForKey:[url absoluteString] withBlock:^(NSData * _Nullable imageData) { + if (imageData) { + ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; + [library writeImageDataToSavedPhotosAlbum:imageData metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + // [self showOnlyTextWithHint:@"保存失败"]; + }else { + // [self showOnlyTextWithHint:@"保存成功"]; + } + }); + }]; + } + + }]; + + }); +} + +#pragma mark - UIScrollViewDelegate +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + //四舍五入 + self.currentPage = roundf(scrollView.contentOffset.x/(self.view.frame.size.width + 2 * KSLPictureBrowseSpace)); + self.pageControl.currentPage = self.currentPage; +} +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + self.currentPage = scrollView.contentOffset.x / scrollView.frame.size.width; + self.pageControl.currentPage = self.currentPage; +} + +#pragma mark - UICollectionViewDelegate, UICollectionViewDataSource +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return 1; +} +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.imagesArray.count; +} +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath { + SLPictureBrowsingCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"SLPictureBrowsingCell" forIndexPath:indexPath]; + NSURL *imgUrl = self.imagesArray[indexPath.row]; + [cell.zoomView setImageUrl:imgUrl]; + return cell; +} +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { +} + +#pragma mark - UICollectionViewDelegateFlowLayout +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + return CGSizeMake([UIScreen mainScreen].bounds.size.width + 2 * KSLPictureBrowseSpace, self.collectionView.frame.size.height); +} +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { + return 0; +} +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { + return UIEdgeInsetsMake(0, 0, 0, 0); +} + +#pragma mark - UIViewControllerTransitioningDelegate +// 自定义转场动画 +//返回一个处理presente动画过渡的对象 +- (id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { + _fromViewController = source; + self.transitionAnimation.transitionType = SLTransitionTypePresent; + if([source conformsToProtocol:@protocol(SLPictureAnimationViewDelegate)]) { + if ([source respondsToSelector:@selector(animationViewOfPictureTransition:)]) { + self.transitionAnimation.fromAnimatonView = [source performSelector:@selector(animationViewOfPictureTransition:) withObject:self.indexPath]; + } + } + self.transitionAnimation.toAnimatonView = [self currentAnimatonView]; + return self.transitionAnimation; +} +//返回一个处理dismiss动画过渡的对象 +- (id )animationControllerForDismissedController:(UIViewController *)dismissed { + self.transitionAnimation.transitionType = SLTransitionTypeDissmiss; + if([_fromViewController conformsToProtocol:@protocol(SLPictureAnimationViewDelegate)]) { + if ([_fromViewController respondsToSelector:@selector(animationViewOfPictureTransition:)]) { + self.transitionAnimation.toAnimatonView = [_fromViewController performSelector:@selector(animationViewOfPictureTransition:) withObject:self.indexPath]; + } + } + self.transitionAnimation.fromAnimatonView = [self currentAnimatonView];; + return self.transitionAnimation; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureTransitionAnimation.h b/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureTransitionAnimation.h new file mode 100644 index 00000000..b9f67fb8 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureTransitionAnimation.h @@ -0,0 +1,30 @@ +// +// SLPictureTransitionAnimation.h +// TELiveClass +// +// Created by wsl on 2020/2/28. +// Copyright © 2020 offcn_c. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +//定义枚举 转场类型 +typedef enum : NSUInteger { + SLTransitionTypePush, + SLTransitionTypePop, + SLTransitionTypePresent, + SLTransitionTypeDissmiss +} SLTransitionType; + +/// 图片浏览转场动画 +@interface SLPictureTransitionAnimation : NSObject +@property (nonatomic, assign) SLTransitionType transitionType; +@property (nonatomic, strong) UIView *toAnimatonView; //动画前的视图 +@property (nonatomic, strong) UIView *fromAnimatonView; //动画后的视图 +//@property (nonatomic, assign) CGRect animatonRect; +//@property (nonatomic, strong) UIView *animatonView; +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureTransitionAnimation.m b/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureTransitionAnimation.m new file mode 100644 index 00000000..dc38fe0b --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebNative/SLPictureTransitionAnimation.m @@ -0,0 +1,95 @@ +// +// SLPictureTransitionAnimation.m +// TELiveClass +// +// Created by wsl on 2020/2/28. +// Copyright © 2020 offcn_c. All rights reserved. +// + +#import "SLPictureTransitionAnimation.h" + +@interface SLPictureTransitionAnimation () +@end + +@implementation SLPictureTransitionAnimation + +#pragma mark - UIViewControllerAnimatedTransitioning +//返回动画时间 +- (NSTimeInterval)transitionDuration:(nullable id )transitionContext { + return 0.3; +} +//所有的过渡动画事务都在这个代理方法里面完成 +- (void)animateTransition:(id )transitionContext { + switch (self.transitionType) { + case SLTransitionTypePush: + [self pushAnimation:transitionContext]; + break; + case SLTransitionTypePop: + [self popAnimation:transitionContext]; + break; + case SLTransitionTypePresent: + [self presentAnimation:transitionContext]; + break; + case SLTransitionTypeDissmiss: + [self dissmissAnimation:transitionContext]; + break; + } +} + +#pragma mark - Push/Pop +- (void)pushAnimation:(id )transitionContext { + [transitionContext completeTransition:YES]; +} +- (void)popAnimation:(id )transitionContext { + [transitionContext completeTransition:YES]; +} + +#pragma mark - Present/Dissmiss +- (void)presentAnimation:(id )transitionContext { + //转场后视图控制器上的视图view + UIView *toView = [transitionContext viewForKey: UITransitionContextToViewKey]; + toView.hidden = true; + //这里有个重要的概念containerView,如果要对视图做转场动画,视图就必须要加入containerView中才能进行,可以理解containerView管理着所有做转场动画的视图 + UIView *containerView = transitionContext.containerView; + //黑色背景视图 + UIView *bgView = [[UIView alloc] initWithFrame: CGRectMake(0,0, containerView.frame.size.width, containerView.frame.size.height)]; + bgView.backgroundColor = [UIColor blackColor]; + [containerView addSubview:toView]; + [containerView addSubview:bgView]; + [containerView addSubview:self.fromAnimatonView]; + //动画 + [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ + if(!CGRectEqualToRect(self.toAnimatonView.frame, CGRectZero)) { + self.fromAnimatonView.frame = self.toAnimatonView.frame; + self.fromAnimatonView.layer.contentsRect = self.toAnimatonView.layer.contentsRect; + } + } completion:^(BOOL finished) { + toView.hidden = NO; + [bgView removeFromSuperview]; + [self.fromAnimatonView removeFromSuperview]; + [transitionContext completeTransition:YES]; + }]; +} +- (void)dissmissAnimation:(id )transitionContext { + //转场前视图控制器上的视图view + UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; + fromView.hidden = YES; + UIView *containerView = transitionContext.containerView; + //黑色背景视图 + UIView *bgView = [[UIView alloc] initWithFrame:CGRectMake( 0,0, containerView.frame.size.width, containerView.frame.size.height)]; + bgView.backgroundColor = fromView.backgroundColor; + [containerView addSubview:bgView]; + [containerView addSubview:self.fromAnimatonView]; + //动画 + [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ + self.fromAnimatonView.frame = self.toAnimatonView.frame; + self.fromAnimatonView.layer.contentsRect = self.toAnimatonView.layer.contentsRect; + bgView.alpha = 0; + } completion:^(BOOL finished) { + [bgView removeFromSuperview]; + [self.fromAnimatonView removeFromSuperview]; + [transitionContext completeTransition:YES]; + }]; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebNative/SLReusableManager.h b/iOS_Tips/DarkMode/WKWebView/WebNative/SLReusableManager.h new file mode 100644 index 00000000..d0f4ee5b --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebNative/SLReusableManager.h @@ -0,0 +1,50 @@ +// +// SLReusableManager.h +// DarkMode +// +// Created by wsl on 2020/6/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SLReusableCell : UIView +@end + +@class SLReusableManager; +@protocol SLReusableDataSource +@required +///行数 +- (NSInteger)numberOfRowsInReusableManager:(SLReusableManager *)reusableManager; +///行位置 +- (CGRect)reusableManager:(SLReusableManager *)reusableManager frameForRowAtIndex:(NSInteger)index; +///行内容 +- (SLReusableCell *)reusableManager:(SLReusableManager *)reusableManager cellForRowAtIndex:(NSInteger)index; +@end +@protocol SLReusableDelegate +///选中行 +- (void)reusableManager:(SLReusableManager *)reusableManager didSelectRowAtIndex:(NSInteger)index; +@end + +@interface SLReusableManager : NSObject +///事件代理 +@property (nonatomic, weak) iddelegate; +///数据源代理 +@property (nonatomic, weak) iddataSource; +/// 父视图 +@property (nonatomic, weak) UIScrollView *scrollView; + +///刷新数据 +- (void)reloadData; +///注册样式 +- (void)registerClass:(Class)class forCellReuseIdentifier:(NSString *)cellID; +///根据cellID从复用池reusablePool取可重用的view,如果没有,重新创建一个新对象返回 +- (SLReusableCell *)dequeueReusableCellWithIdentifier:(nonnull NSString *)cellID index:(NSInteger)index; +///获取索引为index的cell,如果第index的cell不在可见范围内,返回nil +- (SLReusableCell *)cellForRowAtIndex:(NSInteger)index; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebNative/SLReusableManager.m b/iOS_Tips/DarkMode/WKWebView/WebNative/SLReusableManager.m new file mode 100644 index 00000000..5962dd7b --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebNative/SLReusableManager.m @@ -0,0 +1,266 @@ +// +// SLReusableManager.m +// DarkMode +// +// Created by wsl on 2020/6/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLReusableManager.h" + +@interface SLReusableCell () +@property (nonatomic, copy) NSString *cellID; +@property (nonatomic, assign) NSInteger index; +@end +@implementation SLReusableCell +@end + +///复用管理 +@interface SLReusableManager () + +///复用池 +@property (nonatomic, strong) NSMutableDictionary *> *reusablePool; +///注册的类 +@property (nonatomic, strong) NSMutableDictionary *registerClasses; +/// 每一行的坐标位置 +@property (nonatomic, strong) NSMutableArray *frameArray; +/// 当前可见的cells +@property (nonatomic, strong) NSMutableArray *visibleCells; +///记录最后一次的偏移量,用来判断滑动方向 +@property (nonatomic, assign) CGFloat lastContentOffsetY; +///顶部即将展示的索引 +@property (nonatomic, assign) NSInteger willDisplayIndexTop; +///底部即将展示的索引 +@property (nonatomic, assign) NSInteger willDisplayIndexBottom; + +@end +@implementation SLReusableManager + +#pragma mark - Override +- (void)dealloc { + [self removeKVO]; +} +#pragma mark - Getter +- (NSMutableDictionary *)reusablePool { + if (!_reusablePool) { + _reusablePool = [NSMutableDictionary dictionary]; + } + return _reusablePool;; +} +- (NSMutableDictionary *)registerClasses { + if (!_registerClasses) { + _registerClasses = [NSMutableDictionary dictionary]; + } + return _registerClasses; +} +- (NSMutableArray *)frameArray { + if (!_frameArray) { + _frameArray = [NSMutableArray array]; + } + return _frameArray; +} +- (NSMutableArray *)visibleCells { + if (!_visibleCells) { + _visibleCells = [NSMutableArray array]; + } + return _visibleCells;; +} + +#pragma mark - Setter +- (void)setScrollView:(UIScrollView *)scrollView { + _scrollView =scrollView; + [self addKVO]; +} + +#pragma mark - KVO +- (void)addKVO { + [self.scrollView addObserver:self + forKeyPath:@"contentOffset" + options:NSKeyValueObservingOptionNew + context:nil]; +} +- (void)removeKVO{ + [self.scrollView removeObserver:self forKeyPath:@"contentOffset"]; +} +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ + if(object == self.scrollView && [keyPath isEqualToString:@"contentOffset"]) { + if(self.scrollView.contentOffset.y > self.lastContentOffsetY) { + [self willDisplayCellWithDirection:NO]; + [self willDisappearCellWithDirection:YES]; + }else { + [self willDisplayCellWithDirection:YES]; + [self willDisappearCellWithDirection:NO]; + } + self.lastContentOffsetY = self.scrollView.contentOffset.y; + } +} + +#pragma mark - Public +///刷新数据 +- (void)reloadData { + //清空布局信息 + [self.frameArray removeAllObjects]; + for (UIView *subView in self.scrollView.subviews) { + if ([subView isKindOfClass:[SLReusableCell class]]) { + [subView removeFromSuperview]; + } + } + [self.visibleCells removeAllObjects]; + + self.willDisplayIndexTop = -1; + //数据源个数 + NSInteger count = [self.dataSource numberOfRowsInReusableManager:self]; + self.willDisplayIndexBottom = count; + + CGFloat y = 0; + //获取每一行的布局信息 + for (int i = 0; i < count; i++) { + CGRect rect = [self.dataSource reusableManager:self frameForRowAtIndex:i]; + [self.frameArray addObject:[NSValue valueWithCGRect:rect]]; + + if (rect.origin.y + rect.size.height < self.scrollView.contentOffset.y) { + self.willDisplayIndexTop = i; + } + + //按需加载 只加载坐标位置是在当前窗口显示的视图 + if (rect.origin.y + rect.size.height >= self.scrollView.contentOffset.y && rect.origin.y <= self.scrollView.contentOffset.y + self.scrollView.sl_height) { + SLReusableCell *cell = [self.dataSource reusableManager:self cellForRowAtIndex:i]; + cell.frame = rect; + [self.scrollView addSubview:cell]; + [self.visibleCells addObject:cell]; + } + + if (rect.origin.y > self.scrollView.contentOffset.y + self.scrollView.sl_height && self.willDisplayIndexBottom == count) { + self.willDisplayIndexBottom = i; + } + + //下一行的起始纵坐标 + y += rect.size.height; + + //最后 确定了内容大小contentSize + if (i == count - 1) { + self.scrollView.contentSize = CGSizeMake(self.scrollView.sl_width, y); + } + } +} +///注册样式 +- (void)registerClass:(Class)class forCellReuseIdentifier:(NSString *)cellID { + self.reusablePool[cellID] = [NSHashTable weakObjectsHashTable]; + self.registerClasses[cellID] = class; +} +///根据cellID从复用池reusablePool取可重用的view,如果没有,重新创建一个新对象返回 +- (SLReusableCell *)dequeueReusableCellWithIdentifier:(nonnull NSString *)cellID index:(NSInteger)index{ + NSHashTable *hashTable = self.reusablePool[cellID]; + SLReusableCell *cell = hashTable.allObjects.firstObject; + if (cell == nil) { + //复用池reusablePool没有可重用的,就重新创建一个新对象返回 + cell = [[self.registerClasses[cellID] alloc] init]; + cell.cellID = cellID; + CGRect rect = [self.dataSource reusableManager:self frameForRowAtIndex:index]; + cell.frame = rect; + cell.userInteractionEnabled = YES; + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didSelectedAction:)]; + [cell addGestureRecognizer:tap]; + }else { + //从缓冲池中取出可重用的cell + [hashTable removeObject:cell]; + } + cell.index = index; + return cell; +} +///获取索引为index的cell,如果第index的cell不在可见范围内,返回nil +- (SLReusableCell *)cellForRowAtIndex:(NSInteger)index { + for (SLReusableCell *cell in self.visibleCells) { + if (cell.index == index) { + return cell; + } + } + return nil; +} + +#pragma mark - Help Methods +///当前可见cell的索引 其实绘制cell的时候就可以先保存可见的索引,不用每次遍历查询 +- (NSArray *)indexForVisibleRows { + NSMutableArray *indexs = [NSMutableArray array]; + for (NSInteger i = self.willDisplayIndexTop+1; i < self.willDisplayIndexBottom; i++) { + [indexs addObject:@(i)]; + } + return indexs; +} +///即将显示的cell,显示时创建或从缓存池中取出调整坐标位置 top:YES上/NO下 +- (void)willDisplayCellWithDirection:(BOOL)top { + if(top) { + if (_willDisplayIndexTop < 0) return; + CGRect rect = [self.frameArray[self.willDisplayIndexTop] CGRectValue]; + //按需加载 只加载坐标位置是在当前窗口显示的视图 + if (rect.origin.y + rect.size.height >= self.scrollView.contentOffset.y && rect.origin.y <= self.scrollView.contentOffset.y + self.scrollView.sl_height) { + NSLog(@"上 第 %ld 个cell显示",self.willDisplayIndexTop); + SLReusableCell *cell = [self.dataSource reusableManager:self cellForRowAtIndex:self.willDisplayIndexTop]; + cell.frame = rect; + [self.scrollView addSubview:cell]; + self.willDisplayIndexTop -=1; + [self.visibleCells insertObject:cell atIndex:0]; + } + }else { + NSInteger count = [self.dataSource numberOfRowsInReusableManager:self]; + if (_willDisplayIndexBottom >= count) return; + CGRect rect = [self.frameArray[self.willDisplayIndexBottom] CGRectValue]; + //按需加载 只加载坐标位置是在当前窗口显示的视图 + if (rect.origin.y + rect.size.height >= self.scrollView.contentOffset.y && rect.origin.y <= self.scrollView.contentOffset.y + self.scrollView.sl_height) { + NSLog(@"下 第 %ld 个cell显示",self.willDisplayIndexBottom); + SLReusableCell *cell = [self.dataSource reusableManager:self cellForRowAtIndex:self.willDisplayIndexBottom]; + cell.frame = rect; + [self.scrollView addSubview:cell]; + self.willDisplayIndexBottom +=1; + [self.visibleCells addObject:cell]; + } + } +} +//即将消失的cell,在消失时放入缓冲池里,并且重置视图cell的内容 top:YES上/NO下 +- (void)willDisappearCellWithDirection:(BOOL)top { + if(top) { + if (self.willDisplayIndexTop+1 >= self.frameArray.count) return; + CGRect rect = [self.frameArray[self.willDisplayIndexTop+1] CGRectValue]; + if (rect.origin.y + rect.size.height < self.scrollView.contentOffset.y) { + self.willDisplayIndexTop = self.willDisplayIndexTop+1; + NSLog(@"上 第 %ld 个cell消失",self.willDisplayIndexTop); + SLReusableCell *cell = self.visibleCells.firstObject; + + //进入缓冲池后,要清空重置cell上的内容,防止下一个取出时显示之前的内容,我这里重置时用了自己的默认logo,你可以自己重绘默认时的cell内容 +// for (UIView *subView in cell.subviews) { +// subView.layer.contents = (__bridge id)[UIImage imageNamed:@"wsl"].CGImage; +// } +// cell.layer.contents = (__bridge id)[UIImage imageNamed:@"wsl"].CGImage; + + NSHashTable * hashTable= self.reusablePool[cell.cellID]; + [hashTable addObject:cell]; + [self.visibleCells removeObjectAtIndex:0]; + } + }else { + if (self.willDisplayIndexBottom-1 < 0) return; + CGRect rect = [self.frameArray[self.willDisplayIndexBottom-1] CGRectValue]; + if (rect.origin.y > self.scrollView.contentOffset.y + self.scrollView.sl_height) { + self.willDisplayIndexBottom = self.willDisplayIndexBottom-1; + NSLog(@"下 第 %ld 个cell消失",self.willDisplayIndexBottom); + SLReusableCell *cell = self.visibleCells.lastObject; + + //进入缓冲池后,要清空重置cell上的内容,防止下一个取出时显示之前的内容,我这里重置时用了自己的默认logo,你可以自己重绘默认时的cell内容 +// for (UIView *subView in cell.subviews) { +// subView.layer.contents = (__bridge id)[UIImage imageNamed:@"wsl"].CGImage; +// } +// cell.layer.contents = (__bridge id)[UIImage imageNamed:@"wsl"].CGImage; + + NSHashTable * hashTable= self.reusablePool[cell.cellID]; + [hashTable addObject:cell]; + [self.visibleCells removeLastObject]; + } + } +} + +#pragma mark - Events Handle +- (void)didSelectedAction:(UITapGestureRecognizer *)tap { + SLReusableCell *cell = (SLReusableCell *)tap.view; + [self.delegate reusableManager:self didSelectRowAtIndex:cell.index]; +} +@end + diff --git a/iOS_Tips/DarkMode/WKWebView/WebNative/SLWebNativeViewController.h b/iOS_Tips/DarkMode/WKWebView/WebNative/SLWebNativeViewController.h new file mode 100644 index 00000000..b2994f49 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebNative/SLWebNativeViewController.h @@ -0,0 +1,18 @@ +// +// SLWebNativeViewController.h +// DarkMode +// +// Created by wsl on 2020/6/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// WKWebView渲染的部分HTML元素替换为用原生组件显示 +@interface SLWebNativeViewController : SLViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebNative/SLWebNativeViewController.m b/iOS_Tips/DarkMode/WKWebView/WebNative/SLWebNativeViewController.m new file mode 100644 index 00000000..89617fff --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebNative/SLWebNativeViewController.m @@ -0,0 +1,333 @@ +// +// SLWebNativeViewController.m +// DarkMode +// +// Created by wsl on 2020/6/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLWebNativeViewController.h" +#import +#import "YYImage.h" +#import +#import "SLAvPlayer.h" +#import +#import "SLReusableManager.h" +#import "SLPictureBrowseController.h" + +@interface SLWebNativeModel : NSObject +@property (nonatomic, copy) NSString *tagID; //标签ID +@property (nonatomic, copy) NSString *type; //元素类型 +@property (nonatomic, copy) NSString *imgUrl; //图片地址 +@property (nonatomic, copy) NSString *videoUrl; //视频地址 +@property (nonatomic, copy) NSString *audioUrl; //音频地址 +@property (nonatomic, assign) CGFloat width; //该标签元素内容宽 +@property (nonatomic, assign) CGFloat height; //该标签元素内容高 +@end +@implementation SLWebNativeModel +@end + + +@interface SLWebNativeCell : SLReusableCell +@property (nonatomic, strong) YYAnimatedImageView *imageView; +@property (nonatomic, assign) CGSize imageViewSize; +@property (nonatomic, strong) UIImageView *playIcon; +@end +@implementation SLWebNativeCell +- (instancetype)init { + self = [super init]; + if (self) { + [self setupUI]; + } + return self; +} +- (void)setupUI { + _imageView = [[YYAnimatedImageView alloc] init]; + [self addSubview:_imageView]; + [_imageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.left.right.bottom.mas_equalTo(0); + }]; + + _playIcon = [[UIImageView alloc] init]; + _playIcon.image = [UIImage imageNamed:@"play"]; + _playIcon.hidden = YES; + [_imageView addSubview:_playIcon]; + [_playIcon mas_makeConstraints:^(MASConstraintMaker *make) { + make.size.mas_equalTo(CGSizeMake(80, 80)); + make.centerX.mas_equalTo(_imageView.mas_centerX); + make.centerY.mas_equalTo(_imageView.mas_centerY); + }]; +} + +- (void)updateDataWith:(SLWebNativeModel *)model { + _imageView.layer.contentsRect = CGRectMake(0, 0, 1, 1); + __weak typeof(self) weakSelf = self; + [self.imageView yy_setImageWithURL:[NSURL URLWithString:model.imgUrl] placeholder:nil options:YYWebImageOptionShowNetworkActivity completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) { + if(error) return ; + if (image.size.width > image.size.height) { + //宽图 + CGFloat width = weakSelf.imageViewSize.height*image.size.width/image.size.height; + if (width > weakSelf.imageViewSize.width) { + CGFloat proportion = weakSelf.imageViewSize.width/width; + weakSelf.imageView.layer.contentsRect = CGRectMake((1 - proportion)/2, 0, proportion, 1); + } + }else if (image.size.width < image.size.height) { + //长图 + CGFloat height = weakSelf.imageViewSize.width*image.size.height/image.size.width; + if (height > weakSelf.imageViewSize.height) { + CGFloat proportion = weakSelf.imageViewSize.height/height; + weakSelf.imageView.layer.contentsRect = CGRectMake(0,(1 - proportion)/2, 1, proportion); + } + } + }]; + + if ([model.type isEqualToString:@"image"]) { + //图片 + _playIcon.hidden = YES; + }else if ([model.type isEqualToString:@"video"]) { + //视频 + _playIcon.hidden = NO; + [_playIcon mas_updateConstraints:^(MASConstraintMaker *make) { + make.size.mas_equalTo(CGSizeMake(60, 60)); + }]; + }else if ([model.type isEqualToString:@"audio"]) { + //音频 + _playIcon.hidden = NO; + [_playIcon mas_updateConstraints:^(MASConstraintMaker *make) { + make.size.mas_equalTo(CGSizeMake(40, 40)); + }]; + } +} +@end + +/* + HTML中部分非文本元素,替换为用native组件来实现展示,来达到个性化自定义、灵活、提高渲染效率、简化web和OC交互的处理流程。 + 本示例 仅以用native组件替换HTML中的img、video、audio 内容来做展示,当然你也可以替换HTML中其它的标签元素。 + 注意:1.用native组件替换时,我们也需要进行一些native组件复用、按需加载的优化处理,类似于tableView的机制。 + 2.html界面调整时,要去重新调用JS方法获取原生标签的位置并更新native组件的位置。 + 3.如果仅需要处理HTML的图片元素,也可以不用原生组件imageView展示,原生下载处理图片,然后通过oc调用JS设置图片 + */ +@interface SLWebNativeViewController () + +@property (nonatomic, strong) dispatch_semaphore_t semaphore; +@property (nonatomic, strong) WKWebView * webView; +/// 网页加载进度视图 +@property (nonatomic, strong) UIProgressView * progressView; +/// WKWebView 内容的高度 +@property (nonatomic, assign) CGFloat webContentHeight; +/// 原生组件所需的HTML中元素的数据 +@property (nonatomic, strong) NSMutableArray *dataSource; + +///复用管理 +@property (nonatomic, strong) SLReusableManager *reusableManager; +/// 每一行的坐标位置 +@property (nonatomic, strong) NSMutableArray *frameArray; + +@end + +@implementation SLWebNativeViewController + +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + [self getData]; + [self setupUI]; + [self addKVO]; +} +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self.progressView removeFromSuperview]; +} +- (void)dealloc { + [self removeKVO]; +} + +#pragma mark - UI +- (void)setupUI { + self.navigationItem.title = @"Html非文本元素替换为native组件展示"; + [self.view addSubview:self.webView]; + + _semaphore = dispatch_semaphore_create(1); + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebNative.html" ofType:nil]; + NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]]; +} + +#pragma mark - Data +/// 获取原生组件所需的HTML中元素的数据 +- (void)getData { + [self.dataSource removeAllObjects]; + NSData *contentData = [[NSFileManager defaultManager] contentsAtPath:[[NSBundle mainBundle] pathForResource:@"WebNativeJson" ofType:@"txt"]]; + NSDictionary * dataDict = [NSJSONSerialization JSONObjectWithData:contentData options:kNilOptions error:nil]; + for (NSDictionary *dict in dataDict[@"dataList"]) { + SLWebNativeModel *model = [SLWebNativeModel yy_modelWithDictionary:dict]; + [self.dataSource addObject:model]; + } +} + +#pragma mark - Getter +- (WKWebView *)webView { + if(_webView == nil){ + //创建网页配置 + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, SL_kScreenHeight) configuration:config]; + _webView.navigationDelegate = self; + _webView.UIDelegate = self; + } + return _webView; +} +- (UIProgressView *)progressView { + if (!_progressView){ + _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, 2)]; + _progressView.tintColor = [UIColor blueColor]; + _progressView.trackTintColor = [UIColor clearColor]; + } + if (_progressView.superview == nil) { + [self.navigationController.navigationBar addSubview:_progressView]; + } + return _progressView; +} +- (NSMutableArray *)dataSource { + if (!_dataSource) { + _dataSource = [NSMutableArray array]; + } + return _dataSource; +} +- (SLReusableManager *)reusableManager { + if (!_reusableManager) { + _reusableManager = [[SLReusableManager alloc] init]; + _reusableManager.delegate = self; + _reusableManager.dataSource = self; + _reusableManager.scrollView = self.webView.scrollView; + [_reusableManager registerClass:[SLWebNativeCell class] forCellReuseIdentifier:@"cellID"]; + } + return _reusableManager; +} +- (NSMutableArray *)frameArray { + if (!_frameArray) { + _frameArray = [NSMutableArray array]; + } + return _frameArray; +} + +#pragma mark - KVO +///添加键值对监听 +- (void)addKVO { + //监听网页加载进度 + [self.webView addObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) + options:NSKeyValueObservingOptionNew + context:nil]; + //监听网页内容高度 + [self.webView.scrollView addObserver:self + forKeyPath:@"contentSize" + options:NSKeyValueObservingOptionNew + context:nil]; +} +///移除监听 +- (void)removeKVO { + //移除观察者 + [_webView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress))]; + [_webView.scrollView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(contentSize))]; +} +//kvo监听 必须实现此方法 +-(void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context{ + + if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] + && object == _webView) { + // NSLog(@"网页加载进度 = %f",_webView.estimatedProgress); + self.progressView.progress = _webView.estimatedProgress; + if (_webView.estimatedProgress >= 1.0f) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.progressView.progress = 0; + }); + } + }else if ([keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))] + && object == _webView.scrollView) { + if (_webContentHeight == _webView.scrollView.contentSize.height) { + }else { + _webContentHeight = _webView.scrollView.contentSize.height; + } + } +} + +#pragma mark - WKNavigationDelegate +// 页面加载完成之后调用 +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + [self.frameArray removeAllObjects]; + //根据服务器下发的标签相关的数据,用原生组件展示,这里原生组件的创建要注意按需加载和复用,类似于tableView,否则对内存还是有不小的消耗的。 + int i = 0; + SL_WeakSelf; + for (SLWebNativeModel *model in self.dataSource) { + NSString *jsString = [NSString stringWithFormat:@"getElementFrame('%@',%f, %f)",model.tagID,model.width,model.height]; + [_webView evaluateJavaScript:jsString completionHandler:^(id _Nullable data, NSError * _Nullable error) { + dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); + //获取标签位置坐标 + NSDictionary *frameDict = (NSDictionary *)data; + CGRect frame = CGRectMake( + [frameDict[@"x"] floatValue], [frameDict[@"y"] floatValue], [frameDict[@"width"] floatValue], [frameDict[@"height"] floatValue]); + if(!CGRectEqualToRect(frame, CGRectZero)) { + [weakSelf.frameArray addObject:[NSValue valueWithCGRect:frame]]; + } + dispatch_semaphore_signal(weakSelf.semaphore); + if (i == weakSelf.dataSource.count - 1) { + [weakSelf.reusableManager reloadData]; + } + }]; + i++; + } +} + +#pragma mark - SLReusableDataSource +- (NSInteger)numberOfRowsInReusableManager:(SLReusableManager *)reusableManager { + return self.frameArray.count; +} +- (CGRect)reusableManager:(SLReusableManager *)reusableManager frameForRowAtIndex:(NSInteger)index { + CGRect rect = [self.frameArray[index] CGRectValue]; + return rect; +} +- (SLReusableCell *)reusableManager:(SLReusableManager *)reusableManager cellForRowAtIndex:(NSInteger)index { + SLWebNativeCell *cell = (SLWebNativeCell *)[reusableManager dequeueReusableCellWithIdentifier:@"cellID" index:index]; + SLWebNativeModel *model = self.dataSource[index]; + cell.imageViewSize = [self.frameArray[index] CGRectValue].size; + [cell updateDataWith:model]; + return cell; +} + +#pragma mark - SLReusableDelegate +- (void)reusableManager:(SLReusableManager *)reusableManager didSelectRowAtIndex:(NSInteger)index { + SLWebNativeModel *model = self.dataSource[index]; + if ([model.type isEqualToString:@"image"]) { + //图片 + NSLog(@"点击了 %ld 图片", index); + SLPictureBrowseController *pictureBrowseController = [[SLPictureBrowseController alloc] init]; + pictureBrowseController.imagesArray = [NSMutableArray arrayWithArray:@[[NSURL URLWithString:model.imgUrl]]]; + pictureBrowseController.indexPath = [NSIndexPath indexPathForRow:index inSection:0]; + [self presentViewController:pictureBrowseController animated:YES completion:nil]; + + }else if ([model.type isEqualToString:@"video"]) { + //视频 + NSLog(@"点击了 %ld 视频", index); + }else if ([model.type isEqualToString:@"audio"]) { + //音频 + NSLog(@"点击了 %ld 音频", index); + } +} + +#pragma mark - SLPictureAnimationViewDelegate +//用于转场的动画视图 +- (UIView *)animationViewOfPictureTransition:(NSIndexPath *)indexPath { + SLWebNativeCell *imageCell = (SLWebNativeCell *)[self.reusableManager cellForRowAtIndex:indexPath.row]; + UIImageView *tempView = [UIImageView new]; + tempView.image = imageCell.imageView.image; + tempView.layer.contentsRect = imageCell.imageView.layer.contentsRect; + tempView.frame = [imageCell.imageView convertRect:imageCell.imageView.bounds toView:self.view]; + return tempView; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebNative/WebNative.html b/iOS_Tips/DarkMode/WKWebView/WebNative/WebNative.html new file mode 100755 index 00000000..769701bb --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebNative/WebNative.html @@ -0,0 +1,117 @@ + + + + + + WKWebView + NativeView + + + + + + + +
+ +

Welcome To You

+

wsl ~且行且珍惜~ ls

+

+ +

简书

+

Github

+

微信公众号:iOS2679114653

+

QQ群:835303405

+

微博@且行且珍惜_iOS

+

掘金

+ +
+
原生组件UIImageView
+
+
+
原生组件UIImageView
+
+
+
原生组件UIImageView
+
+
+
原生组件UIImageView
+
+
+
原生组件UIImageView
+
+ +
+
原生组件UIImageView
+
+
+
原生组件UIImageView
+
+
+
原生组件UIImageView
+
+
+
原生组件UIImageView
+
+
+
原生组件UIImageView
+
+
+
原生组件UIImageView
+
+
+
原生组件UIImageView
+
+
+
原生组件AVPlayer播放视频
+
+
+ +
原生组件AVPlayer播放音频
+
+ +

以上图片来源于网络

+
+ + + + + + + diff --git a/iOS_Tips/DarkMode/WKWebView/WebNative/WebNativeJson.txt b/iOS_Tips/DarkMode/WKWebView/WebNative/WebNativeJson.txt new file mode 100644 index 00000000..131bd25d --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebNative/WebNativeJson.txt @@ -0,0 +1,106 @@ +{ + "dataList": [ + { + "tagID": "image1", + "type": "image", + "imgUrl": "http://b-ssl.duitang.com/uploads/item/201507/13/20150713182820_5mHce.jpeg", + "width": "320", + "height": "240" + }, + { + "tagID": "image2", + "type": "image", + "imgUrl": "http://b-ssl.duitang.com/uploads/blog/201401/08/20140108182739_kFHxN.thumb.224_0.gif", + "width": "320", + "height": "240" + }, + { + "tagID": "image3", + "type": "image", + "imgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590574073707&di=4c0752ac8777b9e30f85756189ce1c7b&imgtype=0&src=http%3A%2F%2F00imgmini.eastday.com%2Fmobile%2F20181228%2F20181228085224_897c56449dc204f4443d150207f1220f_3.jpeg", + "width": "320", + "height": "240" + }, + { + "tagID": "image4", + "type": "image", + "imgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590574213364&di=c04886803e0c0849be4bcfbb4fa5ea57&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20180328%2F364a77badc2c4aed8ab382e62651110d.gif", + "width": "320", + "height": "240" + }, + { + "tagID": "image5", + "type": "image", + "imgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590574356101&di=29711fb3808a53a67ba9ee0139000ff3&imgtype=0&src=http%3A%2F%2F01.minipic.eastday.com%2F20170625%2F20170625040114_2a27ceccee9fe4e60757d1c22a9f10f4_4.jpeg", + "width": "320", + "height": "240" + }, + { + "tagID": "image6", + "type": "image", + "imgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590574444194&di=3f1d5730bc757a866ebba2aef6248367&imgtype=0&src=http%3A%2F%2Fhiphotos.baidu.com%2F_pennsylvania%2Fpic%2Fitem%2Ff939292bf63db47f1f3089ad.jpg", + "width": "320", + "height": "300" + }, + { + "tagID": "image7", + "type": "image", + "imgUrl": "http://b-ssl.duitang.com/uploads/item/201208/10/20120810150440_NSrXL.gif", + "width": "320", + "height": "240" + }, + { + "tagID": "image8", + "type": "image", + "imgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1590574473190&di=a6d3a0e1cf189e5222d4fb8d21ee0475&imgtype=0&src=http%3A%2F%2Fupload.mnw.cn%2F2016%2F0414%2F1460606040792.jpg", + "width": "320", + "height": "320" + }, + { + "tagID": "image9", + "type": "image", + "imgUrl": "http://img.mp.itc.cn/upload/20170427/b78c6b85d3a1491fa6f5d3b671429d99_th.gif", + "width": "320", + "height": "320" + }, + { + "tagID": "image10", + "type": "image", + "imgUrl": "http://b-ssl.duitang.com/uploads/blog/201401/08/20140108182739_kFHxN.gif", + "width": "320", + "height": "320" + }, + { + "tagID": "image11", + "type": "image", + "imgUrl": "http://b-ssl.duitang.com/uploads/item/201601/15/20160115140217_HeJAm.jpeg", + "width": "320", + "height": "320" + }, + { + "tagID": "image12", + "type": "image", + "imgUrl": "http://www.3dmgame.com/uploads/allimg/180409/369_180409203945_1.jpg", + "width": "320", + "height": "320" + }, + { + "tagID": "video1", + "type": "video", + "imgUrl": "http://a.hiphotos.baidu.com/zhidao/pic/item/0d338744ebf81a4cd90f1d3ad72a6059252da600.jpg", + "videoUrl":"http://baobab.kaiyanapp.com/api/v1/playUrl?vid=39183&editionType=normal&source=qcloud", + "width": "320", + "height": "240" + }, + { + "tagID": "audio1", + "type": "audio", + "imgUrl": "http://a.hiphotos.baidu.com/zhidao/pic/item/0d338744ebf81a4cd90f1d3ad72a6059252da600.jpg", + "audioUrl": "播本地的音频", + "width": "100", + "height": "100" + } + ] +} + + diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView.html b/iOS_Tips/DarkMode/WKWebView/WebTableView.html new file mode 100755 index 00000000..f008ed48 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView.html @@ -0,0 +1,94 @@ + + + + + + WKWebView+UITableView + + + + + + + +
+ +

Welcome To You

+

wsl ~且行且珍惜~ ls

+

+ +

简书

+

Github

+

微信公众号:iOS2679114653

+

QQ群:835303405

+

微博@且行且珍惜_iOS

+

掘金

+ +
+
努力敲代码中~
+
+
+
路飞
+
+
+
路飞的迷妹~
+
+
+
24号传奇
+
+
+
美如画的投篮
+
+ +
+
+
+
+
+
+
+
告别科比
+
+
+ + +

+

+ +

以上图片来源于网络

+ + +
+ + + + + + diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLDynamicItem.h b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLDynamicItem.h new file mode 100644 index 00000000..555a8657 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLDynamicItem.h @@ -0,0 +1,20 @@ +// +// SLDynamicItem.h +// DarkMode +// +// Created by wsl on 2020/5/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///动力元素 力的作用对象 +@interface SLDynamicItem : NSObject +@property (nonatomic, readwrite) CGPoint center; +@property (nonatomic, readonly) CGRect bounds; +@property (nonatomic, readwrite) CGAffineTransform transform; +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLDynamicItem.m b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLDynamicItem.m new file mode 100644 index 00000000..7539d968 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLDynamicItem.m @@ -0,0 +1,19 @@ +// +// SLDynamicItem.m +// DarkMode +// +// Created by wsl on 2020/5/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLDynamicItem.h" + +@implementation SLDynamicItem +- (instancetype)init { + self = [super init]; + if (self) { + _bounds = CGRectMake(0, 0, 1, 1); + } + return self; +} +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLScrollViewController.h b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLScrollViewController.h new file mode 100644 index 00000000..986e876f --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLScrollViewController.h @@ -0,0 +1,18 @@ +// +// SLScrollViewController.h +// DarkMode +// +// Created by wsl on 2020/5/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///继承于UIView,自定义实现UIScrollView的效果 +@interface SLScrollViewController : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLScrollViewController.m b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLScrollViewController.m new file mode 100644 index 00000000..84174e41 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLScrollViewController.m @@ -0,0 +1,250 @@ +// +// SLScrollViewController.m +// DarkMode +// +// Created by wsl on 2020/5/29. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLScrollViewController.h" +#import "SLDynamicItem.h" + +///继承于UIView, 自定义实现UIScrollView的效果 +@interface SLScrollView : UIView + +/// 内容大小 +@property (nonatomic, assign) CGSize contentSize; +/// 滑到顶部、底部最大弹性距离 默认88 +@property(nonatomic, assign) CGFloat maxBounceDistance; + + +/* UIKit 动力学/仿真物理学:https://blog.csdn.net/meiwenjie110/article/details/46771299 + iOS UIScrollView 动画的力学原理 https://mp.weixin.qq.com/s/5JSiTywD0r3_O7l2OxWZxw */ +/// 动力装置 启动力 +@property(nonatomic, strong) UIDynamicAnimator *dynamicAnimator; +/// 惯性力 手指滑动松开后,scrollView借助于惯性力,以手指松开时的初速度以及设置的resistance动力减速度运动,直至停止 +@property(nonatomic, weak) UIDynamicItemBehavior *inertialBehavior; +/// 吸附力 模拟UIScrollView滑到底部或顶部时的回弹效果 +@property(nonatomic, weak) UIAttachmentBehavior *bounceBehavior; + +@end +@implementation SLScrollView + +#pragma mark - Override +- (instancetype)init { + self = [super init]; + if (self) { + [self initScrollView]; + } + return self; +} +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self initScrollView]; + } + return self; +} +// 滚动中单击可以停止滚动 +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesBegan:touches withEvent:event]; + [self.dynamicAnimator removeAllBehaviors]; +} +- (void)initScrollView { + _maxBounceDistance = 100; + self.userInteractionEnabled = YES; + UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGestureRecognizer:)]; + panRecognizer.delegate = self; + [self addGestureRecognizer:panRecognizer]; +} + +#pragma mark - Help Methods +///是否滑到顶部 +- (BOOL)isTop { + if (self.bounds.origin.y <= 0) { + return YES; + }else { + return NO; + } +} +///是否滑达底部 +- (BOOL)isBottom { + if (fabs(self.bounds.origin.y) >= [self maxContentOffsetY]) { + return YES; + }else { + return NO; + } +} +/// 最大偏移量 +- (CGFloat)maxContentOffsetY { + return MAX(0, self.contentSize.height - self.sl_height); +} + +#pragma mark - Event Handles +/// 拖拽手势,模拟UIScrollView滑动 +- (void)handlePanGestureRecognizer:(UIPanGestureRecognizer *)recognizer { + switch (recognizer.state) { + case UIGestureRecognizerStateBegan: { + //开始拖动,移除之前所有的动力行为 + [self.dynamicAnimator removeAllBehaviors]; + } + break; + case UIGestureRecognizerStateChanged: { + CGPoint translation = [recognizer translationInView:self]; + [self scrollViewsSetContentOffsetY:translation.y]; + [recognizer setTranslation:CGPointZero inView:self]; + } + break; + case UIGestureRecognizerStateEnded: { + + // 这个是为了避免在拉到边缘时,以一个非常小的初速度松手不回弹的问题,惯性力的初始速度太小 + if (fabs([recognizer velocityInView:self].y) < 120) { + if ([self isTop]) { + //顶部回弹 + [self performBounceForScrollViewisAtTop:YES]; + } else if ([self isBottom]) { + //底部回弹 + [self performBounceForScrollViewisAtTop:NO]; + } + return; + } + + //动力元素 力的操作对象 + SLDynamicItem *item = [[SLDynamicItem alloc] init]; + item.center = CGPointZero; + __block CGFloat lastCenterY = 0; + UIDynamicItemBehavior *inertialBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[item]]; + //给item添加初始线速度 手指松开时的速度 + [inertialBehavior addLinearVelocity:CGPointMake(0, -[recognizer velocityInView:self].y) forItem:item]; + //减速度 无速度阻尼 + inertialBehavior.resistance = 2; + __weak typeof(self) weakSelf = self; + inertialBehavior.action = ^{ + //惯性力 移动的距离 + [weakSelf scrollViewsSetContentOffsetY:lastCenterY - item.center.y]; + lastCenterY = item.center.y; + }; + //注意,self.inertialBehavior 的修饰符是weak,惯性力结束停止之后,会释放inertialBehavior对象,self.inertialBehavior = nil + self.inertialBehavior = inertialBehavior; + [self.dynamicAnimator addBehavior:inertialBehavior]; + } + break; + default: + break; + } +} +/// 根据拖拽手势的拖拽距离,调整contentOffset +- (void)scrollViewsSetContentOffsetY:(CGFloat)deltaY { + CGRect bounds = self.bounds; + bounds.origin.y = self.bounds.origin.y - deltaY; + if (deltaY < 0) { //上滑 + if ([self isBottom]) { //滑到底部 回弹 + if (fabs(self.bounds.origin.y) <= self.maxBounceDistance + [self maxContentOffsetY]) { + bounds.origin.y = bounds.origin.y >= self.maxBounceDistance + [self maxContentOffsetY] ? self.maxBounceDistance + [self maxContentOffsetY] : bounds.origin.y; + self.bounds = bounds; + [self performBounceIfNeededForScrollViewisAtTop:NO]; + } + }else { + bounds.origin.y = bounds.origin.y > [self maxContentOffsetY] ? [self maxContentOffsetY] : bounds.origin.y; + self.bounds = bounds; + } + }else if (deltaY > 0) { //下滑 + if ([self isTop]) { //滑到顶部 回弹 + if (fabs(self.bounds.origin.y) <= self.maxBounceDistance) { + bounds.origin.y = bounds.origin.y < -_maxBounceDistance ? -_maxBounceDistance : bounds.origin.y; + self.bounds = bounds; + [self performBounceIfNeededForScrollViewisAtTop:YES]; + } + }else { + bounds.origin.y = bounds.origin.y < 0 ? 0 : bounds.origin.y; + self.bounds = bounds; + } + } +} +//两种回弹触发方式: +//1.惯性滚动到边缘处回弹 +- (void)performBounceIfNeededForScrollViewisAtTop:(BOOL)isTop { + if (self.inertialBehavior) { + [self performBounceForScrollViewisAtTop:isTop]; + } +} +//2.手指拉到边缘处回弹 +- (void)performBounceForScrollViewisAtTop:(BOOL)isTop { + if (!self.bounceBehavior) { + //移除惯性力 + [self.dynamicAnimator removeBehavior:self.inertialBehavior]; + + //吸附力操作元素 + SLDynamicItem *item = [[SLDynamicItem alloc] init]; + item.center = self.bounds.origin; + //吸附力的锚点Y + CGFloat attachedToAnchorY = 0; + attachedToAnchorY = isTop ? 0 : [self maxContentOffsetY]; + + //吸附力 + UIAttachmentBehavior *bounceBehavior = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:CGPointMake(0, attachedToAnchorY)]; + //吸附点间的距离 + bounceBehavior.length = 0; + //阻尼/缓冲 + bounceBehavior.damping = 1; + //频率 + bounceBehavior.frequency = 2; + bounceBehavior.action = ^{ + CGRect bounds = self.bounds; + bounds.origin.y = item.center.y; + self.bounds = bounds; + }; + self.bounceBehavior = bounceBehavior; + [self.dynamicAnimator addBehavior:bounceBehavior]; + } +} + +#pragma mark - Getter +- (UIDynamicAnimator *)dynamicAnimator { + if (!_dynamicAnimator) { + _dynamicAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self]; + _dynamicAnimator.delegate = self; + } + return _dynamicAnimator; +} + +#pragma mark - UIDynamicAnimatorDelegate +//动力装置即将启动 +- (void)dynamicAnimatorWillResume:(UIDynamicAnimator *)animator { + +} +//动力装置暂停 +- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator { + +} +#pragma mark - UIGestureRecognizerDelegate +// 避免影响横滑手势 +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self]; + return fabs(velocity.y) > fabs(velocity.x); +} +@end + + +@interface SLScrollViewController () +@end + +@implementation SLScrollViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + SLScrollView *scrollView = [[SLScrollView alloc] initWithFrame:self.view.bounds]; + scrollView.backgroundColor = [UIColor whiteColor]; + scrollView.contentSize = CGSizeMake(self.view.sl_width, 20*100); + for (int i = 0 ; i < 20; i++) { + UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 100 *i, self.view.sl_width, 100)]; + label.backgroundColor = SL_UIColorFromRandomColor; + label.numberOfLines = 2; + label.text = [NSString stringWithFormat:@"%d、 继承于UIView,自定义实现UIScrollView的效果",i]; + label.textAlignment = NSTextAlignmentCenter; + [scrollView addSubview:label]; + } + [self.view addSubview:scrollView]; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController.h b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController.h new file mode 100644 index 00000000..b64bf137 --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController.h @@ -0,0 +1,22 @@ +// +// SLWebTableViewController.h +// DarkMode +// +// Created by wsl on 2020/5/22. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/* + WKWebView + UITableView + 方案1:WebView作为TableView的Header, 撑开webView,显示渲染全部内容,当内容过多时,比如大量图片时,容易造成内存暴涨(不建议使用) + 参考: https://www.jianshu.com/p/42858f95ab43、https://dequan1331.github.io/hybrid-page-kit.html、https://www.jianshu.com/p/3721d736cf68 + */ +@interface SLWebTableViewController : SLViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController.m b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController.m new file mode 100644 index 00000000..aa0f1aae --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController.m @@ -0,0 +1,179 @@ +// +// SLWebTableViewController.m +// DarkMode +// +// Created by wsl on 2020/5/22. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLWebTableViewController.h" +#import + +@interface SLWebTableViewController () + +@property (nonatomic, strong) WKWebView * webView; +@property (nonatomic, strong) UITableView *tableView; +///网页加载进度视图 +@property (nonatomic, strong) UIProgressView * progressView; +/// WKWebView 内容的高度 默认屏幕高 +@property (nonatomic, assign) CGFloat webContentHeight; + +@end + +@implementation SLWebTableViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUi]; + [self addKVO]; +} +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self.progressView removeFromSuperview]; +} +- (void)dealloc { + [self removeKVO]; +} + +#pragma mark - SetupUI +- (void)setupUi { + self.title = @"WKWebView+UITableView(方案1)"; + self.view.backgroundColor = [UIColor whiteColor]; + [self.view addSubview:self.tableView]; + [self configureWebTable]; +} +- (void)configureWebTable { + self.webView.sl_height = _webContentHeight == 0 ? SL_kScreenHeight : _webContentHeight; + self.tableView.tableHeaderView = self.webView; +} + +#pragma mark - Getter +- (UITableView *)tableView { + if (_tableView == nil) { + _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, SL_kScreenHeight) style:UITableViewStyleGrouped]; + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.estimatedRowHeight = 1; + if (@available(iOS 11.0, *)) { + _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + self.automaticallyAdjustsScrollViewInsets = NO; + } + } + return _tableView; +} +- (WKWebView *)webView { + if(_webView == nil){ + //创建网页配置 + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, SL_kScreenHeight) configuration:config]; + if (@available(iOS 11.0, *)) { + _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + self.automaticallyAdjustsScrollViewInsets = NO; + } + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebTableView.html" ofType:nil]; + NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]]; + } + return _webView; +} +- (UIProgressView *)progressView { + if (!_progressView){ + _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, 2)]; + _progressView.tintColor = [UIColor blueColor]; + _progressView.trackTintColor = [UIColor clearColor]; + } + if (_progressView.superview == nil) { + [self.navigationController.navigationBar addSubview:_progressView]; + } + return _progressView; +} + +#pragma mark - KVO +///添加键值对监听 +- (void)addKVO { + //监听网页加载进度 + [self.webView addObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) + options:NSKeyValueObservingOptionNew + context:nil]; + //监听网页内容高度 + [self.webView.scrollView addObserver:self + forKeyPath:@"contentSize" + options:NSKeyValueObservingOptionNew + context:nil]; +} +///移除监听 +- (void)removeKVO { + //移除观察者 + [_webView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress))]; + [_webView.scrollView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(contentSize))]; +} +//kvo监听 必须实现此方法 +-(void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context{ + + if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] + && object == _webView) { + // NSLog(@"网页加载进度 = %f",_webView.estimatedProgress); + self.progressView.progress = _webView.estimatedProgress; + if (_webView.estimatedProgress >= 1.0f) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.progressView.progress = 0; + }); + } + }else if ([keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))] + && object == _webView.scrollView && _webContentHeight != _webView.scrollView.contentSize.height) { + _webContentHeight = _webView.scrollView.contentSize.height; + [self configureWebTable]; + NSLog(@"WebViewContentSize = %@",NSStringFromCGSize(_webView.scrollView.contentSize)) + } +} + +#pragma mark - UIScrollViewDelegate +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { +} +#pragma mark - UITableViewDelegate,UITableViewDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return 20; +} +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 44; +} +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + return 0.1; +} +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{ + return nil; +} +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + UILabel *label = [UILabel new]; + label.text = @"评论"; + label.textColor = UIColor.whiteColor; + label.textAlignment = NSTextAlignmentCenter; + label.backgroundColor = [UIColor orangeColor]; + return label; +} +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellId"]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellId"]; + } + cell.detailTextLabel.numberOfLines = 0; + cell.textLabel.text = [NSString stringWithFormat:@"第%ld条评论",(long)indexPath.row]; + cell.detailTextLabel.text = @"方案1:WebView作为TableView的Header, 撑开webView,显示渲染全部内容,当内容过多时,比如大量图片时,容易造成内存暴涨(不建议使用)"; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController2.h b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController2.h new file mode 100644 index 00000000..4517aa9f --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController2.h @@ -0,0 +1,23 @@ +// +// SLWebTableViewController2.h +// DarkMode +// +// Created by wsl on 2020/5/25. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import +#import "SLDynamicItem.h" + +NS_ASSUME_NONNULL_BEGIN + +/* + WKWebView + UITableView + 方案2:将tableView加到WKWebView.scrollView上, WKWebView加载的HTML最后留一个空白占位div,用于确定tableView的位置,在监听到webView.scrollView.contentSize变化后,不断调整tableView的位置,同时将该div的尺寸设置为tableView的尺寸。禁用tableView和webView.scrollVie的scrollEnabled = NO,通过添加pan手势,手动调整contentOffset。tableView的最大高度为屏幕高度,当内容不足一屏时,高度为内容高度。 + 参考: https://www.jianshu.com/p/42858f95ab43、https://dequan1331.github.io/hybrid-page-kit.html、https://www.jianshu.com/p/3721d736cf68 +*/ +@interface SLWebTableViewController2 : SLViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController2.m b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController2.m new file mode 100644 index 00000000..ebfadada --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController2.m @@ -0,0 +1,417 @@ +// +// SLWebTableViewController2.m +// DarkMode +// +// Created by wsl on 2020/5/25. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLWebTableViewController2.h" +#import +#import "SLDynamicItem.h" +#import "UIScrollView+SLCommon.h" + +@interface SLWebTableViewController2 () + +@property (nonatomic, strong) WKWebView * webView; +@property (nonatomic, strong) UITableView *tableView; +///网页加载进度视图 +@property (nonatomic, strong) UIProgressView * progressView; +/// WKWebView 内容的高度 +@property (nonatomic, assign) CGFloat webContentHeight; + +/// self.view拖拽手势 +@property(nonatomic, strong) UIPanGestureRecognizer *panRecognizer; +/// 顶部、底部最大弹性距离 +@property(nonatomic) CGFloat maxBounceDistance; + +/* UIKit 动力学/仿真物理学:https://blog.csdn.net/meiwenjie110/article/details/46771299 */ +/// 动力装置 启动力 +@property(nonatomic, strong) UIDynamicAnimator *dynamicAnimator; +/// 惯性力 手指滑动松开后,scrollView借助于惯性力,以手指松开时的初速度以及设置的resistance动力减速度运动,直至停止 +@property(nonatomic, weak) UIDynamicItemBehavior *inertialBehavior; +/// 吸附力 模拟UIScrollView滑到底部或顶部时的回弹效果 +@property(nonatomic, weak) UIAttachmentBehavior *bounceBehavior; + +@end + +@implementation SLWebTableViewController2 + +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUi]; + [self addKVO]; +} +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self.progressView removeFromSuperview]; +} +- (void)dealloc { + [self removeKVO]; +} +// 滚动中单击可以停止滚动 +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesBegan:touches withEvent:event]; + [self.dynamicAnimator removeAllBehaviors]; +} + +#pragma mark - SetupUI +- (void)setupUi { + self.title = @"WKWebView+UITableView(方案2)"; + self.view.backgroundColor = [UIColor whiteColor]; + + self.maxBounceDistance = 100; + [self.view addGestureRecognizer:self.panRecognizer]; + [self.view addSubview:self.webView]; +} + +#pragma mark - Getter +- (UITableView *)tableView { + if (_tableView == nil) { + _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, SL_kScreenHeight, SL_kScreenWidth, SL_kScreenHeight) style:UITableViewStyleGrouped]; + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.estimatedRowHeight = 1; + _tableView.scrollEnabled = NO; + if (@available(iOS 11.0, *)) { + _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + self.automaticallyAdjustsScrollViewInsets = NO; + } + } + return _tableView; +} +- (WKWebView *)webView { + if(_webView == nil){ + //创建网页配置 + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, SL_kScreenHeight) configuration:config]; + _webView.navigationDelegate = self; + _webView.scrollView.scrollEnabled = NO; + if (@available(iOS 11.0, *)) { + _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + self.automaticallyAdjustsScrollViewInsets = NO; + } + + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebTableView.html" ofType:nil]; + NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]]; + } + return _webView; +} +- (UIProgressView *)progressView { + if (!_progressView){ + _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, 2)]; + _progressView.tintColor = [UIColor blueColor]; + _progressView.trackTintColor = [UIColor clearColor]; + } + if (_progressView.superview == nil) { + [self.navigationController.navigationBar addSubview:_progressView]; + } + return _progressView; +} +- (UIPanGestureRecognizer *)panRecognizer { + if (!_panRecognizer) { + _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGestureRecognizer:)]; + _panRecognizer.delegate = self; + } + return _panRecognizer; +} + +- (UIDynamicAnimator *)dynamicAnimator { + if (!_dynamicAnimator) { + _dynamicAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; + _dynamicAnimator.delegate = self; + } + return _dynamicAnimator; +} + +#pragma mark - KVO +///添加键值对监听 +- (void)addKVO { + //监听网页加载进度 + [self.webView addObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) + options:NSKeyValueObservingOptionNew + context:nil]; + //监听网页内容高度 + [self.webView.scrollView addObserver:self + forKeyPath:@"contentSize" + options:NSKeyValueObservingOptionNew + context:nil]; + + //监听tableView内容高度 + [self.tableView addObserver:self + forKeyPath:@"contentSize" + options:NSKeyValueObservingOptionNew + context:nil]; +} +///移除监听 +- (void)removeKVO { + //移除观察者 + [_webView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress))]; + [_webView.scrollView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(contentSize))]; + [_tableView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(contentSize))]; +} +//kvo监听 必须实现此方法 +-(void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context{ + + if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] + && object == _webView) { + // NSLog(@"网页加载进度 = %f",_webView.estimatedProgress); + self.progressView.progress = _webView.estimatedProgress; + if (_webView.estimatedProgress >= 1.0f) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.progressView.progress = 0; + }); + } + }else if ([keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))] + && object == _webView.scrollView && _webContentHeight != _webView.scrollView.contentSize.height) { + _webContentHeight = _webView.scrollView.contentSize.height; + [self webViewContentSizeChanged]; + // NSLog(@"WebViewContentSize = %@",NSStringFromCGSize(_webView.scrollView.contentSize)) + }else if ([keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))] + && object == _tableView) { + [self webViewContentSizeChanged]; + } +} + +#pragma mark - EventsHandle +/// 拖拽手势,模拟UIScrollView滑动 +- (void)handlePanGestureRecognizer:(UIPanGestureRecognizer *)recognizer { + switch (recognizer.state) { + case UIGestureRecognizerStateBegan: { + //开始拖动,移除之前所有的动力行为 + [self.dynamicAnimator removeAllBehaviors]; + } + break; + case UIGestureRecognizerStateChanged: { + CGPoint translation = [recognizer translationInView:self.view]; + //拖动过程中调整scrollView.contentOffset + [self scrollViewsSetContentOffsetY:translation.y]; + [recognizer setTranslation:CGPointZero inView:self.view]; + } + break; + case UIGestureRecognizerStateEnded: { + // 这个if是为了避免在拉到边缘时,以一个非常小的初速度松手不回弹的问题 + if (fabs([recognizer velocityInView:self.view].y) < 120) { + if ([self.tableView sl_isTop] && + [self.webView.scrollView sl_isTop]) { + //顶部 + [self performBounceForScrollView:self.webView.scrollView isAtTop:YES]; + } else if ([self.tableView sl_isBottom] && + [self.webView.scrollView sl_isBottom]) { + //底部 + if (self.tableView.frame.size.height < self.view.sl_height) { //tableView不足一屏,webView bounce + [self performBounceForScrollView:self.webView.scrollView isAtTop:NO]; + } else { + [self performBounceForScrollView:self.tableView isAtTop:NO]; + } + } + return; + } + + //动力元素 力的操作对象 + SLDynamicItem *item = [[SLDynamicItem alloc] init]; + item.center = CGPointZero; + __block CGFloat lastCenterY = 0; + UIDynamicItemBehavior *inertialBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[item]]; + //给item添加初始线速度 手指松开时的速度 + [inertialBehavior addLinearVelocity:CGPointMake(0, -[recognizer velocityInView:self.view].y) forItem:item]; + //减速度 无速度阻尼 + inertialBehavior.resistance = 2; + __weak typeof(self) weakSelf = self; + inertialBehavior.action = ^{ + //惯性力 移动的距离 + [weakSelf scrollViewsSetContentOffsetY:lastCenterY - item.center.y]; + lastCenterY = item.center.y; + }; + //注意,self.inertialBehavior 的修饰符是weak,惯性力结束停止之后,会释放inertialBehavior对象,self.inertialBehavior = nil + self.inertialBehavior = inertialBehavior; + [self.dynamicAnimator addBehavior:inertialBehavior]; + } + break; + default: + break; + } +} + +#pragma mark - Help Methods +/// 根据拖拽手势在屏幕上的拖拽距离,调整scrollView.contentOffset +- (void)scrollViewsSetContentOffsetY:(CGFloat)deltaY { + if (deltaY < 0) { //上滑 + if ([self.webView.scrollView sl_isBottom]) { //webView已滑到底,此时应滑动tableView + if ([self.tableView sl_isBottom]) { //tableView也到底 + if (self.tableView.frame.size.height < self.view.sl_height) { //tableView不足一屏,webView bounce + self.tableView.contentOffset = CGPointMake(0, self.tableView.contentSize.height - self.tableView.frame.size.height); + CGFloat bounceDelta = MAX(0, (self.maxBounceDistance - fabs(self.webView.scrollView.contentOffset.y - self.webView.scrollView.sl_maxContentOffsetY)) / self.maxBounceDistance) * 0.5; + self.webView.scrollView.contentOffset = CGPointMake(0, self.webView.scrollView.contentOffset.y - deltaY * bounceDelta); + [self performBounceIfNeededForScrollView:self.webView.scrollView isAtTop:NO]; + } else { + CGFloat bounceDelta = MAX(0, (self.maxBounceDistance - fabs(self.tableView.contentOffset.y - self.tableView.sl_maxContentOffsetY)) / self.maxBounceDistance) * 0.5; + self.tableView.contentOffset = CGPointMake(0, self.tableView.contentOffset.y - deltaY * bounceDelta); + [self performBounceIfNeededForScrollView:self.tableView isAtTop:NO]; + } + } else { + self.tableView.contentOffset = CGPointMake(0, MIN(self.tableView.contentOffset.y - deltaY, [self.tableView sl_maxContentOffsetY])); + } + } else { + self.webView.scrollView.contentOffset = CGPointMake(0, MIN(self.webView.scrollView.contentOffset.y - deltaY, [self.webView.scrollView sl_maxContentOffsetY])); + } + } else if (deltaY > 0) { //下滑 + if ([self.tableView sl_isTop]) { //tableView滑到顶,此时应滑动webView + if ([self.webView.scrollView sl_isTop]) { //webView到顶 + CGFloat bounceDelta = MAX(0, (self.maxBounceDistance - fabs(self.webView.scrollView.contentOffset.y)) / self.maxBounceDistance) * 0.5; + self.webView.scrollView.contentOffset = CGPointMake(0, self.webView.scrollView.contentOffset.y - deltaY * bounceDelta); + [self performBounceIfNeededForScrollView:self.webView.scrollView isAtTop:YES]; + } else { + self.webView.scrollView.contentOffset = CGPointMake(0, MAX(self.webView.scrollView.contentOffset.y - deltaY, 0)); + } + } else { + self.tableView.contentOffset = CGPointMake(0, MAX(self.tableView.contentOffset.y - deltaY, 0)); + } + } +} + +//两种回弹触发方式: +//1.惯性滚动到边缘处回弹 +- (void)performBounceIfNeededForScrollView:(UIScrollView *)scrollView isAtTop:(BOOL)sl_isTop { + if (self.inertialBehavior) { + [self performBounceForScrollView:scrollView isAtTop:sl_isTop]; + } +} +//2.手指拉到边缘处回弹 +- (void)performBounceForScrollView:(UIScrollView *)scrollView isAtTop:(BOOL)sl_isTop { + if (!self.bounceBehavior) { + //移除惯性力 + [self.dynamicAnimator removeBehavior:self.inertialBehavior]; + + //吸附力操作元素 + SLDynamicItem *item = [[SLDynamicItem alloc] init]; + item.center = scrollView.contentOffset; + //吸附力的锚点Y + CGFloat attachedToAnchorY = 0; + if (scrollView == self.webView.scrollView) { + //顶部时吸附力的Y轴锚点是0 底部时的锚点是Y轴最大偏移量 + attachedToAnchorY = sl_isTop ? 0 : [self.webView.scrollView sl_maxContentOffsetY]; + } else { + attachedToAnchorY = [self.tableView sl_maxContentOffsetY]; + } + //吸附力 + UIAttachmentBehavior *bounceBehavior = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:CGPointMake(0, attachedToAnchorY)]; + //吸附点的距离 + bounceBehavior.length = 0; + //阻尼/缓冲 + bounceBehavior.damping = 1; + //频率 + bounceBehavior.frequency = 2; + bounceBehavior.action = ^{ + scrollView.contentOffset = CGPointMake(0, item.center.y); + }; + self.bounceBehavior = bounceBehavior; + [self.dynamicAnimator addBehavior:bounceBehavior]; + } +} + +//改变webView的占位Div标签的高度 以及tableView的位置 +- (void)webViewContentSizeChanged { + //调整占位Div高度 + NSString *jsString = [NSString stringWithFormat:@"changeHeight(%f)", self.tableView.frame.size.height]; + [_webView evaluateJavaScript:jsString completionHandler:^(id _Nullable data, NSError * _Nullable error) { + }]; + + //调整tableView位置 + CGRect frame = self.tableView.frame; + frame.origin.y = self.webView.scrollView.contentSize.height - self.tableView.frame.size.height; + frame.size.height = [self tableViewHeight]; + self.tableView.frame = frame; + + //如果webView的内容还没有滑到底部,tableView已经有滚动,调整tableView位置后滚回顶部 + if (self.webView.scrollView.contentOffset.y > [self separatorYBetweenArticleAndComment] && + self.webView.scrollView.contentOffset.y < [self.webView.scrollView sl_maxContentOffsetY] && + self.tableView.contentOffset.y > 0) { + [self.tableView sl_scrollToTopWithAnimated:NO]; + } +} +- (CGFloat)separatorYBetweenArticleAndComment { + return self.webView.scrollView.contentSize.height - self.tableView.frame.size.height - self.webView.scrollView.frame.size.height; +} + +/// tableView的最大高度为屏幕高度,当内容不足一屏时,高度为内容高度。 +- (CGFloat)tableViewHeight { + if(self.tableView.contentSize.height < SL_kScreenHeight) { + return self.tableView.contentSize.height; + } + return SL_kScreenHeight; +} + +#pragma mark - UIDynamicAnimatorDelegate +//动力装置即将启动 +- (void)dynamicAnimatorWillResume:(UIDynamicAnimator *)animator { + // 防止误触tableView的点击事件 + self.webView.userInteractionEnabled = NO; + self.tableView.userInteractionEnabled = NO; +} +//动力装置暂停 +- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator { + self.webView.userInteractionEnabled = YES; + self.tableView.userInteractionEnabled = YES; +} +#pragma mark - UIGestureRecognizerDelegate +// 避免影响横滑手势 +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self.view]; + return fabs(velocity.y) > fabs(velocity.x); +} + +#pragma mark - WKNavigationDelegate +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + [self.webView.scrollView addSubview:self.tableView]; +} + +#pragma mark - UITableViewDelegate,UITableViewDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return 10; +} +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 44; +} +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + UILabel *label = [UILabel new]; + label.text = @"评论"; + label.textColor = UIColor.whiteColor; + label.textAlignment = NSTextAlignmentCenter; + label.backgroundColor = [UIColor orangeColor]; + return label; +} +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + return 0.1; +} +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{ + return nil; +} +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellId"]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellId"]; + } + cell.detailTextLabel.numberOfLines = 0; + cell.textLabel.text = [NSString stringWithFormat:@"第%ld条评论",(long)indexPath.row]; + cell.detailTextLabel.text = @"方案2:将tableView加到WKWebView.scrollView上, WKWebView加载的HTML最后留一个空白占位div,用于确定tableView的位置; \n 在监听到webView.scrollView.contentSize变化后,不断调整tableView的位置,同时将该div的尺寸设置为tableView的尺寸。\n 禁用tableView和webView.scrollVie的scrollEnabled = NO,通过添加pan手势,手动调整contentOffset。tableView的最大高度为屏幕高度,当内容不足一屏时,高度为内容高度。"; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController3.h b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController3.h new file mode 100644 index 00000000..0deb903a --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController3.h @@ -0,0 +1,21 @@ +// +// SLWebTableViewController3.h +// DarkMode +// +// Created by wsl on 2020/5/27. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/* + WKWebView + UITableView + 方案3:(推荐) WKWebView作为TableView的Header, 但不撑开webView。禁用tableView和webView.scrollVie的scrollEnabled = NO,通过添加pan手势,手动调整contentOffset。WebView的最大高度为屏幕高度,当内容不足一屏时,高度为内容高度。和方案2类似,但是不需要插入占位Div。 +*/ +@interface SLWebTableViewController3 : SLViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController3.m b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController3.m new file mode 100644 index 00000000..9f120ddd --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController3.m @@ -0,0 +1,387 @@ +// +// SLWebTableViewController3.m +// DarkMode +// +// Created by wsl on 2020/5/27. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLWebTableViewController3.h" +#import +#import "SLDynamicItem.h" +#import "UIScrollView+SLCommon.h" + +@interface SLWebTableViewController3 () +{ + int _dataCount; //tableView的数据个数 +} + +@property (nonatomic, strong) WKWebView * webView; +@property (nonatomic, strong) UITableView *tableView; +///网页加载进度视图 +@property (nonatomic, strong) UIProgressView * progressView; +/// WKWebView 内容的高度 +@property (nonatomic, assign) CGFloat webContentHeight; + +/// self.view拖拽手势 +@property(nonatomic, strong) UIPanGestureRecognizer *panRecognizer; +/// 顶部、底部最大弹性距离 +@property(nonatomic) CGFloat maxBounceDistance; + +/* UIKit 动力学/仿真物理学:https://blog.csdn.net/meiwenjie110/article/details/46771299 */ +/// 动力装置 启动力 +@property(nonatomic, strong) UIDynamicAnimator *dynamicAnimator; +/// 惯性力 手指滑动松开后,scrollView借助于惯性力,以手指松开时的初速度以及设置的resistance动力减速度运动,直至停止 +@property(nonatomic, weak) UIDynamicItemBehavior *inertialBehavior; +/// 吸附力 模拟UIScrollView滑到底部或顶部时的回弹效果 +@property(nonatomic, weak) UIAttachmentBehavior *bounceBehavior; + +@end + +@implementation SLWebTableViewController3 + +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUi]; + [self addKVO]; +} +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self.progressView removeFromSuperview]; +} +- (void)dealloc { + [self removeKVO]; +} +// 滚动中单击可以停止滚动 +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesBegan:touches withEvent:event]; + [self.dynamicAnimator removeAllBehaviors]; +} + +#pragma mark - SetupUI +- (void)setupUi { + self.title = @"WKWebView+UITableView(方案3)"; + self.view.backgroundColor = [UIColor whiteColor]; + + self.maxBounceDistance = 100; + [self.view addGestureRecognizer:self.panRecognizer]; + [self.view addSubview:self.tableView]; + self.tableView.tableHeaderView = self.webView; +} + +#pragma mark - Getter +- (UITableView *)tableView { + if (_tableView == nil) { + _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, SL_kScreenHeight) style:UITableViewStyleGrouped]; + if (@available(iOS 11.0, *)) { + _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + self.automaticallyAdjustsScrollViewInsets = NO; + } + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.estimatedRowHeight = 1; + _tableView.scrollEnabled = NO; + } + return _tableView; +} +- (WKWebView *)webView { + if(_webView == nil){ + //创建网页配置 + WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; + _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, 1) configuration:config]; + _webView.navigationDelegate = self; + _webView.scrollView.scrollEnabled = NO; + if (@available(iOS 11.0, *)) { + _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + self.automaticallyAdjustsScrollViewInsets = NO; + } +// NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:SL_GithubUrl]]; +// [_webView loadRequest:request]; + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebTableView.html" ofType:nil]; + NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]]; + } + return _webView; +} +- (UIProgressView *)progressView { + if (!_progressView){ + _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, 2)]; + _progressView.tintColor = [UIColor blueColor]; + _progressView.trackTintColor = [UIColor clearColor]; + } + if (_progressView.superview == nil) { + [self.navigationController.navigationBar addSubview:_progressView]; + } + return _progressView; +} +- (UIPanGestureRecognizer *)panRecognizer { + if (!_panRecognizer) { + _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGestureRecognizer:)]; + _panRecognizer.delegate = self; + } + return _panRecognizer; +} + +- (UIDynamicAnimator *)dynamicAnimator { + if (!_dynamicAnimator) { + _dynamicAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; + _dynamicAnimator.delegate = self; + } + return _dynamicAnimator; +} + +#pragma mark - KVO +///添加键值对监听 +- (void)addKVO { + //监听网页加载进度 + [self.webView addObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) + options:NSKeyValueObservingOptionNew + context:nil]; + //监听网页内容高度 + [self.webView.scrollView addObserver:self + forKeyPath:@"contentSize" + options:NSKeyValueObservingOptionNew + context:nil]; +} +///移除监听 +- (void)removeKVO { + //移除观察者 + [_webView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress))]; + [_webView.scrollView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(contentSize))]; +} +//kvo监听 必须实现此方法 +-(void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context{ + + if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] + && object == _webView) { + // NSLog(@"网页加载进度 = %f",_webView.estimatedProgress); + self.progressView.progress = _webView.estimatedProgress; + if (_webView.estimatedProgress >= 1.0f) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.progressView.progress = 0; + }); + } + }else if ([keyPath isEqualToString:NSStringFromSelector(@selector(contentSize))] + && object == _webView.scrollView && _webContentHeight != _webView.scrollView.contentSize.height) { + _webContentHeight = _webView.scrollView.contentSize.height; + [self webViewContentSizeChanged]; + } +} + +#pragma mark - EventsHandle +/// 拖拽手势,模拟UIScrollView滑动 +- (void)handlePanGestureRecognizer:(UIPanGestureRecognizer *)recognizer { + switch (recognizer.state) { + case UIGestureRecognizerStateBegan: { + //开始拖动,移除之前所有的动力行为 + [self.dynamicAnimator removeAllBehaviors]; + } + break; + case UIGestureRecognizerStateChanged: { + CGPoint translation = [recognizer translationInView:self.view]; + //拖动过程中调整scrollView.contentOffset + [self scrollViewsSetContentOffsetY:translation.y]; + [recognizer setTranslation:CGPointZero inView:self.view]; + } + break; + case UIGestureRecognizerStateEnded: { + // 这个if是为了避免在拉到边缘时,以一个非常小的初速度松手不回弹的问题 + if (fabs([recognizer velocityInView:self.view].y) < 120) { + if ([self.tableView sl_isTop] && + [self.webView.scrollView sl_isTop]) { + //顶部 + [self performBounceForScrollView:self.webView.scrollView isAtTop:YES]; + } else if ([self.tableView sl_isBottom] && + [self.webView.scrollView sl_isBottom]) { + //底部 + if (self.tableView.frame.size.height < self.view.sl_height) { //tableView不足一屏,webView bounce + [self performBounceForScrollView:self.webView.scrollView isAtTop:NO]; + } else { + [self performBounceForScrollView:self.tableView isAtTop:NO]; + } + } + return; + } + + //动力元素 力的操作对象 + SLDynamicItem *item = [[SLDynamicItem alloc] init]; + item.center = CGPointZero; + __block CGFloat lastCenterY = 0; + UIDynamicItemBehavior *inertialBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[item]]; + //给item添加初始线速度 手指松开时的速度 + [inertialBehavior addLinearVelocity:CGPointMake(0, -[recognizer velocityInView:self.view].y) forItem:item]; + //减速度 无速度阻尼 + inertialBehavior.resistance = 2; + __weak typeof(self) weakSelf = self; + inertialBehavior.action = ^{ + //惯性力 移动的距离 + [weakSelf scrollViewsSetContentOffsetY:lastCenterY - item.center.y]; + lastCenterY = item.center.y; + }; + //注意,self.inertialBehavior 的修饰符是weak,惯性力结束停止之后,会释放inertialBehavior对象,self.inertialBehavior = nil + self.inertialBehavior = inertialBehavior; + [self.dynamicAnimator addBehavior:inertialBehavior]; + } + break; + default: + break; + } +} + +#pragma mark - Help Methods +/// 根据拖拽手势在屏幕上的拖拽距离,调整scrollView.contentOffset +- (void)scrollViewsSetContentOffsetY:(CGFloat)deltaY { + if (deltaY < 0) { //上滑 + if ([self.webView.scrollView sl_isBottom]) { //webView已滑到底,此时应滑动tableView + if ([self.tableView sl_isBottom]) { //tableView也到底 + CGFloat bounceDelta = MAX(0, (self.maxBounceDistance - fabs(self.tableView.contentOffset.y - self.tableView.sl_maxContentOffsetY)) / self.maxBounceDistance) * 0.5; + self.tableView.contentOffset = CGPointMake(0, self.tableView.contentOffset.y - deltaY * bounceDelta); + [self performBounceIfNeededForScrollView:self.tableView isAtTop:NO]; + } else { + self.tableView.contentOffset = CGPointMake(0, MIN(self.tableView.contentOffset.y - deltaY, [self.tableView sl_maxContentOffsetY])); + } + } else { + self.webView.scrollView.contentOffset = CGPointMake(0, MIN(self.webView.scrollView.contentOffset.y - deltaY, [self.webView.scrollView sl_maxContentOffsetY])); + + } + } else if (deltaY > 0) { //下滑 + if ([self.tableView sl_isTop]) { //tableView滑到顶,此时应滑动webView + if ([self.webView.scrollView sl_isTop]) { //webView到顶 + CGFloat bounceDelta = MAX(0, (self.maxBounceDistance - fabs(self.webView.scrollView.contentOffset.y)) / self.maxBounceDistance) * 0.5; + self.webView.scrollView.contentOffset = CGPointMake(0, self.webView.scrollView.contentOffset.y - bounceDelta * deltaY); + [self performBounceIfNeededForScrollView:self.webView.scrollView isAtTop:YES]; + } else { + //tableView内容到顶后,调整webView.contentOffset + self.webView.scrollView.contentOffset = CGPointMake(0, MAX(self.webView.scrollView.contentOffset.y - deltaY, 0)); + } + } else { + //tableView内容还未到顶,调整tableView.contentOffset + self.tableView.contentOffset = CGPointMake(0, MAX(self.tableView.contentOffset.y - deltaY, 0)); + } + } +} + +//两种回弹触发方式: +//1.惯性滚动到边缘处回弹 +- (void)performBounceIfNeededForScrollView:(UIScrollView *)scrollView isAtTop:(BOOL)sl_isTop { + if (self.inertialBehavior) { + [self performBounceForScrollView:scrollView isAtTop:sl_isTop]; + } +} +//2.手指拉到边缘处回弹 +- (void)performBounceForScrollView:(UIScrollView *)scrollView isAtTop:(BOOL)isTop { + if (!self.bounceBehavior) { + //移除惯性力 + [self.dynamicAnimator removeBehavior:self.inertialBehavior]; + + //吸附力操作元素 + SLDynamicItem *item = [[SLDynamicItem alloc] init]; + item.center = scrollView.contentOffset; + //吸附力的锚点Y + CGFloat attachedToAnchorY = 0; + if (scrollView == self.tableView) { + //顶部时吸附力的Y轴锚点是0 底部时的锚点是Y轴最大偏移量 + attachedToAnchorY = isTop ? 0 : [self.tableView sl_maxContentOffsetY]; + }else { + attachedToAnchorY = 0; + } + //吸附力 + UIAttachmentBehavior *bounceBehavior = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:CGPointMake(0, attachedToAnchorY)]; + //吸附点的距离 + bounceBehavior.length = 0; + //阻尼/缓冲 + bounceBehavior.damping = 1; + //频率 + bounceBehavior.frequency = 2; + bounceBehavior.action = ^{ + scrollView.contentOffset = CGPointMake(0, item.center.y); + }; + self.bounceBehavior = bounceBehavior; + [self.dynamicAnimator addBehavior:bounceBehavior]; + } +} + +//webViewContentSize发生了变化,需要针对变化做出调整 +- (void)webViewContentSizeChanged { + // webViewHeight的最大高度为屏幕高度,当内容contentSize不足一屏时,高度为内容contentSize高度。 + CGRect frame = self.webView.frame; + frame.size.height = self.webView.scrollView.contentSize.height < SL_kScreenHeight ? self.webView.scrollView.contentSize.height : SL_kScreenHeight; + self.webView.frame = frame; + self.tableView.tableHeaderView = self.webView; + //当WebView的contentSize改变时,tableView滚到顶部WebView,tableView展示内容向下移,更新展示区域 + [self.tableView sl_scrollToTopWithAnimated:NO]; +} + +#pragma mark - UIDynamicAnimatorDelegate +//动力装置即将启动 +- (void)dynamicAnimatorWillResume:(UIDynamicAnimator *)animator { + // 防止误触tableView的点击事件 + self.webView.userInteractionEnabled = NO; + self.tableView.userInteractionEnabled = NO; +} +//动力装置暂停 +- (void)dynamicAnimatorDidPause:(UIDynamicAnimator *)animator { + self.webView.userInteractionEnabled = YES; + self.tableView.userInteractionEnabled = YES; +} +#pragma mark - UIGestureRecognizerDelegate +// 避免影响横滑手势 +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self.view]; + return fabs(velocity.y) > fabs(velocity.x); +} + +#pragma mark - WKNavigationDelegate +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + //这里可以在webView加载完成之后,再刷新显示tableView的数据 + _dataCount = 10; + [self.tableView reloadData]; +} + +#pragma mark - UITableViewDelegate,UITableViewDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return _dataCount == 0 ? 0 : 1; +} +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return _dataCount; +} +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 44; +} +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + UILabel *label = [UILabel new]; + label.text = @"评论"; + label.textColor = UIColor.whiteColor; + label.textAlignment = NSTextAlignmentCenter; + label.backgroundColor = [UIColor orangeColor]; + return label; +} +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + return 0.1; +} +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{ + return nil; +} +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellId"]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellId"]; + } + cell.detailTextLabel.numberOfLines = 0; + cell.textLabel.text = [NSString stringWithFormat:@"第%ld条评论",(long)indexPath.row]; + cell.detailTextLabel.text = @"方案3:\n WKWebView作为TableView的Header, 但不撑开webView。\n 禁用tableView和webView.scrollVie的scrollEnabled = NO,通过添加pan手势,手动调整contentOffset。\n WebView的最大高度为屏幕高度,当内容不足一屏时,高度为内容高度。和方案2类似,但是不需要插入占位Div"; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController4.h b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController4.h new file mode 100644 index 00000000..a4a4c86a --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController4.h @@ -0,0 +1,21 @@ +// +// SLWebTableViewController4.h +// DarkMode +// +// Created by wsl on 2020/5/28. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/* + WKWebView + UITableView + 方案4:(推荐)[UIScrollView addSubView: WKWebView & UITableView]; UIScrollView.contenSize = WKWebView.contenSize + UITableView.contenSize; WKWebView和UITableView的最大高度为一屏高,并禁用scrollEnabled=NO,然后根据UIScrollView的滑动偏移量调整WKWebView和UITableView的展示区域contenOffset +*/ +@interface SLWebTableViewController4 : SLViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController4.m b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController4.m new file mode 100644 index 00000000..eaedc1da --- /dev/null +++ b/iOS_Tips/DarkMode/WKWebView/WebTableView/SLWebTableViewController4.m @@ -0,0 +1,244 @@ +// +// SLWebTableViewController4.m +// DarkMode +// +// Created by wsl on 2020/5/28. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLWebTableViewController4.h" +#import + +@interface SLWebTableViewController4 () +{ + CGFloat _webViewContentHeight; + CGFloat _tableViewContentHeight; + int _dataCount; //tableView的数据个数 +} + +///网页加载进度视图 +@property (nonatomic, strong) UIProgressView * progressView; + +@property (nonatomic, strong) WKWebView *webView; +@property (nonatomic, strong) UITableView *tableView; + +@property (nonatomic, strong) UIScrollView *containerScrollView; +@property (nonatomic, strong) UIView *contentView; + +@end + +@implementation SLWebTableViewController4 + +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + [self seupUI]; + [self addKVO]; +} +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + [self.progressView removeFromSuperview]; +} +- (void)dealloc { + [self removeKVO]; +} +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; +} + +#pragma mark - UI +- (void)seupUI{ + _webViewContentHeight = 0; + _tableViewContentHeight = 0; + self.view.backgroundColor = [UIColor whiteColor]; + self.title = @"WKWebView+UITableView(方案4)"; + + [self.view addSubview:self.containerScrollView]; + [self.containerScrollView addSubview:self.contentView]; + [self.contentView addSubview:self.webView]; + [self.contentView addSubview:self.tableView]; +} + +#pragma mark - Getter +- (WKWebView *)webView{ + if (_webView == nil) { + WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; + _webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration]; + _webView.scrollView.scrollEnabled = NO; + _webView.navigationDelegate = self; + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebTableView.html" ofType:nil]; + NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]]; + } + return _webView; +} +- (UITableView *)tableView{ + if (_tableView == nil) { + _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, self.view.sl_height, self.view.sl_width, self.view.sl_height) style:UITableViewStylePlain]; + _tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0); + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.scrollEnabled = NO; + } + return _tableView; +} +- (UIScrollView *)containerScrollView{ + if (_containerScrollView == nil) { + _containerScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; + _containerScrollView.delegate = self; + _containerScrollView.alwaysBounceVertical = YES; + } + return _containerScrollView; +} +- (UIView *)contentView{ + if (_contentView == nil) { + _contentView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, self.view.sl_width, self.view.sl_height * 2)]; + } + return _contentView; +} +- (UIProgressView *)progressView { + if (!_progressView){ + _progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 0, SL_kScreenWidth, 2)]; + _progressView.tintColor = [UIColor blueColor]; + _progressView.trackTintColor = [UIColor clearColor]; + } + if (_progressView.superview == nil) { + [self.navigationController.navigationBar addSubview:_progressView]; + } + return _progressView; +} + +#pragma mark - KVO +- (void)addKVO{ + [self.webView addObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) + options:NSKeyValueObservingOptionNew + context:nil]; + [self.webView addObserver:self forKeyPath:@"scrollView.contentSize" options:NSKeyValueObservingOptionNew context:nil]; + [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil]; +} +- (void)removeKVO{ + [_webView removeObserver:self + forKeyPath:NSStringFromSelector(@selector(estimatedProgress))]; + [self.webView removeObserver:self forKeyPath:@"scrollView.contentSize"]; + [self.tableView removeObserver:self forKeyPath:@"contentSize"]; +} +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ + if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] + && object == _webView) { + self.progressView.progress = _webView.estimatedProgress; + if (_webView.estimatedProgress >= 1.0f) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.progressView.progress = 0; + }); + } + }else if (object == _webView && [keyPath isEqualToString:@"scrollView.contentSize"] && _webViewContentHeight != _webView.scrollView.contentSize.height) { + _webViewContentHeight = _webView.scrollView.contentSize.height; + [self updateContainerScrollViewContentSize]; + }else if(object == _tableView && [keyPath isEqualToString:@"contentSize"] && _tableViewContentHeight != _tableView.contentSize.height ) { + _tableViewContentHeight = _tableView.contentSize.height; + [self updateContainerScrollViewContentSize]; + } +} + +#pragma mark - Help Methods +/// 根据WebView和tableView的ContentSize变化,调整父scrollView.contentSize、WebView和tableView的高度位置、展示区域 +- (void)updateContainerScrollViewContentSize{ + + self.containerScrollView.contentSize = CGSizeMake(self.view.sl_width, _webViewContentHeight + _tableViewContentHeight); + + //如果内容不满一屏,则webView、tableView高度为内容高,超过一屏则最大高为一屏高 + CGFloat webViewHeight = (_webViewContentHeight < self.view.sl_height) ? _webViewContentHeight : self.view.sl_height ; + CGFloat tableViewHeight = _tableViewContentHeight < self.view.sl_height ? _tableViewContentHeight : self.view.sl_height; + + self.contentView.sl_height = webViewHeight + tableViewHeight; + self.webView.sl_height = webViewHeight <= 0.1 ?0.1 :webViewHeight; + self.tableView.sl_height = tableViewHeight; + self.tableView.sl_y = self.webView.sl_height; + + //更新展示区域 + [self scrollViewDidScroll:self.containerScrollView]; +} + +#pragma mark - UIScrollViewDelegate +- (void)scrollViewDidScroll:(UIScrollView *)scrollView{ + if (_containerScrollView != scrollView) { + return; + } + + CGFloat offsetY = scrollView.contentOffset.y; + CGFloat webViewHeight = self.webView.sl_height; + CGFloat tableViewHeight = self.tableView.sl_height; + + if (offsetY <= 0) { + //顶部下拉 + self.contentView.sl_y = 0; + self.webView.scrollView.contentOffset = CGPointZero; + self.tableView.contentOffset = CGPointZero; + }else if(offsetY < _webViewContentHeight - webViewHeight){ + //父scrollView偏移量的展示范围在webView的最大偏移量内容区域 + //contentView相对位置保持不动,调整webView的contentOffset + self.contentView.sl_y = offsetY; + self.webView.scrollView.contentOffset = CGPointMake(0, offsetY); + self.tableView.contentOffset = CGPointZero; + }else if(offsetY < _webViewContentHeight){ + //webView滑到了底部 + self.contentView.sl_y = _webViewContentHeight - webViewHeight; + self.webView.scrollView.contentOffset = CGPointMake(0, _webViewContentHeight - webViewHeight); + self.tableView.contentOffset = CGPointZero; + }else if(offsetY < _webViewContentHeight + _tableViewContentHeight - tableViewHeight){ + //父scrollView偏移量的展示范围到达tableView的最大偏移量内容区域 + //调整tableView的contentOffset + self.contentView.sl_y = offsetY - webViewHeight; + self.tableView.contentOffset = CGPointMake(0, offsetY - _webViewContentHeight); + self.webView.scrollView.contentOffset = CGPointMake(0, _webViewContentHeight - webViewHeight); + }else if(offsetY <= _webViewContentHeight + _tableViewContentHeight ){ + //tableView滑到了底部 + self.contentView.sl_y = self.containerScrollView.contentSize.height - self.contentView.sl_height; + self.webView.scrollView.contentOffset = CGPointMake(0, _webViewContentHeight - webViewHeight); + self.tableView.contentOffset = CGPointMake(0, _tableViewContentHeight - tableViewHeight); + }else { + + } +} +#pragma mark - WKNavigationDelegate +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + //这里可以在webView加载完成之后,再刷新显示tableView的数据 + _dataCount = 15; + [self.tableView reloadData]; +} + +#pragma mark - UITableViewDelegate,UITableViewDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return _dataCount == 0 ? 0 : 1; +} +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return _dataCount; +} +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 44; +} +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + UILabel *label = [UILabel new]; + label.text = @"评论"; + label.textColor = UIColor.whiteColor; + label.textAlignment = NSTextAlignmentCenter; + label.backgroundColor = [UIColor orangeColor]; + return label; +} +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellId"]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cellId"]; + } + cell.detailTextLabel.numberOfLines = 0; + cell.textLabel.text = [NSString stringWithFormat:@"第%ld条评论",(long)indexPath.row]; + cell.detailTextLabel.text = @" 方案4:(推荐) \n [UIScrollView addSubView: WKWebView & UITableView]; \n UIScrollView.contenSize = WKWebView.contenSize + UITableView.contenSize; \n WKWebView和UITableView的最大高度为一屏高,并禁用scrollEnabled=NO,然后根据UIScrollView的滑动偏移量调整WKWebView和UITableView的展示区域contenOffset。"; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +@end diff --git a/iOS_Tips/DarkMode/WKWebView/wsl.png b/iOS_Tips/DarkMode/WKWebView/wsl.png new file mode 100644 index 00000000..c0faebb2 Binary files /dev/null and b/iOS_Tips/DarkMode/WKWebView/wsl.png differ diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/QiCallTraceCore.c b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/QiCallTraceCore.c new file mode 100644 index 00000000..25c2660b --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/QiCallTraceCore.c @@ -0,0 +1,464 @@ +// +// QiCallTraceCore.c +// Qi_ObjcMsgHook +// +// Created by liusiqi on 2019/11/20. +// Copyright © 2019 QiShare. All rights reserved. +// + +#include "QiCallTraceCore.h" + +#ifdef __aarch64__ + +#pragma mark - fishhook +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * A structure representing a particular intended rebinding from a symbol + * name to its replacement + */ +struct rebinding { + const char *name; + void *replacement; + void **replaced; +}; + +/* + * For each rebinding in rebindings, rebinds references to external, indirect + * symbols with the specified name to instead point at replacement for each + * image in the calling process as well as for all future images that are loaded + * by the process. If rebind_functions is called more than once, the symbols to + * rebind are added to the existing list of rebindings, and if a given symbol + * is rebound more than once, the later rebinding will take precedence. + */ +static int fish_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); + + +#ifdef __LP64__ +typedef struct mach_header_64 mach_header_t; +typedef struct segment_command_64 segment_command_t; +typedef struct section_64 section_t; +typedef struct nlist_64 nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 +#else +typedef struct mach_header mach_header_t; +typedef struct segment_command segment_command_t; +typedef struct section section_t; +typedef struct nlist nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT +#endif + +#ifndef SEG_DATA_CONST +#define SEG_DATA_CONST "__DATA_CONST" +#endif + +struct rebindings_entry { + struct rebinding *rebindings; + size_t rebindings_nel; + struct rebindings_entry *next; +}; + +static struct rebindings_entry *_rebindings_head; + +static int prepend_rebindings(struct rebindings_entry **rebindings_head, + struct rebinding rebindings[], + size_t nel) { + struct rebindings_entry *new_entry = malloc(sizeof(struct rebindings_entry)); + if (!new_entry) { + return -1; + } + new_entry->rebindings = malloc(sizeof(struct rebinding) * nel); + if (!new_entry->rebindings) { + free(new_entry); + return -1; + } + memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel); + new_entry->rebindings_nel = nel; + new_entry->next = *rebindings_head; + *rebindings_head = new_entry; + return 0; +} + +static void perform_rebinding_with_section(struct rebindings_entry *rebindings, + section_t *section, + intptr_t slide, + nlist_t *symtab, + char *strtab, + uint32_t *indirect_symtab) { + uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; + void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); + for (uint i = 0; i < section->size / sizeof(void *); i++) { + uint32_t symtab_index = indirect_symbol_indices[i]; + if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || + symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) { + continue; + } + uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; + char *symbol_name = strtab + strtab_offset; + if (strnlen(symbol_name, 2) < 2) { + continue; + } + struct rebindings_entry *cur = rebindings; + while (cur) { + for (uint j = 0; j < cur->rebindings_nel; j++) { + if (strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { + if (cur->rebindings[j].replaced != NULL && + indirect_symbol_bindings[i] != cur->rebindings[j].replacement) { + *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; + } + indirect_symbol_bindings[i] = cur->rebindings[j].replacement; + goto symbol_loop; + } + } + cur = cur->next; + } + symbol_loop:; + } +} + +static void rebind_symbols_for_image(struct rebindings_entry *rebindings, + const struct mach_header *header, + intptr_t slide) { + Dl_info info; + if (dladdr(header, &info) == 0) { + return; + } + + segment_command_t *cur_seg_cmd; + segment_command_t *linkedit_segment = NULL; + struct symtab_command* symtab_cmd = NULL; + struct dysymtab_command* dysymtab_cmd = NULL; + + uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { + linkedit_segment = cur_seg_cmd; + } + } else if (cur_seg_cmd->cmd == LC_SYMTAB) { + symtab_cmd = (struct symtab_command*)cur_seg_cmd; + } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { + dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; + } + } + + if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || + !dysymtab_cmd->nindirectsyms) { + return; + } + + // Find base symbol/string table addresses + uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; + nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); + char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); + + // Get indirect symbol table (array of uint32_t indices into symbol table) + uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); + + cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 && + strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) { + continue; + } + for (uint j = 0; j < cur_seg_cmd->nsects; j++) { + section_t *sect = + (section_t *)(cur + sizeof(segment_command_t)) + j; + if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { + perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); + } + if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { + perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); + } + } + } + } +} + +static void _rebind_symbols_for_image(const struct mach_header *header, + intptr_t slide) { + rebind_symbols_for_image(_rebindings_head, header, slide); +} + +static int fish_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) { + int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel); + if (retval < 0) { + return retval; + } + // If this was the first call, register callback for image additions (which is also invoked for + // existing images, otherwise, just run on existing images + //首先是遍历 dyld 里的所有的 image,取出 image header 和 slide。注意第一次调用时主要注册 callback + if (!_rebindings_head->next) { + _dyld_register_func_for_add_image(_rebind_symbols_for_image); + } else { + uint32_t c = _dyld_image_count(); + for (uint32_t i = 0; i < c; i++) { + _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); + } + } + return retval; +} + + +#pragma mark - Record + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool _call_record_enabled = true; +static uint64_t _min_time_cost = 1000; //us +static int _max_call_depth = 3; +static pthread_key_t _thread_key; +__unused static id (*orig_objc_msgSend)(id, SEL, ...); + +static qiCallRecord *_qiCallRecords; +//static int otp_record_num; +//static int otp_record_alloc; +static int _qiRecordNum; +static int _qiRecordAlloc; + +typedef struct { + id self; //通过 object_getClass 能够得到 Class 再通过 NSStringFromClass 能够得到类名 + Class cls; + SEL cmd; //通过 NSStringFromSelector 方法能够得到方法名 + uint64_t time; //us + uintptr_t lr; // link register +} thread_call_record; + +typedef struct { + thread_call_record *stack; + int allocated_length; + int index; + bool is_main_thread; +} thread_call_stack; + +static inline thread_call_stack * get_thread_call_stack() { + thread_call_stack *cs = (thread_call_stack *)pthread_getspecific(_thread_key); + if (cs == NULL) { + cs = (thread_call_stack *)malloc(sizeof(thread_call_stack)); + cs->stack = (thread_call_record *)calloc(128, sizeof(thread_call_record)); + cs->allocated_length = 64; + cs->index = -1; + cs->is_main_thread = pthread_main_np(); + pthread_setspecific(_thread_key, cs); + } + return cs; +} + +static void release_thread_call_stack(void *ptr) { + thread_call_stack *cs = (thread_call_stack *)ptr; + if (!cs) return; + if (cs->stack) free(cs->stack); + free(cs); +} + +static inline void push_call_record(id _self, Class _cls, SEL _cmd, uintptr_t lr) { + thread_call_stack *cs = get_thread_call_stack(); + if (cs) { + int nextIndex = (++cs->index); + if (nextIndex >= cs->allocated_length) { + cs->allocated_length += 64; + cs->stack = (thread_call_record *)realloc(cs->stack, cs->allocated_length * sizeof(thread_call_record)); + } + thread_call_record *newRecord = &cs->stack[nextIndex]; + newRecord->self = _self; + newRecord->cls = _cls; + newRecord->cmd = _cmd; + newRecord->lr = lr; + if (cs->is_main_thread && _call_record_enabled) { + struct timeval now; + gettimeofday(&now, NULL); + newRecord->time = (now.tv_sec % 100) * 1000000 + now.tv_usec; + } + } +} + +static inline uintptr_t pop_call_record() { + thread_call_stack *cs = get_thread_call_stack(); + int curIndex = cs->index; + int nextIndex = cs->index--; + thread_call_record *pRecord = &cs->stack[nextIndex]; + + if (cs->is_main_thread && _call_record_enabled) { + struct timeval now; + gettimeofday(&now, NULL); + uint64_t time = (now.tv_sec % 100) * 1000000 + now.tv_usec; + if (time < pRecord->time) { + time += 100 * 1000000; + } + uint64_t cost = time - pRecord->time; + if (cost > _min_time_cost && cs->index < _max_call_depth) { + if (!_qiCallRecords) { + _qiRecordAlloc = 1024; + _qiCallRecords = malloc(sizeof(qiCallRecord) * _qiRecordAlloc); + } + _qiRecordNum++; + if (_qiRecordNum >= _qiRecordAlloc) { + _qiRecordAlloc += 1024; + _qiCallRecords = realloc(_qiCallRecords, sizeof(qiCallRecord) * _qiRecordAlloc); + } + qiCallRecord *log = &_qiCallRecords[_qiRecordNum - 1]; + log->cls = pRecord->cls; + log->depth = curIndex; + log->sel = pRecord->cmd; + log->time = cost; + } + } + return pRecord->lr; +} + +void before_objc_msgSend(id self, SEL _cmd, uintptr_t lr) { + push_call_record(self, object_getClass(self), _cmd, lr); +} + +uintptr_t after_objc_msgSend() { + return pop_call_record(); +} + + +//replacement objc_msgSend (arm64) +// https://blog.nelhage.com/2010/10/amd64-and-va_arg/ +// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf +// https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html +#define call(b, value) \ +__asm volatile ("stp x8, x9, [sp, #-16]!\n"); \ +__asm volatile ("mov x12, %0\n" :: "r"(value)); \ +__asm volatile ("ldp x8, x9, [sp], #16\n"); \ +__asm volatile (#b " x12\n"); + +#define save() \ +__asm volatile ( \ +"stp x8, x9, [sp, #-16]!\n" \ +"stp x6, x7, [sp, #-16]!\n" \ +"stp x4, x5, [sp, #-16]!\n" \ +"stp x2, x3, [sp, #-16]!\n" \ +"stp x0, x1, [sp, #-16]!\n"); + +#define load() \ +__asm volatile ( \ +"ldp x0, x1, [sp], #16\n" \ +"ldp x2, x3, [sp], #16\n" \ +"ldp x4, x5, [sp], #16\n" \ +"ldp x6, x7, [sp], #16\n" \ +"ldp x8, x9, [sp], #16\n" ); + +#define link(b, value) \ +__asm volatile ("stp x8, lr, [sp, #-16]!\n"); \ +__asm volatile ("sub sp, sp, #16\n"); \ +call(b, value); \ +__asm volatile ("add sp, sp, #16\n"); \ +__asm volatile ("ldp x8, lr, [sp], #16\n"); + +#define ret() __asm volatile ("ret\n"); + +__attribute__((__naked__)) +static void hook_Objc_msgSend() { + // Save parameters. + save() + + __asm volatile ("mov x2, lr\n"); + __asm volatile ("mov x3, x4\n"); + + // Call our before_objc_msgSend. + call(blr, &before_objc_msgSend) + + // Load parameters. + load() + + // Call through to the original objc_msgSend. + call(blr, orig_objc_msgSend) + + // Save original objc_msgSend return value. + save() + + // Call our after_objc_msgSend. + call(blr, &after_objc_msgSend) + + // restore lr + __asm volatile ("mov lr, x0\n"); + + // Load original objc_msgSend return value. + load() + + // return + ret() +} + + +#pragma mark public + +void qiCallTraceStart() { + _call_record_enabled = true; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + pthread_key_create(&_thread_key, &release_thread_call_stack); + fish_rebind_symbols((struct rebinding[6]){ + {"objc_msgSend", (void *)hook_Objc_msgSend, (void **)&orig_objc_msgSend}, + }, 1); + }); +} + +void qiCallTraceStop() { + _call_record_enabled = false; +} + +void qiCallConfigMinTime(uint64_t us) { + _min_time_cost = us; +} +void qiCallConfigMaxDepth(int depth) { + _max_call_depth = depth; +} + +qiCallRecord *qiGetCallRecords(int *num) { + if (num) { + *num = _qiRecordNum; + } + return _qiCallRecords; +} + +void qiClearCallRecords() { + if (_qiCallRecords) { + free(_qiCallRecords); + _qiCallRecords = NULL; + } + _qiRecordNum = 0; +} + +#else + +void qiCallTraceStart() {} +void qiCallTraceStop() {} +void qiCallConfigMinTime(uint64_t us) { +} +void qiCallConfigMaxDepth(int depth) { +} +qiCallRecord *qiGetCallRecords(int *num) { + if (num) { + *num = 0; + } + return NULL; +} +void qiClearCallRecords() {} + +#endif diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/QiCallTraceCore.h b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/QiCallTraceCore.h new file mode 100644 index 00000000..cd47140c --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/QiCallTraceCore.h @@ -0,0 +1,36 @@ +// +// QiCallTraceCore.h +// Qi_ObjcMsgHook +// +// Created by liusiqi on 2019/11/20. +// Copyright © 2019 QiShare. All rights reserved. +// + +#ifndef QiCallTraceCore_h +#define QiCallTraceCore_h + +#include +#include + + +/* + 函数调用耗时监测 来源:https://www.jianshu.com/p/bc1c000afdba + */ + +typedef struct { + __unsafe_unretained Class cls; + SEL sel; + uint64_t time; // us (1/1000 ms) + int depth; +} qiCallRecord; + +extern void qiCallTraceStart(void); +extern void qiCallTraceStop(void); + +extern void qiCallConfigMinTime(uint64_t us); //default 1000 +extern void qiCallConfigMaxDepth(int depth); //default 3 + +extern qiCallRecord *qiGetCallRecords(int *num); +extern void qiClearCallRecords(void); + +#endif /* QiCallTraceCore_h */ diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMCpu.h b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMCpu.h new file mode 100644 index 00000000..8271561d --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMCpu.h @@ -0,0 +1,23 @@ +// +// SLAPMCpu.h +// DarkMode +// +// Created by wsl on 2020/7/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///CPU占有率监听 +@interface SLAPMCpu : NSObject +///返回GPU使用情况 占有率 ++ (double)getCpuUsage; +/// 返回CPU使用情况 占有率 +/// @param max 设定CPU使用率最大边界值 +/// @param callback 超出边界后的回调方法 返回此时的堆栈信息 ++ (double)getCpuUsageWithMax:(float)max outOfBoundsCallback:(void(^)(NSString *string))callback; +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMCpu.m b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMCpu.m new file mode 100644 index 00000000..37f7e51e --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMCpu.m @@ -0,0 +1,79 @@ +// +// SLAPMCpu.m +// DarkMode +// +// Created by wsl on 2020/7/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLAPMCpu.h" + +#include + +#import "BSBacktraceLogger.h" + +@implementation SLAPMCpu + +//struct thread_basic_info { +// time_value_t user_time; /* user run time(用户运行时长) */ +// time_value_t system_time; /* system run time(系统运行时长) */ +// integer_t cpu_usage; /* scaled cpu usage percentage(CPU使用率,上限1000) */ +// policy_t policy; /* scheduling policy in effect(有效调度策略) */ +// integer_t run_state; /* run state (运行状态,见下) */ +// integer_t flags; /* various flags (各种各样的标记) */ +// integer_t suspend_count; /* suspend count for thread(线程挂起次数) */ +// integer_t sleep_time; /* number of seconds that thread has been sleeping(休眠时间) */ +//}; +#pragma mark - CPU占有率 ++ (double)getCpuUsage { + kern_return_t kr; + thread_array_t threadList; // 保存当前Mach task的线程列表 + mach_msg_type_number_t threadCount; // 保存当前Mach task的线程个数 + thread_info_data_t threadInfo; // 保存单个线程的信息列表 + mach_msg_type_number_t threadInfoCount; // 保存当前线程的信息列表大小 + thread_basic_info_t threadBasicInfo; // 线程的基本信息 + + // 通过“task_threads”API调用获取指定 task 的线程列表 + // mach_task_self_,表示获取当前的 Mach task + kr = task_threads(mach_task_self(), &threadList, &threadCount); + if (kr != KERN_SUCCESS) { + return -1; + } + double cpuUsage = 0; + for (int i = 0; i < threadCount; i++) { + threadInfoCount = THREAD_INFO_MAX; + // 通过“thread_info”API调用来查询指定线程的信息 + // flavor参数传的是THREAD_BASIC_INFO,使用这个类型会返回线程的基本信息, + // 定义在 thread_basic_info_t 结构体,包含了用户和系统的运行时间、运行状态和调度优先级等 + kr = thread_info(threadList[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount); + if (kr != KERN_SUCCESS) { + return -1; + } + + threadBasicInfo = (thread_basic_info_t)threadInfo; + if (!(threadBasicInfo->flags & TH_FLAGS_IDLE)) { + cpuUsage += threadBasicInfo->cpu_usage; + } + } + + // 回收内存,防止内存泄漏 + vm_deallocate(mach_task_self(), (vm_offset_t)threadList, threadCount * sizeof(thread_t)); + + float cpu = cpuUsage / (double)TH_USAGE_SCALE * 100.0; + return cpu; +} + +/// 返回GPU使用情况 占有率 +/// @param max 设定GPU使用率最大边界值 +/// @param callback 超出边界后的回调方法 返回此时的堆栈信息 ++ (double)getCpuUsageWithMax:(float)max outOfBoundsCallback:(void(^)(NSString *string))callback { + float cpu= [SLAPMCpu getCpuUsage]; + if (cpu/100.0 >= max) { + NSString *callbackString = [BSBacktraceLogger bs_backtraceOfAllThread]; + callback(callbackString); + } + return cpu; +} + + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMFluency.h b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMFluency.h new file mode 100644 index 00000000..b130bb2b --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMFluency.h @@ -0,0 +1,43 @@ +// +// SLAPMFluency.h +// DarkMode +// +// Created by wsl on 2020/7/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +@class SLAPMFluency; +@protocol SLAPMFluencyDelegate +///卡顿监控回调 当callStack不为nil时,表示发生卡顿并捕捉到卡顿时的调用栈;type == SLAPMFluencyTypeRunloop时,fps为0 +- (void)APMFluency:(SLAPMFluency *)fluency didChangedFps:(float)fps callStackOfStuck:(nullable NSString *)callStack; +@end + +/// 卡顿监测策略/类型 +typedef NS_ENUM(NSInteger, SLAPMFluencyType) { + /* 建议 Runloop,不消耗额外的CPU资源,可以获取卡顿时的调用堆栈 */ + SLAPMFluencyTypeRunloop = 0, + /*FPS 无法获取卡顿时的调用堆栈,消耗CPU资源,不利于CPU使用率的监控,但可以作为衡量卡顿程度的指数*/ + SLAPMFluencyTypeFps = 1, + /*所有策略*/ + SLAPMFluencyTypeAll = 2 +}; + +///流畅度监听 是否卡顿 +@interface SLAPMFluency : NSObject + +@property (nonatomic, weak) id delegate; +///卡顿监测策略/类型 默认建议 SLAPMFluencyTypeRunloop +@property (nonatomic, assign) SLAPMFluencyType type; + ++ (instancetype)sharedInstance; +///开始监听 +- (void)startMonitorFluency; +///结束监听 +- (void)stopMonitorFluency; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMFluency.m b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMFluency.m new file mode 100644 index 00000000..1a112fed --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMFluency.m @@ -0,0 +1,300 @@ +// +// SLAPMFluency.m +// DarkMode +// +// Created by wsl on 2020/7/14. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLAPMFluency.h" +#import "SLProxy.h" + +#import "BSBacktraceLogger.h" + +/// 利用runloop 检测主线程每次执行消息循环的时间,当这一时间大于阈值时,就记为发生一次卡顿。 +@interface SLAPMRunLoop : NSObject +{ + int _timeoutCount; // 耗时次数 + CFRunLoopObserverRef _observer; // 观察者 + dispatch_semaphore_t _semaphore; // 信号 + CFRunLoopActivity _activity; // 状态 +} +///卡顿时的调用堆栈信息 +@property (nonatomic, copy) NSString *callStack; +@property (nonatomic, copy) void (^showStuckInfo)(NSString *callStack); +@end + +@implementation SLAPMRunLoop + +#pragma mark - Public +// 开始监听 +- (void)startRunning { + if (_observer) { + return; + } + + // 创建信号 + _semaphore = dispatch_semaphore_create(0); + NSLog(@"dispatch_semaphore_create:%@",getCurTime()); + + // 注册RunLoop状态观察 + CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; + //创建Run loop observer对象 + //第一个参数用于分配observer对象的内存 + //第二个参数用以设置observer所要关注的事件,详见回调函数myRunLoopObserver中注释 + //第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行 + //第四个参数用于设置该observer的优先级 + //第五个参数用于设置该observer的回调函数 + //第六个参数用于设置该observer的运行环境 + _observer = CFRunLoopObserverCreate(kCFAllocatorDefault, + kCFRunLoopAllActivities, + YES, + 0, + &runLoopObserverCallBack, + &context); + CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes); + + // 在子线程监控时长 + dispatch_async(dispatch_get_global_queue(0, 0), ^{ + while (YES) { // 有信号的话 就查询当前runloop的状态 + + // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms) + // 因为下面 runloop 状态改变回调方法runLoopObserverCallBack中会将信号量递增 1,所以每次 runloop 状态改变后,下面的语句都会执行一次 + // 当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒。当其返回不为0时,表示timeout发生。 + long st = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC)); + + if (st != 0) { // 信号量超时了 - 即 runloop 的状态长时间没有发生变更,长期处于某一个状态下 + if (!self->_observer) { + self->_timeoutCount = 0; + self->_semaphore = 0; + self->_activity = 0; + return; + } + + // kCFRunLoopBeforeSources - 即将处理source kCFRunLoopAfterWaiting - 刚从休眠中唤醒 + // 获取kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的状态就可以知道是否有卡顿的情况。 + // kCFRunLoopBeforeSources:停留在这个状态,表示在做很多事情 + if (self->_activity == kCFRunLoopBeforeSources || self->_activity == kCFRunLoopAfterWaiting) { // 发生卡顿,记录卡顿次数 + if (++self->_timeoutCount < 5) { + continue; // 不足 5 次,直接 continue 当次循环,不将timeoutCount置为0 + } + + // 收集此时卡顿的调用堆栈 + NSString *callStack = [BSBacktraceLogger bs_backtraceOfMainThread]; + self.callStack = callStack; + if(self.showStuckInfo) { + self.showStuckInfo(callStack); + } + // NSLog(@" 卡顿了 \n %@", callStack); + } + } + self->_timeoutCount = 0; + } + }); +} + +// 停止监听 +- (void)stopRunning { + if (!_observer) { + return; + } + // 移除观察并释放资源 + CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes); + CFRelease(_observer); + _observer = NULL; +} + +#pragma mark - runloop observer callback +///runloop状态改变回调 就记录一下 +static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { + + SLAPMRunLoop *monitor = (__bridge SLAPMRunLoop*)info; + // 记录状态值 + monitor->_activity = activity; + + // 发送信号 + dispatch_semaphore_t semaphore = monitor->_semaphore; + // 当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒) + long st = dispatch_semaphore_signal(semaphore); + // NSLog(@"dispatch_semaphore_signal:st=%ld,time:%@",st,getCurTime()); + + // if (activity == kCFRunLoopEntry) { + // NSLog(@"runLoopObserverCallBack - %@",@"即将进入RunLoop"); + // } else if (activity == kCFRunLoopBeforeTimers) { + // NSLog(@"runLoopObserverCallBack - %@",@"即将处理Timer"); + // } else if (activity == kCFRunLoopBeforeSources) { + // NSLog(@"runLoopObserverCallBack - %@",@"即将处理Source"); + // } else if (activity == kCFRunLoopBeforeWaiting) { + // NSLog(@"runLoopObserverCallBack - %@",@"即将进入休眠"); + // } else if (activity == kCFRunLoopAfterWaiting) { + // NSLog(@"runLoopObserverCallBack - %@",@"刚从休眠中唤醒"); + // } else if (activity == kCFRunLoopExit) { + // NSLog(@"runLoopObserverCallBack - %@",@"即将退出RunLoop"); + // } else if (activity == kCFRunLoopAllActivities) { + // NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAllActivities"); + // } +} + +#pragma mark - private function +NSString * getCurTime(void) { + NSDateFormatter *format = [[NSDateFormatter alloc] init]; + [format setDateFormat:@"YYYY/MM/dd hh:mm:ss:SSS"]; + NSString *curTime = [format stringFromDate:[NSDate date]]; + return curTime; +} + +@end + +///监测帧频,抖动比较大,无法获取卡顿时的调用栈,所以结合runloop检测主线程消息循环执行的时间来作为卡顿的衡量指标 +@interface SLAPMFps : NSObject +{ + NSTimeInterval _lastTime; //上次屏幕刷新时间 + int _count; //FPS +} +@property (nonatomic, strong) CADisplayLink *displayLink; +@property (nonatomic, assign) float fps; +@property (nonatomic, copy) void (^fpsChanged)(float fps); +@end +@implementation SLAPMFps + +#pragma mark - Public +///开始 +- (void)play { + [self.displayLink setPaused:NO]; +} +///暂停 +- (void)paused { + [self.displayLink setPaused:YES]; +} +///销毁 +- (void)invalidate { + [self paused]; + [self.displayLink invalidate]; + self.displayLink = nil; +} + +#pragma mark - Getter +- (CADisplayLink *)displayLink { + if (!_displayLink) { + _displayLink = [CADisplayLink displayLinkWithTarget:[SLProxy proxyWithTarget:self] selector:@selector(displayLinkTick:)]; + [_displayLink setPaused:YES]; + [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + } + return _displayLink; +} + +//这个方法的执行频率跟当前屏幕的刷新频率是一样的,屏幕每渲染刷新一次,就执行一次,那么1秒的时长执行刷新的次数就是当前的FPS值 +- (void)displayLinkTick:(CADisplayLink *)link{ + // duration 是只读的, 表示屏幕刷新的间隔 = 1/fps + // timestamp 是只读的, 表示上次屏幕渲染的时间点 + // frameInterval 是表示定时器被触发的间隔, 默认值是1, 就是表示跟屏幕的刷新频率一致。 + // NSLog(@"timestamp= %f duration= %f frameInterval= %f",link.timestamp, link.duration, frameInterval); + + //初始化屏幕渲染的时间 + if (_lastTime == 0) { + _lastTime = link.timestamp; + return; + } + //刷新次数累加 + _count++; + //刚刚屏幕渲染的时间与最开始幕渲染的时间差 + NSTimeInterval interval = link.timestamp - _lastTime; + if (interval < 1) { + //不足1秒,继续统计刷新次数 + return; + } + //刷新频率 + float fps = _count / interval; + _fps = fps; + + //1秒之后,初始化时间和次数,重新开始监测 + _lastTime = link.timestamp; + _count = 0; + + if (self.fpsChanged) { + self.fpsChanged(fps); + } +} +@end + + +@interface SLAPMFluency () +@property (nonatomic, strong) SLAPMFps *fps; +@property (nonatomic, strong) SLAPMRunLoop *runLoop; +@end + +@implementation SLAPMFluency +#pragma mark - Override +/// 重写allocWithZone方法,保证alloc或者init创建的实例不会产生新实例,因为该类覆盖了allocWithZone方法,所以只能通过其父类分配内存,即[super allocWithZone] ++ (instancetype)allocWithZone:(struct _NSZone *)zone { + return [self sharedInstance]; +} +/// 重写copyWithZone方法,保证复制返回的是同一份实例 +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + return [SLAPMFluency sharedInstance]; +} + +#pragma mark - Public ++ (instancetype)sharedInstance { + static SLAPMFluency *luency = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + luency = [[super allocWithZone:NULL] init]; + luency.type = SLAPMFluencyTypeRunloop; + }); + return luency; +} + +///开始监听 +- (void)startMonitorFluency { + __weak typeof(self) weakSelf = self; + if (self.type == SLAPMFluencyTypeRunloop) { + [self.runLoop startRunning]; + //防止跟 self.fps.fpsChanged的回调重复执行 + self.runLoop.showStuckInfo = ^(NSString *callStack) { + if ([weakSelf.delegate respondsToSelector:@selector(APMFluency:didChangedFps:callStackOfStuck:)]) { + [weakSelf.delegate APMFluency:weakSelf didChangedFps:weakSelf.fps.fps callStackOfStuck:callStack]; + } + }; + + }else if (self.type == SLAPMFluencyTypeFps) { + [self.fps play]; + self.fps.fpsChanged = ^(float fps) { + if ([weakSelf.delegate respondsToSelector:@selector(APMFluency:didChangedFps:callStackOfStuck:)]) { + [weakSelf.delegate APMFluency:weakSelf didChangedFps:fps callStackOfStuck:[weakSelf.runLoop.callStack copy]]; + weakSelf.runLoop.callStack = nil; + } + }; + }else if (self.type == SLAPMFluencyTypeAll) { + [self.fps play]; + [self.runLoop startRunning]; + self.fps.fpsChanged = ^(float fps) { + if ([weakSelf.delegate respondsToSelector:@selector(APMFluency:didChangedFps:callStackOfStuck:)]) { + [weakSelf.delegate APMFluency:weakSelf didChangedFps:fps callStackOfStuck:[weakSelf.runLoop.callStack copy]]; + weakSelf.runLoop.callStack = nil; + } + }; + } + +} +///结束监听 +- (void)stopMonitorFluency { + [self.fps paused]; + [self.runLoop stopRunning]; +} + +#pragma mark - Getter +- (SLAPMFps *)fps { + if (!_fps) { + _fps = [[SLAPMFps alloc] init]; + } + return _fps; +} +- (SLAPMRunLoop *)runLoop { + if (!_runLoop) { + _runLoop = [[SLAPMRunLoop alloc] init]; + } + return _runLoop;; +} + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMLoadTime.h b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMLoadTime.h new file mode 100644 index 00000000..335530cc --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMLoadTime.h @@ -0,0 +1,14 @@ +// +// SLAPMLoadTime.h +// DarkMode +// +// Created by wsl on 2020/8/4. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +///监测+load方法耗时 引自: https://www.jianshu.com/p/c14987eee107、https://www.cnblogs.com/feng9exe/p/12487662.html +@interface SLAPMLoadTime : NSObject + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMLoadTime.m b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMLoadTime.m new file mode 100644 index 00000000..020953ec --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMLoadTime.m @@ -0,0 +1,102 @@ +// +// SLAPMLoadTime.m +// DarkMode +// +// Created by wsl on 2020/8/4. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLAPMLoadTime.h" +#import +#import +#import +#include +#include +#include +#include + + +#define TIMESTAMP_NUMBER(interval) [NSNumber numberWithLongLong:interval*1000*1000] + +unsigned int count; +const char **classes; + +static NSMutableArray *_loadInfoArray; + +@implementation SLAPMLoadTime ++ (void)load { + + _loadInfoArray = [[NSMutableArray alloc] init]; + + CFAbsoluteTime time1 =CFAbsoluteTimeGetCurrent(); + +// [深入iOS系统底层之程序映像](https://www.jianshu.com/p/3b83193ff851) + + int imageCount = (int)_dyld_image_count(); + + for(int iImg = 0; iImg < imageCount; iImg++) { + + const char* path = _dyld_get_image_name((unsigned)iImg); + NSString *imagePath = [NSString stringWithUTF8String:path]; +// NSLog(@"映像 %@",imagePath); + + NSBundle* mainBundle = [NSBundle mainBundle]; + NSString* bundlePath = [mainBundle bundlePath]; + + if ([imagePath containsString:bundlePath] && ![imagePath containsString:@".dylib"]) { + classes = objc_copyClassNamesForImage(path, &count); + + for (int i = 0; i < count; i++) { + NSString *className = [NSString stringWithCString:classes[i] encoding:NSUTF8StringEncoding]; + if (![className isEqualToString:@""] && className) { + Class class = object_getClass(NSClassFromString(className)); + + SEL originalSelector = @selector(load); + SEL swizzledSelector = @selector(LDAPM_Load); + + Method originalMethod = class_getClassMethod(class, originalSelector); + Method swizzledMethod = class_getClassMethod([SLAPMLoadTime class], swizzledSelector); + + BOOL hasMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); + + if (!hasMethod) { + BOOL didAddMethod = class_addMethod(class, + swizzledSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)); + + if (didAddMethod) { + swizzledMethod = class_getClassMethod(class, swizzledSelector); + + method_exchangeImplementations(originalMethod, swizzledMethod); + } + } + + } + } + } + } + + CFAbsoluteTime time2 =CFAbsoluteTimeGetCurrent(); + + NSLog(@"Hook Time:%f",(time2 - time1) * 1000); +} + ++ (void)LDAPM_Load { + + CFAbsoluteTime start =CFAbsoluteTimeGetCurrent(); + + [self LDAPM_Load]; + + CFAbsoluteTime end =CFAbsoluteTimeGetCurrent(); + // 时间精度 us + NSDictionary *infoDic = @{@"st":TIMESTAMP_NUMBER(start), + @"et":TIMESTAMP_NUMBER(end), + @"name":NSStringFromClass([self class]) + }; + + [_loadInfoArray addObject:infoDic]; + NSLog(@"loadTime %@",infoDic.description); +} + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMManager.h b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMManager.h new file mode 100644 index 00000000..f4951887 --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMManager.h @@ -0,0 +1,53 @@ +// +// SLAPMManager.h +// DarkMode +// +// Created by wsl on 2020/7/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// app性能监控策略/类型 +typedef NS_ENUM(NSInteger, SLAPMType) { + /*无*/ + SLAPMTypeNone = 0, + /*CPU占用率*/ + SLAPMTypeCpu = 1 << 0, + /*内存使用情况*/ + SLAPMTypeMemory = 1 << 1, + /*流畅度、卡顿*/ + SLAPMTypeFluency = 1 << 2, + /*iOS Crash防护模块*/ + SLAPMTypeCrash = 1 << 3, + /*线程数量监控,防止线程爆炸*/ + SLAPMTypeThreadCount = 1 << 4, + /*网络监控*/ + SLAPMTypeNetwork = 1 << 5, + /*VC启动耗时监测*/ + SLAPMTypeVCTime = 1 << 6, + /*所有策略*/ + SLAPMTypeAll = 1 << 7 +}; + + +/// APM 管理者 +@interface SLAPMManager : NSObject + +///是否正在监控 +@property (nonatomic, assign) BOOL isMonitoring; +///app性能监控策略/类型 默认SLAPMTypeAll +@property (nonatomic, assign) SLAPMType type; + ++ (instancetype)manager; + +///开始监控 +- (void)startMonitoring; +///结束监控 +- (void)stopMonitoring; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMManager.m b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMManager.m new file mode 100644 index 00000000..3aacf02f --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMManager.m @@ -0,0 +1,117 @@ +// +// SLAPMManager.m +// DarkMode +// +// Created by wsl on 2020/7/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLAPMManager.h" +#import "SLTimer.h" + +#import "SLAPMCpu.h" +#import "SLAPMMemoryDisk.h" +#import "SLAPMFluency.h" +#import "SLCrashProtector.h" +#import "SLAPMThreadCount.h" +#import "SLAPMURLProtocol.h" + +@interface SLAPMManager () +///任务名称 +@property (nonatomic, copy) NSString *taskName; + +@end + +@implementation SLAPMManager + +#pragma mark - Override +/// 重写allocWithZone方法,保证alloc或者init创建的实例不会产生新实例,因为该类覆盖了allocWithZone方法,所以只能通过其父类分配内存,即[super allocWithZone] ++ (instancetype)allocWithZone:(struct _NSZone *)zone { + return [self manager]; +} +/// 重写copyWithZone方法,保证复制返回的是同一份实例 +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + return [SLAPMManager manager]; +} + +#pragma mark - Public ++ (instancetype)manager { + static SLAPMManager *manager = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + manager = [[super allocWithZone:NULL] init]; + manager.type = SLAPMTypeAll; + }); + return manager; +} +///开始监控 +- (void)startMonitoring { + if (_isMonitoring) return; + _isMonitoring = YES; + + if ((self.type & SLAPMTypeCpu) == SLAPMTypeCpu || (self.type & SLAPMTypeMemory) == SLAPMTypeMemory || self.type == SLAPMTypeAll) { + _taskName = [SLTimer execTask:self selector:@selector(monitoring) start:0.5 interval:1.0/60 repeats:YES async:YES]; + } + + if ((self.type & SLAPMTypeCpu) == SLAPMTypeCrash || self.type == SLAPMTypeAll) { + [SLCrashHandler defaultCrashHandler].delegate = self; + } + + if ((self.type & SLAPMTypeFluency) == SLAPMTypeFluency || self.type == SLAPMTypeAll) { + [SLAPMFluency sharedInstance].delegate = self; + [[SLAPMFluency sharedInstance] startMonitorFluency]; + } + + if ((self.type & SLAPMTypeThreadCount) == SLAPMTypeThreadCount || self.type == SLAPMTypeAll) { + [SLAPMThreadCount startMonitorThreadCount]; + } + + if ((self.type & SLAPMTypeNetwork) == SLAPMTypeNetwork || self.type == SLAPMTypeAll) { + [SLAPMURLProtocol startMonitorNetwork]; + } + +} +///结束监控 +- (void)stopMonitoring { + if (!_isMonitoring) return; + _isMonitoring = NO; + + [SLTimer cancelTask:_taskName]; + [SLAPMFluency sharedInstance].delegate = nil; + [[SLAPMFluency sharedInstance] stopMonitorFluency]; + [SLAPMThreadCount stopMonitorThreadCount]; + [SLAPMURLProtocol stopMonitorNetwork]; +} + +#pragma mark - Monitoring +///监控中 +- (void)monitoring { + + if ((self.type & SLAPMTypeCpu) == SLAPMTypeCpu || self.type == SLAPMTypeAll) { + float CPU = [SLAPMCpu getCpuUsage]; + NSLog(@" CPU使用率:%.2f%%",CPU); + } + + if ((self.type & SLAPMTypeMemory) == SLAPMTypeMemory || self.type == SLAPMTypeAll) { + double useMemory = [SLAPMMemoryDisk getAppUsageMemory]; + double freeMemory = [SLAPMMemoryDisk getFreeMemory]; + double totalMemory = [SLAPMMemoryDisk getTotalMemory]; + NSLog(@" Memory占用:%.1fM 空闲:%.1fM 总共:%.1fM",useMemory, freeMemory, totalMemory); + } + +} + +#pragma mark - Fluency/卡顿监测 +///卡顿监控回调 当callStack不为nil时,表示发生卡顿并捕捉到卡顿时的调用栈 +- (void)APMFluency:(SLAPMFluency *)fluency didChangedFps:(float)fps callStackOfStuck:(nullable NSString *)callStack { + NSLog(@" 卡顿监测  fps:%f \n %@", fps, callStack == nil ? @"流畅":[NSString stringWithFormat:@"卡住了 %@",callStack]); +} + +#pragma mark - SLCrashHandlerDelegate +///异常捕获回调 提供给外界实现自定义处理 ,日志上报等 +- (void)crashHandlerDidOutputCrashError:(SLCrashError *)crashError { + NSString *errorInfo = [NSString stringWithFormat:@" 错误描述:%@ \n 调用栈:%@" ,crashError.errorDesc, crashError.callStackSymbol]; + NSLog(@"%@",errorInfo); +} + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMMemoryDisk.h b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMMemoryDisk.h new file mode 100644 index 00000000..67f7f2bd --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMMemoryDisk.h @@ -0,0 +1,32 @@ +// +// SLAPMMemoryDisk.h +// DarkMode +// +// Created by wsl on 2020/7/18. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 内存磁盘容量监控 +@interface SLAPMMemoryDisk : NSObject + +///当前应用的内存占用情况,和Xcode数值相近 单位MB ++ (double)getAppUsageMemory; +///剩余空闲内存 单位MB ++ (double)getFreeMemory; +/// 总共的内存大小 单位MB ++ (double)getTotalMemory; + +///filePath目录下的文件 占用的磁盘大小 单位MB 默认沙盒Caches目录 此句代码不要频繁定时执行,比较耗内存 ++ (double)getFileUsageDisk:(NSString *)filePath; +///剩余空闲的磁盘容量 单位G ++ (double)getFreeDisk; +///总磁盘容量 单位G ++ (double)getTotalDisk; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMMemoryDisk.m b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMMemoryDisk.m new file mode 100644 index 00000000..f07432bb --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMMemoryDisk.m @@ -0,0 +1,81 @@ +// +// SLAPMMemoryDisk.m +// DarkMode +// +// Created by wsl on 2020/7/18. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLAPMMemoryDisk.h" +#include + +@implementation SLAPMMemoryDisk + + +#pragma mark - Memory / Disk +///当前应用的内存占用情况,和Xcode数值相近 单位MB ++ (double)getAppUsageMemory { + task_vm_info_data_t vmInfo; + mach_msg_type_number_t count = TASK_VM_INFO_COUNT; + if(task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count) == KERN_SUCCESS) { + return (double)vmInfo.phys_footprint / (1024 * 1024); + } else { + return -1.0; + } +} +///剩余空闲内存 单位MB ++ (double)getFreeMemory{ + mach_port_t host_port = mach_host_self(); + mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t); + vm_size_t page_size; + vm_statistics_data_t vm_stat; + kern_return_t kern; + + kern = host_page_size(host_port, &page_size); + if (kern != KERN_SUCCESS) return -1; + kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); + if (kern != KERN_SUCCESS) return -1; + return vm_stat.free_count * page_size / (1024 * 1024); +} +/// 总共的内存大小 单位MB ++ (double)getTotalMemory { + int64_t mem = [[NSProcessInfo processInfo] physicalMemory]; + if (mem < -1) mem = -1; + return mem / (1024 * 1024); +} + +///filePath目录下的文件 占用的磁盘大小 单位MB 默认沙盒Caches目录 ++ (double)getFileUsageDisk:(NSString *)filePath { + if (filePath.length == 0) filePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject; + ///定时执行时,此句代码会导致内存不断增长?0.1M 合理安排执行时机 + NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:filePath error:nil] ; + NSEnumerator *filesEnumerator = [filesArray objectEnumerator]; + filesArray = nil; + + NSString *fileName; + unsigned long long int fileSize = 0; + while (fileName = [filesEnumerator nextObject]) { + @autoreleasepool { + //单个文件大小 + NSDictionary *fileDic = [[NSFileManager defaultManager] attributesOfItemAtPath:[filePath stringByAppendingPathComponent:fileName] error:nil]; + fileSize += [fileDic fileSize]; + } + } + filesEnumerator = nil; + return fileSize / (1024*1024); +} +///剩余空闲的磁盘容量 单位G ++ (double)getFreeDisk { + NSDictionary *fattributes = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:nil]; + NSNumber *freeSize = [fattributes objectForKey:NSFileSystemFreeSize]; + return [freeSize integerValue] / (1024*1024*1024); +} +///总磁盘容量 单位G ++ (double)getTotalDisk { + NSDictionary *fattributes = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:nil]; + NSNumber *totalSize = [fattributes objectForKey:NSFileSystemSize]; + return [totalSize integerValue] / (1024*1024*1024); +} + + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMThreadCount.h b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMThreadCount.h new file mode 100644 index 00000000..a3739c22 --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMThreadCount.h @@ -0,0 +1,23 @@ +// +// SLAPMThreadCount.h +// DarkMode +// +// Created by wsl on 2020/7/23. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///监控线程数量 来源:https://juejin.im/post/5e92a113e51d4547134bdadb +@interface SLAPMThreadCount : NSObject + +///开始监听 ++ (void)startMonitorThreadCount; +///结束监听 ++ (void)stopMonitorThreadCount; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMThreadCount.m b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMThreadCount.m new file mode 100644 index 00000000..17ae5d8a --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMThreadCount.m @@ -0,0 +1,143 @@ +// +// SLAPMThreadCount.m +// DarkMode +// +// Created by wsl on 2020/7/23. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLAPMThreadCount.h" +#include +#include +#import "SLTimer.h" + +#import "BSBacktraceLogger.h" + +#ifndef kk_dispatch_main_async_safe +#define kk_dispatch_main_async_safe(block)\ +if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\ +block();\ +} else {\ +dispatch_async(dispatch_get_main_queue(), block);\ +} +#endif + +static pthread_introspection_hook_t old_pthread_introspection_hook_t = NULL; //hook前原来的函数指针 +static int threadCount = 0; //线程数量 +#define KK_THRESHOLD 40 //线程总数量阈值 +static const int threadIncreaseThreshold = 10; //线程1秒内的增量阈值 + +//线程数量超过40,就会弹窗警告,并且控制台打印所有线程的堆栈;之后阈值每增加5条(45、50、55...)同样警告+打印堆栈;如果线程数量再次少于40条,阈值恢复到40 +static int maxThreadCountThreshold = KK_THRESHOLD; +static dispatch_semaphore_t global_semaphore; //信号量 保证线程安全 +static int threadCountIncrease = 0; //线程增长数量 +static bool isMonitor = false; //是否正在监测 +static NSString *taskName; //任务ID + +@implementation SLAPMThreadCount + +///调用startMonitor函数,开始监控线程数量。在这个函数里用global_semaphore来保证,task_threads获取的线程数量,到hook完成,线程数量不会变化(加解锁之间,没有线程新建跟销毁)。 ++ (void)startMonitorThreadCount { + if (isMonitor) return; + global_semaphore = dispatch_semaphore_create(1); + dispatch_semaphore_wait(global_semaphore, DISPATCH_TIME_FOREVER); + mach_msg_type_number_t count; + thread_act_array_t threads; + //获取线程数量 + task_threads(mach_task_self(), &threads, &count); + //加解锁之间,保证线程的数量不变 + threadCount = count; + /* + 看这个函数名,很像我们平时hook函数一样的。 + 返回值是上面声明的old_pthread_introspection_hook_t函数指针:返回原线程生命周期函数。 + 参数也是函数指针:传入的是我们自定义的线程生命周期函数 + */ + old_pthread_introspection_hook_t = pthread_introspection_hook_install(kk_pthread_introspection_hook_t); + dispatch_semaphore_signal(global_semaphore); + + isMonitor = true; + + //判断是否在主线程 + kk_dispatch_main_async_safe(^{ + taskName = [SLTimer execTask:self selector:@selector(clearThreadCountIncrease) start:0 interval:1.0 repeats:YES async:NO]; + }); +} +//定时器每一秒都将线程增长数置0 ++ (void)clearThreadCountIncrease +{ + threadCountIncrease = 0; +} +///结束监听 ++ (void)stopMonitorThreadCount { + if (!global_semaphore || !taskName) { + return; + } + dispatch_semaphore_wait(global_semaphore, DISPATCH_TIME_FOREVER); + pthread_introspection_hook_t lastHook = pthread_introspection_hook_install(old_pthread_introspection_hook_t); + isMonitor = NO; + [SLTimer cancelTask:taskName]; + dispatch_semaphore_signal(global_semaphore); +} + +/** + 定义函数指针:pthread_introspection_hook_t + event : 线程处于的生命周期(下面枚举了线程的4个生命周期) + thread :线程 + addr :线程栈内存基址 + size :线程栈内存可用大小 + enum { + PTHREAD_INTROSPECTION_THREAD_CREATE = 1, //创建线程 + PTHREAD_INTROSPECTION_THREAD_START, // 线程开始运行 + PTHREAD_INTROSPECTION_THREAD_TERMINATE, //线程运行终止 + PTHREAD_INTROSPECTION_THREAD_DESTROY, //销毁线程 + }; + */ +void kk_pthread_introspection_hook_t(unsigned int event, + pthread_t thread, void *addr, size_t size) +{ + if (old_pthread_introspection_hook_t) { + //执行原来的线程生命周期函数 + old_pthread_introspection_hook_t(event, thread, addr, size); + } + + dispatch_semaphore_wait(global_semaphore, DISPATCH_TIME_FOREVER); + if (event == PTHREAD_INTROSPECTION_THREAD_CREATE) { + //创建线程 + threadCount = threadCount + 1; //线程总量加1 + if (isMonitor && (threadCount > maxThreadCountThreshold)) { + //如果线程总数大于监测的阈值,阈值+5;发出警告⚠️ + maxThreadCountThreshold += 5; + kk_Alert_Log_CallStack(false, 0); + } + threadCountIncrease = threadCountIncrease + 1; + if (isMonitor && (threadCountIncrease > threadIncreaseThreshold)) { + //如果线程在1秒内的增长数超过了阈值,发出警告⚠️ + kk_Alert_Log_CallStack(true, threadCountIncrease); + } + } + else if (event == PTHREAD_INTROSPECTION_THREAD_DESTROY){ + //销毁线程 + threadCount = threadCount - 1; //线程总量-1 + if (threadCount < KK_THRESHOLD) { + //如果线程数量再次少于40条,阈值恢复到40 + maxThreadCountThreshold = KK_THRESHOLD; + } + if (threadCountIncrease > 0) { + //线程增量-1 + threadCountIncrease = threadCountIncrease - 1; + } + } + dispatch_semaphore_signal(global_semaphore); +} + +///发出警告 输出调用堆栈 +void kk_Alert_Log_CallStack(bool isIncreaseLog, int num) +{ + if (isIncreaseLog) { + NSLog(@"⚠️ 1秒钟开启了 %d 条线程!", num); + } + NSLog(@"⚠️ 线程监听:%@",[BSBacktraceLogger bs_backtraceOfAllThread]); +} + +@end + diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMURLProtocol.h b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMURLProtocol.h new file mode 100644 index 00000000..3a94f668 --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMURLProtocol.h @@ -0,0 +1,23 @@ +// +// SLAPMURLProtocol.h +// DarkMode +// +// Created by wsl on 2020/8/3. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///网络监控 TCP 建立连接时间 、DNS 时间、 SSL时间、首包时间、响应时间 、流量 +@interface SLAPMURLProtocol : NSURLProtocol + +///开始监听网络 ++ (void)startMonitorNetwork; +///结束监听网络 ++ (void)stopMonitorNetwork; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMURLProtocol.m b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMURLProtocol.m new file mode 100644 index 00000000..ffa38742 --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLAPMURLProtocol.m @@ -0,0 +1,226 @@ +// +// SLAPMURLProtocol.m +// DarkMode +// +// Created by wsl on 2020/8/3. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLAPMURLProtocol.h" + +///计算请求报文的大小 即上行流量 +@interface NSURLRequest (SLDataLength) +@end +@implementation NSURLRequest (SLDataLength) +- (NSUInteger)sl_getLineLength { + NSString *lineStr = [NSString stringWithFormat:@"%@ %@ %@\n", self.HTTPMethod, self.URL.path, @"HTTP/1.1"]; + NSData *lineData = [lineStr dataUsingEncoding:NSUTF8StringEncoding]; + return lineData.length; +} +- (NSUInteger)sl_getHeadersLengthWithCookie { + NSUInteger headersLength = 0; + + NSDictionary *headerFields = self.allHTTPHeaderFields; + NSDictionary *cookiesHeader = [self sl_getCookies]; + + // 添加 cookie 信息 + if (cookiesHeader.count) { + NSMutableDictionary *headerFieldsWithCookies = [NSMutableDictionary dictionaryWithDictionary:headerFields]; + [headerFieldsWithCookies addEntriesFromDictionary:cookiesHeader]; + headerFields = [headerFieldsWithCookies copy]; + } + // NSLog(@"%@", headerFields); + NSString *headerStr = @""; + + for (NSString *key in headerFields.allKeys) { + headerStr = [headerStr stringByAppendingString:key]; + headerStr = [headerStr stringByAppendingString:@": "]; + if ([headerFields objectForKey:key]) { + headerStr = [headerStr stringByAppendingString:headerFields[key]]; + } + headerStr = [headerStr stringByAppendingString:@"\n"]; + } + NSData *headerData = [headerStr dataUsingEncoding:NSUTF8StringEncoding]; + headersLength = headerData.length; + return headersLength; +} +- (NSDictionary *)sl_getCookies { + NSDictionary *cookiesHeader; + NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; + NSArray *cookies = [cookieStorage cookiesForURL:self.URL]; + if (cookies.count) { + cookiesHeader = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; + } + return cookiesHeader; +} +- (NSUInteger)sl_getBodyLength { + NSDictionary *headerFields = self.allHTTPHeaderFields; + NSUInteger bodyLength = [self.HTTPBody length]; + if ([headerFields objectForKey:@"Content-Encoding"]) { + NSData *bodyData; + if (self.HTTPBody == nil) { + uint8_t d[1024] = {0}; + NSInputStream *stream = self.HTTPBodyStream; + NSMutableData *data = [[NSMutableData alloc] init]; + [stream open]; + while ([stream hasBytesAvailable]) { + NSInteger len = [stream read:d maxLength:1024]; + if (len > 0 && stream.streamError == nil) { + [data appendBytes:(void *)d length:len]; + } + } + bodyData = [data copy]; + [stream close]; + } else { + bodyData = self.HTTPBody; + } + bodyLength = bodyData.length; + } + return bodyLength; +} +@end + + +//为了避免 canInitWithRequest 和 canonicalRequestForRequest 出现死循环 +static NSString * const SLHTTPHandledIdentifier = @"SLHTTPHandledIdentifier"; +@interface SLAPMURLProtocol () + +@property (nonatomic, strong) NSURLSession *session; //会话 +@property (nonatomic, strong) NSURLSessionDataTask *dataTask; + +@property (nonatomic, strong) NSURLResponse *response; //响应头 +@property (nonatomic, strong) NSMutableData *data; //返回的数据 + +@end + +@implementation SLAPMURLProtocol + +#pragma mark - Public +///开始监听网络 ++ (void)startMonitorNetwork { + [NSURLProtocol registerClass:[SLAPMURLProtocol class]]; +} +///结束监听网络 ++ (void)stopMonitorNetwork { + [NSURLProtocol unregisterClass:[SLAPMURLProtocol class]]; +} + +#pragma mark - Override ++ (BOOL)canInitWithRequest:(NSURLRequest *)request { + if (![request.URL.scheme isEqualToString:@"http"] && + ![request.URL.scheme isEqualToString:@"https"]) { + return NO; + } + //拦截过的请求不再拦截 + if ([NSURLProtocol propertyForKey:SLHTTPHandledIdentifier inRequest:request] ) { + return NO; + } + return YES; +} ++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { + //标示该request已经处理过了,防止无限循环 + NSMutableURLRequest *mutableReqeust = [request mutableCopy]; + [NSURLProtocol setProperty:@YES + forKey:SLHTTPHandledIdentifier + inRequest:mutableReqeust]; + return [mutableReqeust copy]; +} +- (void)startLoading { + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSOperationQueue *sessionDelegateQueue = [[NSOperationQueue alloc] init]; + sessionDelegateQueue.maxConcurrentOperationCount = 1; + sessionDelegateQueue.name = @"com.wsl2ls.APMURLProtocol.queue"; + _session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:sessionDelegateQueue]; + _dataTask = [_session dataTaskWithRequest:self.request]; + [_dataTask resume]; +} +- (void)stopLoading { + [_dataTask cancel]; + _dataTask = nil; + [_session invalidateAndCancel]; + _session = nil; + + //接收流量 不包括响应头NSURLResponse + NSLog(@"接收流量大小:%.2fM",self.data.length/(1024.0*1024.0)); + + NSUInteger sendLength = [self.request sl_getLineLength] + [self.request sl_getBodyLength] + [self.request sl_getHeadersLengthWithCookie]; + NSLog(@"发送流量大小:%ldB",sendLength); +} + +#pragma mark - Getter +- (NSMutableData *)data { + if (!_data) { + _data = [NSMutableData data]; + } + return _data; +} + +#pragma mark - NSURLSessionDataDelegate +//接收到返回的响应信息时(还未开始下载), 执行的代理方法 +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { + [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed]; + completionHandler(NSURLSessionResponseAllow); + self.response = response; +} +//接收到服务器返回的数据 调用多次,数据是分批返回的 +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + [self.data appendData:data]; + [self.client URLProtocol:self didLoadData:data]; +} + +#pragma mark - NSURLSessionTaskDelegate +///告诉代理会话已收集完任务的度量 实现对网络请求中 DNS 查询/TCP 建立连接/TLS 握手/请求响应等各环节时间的统计 +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) { + + //数组包含了在执行任务时产生的每个请求/响应事务中收集的指标。 + NSArray *array = metrics.transactionMetrics; + //任务从创建到完成花费的总时间,任务的创建时间是任务被实例化时的时间;任务完成时间是任务的内部状态将要变为完成的时间。 + NSDateInterval *taskInterval = metrics.taskInterval; + NSLog(@"请求时长:%f",taskInterval.duration); + + for (NSURLSessionTaskTransactionMetrics*transactionMetrics in array) { + NSLog(@"请求开始:%@",transactionMetrics.fetchStartDate); + NSLog(@"请求完成:%@",transactionMetrics.responseEndDate); + NSLog(@"请求协议:%@",transactionMetrics.networkProtocolName); + NSLog(@"DNS 解析开始时间:%@",transactionMetrics.domainLookupStartDate); + NSLog(@"DNS 解析完成时间:%@",transactionMetrics.domainLookupEndDate); + NSLog(@"客户端与服务器开始建立 TCP 连接的时间:%@",transactionMetrics.connectStartDate); + NSLog(@"HTTPS 的 TLS 握手开始时间:%@",transactionMetrics.secureConnectionStartDate); + NSLog(@"HTTPS 的 TLS 握手结束时间:%@",transactionMetrics.secureConnectionEndDate); + NSLog(@"客户端与服务器建立 TCP 连接完成时间:%@",transactionMetrics.connectEndDate); + + NSLog(@"开始传输 HTTP请求header 第一个字节的时间:%@",transactionMetrics.requestStartDate); + NSLog(@"HTTP请求最后一个字节传输完成的时间:%@",transactionMetrics.requestEndDate); + NSLog(@"客户端从服务器接收到响应的第一个字节的时间:%@",transactionMetrics.responseStartDate); + NSLog(@"客户端从服务器接收到最后一个字节的时间:%@",transactionMetrics.responseEndDate); + } +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task + didSendBodyData:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent +totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + + +} + + +//请求结束或者是失败的时候调用 +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { + if (!error) { + [self.client URLProtocolDidFinishLoading:self]; + } else if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { + } else { + [self.client URLProtocol:self didFailWithError:error]; + } +} +//告诉代理,远程服务器请求了HTTP重定向 +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler { + if (response != nil){ + self.response = response; + [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response]; + } +} + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLSystemAppInfo.h b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLSystemAppInfo.h new file mode 100644 index 00000000..cbb31b7f --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLSystemAppInfo.h @@ -0,0 +1,68 @@ +// +// SLSystemAppInfo.h +// DarkMode +// +// Created by wsl on 2020/8/4. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +typedef NS_ENUM(NSInteger, SLAuthorizationStatus) { + SLAuthorizationStatusUnknow = -1, //未知的 + SLAuthorizationStatusNotDetermined = 0, //用户还没有选择过(第一次) 这时会自动出现系统询问授权弹窗,之后不会 + SLAuthorizationStatusRestricted, //家长控制,限制用户授权的权限 + SLAuthorizationStatusDenied, //用户拒绝授权 + SLAuthorizationStatusAuthorized, //已授权 + SLAuthorizationStatusAuthorizedAlways, //始终都授权,比如定位,蓝牙 + SLAuthorizationStatusAuthorizedWhenInUse, //仅当应用使用时授权 比如定位 + SLAuthorizationStatusProvisional, //临时授权,用完一次即权利解除,下次再申请 + SLAuthorizationStatusUnsupported, //该硬件不支持授权的功能,比如蓝牙、FaceID、摄像头,设备可能不支持 + SLAuthorizationStatusOff //请求授权的功能处于关闭状态,比如蓝牙 +}; + +NS_ASSUME_NONNULL_BEGIN + +///包含系统、应用、隐私权限的信息 +@interface SLSystemAppInfo : NSObject + ++ (instancetype)manager; ++ (void)test; + +#pragma mark - System Info +///获取手机型号 iPhone 8... ++ (NSString *)iphoneType; +///获取手机系统版本 13.4 ++ (NSString *)systemVersion; +///获取设备类型 iPhone/iPad/iPod touch ++ (NSString *)deviceModel; +///根据地区语言返回设备类型字符串 (国际化区域名称) ++(NSString *)localDeviceModel; +///操作系统名称 iOS ++ (NSString *)systemName; +///获取用户手机别名 用户定义的名称 通用-关于本机-名称 wsl的iphone ++ (NSString *)userPhoneName; +///设备唯一标识的字母数字字符串 但如果用户重新安装,那么这个 UUID 就会发生变化。 C5668446-C443-4898-A213-209AECE3626C ++ (NSString *)uuidString; +///是否是iPhoneX系列/刘海屏 ++ (BOOL)isIPhoneXSeries; +/// 获取电话运营商信息 ++ (NSString *)telephonyInfo; +/// 获取网络类型 ++(NSString*)networkType; +///获取设备当前网络IP地址 ++ (NSString *)getIPAddress:(BOOL)preferIPv4; + +#pragma mark - App Info +///获取APP名字 SLTips ++ (NSString *)appName; +///获取APP bundle id com.wsl2ls.tips ++ (NSString *)appBundleId; +///获取当前App版本号 1.1.0 ++ (NSString *)appVersion; +///获取当前App编译版本号 1 ++ (NSString *)appBuild; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLSystemAppInfo.m b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLSystemAppInfo.m new file mode 100644 index 00000000..974e58fc --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/SLSystemAppInfo.m @@ -0,0 +1,598 @@ +// +// SLSystemAppInfo.m +// DarkMode +// +// Created by wsl on 2020/8/4. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLSystemAppInfo.h" +#import "sys/utsname.h" +#import +#import +#import +#include +#include +#include +#import +#import +#import +#import +#import +#import +#import + + +#define SL_IOS_CELLULAR @"pdp_ip0" +#define SL_IOS_WIFI @"en0" +//#define IOS_VPN @"utun0" +#define SL_IP_ADDR_IPv4 @"ipv4" +#define SL_IP_ADDR_IPv6 @"ipv6" + +@interface SLSystemAppInfo () +@end +@implementation SLSystemAppInfo + ++ (void)test { + + [self iphoneType]; + [self systemVersion]; + [self deviceModel]; + [self userPhoneName]; + [self systemName]; + [self uuidString]; + [self localDeviceModel]; + [self telephonyInfo]; + [self networkType]; + [self getIPAddress:YES]; + + [self appBundleId]; + [self appVersion]; + [self appBuild]; + [self appName]; + + [self checkPushAuthorization]; + [self checkPhotoLibraryAuthorization:nil]; + [self checkLocationAuthorization]; + [self checkCameraAuthorization:nil]; + [self checkMicrophoneAuthorization:nil]; + +} + +#pragma mark - Override +/// 重写allocWithZone方法,保证alloc或者init创建的实例不会产生新实例,因为该类覆盖了allocWithZone方法,所以只能通过其父类分配内存,即[super allocWithZone] ++ (instancetype)allocWithZone:(struct _NSZone *)zone { + return [self manager]; +} +/// 重写copyWithZone方法,保证复制返回的是同一份实例 +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + return [SLSystemAppInfo manager]; +} + +#pragma mark - Public ++ (instancetype)manager { + static SLSystemAppInfo *manager = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + manager = [[super allocWithZone:NULL] init]; + }); + return manager; +} + +#pragma mark - System Info + +///获取手机型号 iPhone 8... ++ (NSString *)iphoneType{ + struct utsname systemInfo; + uname(&systemInfo); + NSString *deviceString = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; + NSDictionary *dict = @{@"iPhone1,1":@"iPhone 1G", + @"iPhone1,2":@"iPhone 3G", + @"iPhone2,1":@"iPhone 3GS", + @"iPhone3,1":@"iPhone 4", + @"iPhone3,2":@"iPhone 4", + @"iPhone4,1":@"iPhone 4S", + @"iPhone5,1":@"iPhone 5", + @"iPhone5,2":@"iPhone 5", + @"iPhone5,3":@"iPhone 5C", + @"iPhone5,4":@"iPhone 5C", + @"iPhone6,1":@"iPhone 5S", + @"iPhone6,2":@"iPhone 5S", + @"iPhone7,1":@"iPhone 6 Plus", + @"iPhone7,2":@"iPhone 6", + @"iPhone8,1":@"iPhone 6S", + @"iPhone8,2":@"iPhone 6S Plus", + @"iPhone8,4":@"iPhone SE", + @"iPhone9,1":@"iPhone 7", + @"iPhone9,3":@"iPhone 7", + @"iPhone9,2":@"iPhone 7 Plus", + @"iPhone9,4":@"iPhone 7 Plus", + @"iPhone10,1":@"iPhone 8", + @"iPhone10.4":@"iPhone 8", + @"iPhone10,2":@"iPhone 8 Plus", + @"iPhone10,5":@"iPhone 8 Plus", + @"iPhone10,3":@"iPhone X", + @"iPhone10,6":@"iPhone X", + @"iPhone11,8":@"iPhone XR", + @"iPhone11,2":@"iPhone XS", + @"iPhone11,4":@"iPhone XS Max", + @"iPhone11,6":@"iPhone XS Max", + @"iPhone12,1":@"iPhone 11", + @"iPhone12,3":@"iPhone 11 Pro", + @"iPhone12,5":@"iPhone 11 Pro Max", + @"i386":@"Simulator", + @"x86_64":@"Simulator" + }; + return dict[deviceString] == nil ? deviceString : dict[deviceString]; +} +///获取手机系统版本 13.4 ++ (NSString *)systemVersion { + NSString *systemVersion = [[UIDevice currentDevice] systemVersion]; + return systemVersion; +} +///获取设备类型 iPhone/iPad/iPod touch ++ (NSString *)deviceModel { + NSString* deviceModel = [[UIDevice currentDevice] model]; + return deviceModel; +} +///根据地区语言返回设备类型字符串 (国际化区域名称) ++(NSString *)localDeviceModel { + NSString* localizedModel = [[UIDevice currentDevice] localizedModel]; + return localizedModel;; +} +///操作系统名称 iOS ++ (NSString *)systemName { + NSString* systemName = [[UIDevice currentDevice] systemName]; + return systemName; +} +///获取用户手机别名 用户定义的名称 通用-关于本机-名称 wsl的iphone ++ (NSString *)userPhoneName { + NSString* userPhoneName = [[UIDevice currentDevice] name]; + return userPhoneName; +} +///设备唯一标识的字母数字字符串,但如果用户重新安装,那么这个 UUID 就会发生变化。 C5668446-C443-4898-A213-209AECE3626C ++ (NSString *)uuidString { + NSString *UUIDString = [[UIDevice currentDevice] identifierForVendor].UUIDString; + return UUIDString; +} +///是否是iPhoneX系列/刘海屏 ++ (BOOL)isIPhoneXSeries{ + BOOL iPhoneXSeries = NO; + if (UIDevice.currentDevice.userInterfaceIdiom != UIUserInterfaceIdiomPhone) { + return iPhoneXSeries; + } + if (@available(iOS 11.0, *)) { + UIWindow *mainWindow = [self getKeyWindow]; + if (mainWindow.safeAreaInsets.bottom > 0.0) { + iPhoneXSeries = YES; + } + } + return iPhoneXSeries; +} ++ (UIWindow *)getKeyWindow{ + UIWindow *keyWindow = nil; + if ([[UIApplication sharedApplication].delegate respondsToSelector:@selector(window)]) { + keyWindow = [[UIApplication sharedApplication].delegate window]; + }else{ + NSArray *windows = [UIApplication sharedApplication].windows; + for (UIWindow *window in windows) { + if (!window.hidden) { + keyWindow = window; + break; + } + } + } + return keyWindow; +} +/// 获取电话运营商信息 ++ (NSString *)telephonyInfo { + CTTelephonyNetworkInfo *info = [[CTTelephonyNetworkInfo alloc] init]; + CTCarrier *carrier = [info subscriberCellularProvider]; + NSString *mCarrier = [NSString stringWithFormat:@"%@",[carrier carrierName]]; + return mCarrier; +} +/// 获取网络类型 ++(NSString*)networkType { + /** + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyGPRS __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyEdge __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyWCDMA __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyHSDPA __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyHSUPA __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMA1x __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMAEVDORev0 __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMAEVDORevA __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMAEVDORevB __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyeHRPD __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyLTE __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0); + **/ + // + CTTelephonyNetworkInfo* info=[[CTTelephonyNetworkInfo alloc] init]; + if (@available(iOS 12.0, *)) { + NSDictionary *dict= info.serviceCurrentRadioAccessTechnology; + // NSLog(@"%@",dict); + } else { + } + NSString *networkType = info.currentRadioAccessTechnology; + return networkType; +} +///获取设备当前网络IP地址 ++ (NSString *)getIPAddress:(BOOL)preferIPv4 { + NSArray *searchArray = preferIPv4 ? + @[ /*IOS_VPN @"/" SL_IP_ADDR_IPv4, IOS_VPN @"/" SL_IP_ADDR_IPv6,*/ SL_IOS_WIFI @"/" SL_IP_ADDR_IPv4, SL_IOS_WIFI @"/" SL_IP_ADDR_IPv6, SL_IOS_CELLULAR @"/" SL_IP_ADDR_IPv4, SL_IOS_CELLULAR @"/" SL_IP_ADDR_IPv6 ] : + @[ /*IOS_VPN @"/" SL_IP_ADDR_IPv6, IOS_VPN @"/" SL_IP_ADDR_IPv4,*/ SL_IOS_WIFI @"/" SL_IP_ADDR_IPv6, SL_IOS_WIFI @"/" SL_IP_ADDR_IPv4, SL_IOS_CELLULAR @"/" SL_IP_ADDR_IPv6, SL_IOS_CELLULAR @"/" SL_IP_ADDR_IPv4 ] ; + + NSDictionary *addresses = [[self class] getIPAddresses]; + __block NSString *address; + [searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) + { + address = addresses[key]; + if(address) *stop = YES; + } ]; + return address ? address : @"0.0.0.0"; +} +//获取所有相关IP信息 ++ (NSDictionary *)getIPAddresses { + NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; + + // retrieve the current interfaces - returns 0 on success + struct ifaddrs *interfaces; + if(!getifaddrs(&interfaces)) { + // Loop through linked list of interfaces + struct ifaddrs *interface; + for(interface=interfaces; interface; interface=interface->ifa_next) { + if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) { + continue; // deeply nested code harder to read + } + const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr; + char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ]; + if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) { + NSString *name = [NSString stringWithUTF8String:interface->ifa_name]; + NSString *type; + if(addr->sin_family == AF_INET) { + if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) { + type = SL_IP_ADDR_IPv4; + } + } else { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr; + if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) { + type = SL_IP_ADDR_IPv6; + } + } + if(type) { + NSString *key = [NSString stringWithFormat:@"%@/%@", name, type]; + addresses[key] = [NSString stringWithUTF8String:addrBuf]; + } + } + } + // Free memory + freeifaddrs(interfaces); + } + return [addresses count] ? addresses : nil; +} + + + +#pragma mark - App Info +///获取APP名字 SLTips ++ (NSString *)appName { + NSString *appCurName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; + if (!appCurName) { + appCurName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + } + return appCurName; +} +///获取APP bundle id com.wsl2ls.tips ++ (NSString *)appBundleId { + NSString *appBundleId = [[NSBundle mainBundle] bundleIdentifier]; + return appBundleId; +} +///获取当前App版本号 1.1.0 ++ (NSString *)appVersion { + NSString *appCurVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; + return appCurVersion; +} +///获取当前App编译版本号 1 ++ (NSString *)appBuild { + NSString *appBuildVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; + return appBuildVersion; +} + +#pragma mark - 隐私权限 +/*资料:https://www.jianshu.com/p/5f05bc8395f1 */ + +///检测推送通知权限 ++ (SLAuthorizationStatus)checkPushAuthorization { + __block SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0 + if ([[UIApplication sharedApplication] currentUserNotificationSettings].types == UIUserNotificationTypeNone) { + authorizationStatus = SLAuthorizationStatusDenied; + return authorizationStatus; + }else { + authorizationStatus = SLAuthorizationStatusAuthorized; + return authorizationStatus; + } +#else + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { + UNAuthorizationStatus pushAuthorizationStatus = settings.authorizationStatus; + switch (pushAuthorizationStatus) { + case UNAuthorizationStatusNotDetermined: + authorizationStatus = SLAuthorizationStatusNotDetermined; + break; + case UNAuthorizationStatusDenied: + authorizationStatus = SLAuthorizationStatusDenied; + break; + case UNAuthorizationStatusAuthorized: + authorizationStatus = SLAuthorizationStatusAuthorized; + break; + case UNAuthorizationStatusProvisional: + ///临时授权,用完权利解除,下次再申请 + authorizationStatus = SLAuthorizationStatusProvisional; + break; + default: + authorizationStatus = SLAuthorizationStatusUnknow; + break; + } + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); +#endif + return authorizationStatus; +} + +///检查相册访问权限 handler 用户授权结果的回调 ++ (SLAuthorizationStatus)checkPhotoLibraryAuthorization:(void(^)(SLAuthorizationStatus status))handler { + __block SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 //iOS 8.0以下使用AssetsLibrary.framework + ALAuthorizationStatus status = [ALAssetsLibrary authorizationStatus]; + authorizationStatus = (SLAuthorizationStatus)status; +#else //iOS 8.0以上使用Photos.framework + PHAuthorizationStatus current = [PHPhotoLibrary authorizationStatus]; + authorizationStatus = (SLAuthorizationStatus)current; + //用户还没有做出过是否授权的选择时 + if (current == PHAuthorizationStatusNotDetermined) { + //只有第一次请求授权时才会自动出现系统弹窗,之后再请求授权时也不会弹出系统询问弹窗 + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (handler) { + authorizationStatus = (SLAuthorizationStatus)status; + handler(authorizationStatus); + } + }); + }]; + } +#endif + if (handler) { + handler(authorizationStatus); + } + return authorizationStatus; +} + +///检查定位权限 ++ (SLAuthorizationStatus)checkLocationAuthorization { + SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; + //定位服务是否可用 + if ([CLLocationManager locationServicesEnabled]) { + CLAuthorizationStatus state = [CLLocationManager authorizationStatus]; + if (state == kCLAuthorizationStatusNotDetermined) { + authorizationStatus = SLAuthorizationStatusNotDetermined; + }else if(state == kCLAuthorizationStatusRestricted){ + authorizationStatus = SLAuthorizationStatusRestricted; + }else if(state == kCLAuthorizationStatusDenied){ + authorizationStatus = SLAuthorizationStatusDenied; + }else if(state == kCLAuthorizationStatusAuthorizedAlways){ + authorizationStatus = SLAuthorizationStatusAuthorizedAlways; + }else if(state == kCLAuthorizationStatusAuthorizedWhenInUse){ + authorizationStatus = SLAuthorizationStatusAuthorizedWhenInUse; + } + }else{ + //定位服务不可用 + authorizationStatus = SLAuthorizationStatusUnsupported; + } + return authorizationStatus; +} + +///检查相机/摄像头权限 ++ (SLAuthorizationStatus)checkCameraAuthorization:(void(^)(SLAuthorizationStatus status))handler { + __block SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; + NSString *mediaType = AVMediaTypeVideo;//读取媒体类型 + AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];//读取设备授权状态 + authorizationStatus = (SLAuthorizationStatus)authStatus; + if (authStatus == AVAuthorizationStatusNotDetermined) { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { + if (granted) { + authorizationStatus = SLAuthorizationStatusAuthorized; + }else{ + authorizationStatus = SLAuthorizationStatusDenied; + } + if (handler) { + handler(authorizationStatus); + } + }]; + } + if (handler) { + handler(authorizationStatus); + } + return authorizationStatus; +} + +///检查话筒/麦克风权限 ++ (SLAuthorizationStatus)checkMicrophoneAuthorization:(void(^)(SLAuthorizationStatus status))handler { + __block SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; + NSString *mediaType = AVMediaTypeAudio;//读取媒体类型 + AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];//读取设备授权状态 + authorizationStatus = (SLAuthorizationStatus)authStatus; + if (authStatus == AVAuthorizationStatusNotDetermined) { + [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) { + if (granted) { + authorizationStatus = SLAuthorizationStatusAuthorized; + } else { + authorizationStatus = SLAuthorizationStatusDenied; + } + if (handler) { + handler(authorizationStatus); + } + }]; + } + if (handler) { + handler(authorizationStatus); + } + return authorizationStatus; +} + +///检测通讯录权限 ++ (SLAuthorizationStatus)checkContactsAuthorization:(void(^)(SLAuthorizationStatus status))handler { + __block SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; + if (@available(iOS 9.0, *)) {//iOS9.0之后 + CNAuthorizationStatus authStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts]; + authorizationStatus = (SLAuthorizationStatus)authStatus; + if (authStatus == CNAuthorizationStatusNotDetermined) { + CNContactStore *contactStore = [[CNContactStore alloc] init]; + [contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError *error) { + if (!error){ + if (granted) { + authorizationStatus = SLAuthorizationStatusAuthorized; + } else { + authorizationStatus = SLAuthorizationStatusDenied; + } + if (handler) { + handler(authorizationStatus); + } + } + + }]; + } + }else{//iOS9.0之前 + ABAuthorizationStatus authorStatus = ABAddressBookGetAuthorizationStatus(); + authorizationStatus = (SLAuthorizationStatus)authorStatus; + } + if (handler) { + handler(authorizationStatus); + } + return authorizationStatus; +} + +///检测日历权限 ++ (SLAuthorizationStatus)checkCalendarAuthorization:(void(^)(SLAuthorizationStatus status))handler { + __block SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; + EKAuthorizationStatus status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent]; + authorizationStatus = (SLAuthorizationStatus)status; + if (status == EKAuthorizationStatusNotDetermined) { + EKEventStore *store = [[EKEventStore alloc] init]; + [store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) { + if (error) {} else { + if (granted) { + authorizationStatus = SLAuthorizationStatusAuthorized; + } else { + authorizationStatus = SLAuthorizationStatusDenied; + } + if (handler) { + handler(authorizationStatus); + } + } + }]; + } + if (handler) { + handler(authorizationStatus); + } + return authorizationStatus; +} + +///检测提醒事项权限 ++ (SLAuthorizationStatus)checkRemindAuthorization:(void(^)(SLAuthorizationStatus status))handler { + __block SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; + EKAuthorizationStatus status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeReminder]; + authorizationStatus = (SLAuthorizationStatus)status; + if (status == EKAuthorizationStatusNotDetermined) { + EKEventStore *store = [[EKEventStore alloc] init]; + [store requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError * _Nullable error) { + if (!error){ + if (granted) { + authorizationStatus = SLAuthorizationStatusAuthorized; + } else { + authorizationStatus = SLAuthorizationStatusDenied; + } + if (handler) { + handler(authorizationStatus); + } + } + }]; + } + if (handler) { + handler(authorizationStatus); + } + return authorizationStatus; +} + +///检测蓝牙权限 +- (void)checkBluetoothAuthorization { + if (@available(iOS 13.1, *)) { + CBManagerAuthorization authorization = [CBManager authorization]; + SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; + if (authorization == CBManagerAuthorizationAllowedAlways) { + authorizationStatus = SLAuthorizationStatusAuthorizedAlways; + }else { + authorizationStatus = (SLAuthorizationStatus)authorization; + } + } else { + CBCentralManager *bluetoothManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; + } +} +///CBCentralManagerDelegate +- (void)centralManagerDidUpdateState:(CBCentralManager *)central { + SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; + CBManagerState state = central.state; + if (state == CBManagerStateResetting) { + //重置或重新连接 + authorizationStatus = SLAuthorizationStatusUnknow; + } else if (state == CBManagerStateUnsupported) { + //不支持蓝牙功能 + authorizationStatus = SLAuthorizationStatusUnsupported; + } else if (state == CBManagerStateUnauthorized) { + //拒绝授权 + authorizationStatus = SLAuthorizationStatusDenied; + } else if (state == CBManagerStatePoweredOff) { + //蓝牙处于关闭状态 + authorizationStatus = SLAuthorizationStatusOff; + } else if (state == CBManagerStatePoweredOn) { + //已授权 + authorizationStatus = SLAuthorizationStatusAuthorized; + } +} + +///请求FaceID权限 ++ (void)checkFaceIDAuthorization { + __block SLAuthorizationStatus authorizationStatus = SLAuthorizationStatusUnknow; + if (@available(iOS 11.0, *)) { + LAContext *authenticationContext = [[LAContext alloc]init]; + NSError *error = nil; + ///是否能验证人脸数据 + BOOL canEvaluatePolicy = [authenticationContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]; + if (canEvaluatePolicy) { + if (authenticationContext.biometryType == LABiometryTypeFaceID) { + //验证当前人脸数据 + [authenticationContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"开始验证" reply:^(BOOL success, NSError * _Nullable error) { + if (!error) { + if (success) { + //验证通过 + }else { + //验证失败 + } + } + }]; + } + }else { + if (error.code == -8) { + NSLog(@"错误次数太多,被锁定"); + }else{ + NSLog(@"没有设置人脸数据,请前往设置"); + } + } + }else { + authorizationStatus = SLAuthorizationStatusUnsupported; + } + +} + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/UIViewController+SLAPMVCTime.h b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/UIViewController+SLAPMVCTime.h new file mode 100644 index 00000000..ea3e7117 --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/UIViewController+SLAPMVCTime.h @@ -0,0 +1,18 @@ +// +// UIViewController+SLAPMVCTime.h +// DarkMode +// +// Created by wsl on 2020/8/4. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///ViewController启动耗时监测 -loadView --> -viewDidAppear +@interface UIViewController (SLAPMVCTime) + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/UIViewController+SLAPMVCTime.m b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/UIViewController+SLAPMVCTime.m new file mode 100644 index 00000000..a198b185 --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/APMMonitor/UIViewController+SLAPMVCTime.m @@ -0,0 +1,163 @@ +// +// UIViewController+SLAPMVCTime.m +// DarkMode +// +// Created by wsl on 2020/8/4. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "UIViewController+SLAPMVCTime.h" +#import + +static char const kSLFakeKVORemoverKey; //关联的对象Key +static char const kSLVCBeginDateKey; //vc开始加载时间的key + +static NSString *const kSLFakeKeyPath = @"SL_FakeKeyPath"; //假冒的被观察属性key + +///冒充KVO观察者,是为了生成观察目标的KVO子类 +@interface SLFakeKVOObserver : NSObject +@end +@implementation SLFakeKVOObserver ++ (instancetype)shared { + static id sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} +@end +///负责移除冒充的KVO观察者 +@interface SLFakeKVORemover : NSObject +@property (nonatomic, weak) id target; //被观察的目标 +@end +@implementation SLFakeKVORemover +- (void)dealloc { + [_target removeObserver:[SLFakeKVOObserver shared] forKeyPath:kSLFakeKeyPath]; +} +@end + +@implementation UIViewController (SLAPMVCTime) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class class = [UIViewController class]; + if (![NSStringFromClass(class) hasPrefix:@"SL"]) { + //仅仅测自己创建的VC + return; + } + ///hook vc的初始化方法 + [self swizzleMethodInClass:class originalMethod:@selector(initWithNibName:bundle:) swizzledSelector:@selector(apm_initWithNibName:bundle:)]; + [self swizzleMethodInClass:class originalMethod:@selector(initWithCoder:) swizzledSelector:@selector(apm_initWithCoder:)]; + }); +} +- (instancetype)apm_initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil { + [self createAndHookKVOClass]; + [self apm_initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + return self; +} +- (nullable instancetype)apm_initWithCoder:(NSCoder *)aDecoder { + [self createAndHookKVOClass]; + [self apm_initWithCoder:aDecoder]; + return self; +} +///创建vc的KVO子类并hook子类的相关方法 +- (void)createAndHookKVOClass { + //设置KVO,会触发runtime来创建VC的KVO子类 + [self addObserver:[SLFakeKVOObserver shared] forKeyPath:kSLFakeKeyPath options:NSKeyValueObservingOptionNew context:nil]; + + //保存观察目标VC,当VC实例释放时,移除KVO + SLFakeKVORemover *remover = [[SLFakeKVORemover alloc] init]; + remover.target = self; + objc_setAssociatedObject(self, &kSLFakeKVORemoverKey, remover, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + //获取VC的KVO子类 NSKVONotifying_ViewController + Class kvoClass = object_getClass(self); + + //判断当前的IMP和我们的IMP在之前是否已hook + IMP currentViewDidLoadImp = class_getMethodImplementation(kvoClass, @selector(viewDidLoad)); + if (currentViewDidLoadImp == (IMP)apm_viewDidLoad) { + return; + } + + //KVO子类的父类,即当前类,原来的类 + Class originCls = class_getSuperclass(kvoClass); + + // 获取原来实现的encoding + const char *originLoadViewEncoding = method_getTypeEncoding(class_getInstanceMethod(originCls, @selector(loadView))); + const char *originViewDidLoadEncoding = method_getTypeEncoding(class_getInstanceMethod(originCls, @selector(viewDidLoad))); + const char *originViewWillAppearEncoding = method_getTypeEncoding(class_getInstanceMethod(originCls, @selector(viewWillAppear:))); + const char *originViewDidAppearEncoding = method_getTypeEncoding(class_getInstanceMethod(originCls, @selector(viewDidAppear:))); + + // 添加方法,因为生成的KVO子类本身并没有实现loadView等方法,如果已实现了会添加失败。 + class_addMethod(kvoClass, @selector(loadView), (IMP)apm_loadView, originLoadViewEncoding); + class_addMethod(kvoClass, @selector(viewDidLoad), (IMP)apm_viewDidLoad, originViewDidLoadEncoding); + class_addMethod(kvoClass, @selector(viewWillAppear:), (IMP)apm_viewWillAppear, originViewWillAppearEncoding); + class_addMethod(kvoClass, @selector(viewDidAppear:), (IMP)apm_viewDidAppear, originViewDidAppearEncoding); +} +///方法实现交换 ++ (void)swizzleMethodInClass:(Class) class originalMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector { + Method originalMethod = class_getInstanceMethod(class, originalSelector); + Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); + BOOL didAddMethod = class_addMethod(class, + originalSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)); + + if (didAddMethod) { + class_replaceMethod(class, + swizzledSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + method_exchangeImplementations(originalMethod, swizzledMethod); + } +} + +#pragma mark - IMP of Hook +static void apm_loadView(UIViewController *kvo_self, SEL _sel) { + IMP origin_imp = apm_originalMethodImplementation(kvo_self, _sel); + void (*func)(UIViewController *, SEL) = (void (*)(UIViewController *, SEL))origin_imp; + + //记录开始加载的时间 + objc_setAssociatedObject(kvo_self, &kSLVCBeginDateKey, [NSDate date], OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + //执行原来origin_cls的方法实现 + func(kvo_self, _sel); +} +static void apm_viewDidLoad(UIViewController *kvo_self, SEL _sel) { + IMP origin_imp = apm_originalMethodImplementation(kvo_self, _sel); + void (*func)(UIViewController *, SEL) = (void (*)(UIViewController *, SEL))origin_imp; + func(kvo_self, _sel); +} +static void apm_viewWillAppear(UIViewController *kvo_self, SEL _sel, BOOL animated) { + IMP origin_imp = apm_originalMethodImplementation(kvo_self, _sel); + void (*func)(UIViewController *, SEL, BOOL) = (void (*)(UIViewController *, SEL, BOOL))origin_imp; + func(kvo_self, _sel, animated); +} +static void apm_viewDidAppear(UIViewController *kvo_self, SEL _sel, BOOL animated) { + IMP origin_imp = apm_originalMethodImplementation(kvo_self, _sel); + void (*func)(UIViewController *, SEL, BOOL) = (void (*)(UIViewController *, SEL, BOOL))origin_imp; + func(kvo_self, _sel, animated); + + NSDate *beginDate = objc_getAssociatedObject(kvo_self, &kSLVCBeginDateKey); + if (beginDate) { + //计算方法耗时 + NSTimeInterval duration = -[beginDate timeIntervalSinceNow]; + NSLog(@"VC: %@ -loadView --> -viewDidAppear 用时: %f", [kvo_self class], duration); + } + //重置记录的开始时间 + objc_setAssociatedObject(kvo_self, &kSLVCBeginDateKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +///返回原方法的IMP实现 +static IMP apm_originalMethodImplementation(UIViewController *kvo_self, SEL _sel) { + Class kvo_cls = object_getClass(kvo_self); + Class origin_cls = class_getSuperclass(kvo_cls); + IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel)); + assert(origin_imp != NULL); + return origin_imp; +} + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/SLAPMViewController.h b/iOS_Tips/DarkMode/WorkIssues/APM/SLAPMViewController.h new file mode 100644 index 00000000..21c3dec2 --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/SLAPMViewController.h @@ -0,0 +1,18 @@ +// +// SLAPMViewController.h +// DarkMode +// +// Created by wsl on 2020/7/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +///iOS APM应用性能监控管理 +@interface SLAPMViewController : SLViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/APM/SLAPMViewController.m b/iOS_Tips/DarkMode/WorkIssues/APM/SLAPMViewController.m new file mode 100644 index 00000000..0478ea16 --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/APM/SLAPMViewController.m @@ -0,0 +1,84 @@ +// +// SLAPMViewController.m +// DarkMode +// +// Created by wsl on 2020/7/13. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLAPMViewController.h" +#import "SLAPMManager.h" + +#import "SLSystemAppInfo.h" + +/* + 参考资料: + https://www.jianshu.com/p/95df83780c8f + https://www.jianshu.com/p/8123fc17fe0e + https://juejin.im/post/5e92a113e51d4547134bdadb + */ + +@interface SLAPMViewController () +@end + +@implementation SLAPMViewController + +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + self.navigationItem.title = @"APM监控"; + [self setupNavigationBar]; + [SLAPMManager manager].type = SLAPMTypeNetwork; + +} +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; +} +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; +} + +#pragma mark - UI +- (void)setupNavigationBar { + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:([SLAPMManager manager].isMonitoring ? @"停止监控":@"开始监控") style:UIBarButtonItemStyleDone target:self action:@selector(changeMonitorState)]; +} + +#pragma mark - Help Methods +///测试卡顿/流畅度 +- (void)testFluency { + //耗时任务 + // sleep(1); + dispatch_async(dispatch_get_global_queue(0, 0), ^{ + sleep(1); + }); +} +///测试网络监控 +- (void)testNetworkMonitor { + UIImageView *imageView = [[UIImageView alloc] init]; + imageView.contentMode = UIViewContentModeScaleAspectFit; + [self.view addSubview:imageView]; + [imageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.left.right.bottom.mas_equalTo(0); + }]; + + dispatch_async(dispatch_get_global_queue(0, 0), ^{ + UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://b-ssl.duitang.com/uploads/item/201507/13/20150713182820_5mHce.jpeg"]]]; + dispatch_async(dispatch_get_main_queue(), ^{ + imageView.image = image; + }); + }); +} + +#pragma mark - Events Handle +///改变监听状态 +- (void)changeMonitorState{ + if ([SLAPMManager manager].isMonitoring) { + [[SLAPMManager manager] stopMonitoring]; + }else { + [[SLAPMManager manager] startMonitoring]; + } + [self setupNavigationBar]; +} + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/SLWorkIssuesViewController.h b/iOS_Tips/DarkMode/WorkIssues/SLWorkIssuesViewController.h new file mode 100644 index 00000000..e081a39e --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/SLWorkIssuesViewController.h @@ -0,0 +1,18 @@ +// +// SLWorkIssuesViewController.h +// DarkMode +// +// Created by wsl on 2020/6/11. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///工作中遇到过的问题,踩过的坑 +@interface SLWorkIssuesViewController : UITableViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/SLWorkIssuesViewController.m b/iOS_Tips/DarkMode/WorkIssues/SLWorkIssuesViewController.m new file mode 100644 index 00000000..36910a88 --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/SLWorkIssuesViewController.m @@ -0,0 +1,126 @@ +// +// SLWorkIssuesViewController.m +// DarkMode +// +// Created by wsl on 2020/6/11. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLWorkIssuesViewController.h" +#import "SLMenuViewController.h" +#import "SLWebViewController.h" +#import "SLBinaryResetViewController.h" +#import "SLAPMViewController.h" +#import "SLUnusedResourceViewController.h" +#import "SLScrollviewNesteVC.h" + +@interface SLWorkIssuesViewController () +@property (nonatomic, strong) NSMutableArray *titlesArray; +@property (nonatomic, strong) NSMutableArray *urlArray; +@property (nonatomic, strong) NSMutableArray *classArray; +@end + +@implementation SLWorkIssuesViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUI]; + [self getData]; +} +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.navigationController.navigationBar.hidden = NO; +} +- (BOOL)prefersStatusBarHidden { + return NO; +} + +#pragma mark - UI +- (void)setupUI { + self.navigationItem.title = @"工作中踩过的坑"; + self.navigationController.navigationBar.translucent = YES; + self.tableView.estimatedRowHeight = 1; + [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellID"]; +} + +#pragma mark - Data +- (void)getData { + [self.titlesArray addObjectsFromArray:@[ + @"键盘和UIMenuController不能同时存在的问题", + @"全屏侧滑手势/UIScrollView/UISlider间滑动手势冲突", + @"UITableView/UICollectionView获取特定位置的cell", + @"UIScrollView视觉差动画", + @"iOS 传感器集锦", + @"iOS 自定义转场动画", + @"二进制重排优化启动时间", + @"iOS APM应用性能监控管理(doing)", + @"ipa瘦身之扫描无用资源", + @"多个UIScrollView嵌套"]]; + [self.urlArray addObjectsFromArray:@[@"", + @"https://juejin.im/post/5c0e1e73f265da616413d828", + @"https://juejin.im/post/5c0e1df95188250d2722a3bc", + @"https://juejin.im/post/5c088b45f265da610e7fe156", + @"https://juejin.im/post/5c088a1051882517165dd15d", + @"https://juejin.im/post/5c088ba36fb9a049fb43737b", + @"二进制重排", + @"APM", + @"ipa瘦身", + @"UIScrollView嵌套"]]; + [self.classArray addObjectsFromArray:@[[SLMenuViewController class], + [SLWebViewController class], + [SLWebViewController class], + [SLWebViewController class], + [SLWebViewController class], + [SLWebViewController class], + [SLBinaryResetViewController class], + [SLAPMViewController class], + [SLUnusedResourceViewController class], + [SLScrollviewNesteVC class]]]; + [self.tableView reloadData]; +} + +#pragma mark - Getter +- (NSMutableArray *)titlesArray { + if (_titlesArray == nil) { + _titlesArray = [NSMutableArray array]; + } + return _titlesArray; +} +- (NSMutableArray *)urlArray { + if (!_urlArray) { + _urlArray = [NSMutableArray array]; + } + return _urlArray;; +} +- (NSMutableArray *)classArray { + if (_classArray == nil) { + _classArray = [NSMutableArray array]; + } + return _classArray; +} + +#pragma mark - UITableViewDelegate, UITableViewDataSource +- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.titlesArray.count; +} +- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellID" forIndexPath:indexPath]; + cell.textLabel.numberOfLines = 0; + cell.textLabel.text = [NSString stringWithFormat:@"%ld、%@",(long)indexPath.row,self.titlesArray[indexPath.row]]; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:NO]; + UIViewController *nextVc = [[self.classArray[indexPath.row] alloc] init]; + NSString *urlString = self.urlArray[indexPath.row]; + nextVc.navigationItem.title = self.titlesArray[indexPath.row]; + switch (indexPath.row) { + default: + if (urlString.length > 0 && [urlString hasPrefix:@"http"]) { + ((SLWebViewController *)nextVc).urlString = urlString; + } + [self.navigationController pushViewController:nextVc animated:YES]; + break; + } +} +@end diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLMenuView.h" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLMenuView.h" new file mode 100644 index 00000000..0e9ea39a --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLMenuView.h" @@ -0,0 +1,25 @@ +// +// SLMenuView.h +// DarkMode +// +// Created by wsl on 2020/9/3. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class SLMenuView; +@protocol SLMenuViewDelegate +- (void)menuView:(SLMenuView *)menuView didSelectItemAtIndex:(NSInteger)index; +@end + +@interface SLMenuView : UIView +@property (nonatomic, weak) iddelegate; +@property (nonatomic, strong) NSArray *titles; +@property (nonatomic, assign) NSInteger currentPage; + +@end + +NS_ASSUME_NONNULL_END diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLMenuView.m" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLMenuView.m" new file mode 100644 index 00000000..c240c29e --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLMenuView.m" @@ -0,0 +1,137 @@ +// +// SLMenuView.m +// DarkMode +// +// Created by wsl on 2020/9/3. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLMenuView.h" + + +@interface SLMenuViewCell : UICollectionViewCell +@property (nonatomic, strong) UILabel *titleLabel; +@end +@implementation SLMenuViewCell +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setupUI]; + } + return self; +} +- (void)setupUI { + [self.contentView addSubview:self.titleLabel]; + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(self.contentView); + }]; +} +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [[UILabel alloc] init]; + _titleLabel.textAlignment = NSTextAlignmentCenter; + } + return _titleLabel; +} +@end + +@interface SLMenuView () +@property (nonatomic, strong) UICollectionView *collectionView; +@property (nonatomic, strong) UIView *indicatorView; +@end +@implementation SLMenuView + +#pragma mark - Override +- (void)didMoveToSuperview { + if (self.superview) { + [self setupUI]; + } +} +- (void)didMoveToWindow { + if (self.superview) { + [self setupUI]; + } +} +- (void)layoutSubviews { + self.collectionView.frame = self.bounds; + NSInteger count = self.titles.count == 0 ? 1 : self.titles.count; + self.indicatorView.frame = CGRectMake(_currentPage*self.bounds.size.width/count, self.bounds.size.height-2, self.bounds.size.width/count, 2); +} + +#pragma mark - UI +- (void)setupUI { + [self addSubview:self.collectionView]; + [self addSubview:self.indicatorView]; +} + +#pragma mark - Getter +- (UICollectionView *)collectionView { + if (_collectionView == nil) { + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; + _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + _collectionView.backgroundColor = [UIColor clearColor]; + _collectionView.delegate = self; + _collectionView.dataSource = self; + _collectionView.showsHorizontalScrollIndicator = NO; + [_collectionView registerClass:[SLMenuViewCell class] forCellWithReuseIdentifier:@"ItemId"]; + } + return _collectionView; +} +- (UIView *)indicatorView { + if (!_indicatorView) { + _indicatorView = [[UIView alloc] init]; + _indicatorView.backgroundColor = [UIColor colorWithRed:11/255.0 green:112/255.0 blue:230/255.0 alpha:1.0]; + } + return _indicatorView; +} + +#pragma mark - Setter +- (void)setCurrentPage:(NSInteger)currentPage { + _currentPage = currentPage; + NSInteger count = self.titles.count == 0 ? 1 : self.titles.count; + self.indicatorView.frame = CGRectMake(_currentPage*self.bounds.size.width/count, self.bounds.size.height-2, self.bounds.size.width/count, 2); + [self.collectionView reloadData]; +} + +#pragma mark - UICollectionViewDelegate, UICollectionViewDataSource +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return 1; +} +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.titles.count; +} +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + SLMenuViewCell * item = [collectionView dequeueReusableCellWithReuseIdentifier:@"ItemId" forIndexPath:indexPath]; + item.titleLabel.text = self.titles[indexPath.row]; + if (indexPath.row == self.currentPage) { + item.titleLabel.font = [UIFont boldSystemFontOfSize:18]; + item.titleLabel.textColor = [UIColor colorWithRed:11/255.0 green:112/255.0 blue:230/255.0 alpha:1.0]; + }else { + item.titleLabel.font = [UIFont systemFontOfSize:15]; + item.titleLabel.textColor = [UIColor blackColor]; + } + return item; +} +- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + [collectionView deselectItemAtIndexPath:indexPath animated:NO]; + [self.delegate menuView:self didSelectItemAtIndex:indexPath.row]; +} + +#pragma mark - UICollectionViewDelegateFlowLayout +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + return CGSizeMake(self.bounds.size.width/self.titles.count, self.bounds.size.height); +} +//列间距 +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { + return 0; +} +//行间距 +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { + return 0; +} +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { + return UIEdgeInsetsMake(0, 0, 0, 0); +} + +@end diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLPanTableView.h" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLPanTableView.h" new file mode 100644 index 00000000..94a9c149 --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLPanTableView.h" @@ -0,0 +1,17 @@ +// +// SLPanTableView.h +// DarkMode +// +// Created by wsl on 2020/9/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///此类主要用于TableView的滑动手势向下层传递 +@interface SLPanTableView : UITableView +@end + +NS_ASSUME_NONNULL_END diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLPanTableView.m" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLPanTableView.m" new file mode 100644 index 00000000..919c7534 --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLPanTableView.m" @@ -0,0 +1,16 @@ +// +// SLPanTableView.m +// DarkMode +// +// Created by wsl on 2020/9/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLPanTableView.h" + +@implementation SLPanTableView +//是否允许多个手势识别器共同识别,一个控件的手势识别后是否阻断手势识别继续向下传播,默认返回NO,上层对象识别后则不再继续传播;如果为YES,响应者链上层对象触发手势识别后,如果下层对象也添加了手势并成功识别也会继续执行。 +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + return [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]; +} +@end diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJianShu.h" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJianShu.h" new file mode 100644 index 00000000..f046e1fb --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJianShu.h" @@ -0,0 +1,17 @@ +// +// SLScrollViewJianShu.h +// DarkMode +// +// Created by wsl on 2020/9/15. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SLScrollViewJianShu : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJianShu.m" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJianShu.m" new file mode 100644 index 00000000..b94ff87d --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJianShu.m" @@ -0,0 +1,302 @@ +// +// SLScrollViewJianShu.m +// DarkMode +// +// Created by wsl on 2020/9/15. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLScrollViewJianShu.h" +#import "SLMenuView.h" +#import +#import "SLPanTableView.h" + +///mainScrollView头部高度 +static CGFloat mainScrollViewHeadHeight = 250; +///选项卡/菜单栏高度 +static CGFloat tabHeight = 50; + +@interface SLScrollViewJianShu () + + +@property (nonatomic, strong) UIView *navigationView; +@property (nonatomic, strong) UIScrollView *mainScrollView; +@property (nonatomic, strong) UIImageView *headView; +@property (nonatomic, assign) BOOL isTopHovering; //正在顶部悬停 + +@property (nonatomic, strong) UIView *containerView; +@property (nonatomic, strong) SLMenuView *menuView; +@property (nonatomic, strong) UIScrollView *tabScrollView; + +//默认 20 +@property (nonatomic, assign) NSInteger dataCount; +//滑动到当前子列表时的偏移量,主要处理顶部未悬停且子列表未置顶偏移量不为0时的情况 +@property (nonatomic, assign) CGPoint lastContentOffset; +@end + +@implementation SLScrollViewJianShu + +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + [self setupUI]; + [self getData]; +} +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.navigationController.navigationBar.hidden = YES; +} +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + self.navigationController.navigationBar.hidden = NO; +} + +#pragma mark - UI +- (void)setupUI { + + [self.view addSubview:self.mainScrollView]; + + [self.mainScrollView addSubview:self.headView]; + self.headView.frame = CGRectMake(0, 0, 100, 100); + self.headView.center = CGPointMake(SL_kScreenWidth/2.0, mainScrollViewHeadHeight/2.0); + + self.containerView.frame = CGRectMake(0, mainScrollViewHeadHeight, SL_kScreenWidth, SL_kScreenHeight-SL_TopNavigationBarHeight); + [self.mainScrollView addSubview:self.containerView]; + self.mainScrollView.contentSize = CGSizeMake(SL_kScreenWidth, mainScrollViewHeadHeight+self.containerView.sl_height); + + [self.containerView addSubview:self.menuView]; + [self.menuView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.top.mas_equalTo(0); + make.height.mas_equalTo(tabHeight); + }]; + self.menuView.titles = @[@"你好",@"我好",@"大家好"]; + self.menuView.currentPage = 0; + + [self.containerView addSubview:self.tabScrollView]; + [self.tabScrollView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.bottom.mas_equalTo(self.containerView); + make.top.mas_equalTo(tabHeight); + }]; + [self.containerView layoutIfNeeded]; + self.tabScrollView.contentSize = CGSizeMake(SL_kScreenWidth*self.menuView.titles.count,self.tabScrollView.frame.size.height); + + for (int i = 0; i < self.menuView.titles.count; i++) { + SLPanTableView *tableView = [self subTableView]; + tableView.tag = 10+i; + tableView.frame = CGRectMake(i*self.tabScrollView.sl_width, 0, self.tabScrollView.sl_width, self.tabScrollView.sl_height); + [self.tabScrollView addSubview:tableView]; + } + + [self.view addSubview:self.navigationView]; +} + +#pragma mark - Data +- (void)getData { + self.dataCount = 20; + [[self currentSubListTabView].mj_header beginRefreshing]; +} + +#pragma mark - Getter +- (UIView *)navigationView { + if (!_navigationView) { + _navigationView = [[UIView alloc] initWithFrame:CGRectMake(0, 0,SL_kScreenWidth , SL_TopNavigationBarHeight)]; + _navigationView.backgroundColor = [UIColor clearColor]; + UIButton *nav_return_white = [[UIButton alloc] init]; + [nav_return_white setImage:[UIImage imageNamed:@"nav_return_white"] forState:UIControlStateNormal]; + [nav_return_white addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside]; + [_navigationView addSubview:nav_return_white]; + [nav_return_white mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(10); + make.size.mas_equalTo(CGSizeMake(15, 20)); + make.bottom.mas_equalTo(-10); + }]; + } + return _navigationView; +} +- (UIScrollView *)mainScrollView { + if (!_mainScrollView) { + _mainScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; + _mainScrollView.delegate = self; + _mainScrollView.bounces = YES; + _mainScrollView.showsVerticalScrollIndicator = NO; + _mainScrollView.backgroundColor = [UIColor colorWithRed:11/255.0 green:112/255.0 blue:230/255.0 alpha:1.0]; + if (@available(iOS 11.0, *)) { + _mainScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + } + return _mainScrollView; +} +- (UIImageView *)headView { + if (!_headView) { + _headView = [[UIImageView alloc] init]; + _headView.image = [UIImage imageNamed:@"wsl"]; + _headView.contentMode = UIViewContentModeScaleAspectFit; + _headView.layer.cornerRadius = 50; + _headView.clipsToBounds = YES; + } + return _headView; +} +- (UIView *)containerView { + if (!_containerView) { + _containerView = [[UIView alloc] init]; + _containerView.backgroundColor = [UIColor redColor]; + } + return _containerView; +} +- (SLMenuView *)menuView { + if (!_menuView) { + _menuView = [[SLMenuView alloc] init]; + _menuView.backgroundColor = [UIColor colorWithRed:248/255.0 green:248/255.0 blue:248/255.0 alpha:1.0]; + _menuView.delegate = self; + _menuView.layer.borderWidth = 1.0; + _menuView.layer.borderColor = [UIColor colorWithRed:228/255.0 green:228/255.0 blue:228/255.0 alpha:1.0].CGColor; + } + return _menuView; +} +- (UIScrollView *)tabScrollView { + if (!_tabScrollView) { + _tabScrollView = [[UIScrollView alloc] init]; + _tabScrollView.backgroundColor = [UIColor blueColor]; + _tabScrollView.pagingEnabled = YES; + _tabScrollView.delegate = self; + _tabScrollView.bounces = NO; + if (@available(iOS 11.0, *)) { + _tabScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + } + return _tabScrollView; +} +- (SLPanTableView *)subTableView { + SLPanTableView *tableView = [[SLPanTableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped]; + tableView.delegate = self; + tableView.dataSource = self; + tableView.estimatedRowHeight = 0; + if (@available(iOS 11.0, *)) { + tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellId"]; + SL_WeakSelf; + tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.dataCount = 20; + [tableView reloadData]; + [tableView.mj_header endRefreshing]; + }); + }]; + tableView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.dataCount += 20; + [tableView reloadData]; + [tableView.mj_footer endRefreshing]; + }); + }]; + return tableView; +} + +#pragma mark - EventsHandle +- (void)back { + [self.navigationController popViewControllerAnimated:YES]; +} + +#pragma mark - HelpMethods +///当前子列表 +- (SLPanTableView *)currentSubListTabView { + return [self.tabScrollView viewWithTag:10+self.menuView.currentPage]; +} + +#pragma mark - UITableViewDelegate,UITableViewDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.dataCount; +} +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 88; +} +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 0.1; +} +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + return nil; +} +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + return 0.1; +} +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { + return nil; +} +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellId" forIndexPath:indexPath]; + cell.textLabel.text = [NSString stringWithFormat:@"%ld",indexPath.row]; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +#pragma mark - SLMenuViewDelegate +- (void)menuView:(SLMenuView *)menuView didSelectItemAtIndex:(NSInteger)index { + [self.tabScrollView setContentOffset:CGPointMake(index* self.tabScrollView.sl_width, 0) animated:YES]; +} + +#pragma mark - UIScrollViewDelegate +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + + if (scrollView == self.tabScrollView) return; + + if (scrollView == self.mainScrollView) { + if (self.mainScrollView.contentOffset.y >= mainScrollViewHeadHeight-SL_TopNavigationBarHeight || _isTopHovering) { + //滑到顶部悬停 + _isTopHovering = YES; + self.mainScrollView.contentOffset = CGPointMake(0, mainScrollViewHeadHeight-SL_TopNavigationBarHeight); + } + if(_isTopHovering) { + self.navigationController.navigationBar.hidden = NO; + }else { + self.navigationController.navigationBar.hidden = YES; + } + + if (self.mainScrollView.contentOffset.y <= 0 ) { + //头部放大 + self.headView.transform = CGAffineTransformMakeScale(fabs(self.mainScrollView.contentOffset.y)/self.headView.sl_height+1, fabs(self.mainScrollView.contentOffset.y)/self.headView.sl_height+1); + } + } + + //子列表 + if (scrollView.superview == self.tabScrollView) { + if((!_isTopHovering && self.mainScrollView.contentOffset.y > 0) || (self.mainScrollView.contentOffset.y < 0 ) ) { + //如果主mainScrollView还未到顶部悬停,则选项子列表subTableView偏移量保持不变 + if (scrollView.contentOffset.y < 0) { + self.lastContentOffset = CGPointZero; + } + scrollView.contentOffset = self.lastContentOffset; + } + if(_isTopHovering && scrollView.contentOffset.y > 0) { + //如果主mainScrollView在顶部悬停,而此时选项子列表subTableView还未到顶部,则保持mainScrollView继续悬停 + self.mainScrollView.contentOffset = CGPointMake(0, mainScrollViewHeadHeight-SL_TopNavigationBarHeight); + } + if (scrollView.contentOffset.y < 0) { + //如果subTableView滑动到了顶部,即将取消顶部悬停 + _isTopHovering = NO; + } + } +} +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + if (scrollView == self.tabScrollView) { + self.menuView.currentPage = roundf(self.tabScrollView.contentOffset.x/self.tabScrollView.sl_width); + self.lastContentOffset = [self currentSubListTabView].contentOffset; + } +} +- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { + if (scrollView == self.tabScrollView) { + self.menuView.currentPage = roundf(self.tabScrollView.contentOffset.x/self.tabScrollView.sl_width); + self.lastContentOffset = [self currentSubListTabView].contentOffset; + } +} + +@end diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJuejin.h" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJuejin.h" new file mode 100644 index 00000000..88bffbe2 --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJuejin.h" @@ -0,0 +1,18 @@ +// +// SLScrollViewJuejin.h +// DarkMode +// +// Created by wsl on 2020/9/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///掘金APP个人中心页的ScrollView嵌套样式 +@interface SLScrollViewJuejin : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJuejin.m" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJuejin.m" new file mode 100644 index 00000000..18bb53ad --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewJuejin.m" @@ -0,0 +1,298 @@ +// +// SLScrollViewJuejin.m +// DarkMode +// +// Created by wsl on 2020/9/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLScrollViewJuejin.h" +#import "SLMenuView.h" +#import +#import "SLPanTableView.h" + +///mainScrollView头部高度 +static CGFloat mainScrollViewHeadHeight = 250; +///选项卡/菜单栏高度 +static CGFloat tabHeight = 50; + +@interface SLScrollViewJuejin () + +@property (nonatomic, strong) UIView *navigationView; +@property (nonatomic, strong) UIScrollView *mainScrollView; +@property (nonatomic, strong) UIImageView *headView; +@property (nonatomic, assign) BOOL isTopHovering; //正在顶部悬停 + +@property (nonatomic, strong) UIView *containerView; +@property (nonatomic, strong) SLMenuView *menuView; +@property (nonatomic, strong) UIScrollView *tabScrollView; + +//默认 20 +@property (nonatomic, assign) NSInteger dataCount; +//滑动到当前子列表时的偏移量,主要处理顶部未悬停且子列表未置顶偏移量不为0时的情况 +@property (nonatomic, assign) CGPoint lastContentOffset; + +@end + +@implementation SLScrollViewJuejin + +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + [self setupUI]; + [self getData]; +} +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.navigationController.navigationBar.hidden = YES; +} +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + self.navigationController.navigationBar.hidden = NO; +} + +#pragma mark - UI +- (void)setupUI { + + [self.view addSubview:self.mainScrollView]; + + [self.mainScrollView addSubview:self.headView]; + self.headView.frame = CGRectMake(0, 0, 100, 100); + self.headView.center = CGPointMake(SL_kScreenWidth/2.0, mainScrollViewHeadHeight/2.0); + + self.containerView.frame = CGRectMake(0, mainScrollViewHeadHeight, SL_kScreenWidth, SL_kScreenHeight-SL_TopNavigationBarHeight); + [self.mainScrollView addSubview:self.containerView]; + self.mainScrollView.contentSize = CGSizeMake(SL_kScreenWidth, mainScrollViewHeadHeight+self.containerView.sl_height); + + [self.containerView addSubview:self.menuView]; + [self.menuView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.top.mas_equalTo(0); + make.height.mas_equalTo(tabHeight); + }]; + self.menuView.titles = @[@"你好",@"我好",@"大家好"]; + self.menuView.currentPage = 0; + + [self.containerView addSubview:self.tabScrollView]; + [self.tabScrollView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.bottom.mas_equalTo(self.containerView); + make.top.mas_equalTo(tabHeight); + }]; + [self.containerView layoutIfNeeded]; + self.tabScrollView.contentSize = CGSizeMake(SL_kScreenWidth*self.menuView.titles.count,self.tabScrollView.frame.size.height); + + for (int i = 0; i < self.menuView.titles.count; i++) { + SLPanTableView *tableView = [self subTableView]; + tableView.tag = 10+i; + tableView.frame = CGRectMake(i*self.tabScrollView.sl_width, 0, self.tabScrollView.sl_width, self.tabScrollView.sl_height); + [self.tabScrollView addSubview:tableView]; + } + + [self.view addSubview:self.navigationView]; +} + +#pragma mark - Data +- (void)getData { + self.dataCount = 20; + [[self currentSubListTabView].mj_header beginRefreshing]; +} + +#pragma mark - Getter +- (UIView *)navigationView { + if (!_navigationView) { + _navigationView = [[UIView alloc] initWithFrame:CGRectMake(0, 0,SL_kScreenWidth , SL_TopNavigationBarHeight)]; + _navigationView.backgroundColor = [UIColor clearColor]; + UIButton *nav_return_white = [[UIButton alloc] init]; + [nav_return_white setImage:[UIImage imageNamed:@"nav_return_white"] forState:UIControlStateNormal]; + [nav_return_white addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside]; + [_navigationView addSubview:nav_return_white]; + [nav_return_white mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(10); + make.size.mas_equalTo(CGSizeMake(15, 20)); + make.bottom.mas_equalTo(-10); + }]; + } + return _navigationView; +} +- (UIScrollView *)mainScrollView { + if (!_mainScrollView) { + _mainScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; + _mainScrollView.delegate = self; + _mainScrollView.bounces = NO; + _mainScrollView.showsVerticalScrollIndicator = NO; + _mainScrollView.backgroundColor = [UIColor colorWithRed:11/255.0 green:112/255.0 blue:230/255.0 alpha:1.0]; + if (@available(iOS 11.0, *)) { + _mainScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + } + return _mainScrollView; +} +- (UIImageView *)headView { + if (!_headView) { + _headView = [[UIImageView alloc] init]; + _headView.image = [UIImage imageNamed:@"wsl"]; + _headView.contentMode = UIViewContentModeScaleAspectFit; + _headView.layer.cornerRadius = 50; + _headView.clipsToBounds = YES; + } + return _headView; +} +- (UIView *)containerView { + if (!_containerView) { + _containerView = [[UIView alloc] init]; + _containerView.backgroundColor = [UIColor redColor]; + } + return _containerView; +} +- (SLMenuView *)menuView { + if (!_menuView) { + _menuView = [[SLMenuView alloc] init]; + _menuView.backgroundColor = [UIColor colorWithRed:248/255.0 green:248/255.0 blue:248/255.0 alpha:1.0]; + _menuView.delegate = self; + _menuView.layer.borderWidth = 1.0; + _menuView.layer.borderColor = [UIColor colorWithRed:228/255.0 green:228/255.0 blue:228/255.0 alpha:1.0].CGColor; + } + return _menuView; +} +- (UIScrollView *)tabScrollView { + if (!_tabScrollView) { + _tabScrollView = [[UIScrollView alloc] init]; + _tabScrollView.backgroundColor = [UIColor blueColor]; + _tabScrollView.pagingEnabled = YES; + _tabScrollView.delegate = self; + _tabScrollView.bounces = NO; + if (@available(iOS 11.0, *)) { + _tabScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + } + return _tabScrollView; +} +- (SLPanTableView *)subTableView { + SLPanTableView *tableView = [[SLPanTableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped]; + tableView.delegate = self; + tableView.dataSource = self; + tableView.estimatedRowHeight = 0; + if (@available(iOS 11.0, *)) { + tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellId"]; + SL_WeakSelf; + tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.dataCount = 20; + [tableView reloadData]; + [tableView.mj_header endRefreshing]; + }); + }]; + tableView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.dataCount += 20; + [tableView reloadData]; + [tableView.mj_footer endRefreshing]; + }); + }]; + return tableView; +} + +#pragma mark - EventsHandle +- (void)back { + [self.navigationController popViewControllerAnimated:YES]; +} + +#pragma mark - HelpMethods +///当前子列表 +- (SLPanTableView *)currentSubListTabView { + return [self.tabScrollView viewWithTag:10+self.menuView.currentPage]; +} + +#pragma mark - UITableViewDelegate,UITableViewDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.dataCount; +} +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 88; +} +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 0.1; +} +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + return nil; +} +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + return 0.1; +} +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { + return nil; +} +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellId" forIndexPath:indexPath]; + cell.textLabel.text = [NSString stringWithFormat:@"%ld",indexPath.row]; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +#pragma mark - SLMenuViewDelegate +- (void)menuView:(SLMenuView *)menuView didSelectItemAtIndex:(NSInteger)index { + [self.tabScrollView setContentOffset:CGPointMake(index* self.tabScrollView.sl_width, 0) animated:YES]; +} + +#pragma mark - UIScrollViewDelegate +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + + if (scrollView == self.tabScrollView) return; + + if (scrollView == self.mainScrollView) { + if (self.mainScrollView.contentOffset.y >= mainScrollViewHeadHeight-SL_TopNavigationBarHeight || _isTopHovering) { + //滑到顶部悬停 + _isTopHovering = YES; + self.mainScrollView.contentOffset = CGPointMake(0, mainScrollViewHeadHeight-SL_TopNavigationBarHeight); + } + if(_isTopHovering) { + self.navigationController.navigationBar.hidden = NO; + }else { + self.navigationController.navigationBar.hidden = YES; + } + } + + //子列表 + if (scrollView.superview == self.tabScrollView) { + if(!_isTopHovering && self.mainScrollView.contentOffset.y > 0) { + //如果主mainScrollView还未到顶部悬停,则选项子列表subTableView偏移量保持不变 + if (scrollView.contentOffset.y < 0) { + self.lastContentOffset = CGPointZero; + } + scrollView.contentOffset = self.lastContentOffset; + } + if(_isTopHovering && scrollView.contentOffset.y > 0) { + //如果主mainScrollView在顶部悬停,而此时选项子列表subTableView还未到顶部,则保持mainScrollView继续悬停 + self.mainScrollView.contentOffset = CGPointMake(0, mainScrollViewHeadHeight-SL_TopNavigationBarHeight); + } + if (scrollView.contentOffset.y < 0) { + //如果subTableView滑动到了顶部,即将取消顶部悬停 + _isTopHovering = NO; + } + } +} +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + if (scrollView == self.tabScrollView) { + self.menuView.currentPage = roundf(self.tabScrollView.contentOffset.x/self.tabScrollView.sl_width); + self.lastContentOffset = [self currentSubListTabView].contentOffset; + } +} +- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { + if (scrollView == self.tabScrollView) { + self.menuView.currentPage = roundf(self.tabScrollView.contentOffset.x/self.tabScrollView.sl_width); + self.lastContentOffset = [self currentSubListTabView].contentOffset; + } +} + +@end + diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewWeibo.h" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewWeibo.h" new file mode 100644 index 00000000..20a7330e --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewWeibo.h" @@ -0,0 +1,18 @@ +// +// SLScrollViewWeibo.h +// DarkMode +// +// Created by wsl on 2020/9/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///微博发现页ScrollView嵌套样式 +@interface SLScrollViewWeibo : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewWeibo.m" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewWeibo.m" new file mode 100644 index 00000000..5261431c --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollViewWeibo.m" @@ -0,0 +1,299 @@ +// +// SLScrollViewWeibo.m +// DarkMode +// +// Created by wsl on 2020/9/8. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLScrollViewWeibo.h" +#import "SLMenuView.h" +#import +#import "SLPanTableView.h" + +///mainScrollView头部高度 +static CGFloat mainScrollViewHeadHeight = 250; +///选项卡/菜单栏高度 +static CGFloat tabHeight = 64; + +@interface SLScrollViewWeibo () + +@property (nonatomic, strong) UIView *navigationView; +@property (nonatomic, strong) UIScrollView *mainScrollView; +@property (nonatomic, strong) UIImageView *headView; +@property (nonatomic, assign) BOOL isTopHovering; //正在顶部悬停 + +@property (nonatomic, strong) UIView *containerView; +@property (nonatomic, strong) SLMenuView *menuView; +@property (nonatomic, strong) UIScrollView *tabScrollView; + +@property (nonatomic, assign) NSInteger dataCount; //默认 20 + +@end + +@implementation SLScrollViewWeibo + +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + [self getData]; + [self setupUI]; +} +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.navigationController.navigationBar.hidden = YES; +} +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + self.navigationController.navigationBar.hidden = NO; +} + +#pragma mark - UI +- (void)setupUI { + + [self.view addSubview:self.mainScrollView]; + + [self.mainScrollView addSubview:self.headView]; + self.headView.frame = CGRectMake(0, 0, 100, 100); + self.headView.center = CGPointMake(SL_kScreenWidth/2.0, mainScrollViewHeadHeight/2.0); + + self.containerView.frame = CGRectMake(0, mainScrollViewHeadHeight, SL_kScreenWidth, SL_kScreenHeight); + [self.mainScrollView addSubview:self.containerView]; + self.mainScrollView.contentSize = CGSizeMake(SL_kScreenWidth, mainScrollViewHeadHeight+self.containerView.sl_height); + + [self.containerView addSubview:self.menuView]; + [self.menuView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.top.mas_equalTo(0); + make.height.mas_equalTo(tabHeight); + }]; + self.menuView.titles = @[@"你好",@"我好",@"大家好"]; + self.menuView.currentPage = 0; + + [self.containerView addSubview:self.tabScrollView]; + [self.tabScrollView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.right.bottom.mas_equalTo(self.containerView); + make.top.mas_equalTo(tabHeight); + }]; + [self.containerView layoutIfNeeded]; + self.tabScrollView.contentSize = CGSizeMake(SL_kScreenWidth*self.menuView.titles.count,self.tabScrollView.frame.size.height); + + for (int i = 0; i < self.menuView.titles.count; i++) { + UITableView *tableView = [self subTableView]; + tableView.tag = 10+i; + tableView.scrollEnabled = NO; + tableView.frame = CGRectMake(i*self.tabScrollView.sl_width, 0, self.tabScrollView.sl_width, self.tabScrollView.sl_height); + [self.tabScrollView addSubview:tableView]; + } + + [self.view addSubview:self.navigationView]; +} + +#pragma mark - Data +- (void)getData { + self.dataCount = 20; +} + +#pragma mark - Getter +- (UIView *)navigationView { + if (!_navigationView) { + _navigationView = [[UIView alloc] initWithFrame:CGRectMake(10,0,15 , SL_TopNavigationBarHeight)]; + _navigationView.backgroundColor = [UIColor clearColor]; + UIButton *nav_return_white = [[UIButton alloc] init]; + [nav_return_white setImage:[UIImage imageNamed:@"nav_return_white"] forState:UIControlStateNormal]; + [nav_return_white addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside]; + [_navigationView addSubview:nav_return_white]; + [nav_return_white mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.mas_equalTo(0); + make.size.mas_equalTo(CGSizeMake(15, 20)); + make.bottom.mas_equalTo(-20); + }]; + } + return _navigationView; +} +- (UIScrollView *)mainScrollView { + if (!_mainScrollView) { + _mainScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; + _mainScrollView.delegate = self; + _mainScrollView.showsVerticalScrollIndicator = NO; + _mainScrollView.backgroundColor = [UIColor colorWithRed:11/255.0 green:112/255.0 blue:230/255.0 alpha:1.0]; + if (@available(iOS 11.0, *)) { + _mainScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + SL_WeakSelf + _mainScrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.dataCount = 20; + [weakSelf.mainScrollView.mj_header endRefreshing]; + }); + }]; + } + return _mainScrollView; +} +- (UIImageView *)headView { + if (!_headView) { + _headView = [[UIImageView alloc] init]; + _headView.image = [UIImage imageNamed:@"wsl"]; + _headView.contentMode = UIViewContentModeScaleAspectFit; + _headView.layer.cornerRadius = 50; + _headView.clipsToBounds = YES; + } + return _headView; +} +- (UIView *)containerView { + if (!_containerView) { + _containerView = [[UIView alloc] init]; + _containerView.backgroundColor = [UIColor redColor]; + } + return _containerView; +} +- (SLMenuView *)menuView { + if (!_menuView) { + _menuView = [[SLMenuView alloc] init]; + _menuView.backgroundColor = [UIColor orangeColor]; + _menuView.delegate = self; + _menuView.layer.borderWidth = 1.0; + _menuView.layer.borderColor = [UIColor colorWithRed:228/255.0 green:228/255.0 blue:228/255.0 alpha:1.0].CGColor; + } + return _menuView; +} +- (UIScrollView *)tabScrollView { + if (!_tabScrollView) { + _tabScrollView = [[UIScrollView alloc] init]; + _tabScrollView.backgroundColor = [UIColor blueColor]; + _tabScrollView.pagingEnabled = YES; + _tabScrollView.delegate = self; + _tabScrollView.bounces = NO; + if (@available(iOS 11.0, *)) { + _tabScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + } + return _tabScrollView; +} +- (UITableView *)subTableView { + UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped]; + tableView.delegate = self; + tableView.dataSource = self; + tableView.estimatedRowHeight = 0; + if (@available(iOS 11.0, *)) { + tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } else { + } + [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellId"]; + SL_WeakSelf; + tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.dataCount = 20; + [tableView reloadData]; + [tableView.mj_header endRefreshing]; + }); + }]; + tableView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + weakSelf.dataCount += 20; + [tableView reloadData]; + [tableView.mj_footer endRefreshing]; + }); + }]; + return tableView; +} + +#pragma mark - EventsHandle +- (void)back { + if (self.isTopHovering) { + [self.mainScrollView setContentOffset:CGPointZero animated:YES]; + }else { + [self.navigationController popViewControllerAnimated:YES]; + } +} + +#pragma mark - HelpMethods +///当前子列表 +- (SLPanTableView *)currentSubListTabView { + return [self.tabScrollView viewWithTag:10+self.menuView.currentPage]; +} + +#pragma mark - UITableViewDelegate,UITableViewDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.dataCount; +} +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 88; +} +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return 0.1; +} +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + return nil; +} +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + return 0.1; +} +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { + return nil; +} +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellId" forIndexPath:indexPath]; + cell.textLabel.text = [NSString stringWithFormat:@"%ld",indexPath.row]; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +#pragma mark - SLMenuViewDelegate +- (void)menuView:(SLMenuView *)menuView didSelectItemAtIndex:(NSInteger)index { + [self.tabScrollView setContentOffset:CGPointMake(index* self.tabScrollView.sl_width, 0) animated:YES]; +} + +#pragma mark - UIScrollViewDelegate +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + + if (scrollView == self.tabScrollView) return; + + if (scrollView == self.mainScrollView) { + if (self.mainScrollView.contentOffset.y >= mainScrollViewHeadHeight && !_isTopHovering) { + //滑到顶部悬停 + _isTopHovering = YES; + self.mainScrollView.scrollEnabled = NO; + self.mainScrollView.bounces = NO; + self.mainScrollView.contentOffset = CGPointMake(0, mainScrollViewHeadHeight); + for (int i = 0; i < self.menuView.titles.count; i++) { + UIView *subView = [self.tabScrollView viewWithTag:10+i]; + if ([subView isKindOfClass:[UITableView class]]) { + [(UITableView *)subView setScrollEnabled:YES]; + } + } + } + } +} +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + if (scrollView == self.tabScrollView) { + self.menuView.currentPage = roundf(self.tabScrollView.contentOffset.x/self.tabScrollView.sl_width); + } +} +- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { + if (scrollView == self.tabScrollView) { + self.menuView.currentPage = roundf(self.tabScrollView.contentOffset.x/self.tabScrollView.sl_width); + } + + if (scrollView == self.mainScrollView && self.mainScrollView.contentOffset.y != mainScrollViewHeadHeight) { + _isTopHovering = NO; + self.mainScrollView.scrollEnabled = YES; + self.mainScrollView.bounces = YES; + for (int i = 0; i < self.menuView.titles.count; i++) { + UIView *subView = [self.tabScrollView viewWithTag:10+i]; + if ([subView isKindOfClass:[UITableView class]]) { + [(UITableView *)subView setScrollEnabled:NO]; + [(UITableView *)subView setContentOffset:CGPointZero]; + } + } + } +} + +@end diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollviewNesteVC.h" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollviewNesteVC.h" new file mode 100644 index 00000000..4491ab4a --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollviewNesteVC.h" @@ -0,0 +1,18 @@ +// +// SLScrollviewNesteVC.h +// DarkMode +// +// Created by wsl on 2020/9/2. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///多个ScrollView/TableView/CollectionView嵌套 +@interface SLScrollviewNesteVC : UITableViewController + +@end + +NS_ASSUME_NONNULL_END diff --git "a/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollviewNesteVC.m" "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollviewNesteVC.m" new file mode 100644 index 00000000..8b9b7807 --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/ScrollView\345\265\214\345\245\227/SLScrollviewNesteVC.m" @@ -0,0 +1,84 @@ +// +// SLScrollviewNesteVC.m +// DarkMode +// +// Created by wsl on 2020/9/2. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLScrollviewNesteVC.h" +#import "SLScrollViewJuejin.h" +#import "SLScrollViewWeibo.h" +#import "SLScrollViewJianShu.h" + +@interface SLScrollviewNesteVC () +@property (nonatomic, strong) NSMutableArray *titlesArray; +@property (nonatomic, strong) NSMutableArray *classArray; +@end + +@implementation SLScrollviewNesteVC + +- (void)viewDidLoad { + [super viewDidLoad]; + [self setupUI]; + [self getData]; +} +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.navigationController.navigationBar.hidden = NO; +} +- (BOOL)prefersStatusBarHidden { + return NO; +} + +#pragma mark - UI +- (void)setupUI { + self.navigationController.navigationBar.translucent = YES; + self.tableView.estimatedRowHeight = 1; + [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellID"]; +} + +#pragma mark - Data +- (void)getData { + [self.titlesArray addObjectsFromArray:@[ + @"掘金APP个人中心页样式", + @"微博发现页ScrollView嵌套样式", + @"简书APP个人中心页样式"]]; + [self.classArray addObjectsFromArray:@[[SLScrollViewJuejin class], + [SLScrollViewWeibo class], + [SLScrollViewJianShu class]]]; + [self.tableView reloadData]; +} + +#pragma mark - Getter +- (NSMutableArray *)titlesArray { + if (_titlesArray == nil) { + _titlesArray = [NSMutableArray array]; + } + return _titlesArray; +} +- (NSMutableArray *)classArray { + if (_classArray == nil) { + _classArray = [NSMutableArray array]; + } + return _classArray; +} + +#pragma mark - UITableViewDelegate, UITableViewDataSource +- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.titlesArray.count; +} +- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellID" forIndexPath:indexPath]; + cell.textLabel.numberOfLines = 0; + cell.textLabel.text = [NSString stringWithFormat:@"%ld、%@",(long)indexPath.row,self.titlesArray[indexPath.row]]; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:NO]; + UIViewController *nextVc = [[self.classArray[indexPath.row] alloc] init]; + nextVc.navigationItem.title = self.titlesArray[indexPath.row]; + [self.navigationController pushViewController:nextVc animated:YES]; +} + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/UIMenuController/SLMenuViewController.h b/iOS_Tips/DarkMode/WorkIssues/UIMenuController/SLMenuViewController.h new file mode 100644 index 00000000..4adf669c --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/UIMenuController/SLMenuViewController.h @@ -0,0 +1,17 @@ +// +// SLMenuViewController.h +// DarkMode +// +// Created by wsl on 2020/3/9. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SLMenuViewController : UIViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS_Tips/DarkMode/WorkIssues/UIMenuController/SLMenuViewController.m b/iOS_Tips/DarkMode/WorkIssues/UIMenuController/SLMenuViewController.m new file mode 100644 index 00000000..4d40e5bf --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/UIMenuController/SLMenuViewController.m @@ -0,0 +1,111 @@ +// +// SLMenuViewController.m +// DarkMode +// +// Created by wsl on 2020/3/9. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLMenuViewController.h" + + +@interface SLTextView : UITextView +@property (nonatomic, weak) UIResponder *overrideNextResponder; //覆盖下一个响应者 +@end +@implementation SLTextView + +- (UIResponder *)nextResponder { + if(_overrideNextResponder == nil){ + return [super nextResponder]; + } else { + return _overrideNextResponder; + } +} +// UIMenuController 菜单可以执行操作 +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { + if (_overrideNextResponder != nil) { + return NO; + } + return [super canPerformAction:action withSender:sender]; +} +@end + +@interface SLLable : UILabel +@end +@implementation SLLable +// UIMenuController 菜单可以执行操作 +-(BOOL)canPerformAction:(SEL)action withSender:(id)sender { + if (action == @selector(save:) || + action == @selector(note:) || + action == @selector(copy:)) { + return YES; + } + return NO; +} +// 能否成为第一响应者 +- (BOOL)canBecomeFirstResponder { + return YES; +} +- (void)note:(id)sender { + +} +- (void)save:(id)sender { + +} +- (void)copy:(id)sender { + +} +@end + + +@interface SLMenuViewController () +@property (weak, nonatomic) IBOutlet SLTextView *textView; +@property (weak, nonatomic) IBOutlet SLLable *titleLabel; +@end + +@implementation SLMenuViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.title = @"键盘和UIMenuController并存解决"; + UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressShowMenuView:)]; + [self.titleLabel addGestureRecognizer:longPressGestureRecognizer]; +} + +- (void)dealloc { + +} + +//长按显示菜单 UIMenuController +- (void)longPressShowMenuView:(UILongPressGestureRecognizer *)longPress { + //编辑过程中,self.textView是第一响应者 + if(self.textView.isFirstResponder){ + //如果textView是第一响应者,则对titleLabel进行响应链透传,覆盖self.textView的下一个响应者 + self.textView.overrideNextResponder = self.titleLabel; + //添加菜单隐藏的监听,当菜单隐藏时,要重置self.textView.overrideNextResponder = nil + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuViewDidHide:) name:UIMenuControllerDidHideMenuNotification object:nil]; + }else { + //如果当前无第一响应者,就成为第一响应者 + [self.titleLabel becomeFirstResponder]; + } + + UIMenuController *menuController = [UIMenuController sharedMenuController]; + UIMenuItem *saveItems = [[UIMenuItem alloc] initWithTitle:@"保存" action:@selector(save:)]; + UIMenuItem *noteItem = [[UIMenuItem alloc] initWithTitle:@"笔记" action:@selector(note:)]; + menuController.menuItems = @[noteItem, saveItems]; + if (@available(iOS 13.0, *)) { + [menuController showMenuFromView:self.view rect:self.titleLabel.frame]; + } else { + [menuController setTargetRect:self.titleLabel.frame inView:self.view]; + [menuController setMenuVisible:YES animated:YES]; + } +} + +// 隐藏菜单UIMenuController的通知 +- (void)menuViewDidHide:(NSNotification*)notification { + //重置,不影响原有的响应链 + self.textView.overrideNextResponder = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerDidHideMenuNotification object:nil]; +} + +@end diff --git a/iOS_Tips/DarkMode/WorkIssues/UIMenuController/SLMenuViewController.xib b/iOS_Tips/DarkMode/WorkIssues/UIMenuController/SLMenuViewController.xib new file mode 100644 index 00000000..d4888635 --- /dev/null +++ b/iOS_Tips/DarkMode/WorkIssues/UIMenuController/SLMenuViewController.xib @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/Order File.png" "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/Order File.png" new file mode 100644 index 00000000..d183b372 Binary files /dev/null and "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/Order File.png" differ diff --git "a/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/Other c Flags.png" "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/Other c Flags.png" new file mode 100644 index 00000000..2e1273d0 Binary files /dev/null and "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/Other c Flags.png" differ diff --git "a/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/SLBinaryResetViewController.h" "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/SLBinaryResetViewController.h" new file mode 100644 index 00000000..23c47e97 --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/SLBinaryResetViewController.h" @@ -0,0 +1,20 @@ +// +// SLBinaryResetViewController.h +// DarkMode +// +// Created by wsl on 2020/7/6. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +//参考:https://juejin.cn/post/6844904130406793224 +// https://github.com/rhythmkay/PGOAnalyzer +///二进制重排优化启动时间 +@interface SLBinaryResetViewController : SLViewController + +@end + +NS_ASSUME_NONNULL_END diff --git "a/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/SLBinaryResetViewController.m" "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/SLBinaryResetViewController.m" new file mode 100644 index 00000000..5893b7e9 --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/SLBinaryResetViewController.m" @@ -0,0 +1,136 @@ +// +// SLBinaryResetViewController.m +// DarkMode +// +// Created by wsl on 2020/7/6. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLBinaryResetViewController.h" +#import +#import + +static BOOL isBecomeActive = NO; //是否启动完成,即首页渲染完毕 +@interface SLBinaryResetViewController () +@property (nonatomic, strong) UITextView *textView; +@end + +@implementation SLBinaryResetViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = UIColor.whiteColor; + self.navigationItem.title = @"二进制重排优化启动时间"; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"生成Order文件" style:UIBarButtonItemStyleDone target:self action:@selector(getOrderFile)]; + self.textView = [[UITextView alloc] initWithFrame:self.view.bounds]; + self.textView.editable = NO; + [self.view addSubview:self.textView]; +} +///获取启动加载时执行的排序后的所有函数符号,得到顺序执行的函数符号后,这些配置和clang插桩代码就可以删除了(除了Order File配置) +- (void)getOrderFile{ + NSMutableArray * symbolNames = [NSMutableArray array]; + while (YES) { + //offsetof 就是针对某个结构体找到某个属性相对这个结构体的偏移量 + //出队,依次取出启动时执行的方法 + SLSymbolNode *node = OSAtomicDequeue(&symbolList, offsetof(SLSymbolNode, next)); + if (node == NULL) { + break; + } + Dl_info info; + dladdr(node->pc, &info); + //根据内存地址获取函数名称 + NSString * name = @(info.dli_sname); + BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["]; + NSString * symbolName = isObjc ? name: [@"_" stringByAppendingString:name]; + [symbolNames addObject:symbolName]; + // NSLog(@"%@",symbolName); + } + //取反 + NSEnumerator * emt = [symbolNames reverseObjectEnumerator]; + //去重 + NSMutableArray *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count]; + NSString * name; + while (name = [emt nextObject]) { + if (![funcs containsObject:name]) { + [funcs addObject:name]; + } + } + //干掉自己! + [funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]]; + //将数组变成字符串 + NSString * funcStr = [funcs componentsJoinedByString:@"\n"]; + //写入 + NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"wsl.order"]; + NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding]; + BOOL result = [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil]; + if (result) { + NSLog(@"二进制重排后的函数执行序列文件Order:%@",filePath); + }else{ + NSLog(@"文件写入出错"); + } + + self.textView.text = funcStr; +} + + +//原子队列 存储启动时加载的所有函数方法 +static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT; +//定义符号结构体 +typedef struct { + void *pc; + void *next; +}SLSymbolNode; + +/* + 所有处理完之后,最后需要Write Link Map File改为NO,把Other C Flags/Other Swift Flags的配置删除掉。 + 因为这个配置会在我们代码中自动插入跳转执行 __sanitizer_cov_trace_pc_guard。重排完就不需要了,需要去除掉。 + 同时把ViewController中的 __sanitizer_cov_trace_pc_guard也要去除掉。 + */ +/// clang插桩代码 +void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, + uint32_t *stop) { + static uint64_t N; // Counter for the guards. + if (start == stop || *start) return; // Initialize only once. + // printf("INIT: %p %p\n", start, stop); + for (uint32_t *x = start; x < stop; x++) + *x = ++N; // Guards should start from 1. +} + +/* + 静态插桩,相当于此函数在编译时插在了每一个函数体里,这个函数会捕获到所有程序运行过程中执行的方法。 + 我们只需要捕获应用启动时执行的方法就行,把启动过程中执行的函数地址存储在symbolList中。 + */ +static void*previousPc; +void __sanitizer_cov_trace_pc_guard(uint32_t *guard) { + // if (!*guard) return; // Duplicate the guard check. + /* 精确定位 哪里开始 到哪里结束! 在这里面做判断写条件!*/ + if(isBecomeActive) { + //如果启动完成,后序的函数执行顺序完全取决于用户的操作,就不需要捕获了,只要捕获启动时首页渲染完毕时即可 + return; + } + + //它的作用其实就是去读取 x30寄存器 中所存储的要返回时下一条指令的地址. 所以他名称叫做 __builtin_return_address . 换句话说 , 这个地址就是我当前这个函数执行完毕后 , 要返回到哪里去的函数地址 . + void *PC = __builtin_return_address(0); + + SLSymbolNode *node = malloc(sizeof(SLSymbolNode)); + *node = (SLSymbolNode){PC,NULL}; + + //防止循环引用,故在此过滤 + if (previousPc == PC) { return; } + previousPc = PC; + + Dl_info info; + dladdr(node->pc, &info); + //根据内存地址获取函数名称 + NSString * name = @(info.dli_sname); + //首页渲染完毕,即-[SceneDelegate sceneDidBecomeActive:]执行完毕后 + if ([name isEqualToString:@"-[SceneDelegate sceneDidBecomeActive:]"]) { + isBecomeActive = YES; + } + + //入队 + // offsetof 用在这里是为了入队添加下一个节点找到 前一个节点next指针的位置 + OSAtomicEnqueue(&symbolList, node, offsetof(SLSymbolNode, next)); +} + +@end diff --git "a/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/Write Link Map File.png" "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/Write Link Map File.png" new file mode 100644 index 00000000..b5560a02 Binary files /dev/null and "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/Write Link Map File.png" differ diff --git "a/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/wsl.order" "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/wsl.order" new file mode 100644 index 00000000..3ff2cdf2 --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\344\272\214\350\277\233\345\210\266\351\207\215\346\216\222/wsl.order" @@ -0,0 +1,66 @@ ++[BSBacktraceLogger load] ++[SLZombieSafeFree load] ++[NSString(SLCrashProtector) load] +___34+[NSString(SLCrashProtector) load]_block_invoke +_SL_ExchangeInstanceMethod ++[NSArray(SLCrashProtector) load] +___33+[NSArray(SLCrashProtector) load]_block_invoke ++[NSMutableDictionary(SLCrashProtector) load] +___45+[NSMutableDictionary(SLCrashProtector) load]_block_invoke ++[NSDictionary(SLCrashProtector) load] +___38+[NSDictionary(SLCrashProtector) load]_block_invoke ++[NSMutableArray(SLCrashProtector) load] +___40+[NSMutableArray(SLCrashProtector) load]_block_invoke ++[NSObject(SLCrashProtector) load] +___34+[NSObject(SLCrashProtector) load]_block_invoke ++[NSObject(SLCrashProtector) unrecognizedSelectorCrashProtector] +_SL_ExchangeClassMethod ++[NSObject(SLCrashProtector) KVOCrashProtector] ++[NSObject(SLCrashProtector) KVCCrashProtector] ++[NSMutableString(SLCrashProtector) load] +___41+[NSMutableString(SLCrashProtector) load]_block_invoke ++[UINavigationController(SLMLeakFinder) load] +___45+[UINavigationController(SLMLeakFinder) load]_block_invoke ++[UIViewController(SLMLeakFinder) load] +___39+[UIViewController(SLMLeakFinder) load]_block_invoke +_main +-[NSObject(SLCrashProtector) sl_KVODealloc] +-[NSMutableDictionary(SLCrashProtector) sl_setObject:forKey:] +-[NSMutableArray(SLCrashProtector) sl_mObjectAtIndex:] +-[NSMutableDictionary(SLCrashProtector) sl_removeObjectForKey:] +-[NSMutableArray(SLCrashProtector) sl_initWithObjects:count:] +-[NSMutableArray(SLCrashProtector) sl_replaceObjectAtIndex:withObject:] +-[NSArray(SLCrashProtector) sl_singleObjectAtIndex:] +-[NSArray(SLCrashProtector) sl_objectAtIndex:] +-[NSMutableDictionary(SLCrashProtector) sl_initWithObjects:forKeys:count:] +-[NSDictionary(SLCrashProtector) sl_initWithObjects:forKeys:] +-[NSMutableArray(SLCrashProtector) sl_removeObjectsInRange:] +-[NSObject(SLCrashProtector) sl_addObserver:forKeyPath:options:context:] +_IsSystemClass +-[NSString(SLCrashProtector) sl_characterAtIndex:] +-[NSMutableArray(SLCrashProtector) sl_mObjectAtIndexedSubscript:] +-[AppDelegate application:didFinishLaunchingWithOptions:] +_SLSetUncaughtExceptionHandler +-[NSMutableDictionary(SLCrashProtector) sl_setObject:forKeyedSubscript:] +-[NSArray(SLCrashProtector) sl_objectAtIndexedSubscript:] +-[NSObject(SLCrashProtector) sl_setValue:forKey:] +-[SceneDelegate window] +-[SceneDelegate setWindow:] +-[SceneDelegate scene:willConnectToSession:options:] +-[SLNavigationController viewDidLoad] +-[ViewController prefersStatusBarHidden] +-[SceneDelegate sceneWillEnterForeground:] +-[NSObject(SLCrashProtector) sl_forwardingTargetForSelector:] +-[NSObject(SLCrashProtector) isOverideForwardingMethods:] +-[ViewController setTableView:] +-[ViewController viewDidLoad] +-[ViewController setupUI] +-[NSString(SLCrashProtector) sl_substringWithRange:] +-[ViewController tableView] +-[ViewController getData] +-[ViewController dataSource] +-[ViewController classArray] +-[ViewController tableView:numberOfRowsInSection:] +-[ViewController viewWillAppear:] +-[ViewController tableView:cellForRowAtIndexPath:] +-[SceneDelegate sceneDidBecomeActive:] diff --git "a/iOS_Tips/DarkMode/WorkIssues/\345\267\262\346\265\217\350\247\210.md" "b/iOS_Tips/DarkMode/WorkIssues/\345\267\262\346\265\217\350\247\210.md" new file mode 100644 index 00000000..1c9e121b --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\345\267\262\346\265\217\350\247\210.md" @@ -0,0 +1,68 @@ + + +## 已浏览 + +* 1、[iOS插件化架构探索](https://mp.weixin.qq.com/s/QJ9YHX-Uy6lDIhJe_5wPGw) +* 2、[WKWebview秒开实践分享及问题解决方案](https://juejin.im/post/6887161842406260744) +* 3、[iOS基础](https://juejin.im/user/4371313963043661/posts) +* 4、[【性能优化】今日头条iOS客户端启动速度优化](https://www.jianshu.com/p/7096478ccbe7) +* 5、 [iOS为什么必须在主线程操作UI?](https://juejin.im/post/6844903763011076110) +* 6、[iOS Rendering 渲染全解析(长文干货)](https://juejin.im/post/6844904162765832206#heading-18) +* 7、[iOS面试指南(2020年6月)参考答案](https://mp.weixin.qq.com/s/n8d0WSQs0n0SlmxqdUU-jg) +* 8、[关于iOS离屏渲染的深入研究](https://zhuanlan.zhihu.com/p/72653360) +* 9、[如何通过静态分析提高iOS代码质量](https://juejin.im/post/6844904164552605709#heading-10) +* 10、[OC项目转Swift指南](https://juejin.im/post/6844904078166720520) +* 11、[iOS蓝牙知识快速入门(详尽版)](https://juejin.im/post/6844903824847536135) +* 12、[译】更好的了解Xcode构建系统](https://juejin.im/post/6844904200887861262) +* 13、[iOS组件化方案的几种实现](https://www.jianshu.com/p/2a7e2aa0748b) +* 14、[分析MVC, MVP, MVVM 和 VIPER](https://blog.csdn.net/weixin_40200876/article/details/87635190) +* 15、[RAC相关](https://www.jianshu.com/p/cd4031fbf8ff) +* 16、[synchronized的内部实现原理](http://yulingtianxia.com/blog/2015/11/01/More-than-you-want-to-know-about-synchronized/) +* 17、[轻量级低风险 iOS 热更新方案](https://mp.weixin.qq.com/s/2re_s3NmOvE9RXlbGQqGDA) +* 18、[iOS大解密:玄之又玄的KVO](https://mp.weixin.qq.com/s/0Yfb-FYorH5GZ3ZB6bMCUQ) +* 19、[iOS AOP框架Aspects实现原理](https://www.jianshu.com/p/2345cc034d6b) +* 20、[FBRetainCycleDetector不能扫描__block变量的问题分析和解决方案](https://developer.aliyun.com/article/66857) +* 21、[iOS 任务调度器:为 CPU 和内存减负](https://www.jianshu.com/p/f2a610c77d26) +* 22、[iOS学习之深入理解程序编译过程](https://juejin.im/post/6844903535050489869) +* 23、[计算机那些事(8)——图形图像渲染原理](http://chuquan.me/2018/08/26/graphics-rending-principle-gpu/) +* 24、[iOS 图像渲染过程解析](https://www.jianshu.com/p/6b9a5f16644b) +* 25、[iOS面试题-Swift篇](https://blog.csdn.net/olsQ93038o99S/article/details/107031322?utm_medium=distribute.pc_relevant.none-task-blog-title-6&spm=1001.2101.3001.4242) +* 26、[解决「HTTPDNS + HTTPS」的证书校验问题](https://kangzubin.com/httpdns-https/) +* 27、[从SIL看Swift函数派发机制](https://mp.weixin.qq.com/s/KvwFyc1X_anTt-DTw86u7Q) +* 28、[iOS 锁的简单实现与总结](https://www.jianshu.com/p/a33959324cc7?from=singlemessage) +* 29、[引用计数带来的一次讨论](https://www.jianshu.com/p/e3690f3e4675) +* 30、[小程序「同层渲染」那些事(keng)?](https://juejin.cn/post/6881502813105422349) +* 31、[深入理解JavaScriptCore](https://mp.weixin.qq.com/s/H5wBNAm93uPJDvCQCg0_cg9) +* 32、[iOS WKWebView 同步返回值给 JS](https://mp.weixin.qq.com/s/kKdNG40qbMHigwZIsJRyQQ) +* 33、[Hybrid 实战:如何完整下载一个 wap 页面](https://mp.weixin.qq.com/s/rR4lT0iPSStoHk2Kar8i9A) +* 34、[UIView 动画降帧探究](https://mp.weixin.qq.com/s/EcVrrT1M4mI4f4d2b3qV0Q) +* 35、[iOS AOP 方案的对比与思考](https://juejin.cn/post/6898192050512986126) +* 36、[如何对 iOS 启动阶段耗时进行分析](https://www.jianshu.com/p/c0c4f19d317f) +* 37、[UIView 的渲染过程](https://www.jianshu.com/p/365cf516dbcb) +* 38、[IOS UIView开始深入 绘制像素到屏幕上](https://www.cnblogs.com/alunchen/p/5614355.html) +* 39、[iOS深思篇 | 启动时间的度量和优化](https://www.cnblogs.com/feng9exe/p/12487662.html) +* 40、[Hook load方法耗时](https://github.com/tripleCC/Laboratory/tree/master/HookLoadMethods) +* 41、[__attribute__ 机制使用](https://www.jianshu.com/p/e2dfccc32c80) +* 42、[iOS中 性能优化之浅谈load与initialize](https://blog.csdn.net/Lea__DongYang/article/details/79702537) +* 43、[#iOS性能优化](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&__biz=MzI2NTAxMzg2MA==&scene=1&album_id=1569172435344637952&count=3#wechat_redirect) +* 44、[监控所有的OC方法耗时](https://juejin.cn/post/6844903875804135431) +* 45、[深入剖析iOS动态链接库](https://www.jianshu.com/p/1de663f64c05) +* 46、[【IOS开发高级系列】dyld专题](https://www.jianshu.com/p/5f337da8fbef) +* 47、[iOS图片加载速度极限优化—FastImageCache解析](https://note.youdao.com/ynoteshare1/index.html?id=c66e46ed804849833a3189952afcf2c0&type=note) +* 48、[深入iOS系统底层之程序映像](https://www.jianshu.com/p/3b83193ff851) +* 49、[深入iOS系统底层之crash解决方法](https://www.jianshu.com/p/cf0945f9c1f8) +* 50、[深入iOS系统底层之CPU寄存器](https://www.jianshu.com/p/6d7a57794122) +* 51、[漫谈iOS Crash收集框架](http://www.cocoachina.com/articles/12301) +* 52、[iOS crash log手动解析](https://www.jianshu.com/p/98038765743b) +* 53、[iOS Crash分析必备:符号化系统库方法](https://www.jianshu.com/p/f9eeeecff8d8) +* 54、[使用symbolicatecrash解析了一个crash log](https://www.jianshu.com/p/0a1c029e910f) +* 55、[如何符号化Objective-C调用栈](https://www.jianshu.com/p/3e5097a9be46) +* 56、[移动端监控体系之技术原理剖析](https://www.jianshu.com/p/8123fc17fe0e) +* 57、[iOS无埋点数据SDK实践之路](https://www.jianshu.com/p/69ce01e15042) +* 58、[APM系列(二):电量消耗速率监控](https://kyson.cn/index.php/archives/150/) +* 59、[有赞webview加速平台探索与建设(一)](https://tech.youzan.com/youzan-webview-goldwing-one/) +* 60、[iOS 网络基础和网络优化](https://juejin.cn/post/6868945803856052231) +* 61、[llvm 编译器高级用法:第三方库插桩](https://mp.weixin.qq.com/s/RKg8f6B2jSNuFEImtMnq2Q) +* 62、[iOS 性能监控:Runloop 卡顿监控的坑](https://mp.weixin.qq.com/s/vMRQ0VuHLxpaY9oCNd5G8w) +* 63、[iOS链接原理解析与应用实践](https://mp.weixin.qq.com/s/_3WXnDolNICs2euoJph44A) +* 64、[iOS开发--我与面试官有个约会](https://juejin.cn/post/6908303868086452237) diff --git "a/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLResourceInfo.h" "b/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLResourceInfo.h" new file mode 100644 index 00000000..a241dc59 --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLResourceInfo.h" @@ -0,0 +1,28 @@ +// +// SLResourceInfo.h +// DarkMode +// +// Created by wsl on 2020/8/21. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SLResourceInfo : NSObject + +///文件路径 +@property (nonatomic, copy) NSString *fileName; +///文件路径 +@property (nonatomic, copy) NSString *filePath; +///文件大小 +@property (nonatomic, assign) CGFloat fileSize; +/// 是否为文件夹 +@property (nonatomic, assign) BOOL isFolder; +/// 文件类型 +@property (nonatomic, copy) NSString *fileType; + +@end + +NS_ASSUME_NONNULL_END diff --git "a/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLResourceInfo.m" "b/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLResourceInfo.m" new file mode 100644 index 00000000..4aa0107c --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLResourceInfo.m" @@ -0,0 +1,13 @@ +// +// SLResourceInfo.m +// DarkMode +// +// Created by wsl on 2020/8/21. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLResourceInfo.h" + +@implementation SLResourceInfo + +@end diff --git "a/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLUnusedResourceViewController.h" "b/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLUnusedResourceViewController.h" new file mode 100644 index 00000000..b51c88bf --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLUnusedResourceViewController.h" @@ -0,0 +1,18 @@ +// +// SLUnusedResourceViewController.h +// DarkMode +// +// Created by wsl on 2020/8/20. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +///扫描项目无用的图片、类等文件资源, 此示例主要针对于此项目中的图片资源,其他类型资源实现原理相同 +@interface SLUnusedResourceViewController : UITableViewController + +@end + +NS_ASSUME_NONNULL_END diff --git "a/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLUnusedResourceViewController.m" "b/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLUnusedResourceViewController.m" new file mode 100644 index 00000000..339fc0ee --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/SLUnusedResourceViewController.m" @@ -0,0 +1,185 @@ +// +// SLUnusedResourceViewController.m +// DarkMode +// +// Created by wsl on 2020/8/20. +// Copyright © 2020 https://github.com/wsl2ls ----- . All rights reserved. +// + +#import "SLUnusedResourceViewController.h" +#import "SLResourceInfo.h" + +/* 资料: + https://www.jianshu.com/p/cef2f6becbe6 + https://github.com/tinymind/LSUnusedResources + 正则表达式入门:http://www.regexlab.com/zh/regref.htm + 正则表达式在线工具: https://tool.oschina.net/regex/ + */ +@interface SLUnusedResourceViewController () +@property (nonatomic, strong) NSMutableArray *dataSource; +@end + +@implementation SLUnusedResourceViewController + +#pragma mark - Override +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + [self setupUI]; + [self getUnusedResourceData]; +} + +#pragma mark - UI +- (void)setupUI { + self.navigationController.navigationBar.translucent = YES; + self.tableView.estimatedRowHeight = 1; + self.navigationItem.title = @"正在扫描无用图片..."; +} + +#pragma mark - Data +///获取没有用的图片信息 +- (void)getUnusedResourceData { + + //文件目录路径 + NSString *folderPath = @"/Users/wsl/GitHub/iOS_Tips/iOS_Tips/DarkMode"; + + NSString *imgTypes = @"jpeg|jpg|png|gif|imageset"; + NSString *imgExpression = [NSString stringWithFormat:@"([a-zA-Z0-9_-]*)(@[23]x)?\\.(%@)",imgTypes]; + NSMutableArray *imgResources = [self searchAllUnderFolderPath:folderPath fileTypes:imgTypes regularExpression:imgExpression]; + + NSString *fileTypes = @"h|m$|swift|xib|storyboard|plist"; + NSString *fileExpression = [NSString stringWithFormat:@"([a-zA-Z0-9_-]*)(\\.)(%@)",fileTypes]; + NSMutableArray *files = [self searchAllUnderFolderPath:folderPath fileTypes:fileTypes regularExpression:fileExpression]; + + NSMutableArray *unusedImgs = [NSMutableArray array]; + dispatch_async(dispatch_get_global_queue(0, 0), ^{ + for (SLResourceInfo *imgInfo in imgResources) { + //其他文件中是否使用了该图片 + BOOL isUsed = NO; + for (SLResourceInfo *fileInfo in files) { + //文件内容 + NSString *content = [NSString stringWithContentsOfFile:fileInfo.filePath encoding:NSUTF8StringEncoding error:nil]; + if (content == nil) continue; + + /* + 文件类型、语法、用法等不同,匹配规则就不同,依项目情况而定 + @"@\"%@\"" 例 @"wsl" + @"@\"(%@)(\.(jpeg|jpg|png|gif))?\"" 例 @"wsl.png" + @"imageNamed:@\"(.+)\""; + @"(imageNamed|contentOfFile):@\"(.*)\"" + + @"image name=\"(.+?)\"" xib格式(.xib .storyboard) + (stickers_%d) + */ + //去掉.png等后缀 + NSString *imgName = [imgInfo.fileName stringByReplacingOccurrencesOfString:imgInfo.fileType withString:@""]; + NSRange range = [imgName rangeOfString:@"@"]; + if (range.length) { + //去掉 @2x @3x + imgName = [imgName stringByReplacingOccurrencesOfString:[imgName substringFromIndex:range.location] withString:@""]; + } + //匹配规则 + // NSString *regularExpStr = [NSString stringWithFormat:@"@\"(%@)\"",imgName]; + NSString *regularExpStr = [NSString stringWithFormat:@"@\"(%@)\"",imgName]; + NSRegularExpression *regularExp = [[NSRegularExpression alloc] initWithPattern:regularExpStr options:NSRegularExpressionCaseInsensitive error:nil]; + NSArray *resultArr = [regularExp matchesInString:content options:NSMatchingReportProgress range:NSMakeRange(0, content.length)]; + if(resultArr.count > 0){ + // 取出匹配出来的字符串 + NSString *subStr = [content substringWithRange:resultArr.firstObject.range]; + // NSLog(@"%@",subStr); + isUsed = YES; + break; + } + } + //没有使用过,加入无用待处理数组 + if (!isUsed) [unusedImgs addObject:imgInfo]; + } + + for (SLResourceInfo *unusedImg in unusedImgs) { + // NSLog(@"⚠️ 没用 %@", unusedImg.fileName); + } + + self.dataSource = unusedImgs; + SL_DISPATCH_ON_MAIN_THREAD(^{ + self.navigationItem.title = @"ipa瘦身之扫描无用资源"; + [self.tableView reloadData]; + }); + + }); + +} + +#pragma mark - Getter +- (NSMutableArray *)dataSource { + if (!_dataSource) { + _dataSource = [NSMutableArray array]; + } + return _dataSource;; +} + +#pragma mark - HelpMethods + +/// 在searchPath路径下搜索所有suffixs类型的文件 +/// @param searchPath 搜索路径 +/// @param suffixs 文件后缀/格式 多种格式用|隔开即可 例如@"jpeg|jpg|png|gif|imageset" +/// @param expression 正则表达式/匹配规则 +- (NSMutableArray *)searchAllUnderFolderPath:(NSString *)searchPath fileTypes:(NSString *)suffixs regularExpression:(NSString *)expression { + NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:searchPath error:nil] ; + NSEnumerator *filesEnumerator = [filesArray objectEnumerator]; + filesArray = nil; + NSMutableArray *resourceInfos = [NSMutableArray array]; + //相对于searchPath的子路径 + NSString *subPath; + while (subPath = [filesEnumerator nextObject]) { + @autoreleasepool { + //匹配对象 + NSString *fileName = subPath.lastPathComponent; + + //匹配规则 + NSString *regularExpStr = expression; + NSRegularExpression *regularExp = [[NSRegularExpression alloc] initWithPattern:regularExpStr options:NSRegularExpressionCaseInsensitive error:nil]; + NSArray *resultArr = [regularExp matchesInString:fileName options:NSMatchingReportProgress range:NSMakeRange(0, fileName.length)]; + if(resultArr.count == 0) continue; + //取出匹配出来的字符串 + // NSString *subStr = [fileName substringWithRange:resultArr.firstObject.range]; + // NSLog(@"%@",subStr); + + //全路径 + NSString *fullPath = [searchPath stringByAppendingPathComponent:subPath]; + //文件属性信息 + NSDictionary *fileDic = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:nil]; + + SLResourceInfo *info = [[SLResourceInfo alloc] init]; + info.filePath = fullPath; + info.fileSize = [fileDic fileSize]; + info.isFolder = [[fileDic fileType] isEqualToString:@"NSFileTypeDirectory"] ? YES : NO; + info.fileName = fileName; + info.fileType = [fileName substringFromIndex:[fileName rangeOfString:@"."].location]; + [resourceInfos addObject:info]; + } + } + return resourceInfos; +} + + +#pragma mark - EventsHandle + + +#pragma mark - UITableViewDelegate, UITableViewDataSource +- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.dataSource.count; +} +- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { + UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellID"]; + if(!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cellID"]; + } + SLResourceInfo *info = self.dataSource[indexPath.row]; + cell.imageView.image = [UIImage imageWithContentsOfFile:info.filePath]; + cell.textLabel.text = info.fileName; + return cell; +} +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { +} + +@end diff --git "a/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/unused.png" "b/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/unused.png" new file mode 100644 index 00000000..6bbf7c21 Binary files /dev/null and "b/iOS_Tips/DarkMode/WorkIssues/\346\237\245\346\211\276\351\241\271\347\233\256\346\227\240\347\224\250\350\265\204\346\272\220/unused.png" differ diff --git "a/iOS_Tips/DarkMode/WorkIssues/\347\254\224\350\256\260.md" "b/iOS_Tips/DarkMode/WorkIssues/\347\254\224\350\256\260.md" new file mode 100644 index 00000000..fb81affb --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\347\254\224\350\256\260.md" @@ -0,0 +1,204 @@ + + ## 一、性能优化 + +* 1.0、APM性能监控 + +> CPU占用率、内存/磁盘使用率、卡顿监控定位、Crash防护、线程数量监控、网络监控(TCP 建立连接时间 、DNS 时间、 SSL时间、首包时间、响应时间 、流量)、ViewController启动耗时监测 、load方法耗时、方法耗时监控...... + +* 1.1、内存优化 + +> 合理的线程分配、使用正确的API、延时加载、按需加载/复用、处理内存警告、自动释放池@autoreleasepool、图片降采样/分片加载、NSCache、避免循环引用导致的内存泄漏 + +* 1.2、卡顿优化 + +> 优化业务流程、 预处理、 缓存/空间换时间、避免圆角/阴影/光栅化/透明造成的离屏渲染、异步解码/异步绘制、合并图层、图像字节对齐 + +* 1.3、 安装包瘦身 + +> 去掉无用/重复资源、压缩资源、矢量图/图片格式、编译选项优化、可执行文件瘦身、如果不支持32位以及 iOS8 就去掉 armv7 、xcassets管理常用的、较小的图图片、大图放在 Bundle 内管理、静态库瘦身 + +* 1.4、启动时间优化 + +> 合并减少动态库和类、尽量不要用C++虚函数(创建虚函数表有开销),、二进制重排/减少缺页中断、load 的方法处理和耗时监听、异步延时加载、业务流程优化、rootViewController的加载 + +* 1.5、编译时间优化 + +> + +* 1.6、网络优化 + +> 合并多个请求、304缓存 + +* 1.7、健壮性/稳定性 + +> 启动连续闪退保护、Crash防护、Crash分析 + +* 1.8、安全性 + +> 代码混淆 + +> * 资料 + +> [iOS-Performance-Optimization](https://github.com/skyming/iOS-Performance-Optimization) +> [深入剖析 iOS 性能优化-戴明](https://ming1016.github.io/2017/06/20/deeply-ios-performance-optimization/#more) +> [DoraemonKit](https://github.com/didi/DoraemonKit/blob/master/README_CN.md) +> [乐少](https://www.jianshu.com/u/9c51a213b02e) +> [iOS 性能监控(三)—— 方法耗时监控](https://www.jianshu.com/p/bc1c000afdba) +> [iOS crash 日志堆栈解析](https://juejin.im/post/6844903598011187213) +> [iOS崩溃crash大解析](https://www.jianshu.com/p/1b804426d212) +> [iOS实录14:浅谈iOS Crash(一)](https://www.jianshu.com/p/3261493e6d9e) +> [iOS Memory 内存详解 (长文)](https://juejin.im/post/6844903902169710600) +> [iOS 性能优化调试专题](https://www.jianshu.com/c/fcb00b489a85) +> [iOS Memory Deep Dive](https://www.jianshu.com/p/dad9f27e412e) +> [iOS性能优化系列篇之“列表流畅度优化”](https://juejin.im/post/6844903656769208334) +> [iOS瘦身之道](https://juejin.im/post/6844903845340921869) +> [iOS应用瘦身总结](https://juejin.im/post/6844903849732341774) +> [如何使用 Clang Plugin 找到项目中的无用代码](https://blog.gocy.tech/2017/09/12/DetectingUselessCodeWithClang-p1/) +> [基于clang插件的一种iOS包大小瘦身方案](https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&mid=2247488360&idx=1&sn=94fba30a87d0f9bc0b9ff94d3fed3386&source=41#wechat_redirect) +> [iOS 启动速度优化和安装包优化简单总结](https://juejin.im/post/6844903821387235341) +> [iOS代码瘦身实践:删除无用的类](https://juejin.im/post/6844903921169727496) +> [优化 App 的启动时间](http://yulingtianxia.com/blog/2016/10/30/Optimizing-App-Startup-Time/) +> [AppStartTime](https://github.com/JunyiXie/AppStartTime) +> [iOS App 启动性能优化](https://mp.weixin.qq.com/s/Kf3EbDIUuf0aWVT-UCEmbA) +> [iOS一次立竿见影的启动时间优化](https://juejin.im/post/6844903525172903944) +> [iOS Dynamic Framework 对App启动时间影响实测](https://www.jianshu.com/p/3263009e9228) +> [iOS优化篇之App启动时间优化](https://juejin.im/post/6861917375382929415) +> [iOS如何提高10倍以上编译速度](https://juejin.im/post/6844904169124397070) +> [为什么 Debug Information Format 改为 DWARF 可以提高编译速度?](https://zhuanlan.zhihu.com/p/112764192) +> [移动 APP 网络优化概述](http://blog.cnbang.net/tech/3531/) +> [二进制重排-编译插桩与动态trace结合](https://github.com/rhythmkay/PGOAnalyzer) +> [关于WKWebView 秒开方案](https://www.jianshu.com/p/74fad07ec640) +> [如何自建 Crash 平台](https://www.xuyanlan.com/2019/02/20/iOS-crash-report/) +> [iOS客户端稳定性体系](https://778477.github.io/2018/02/23/2018-02-23-iOS客户端稳定性体系/) +> [iOS 包瘦身浅析](https://github.com/778477/iOS-LinkMapAnalyzer) +> [iOS 崩溃排查技巧:如何获取系统库源码](https://mp.weixin.qq.com/s/YjJo-FB9weGgxEaOfCwBvg) +> [iOS 性能优化:优化 App 启动速度](https://mp.weixin.qq.com/s/h3vB_zEJBAHCfGmD5EkMcw) +> [iOS 性能优化:使用 MetricKit 2.0 收集数据](https://mp.weixin.qq.com/s/cbP0QlxVlr5oeTrf6yYfFw) +> [iOS性能优化实践:头条抖音如何实现OOM崩溃率下降50%+](https://mp.weixin.qq.com/s/4-4M9E8NziAgshlwB7Sc6g) +> [抖音品质建设 - iOS启动优化《原理篇》](https://mp.weixin.qq.com/s/3-Sbqe9gxdV6eI1f435BDg) +> [iOS Rendering 渲染全解析(长文干货)](https://juejin.im/post/6844904162765832206) +> [再谈 iOS App Crash 防护](https://mp.weixin.qq.com/s/l5S_g1PBCiwm4KDtqNFb-A) +> [iOS 网络优化: 使你的 App 网络交互更流畅](https://mp.weixin.qq.com/s/YLvpYBwaz8L6fnmzL78Mfg) +> [GoldHouse-for-iOS](https://github.com/BiBoyang/GoldHouse-for-iOS) +> [卡顿率降低50%!京东商城APP卡顿监控及优化实践](https://mp.weixin.qq.com/s/aJeAUAjcKOMvznDMGj2UUA) +> [【性能优化】今日头条iOS客户端启动速度优化](https://www.jianshu.com/p/7096478ccbe7) +> [iOS 崩溃排查入门:总览](https://ai-chan.top/2020/10/02/iOS-crash/) +> [卡顿率降低50%!京东商城APP卡顿监控及优化实践](https://mp.weixin.qq.com/s/aJeAUAjcKOMvznDMGj2UUA) + +## 二、数据结构与算法 + +* 1、 [LeetcodeTop](https://github.com/afatcoder/LeetcodeTop) +* 2、[小浩算法](https://www.geekxh.com) +* 3、[labuladong的算法小抄](https://labuladong.gitbook.io/algo/) + +## 三、面试题相关 + +* 1、[iOS-InterviewQuestion-collection](https://github.com/liberalisman/iOS-InterviewQuestion-collection) +* 2、[阿里、字节 一套高效的iOS面试题解答](https://github.com/colourful987/bytedance-alibaba-interview) +* 3、[2019年iOS面试反思总结](https://juejin.im/post/6844903942644563982) +* 4、[IOS面试考察(九):性能优化相关问题](https://juejin.im/post/6844904131941892110#heading-50) +* 5、[Theendisthebegi ](https://www.jianshu.com/u/b836babfef41) +* 6、[analyze](https://github.com/draveness/analyze) +* 7、[iOS 方案之本之使用汇编可以 Hook objc_msgSend](https://zhuanlan.zhihu.com/c_1221108308322443264) +* 8、[iOS Rendering 渲染全解析(长文干货)](https://juejin.im/post/6844904162765832206) +* 9、[iOS | 面试知识整理 - OC基础 (二)](https://juejin.im/post/6844903945052110856) +* 10、[各种精选面试题](https://github.com/iOShuyang/Book-Recommended-Interview) +* 11、[iOS | 面试知识整理 - Swift 基础(九)](https://juejin.im/post/6844903955156336654) +* 12、[iOSDevLevelingUp](https://github.com/ShannonChenCHN/iOSDevLevelingUp) +* 13、[iOS 基础](https://juejin.im/user/940837683069549/posts) +* 14、[深入理解RunLoop](https://blog.ibireme.com/2015/05/18/runloop/) +* 15、[BoyangBlog](https://github.com/BiBoyang/BoyangBlog.git) +* 16、[阿里、字节:一套高效的iOS面试题](https://juejin.im/post/6844904064937902094) +* 17、[《招聘一个靠谱的 iOS》](https://github.com/ChenYilong/iOSInterviewQuestions) +* 18、[聚焦于iOS开发面试题和开发笔记](https://github.com/DevDragonLi/iOSInterviewsAndDevNotes) + +## 四、逆向与安全 + +* 1、 [趣探 Mach-O](https://juejin.im/post/5a0c5c5e51882555cc416602) +* 2、 [iOS程序员的自我修养 - 编译、链接、装载](https://juejin.im/post/5d5273b1f265da03f233c2d6) +* 3、[[iOS翻译]编译器](https://juejin.im/post/6854573220612931592) +* 4、[Injection:iOS热重载背后的黑魔法](https://mp.weixin.qq.com/s/hFnHdOP6pmIwzZck-zXE8g) +* 5、[高效逆向 - 为任意iOS App生成符号表](https://github.com/imoldman/DSYMCreator) +* 6、[iOS LLVM-Clang 浅谈](https://www.jianshu.com/p/7ceca351a045) +* 7、[iOSer逆向](https://iosre.com) +* 8、[fishhook 原理探究](https://mp.weixin.qq.com/s/uP3PASr7IoOMCQ-yy4RanA) +* 9、[Mach-O文件](https://www.jianshu.com/p/7c87e115492d)、[深入iOS系统底层之程序映像](https://www.jianshu.com/p/3b83193ff851) +* 10、[深入iOS底层系列](https://www.jianshu.com/nb/21164557) +* 11、[iOS App 安全加固方案调研](https://mp.weixin.qq.com/s?__biz=MzI2NzI4MTEwNA==&mid=2247485642&idx=1&sn=dce8e7581d94c8d8d2b79366f6223161&chksm=ea807f75ddf7f663daf6776b0f5a98aeea2e735e08c3a5268fd00f45b5a893d0e8dbac03b0f8&mpshare=1&scene=23&srcid=%23rd) + +## 五、音视频 + +* 1、[(强烈推荐)移动端音视频从零到上手](https://juejin.im/post/6844903889007820813) +* 2、[移动开发者的必知音视频基础知识](https://juejin.im/post/6844904039503626247) +* 3、[GoPlay是一款基于FFmpeg/OpenGLES2.0的iOS播放器](https://github.com/letqingbin/GoPlay) +* 4、[CainCamera](https://github.com/CainKernel/blog) +* 5、[Android 音视频开发学习思路](https://www.cnblogs.com/renhui/p/7452572.html) +* 6、[loyinglin](https://github.com/loyinglin) +* 7、[CC老师](https://www.jianshu.com/u/1b4c832fb2ca) + +## 六、跨平台/热更新 + +* 1、DSBridge /JSBridge /JSPatch/ +* 2、[写给自己看系列之WebViewJavascriptBridge源码分析](https://juejin.im/post/6844904097192247303) +* 3、[OCRunner:完全体的iOS热修复方案](https://silverfruity.github.io/2020/09/04/OCRunner/) +* 4、[DynamicOC](https://github.com/letqingbin/DynamicOC) +* 5、[js-native 通信的 6 种方式](https://mp.weixin.qq.com/s/_Xo6O3NoE1z9AIMJm_uSsA) +* 6、[美团外卖客户端容器化架构的演进](https://mp.weixin.qq.com/s/kW5wu7GM7pMRRvN-dQvE2g) +* 7、[SYWebViewBridge](https://mp.weixin.qq.com/s/JDCyWg1AYemxbnFbvY5E9w) +* 8、[JSPatch-实现原理详解](https://github.com/bang590/JSPatch/wiki/JSPatch-实现原理详解) +* 9、[WKWebview秒开实践分享及问题解决方案](https://juejin.im/post/6887161842406260744) +* 10、[轻量级低风险 iOS 热更新方案](https://mp.weixin.qq.com/s/2re_s3NmOvE9RXlbGQqGDA) +* 11、[有赞webview加速平台探索与建设(一)](https://tech.youzan.com/youzan-webview-goldwing-one/) +* 12、[ReactNative iOS 框架源码解析](https://juejin.cn/post/6844904190385487886) +* 13、[写一个易于维护使用方便性能可靠的Hybrid框架](https://juejin.cn/post/6844903733583675406) +* 14、[今日头条品质优化 - 图文详情页秒开实践](https://mp.weixin.qq.com/s/Xqr6rQBbx7XPoBESEFuXJw) + +## 七、Shell脚本 + +* 1、[Shell脚本编程30分钟入门](https://github.com/qinjx/30min_guides/blob/master/shell.md) + + +## 八、网络 + +* 1、[TCP序列号和确认号详解](https://blog.csdn.net/fujibao/article/details/80857180) +* 2、[探索现代的移动网络](https://mp.weixin.qq.com/s/ds6QkVrBwcurxp3RkvZe8Q) + +## 九、效率 + +* 1、[iOS教你如何像RN一样实时编译](https://juejin.im/post/6850037272415813645) +* 2、[Injection:iOS热重载背后的黑魔法](https://mp.weixin.qq.com/s/hFnHdOP6pmIwzZck-zXE8g) +* 3、[iOS开发老司机的神兵利器-效率工具](https://juejin.im/post/6844904205640007687) +* 4、[Xcode配置文件xcconfig](https://juejin.im/post/6844903766282469390) + + +## 十、架构/设计模式 + +* 1、[IOS设计模式探索(大话设计模式)](https://github.com/huang303513/Design-Pattern-For-iOS.git) +* 2、[面向对象设计的设计原则和设计模式](https://github.com/knightsj/object-oriented-design) +* 3、[iOS VIPER架构实践(一):从MVC到MVVM到VIPER](https://juejin.cn/post/6844903491941433351) +* 4、[浅谈 MVC、MVP 和 MVVM 架构模式](https://draveness.me/mvx/) +* 5、[iOS MVC、MVVM、MVP架构模式浅浅析](https://juejin.cn/post/6844903798591193095) + +## 十一、优秀三方源码解析 + +* 1、[优秀的三方源码](https://github.com/iOShuyang/Book-Recommend-Github) +* 2、[波儿菜](https://juejin.im/user/2735240659351112) +* 3、[写给自己看系列之WebViewJavascriptBridge源码分析](https://juejin.im/post/6844904097192247303) +* 4、[JSBridge 实现原理解析](https://github.com/mcuking/blog/issues/39) +* 5、[MLeaksFinder]() +* 6、[JSPatch]() +* 7、[Fishook]() +* 8、[Aspect]() +* 9、[YYCache 源码解析(一):使用方法,架构与内存缓存的设计](https://mp.weixin.qq.com/s/tTAKHNiCVJ64738VIA__ZA) + + +## 其它 + +* 1、[CocoaPods 都做了什么?](https://www.jianshu.com/p/84936d9344ff) +* 2、[1. 版本管理工具及 Ruby 工具链环境](https://mp.weixin.qq.com/s/s2yJEb2P0_Kk-rIpYBi_9A) +* 3、[2. 整体把握 CocoaPods 核心组件](https://looseyi.github.io/post/sourcecode-cocoapods/02-cocoapods-corecomponents/) +* 4、[OCLint 实现 Code Review - 给你的代码提提质量](https://juejin.im/post/6844903853775650830) +* 5、[(译)开源软件架构之 LLVM(The Architecture of Open Source Applications LLVM)](https://juejin.im/post/6844904034134917134) +* 6、[你真的会用 CocoaPods 吗?](https://juejin.im/post/6844903506734759949) +* 7、[你知道 Git 是如何做版本控制的吗?](https://juejin.im/post/6844903967525208078) +* 8、[如何有效地进行代码 Review?](https://mp.weixin.qq.com/s/uFivYfX53s5zAe6hacznlg) diff --git "a/iOS_Tips/DarkMode/WorkIssues/\351\235\242\350\257\225\351\242\230.md" "b/iOS_Tips/DarkMode/WorkIssues/\351\235\242\350\257\225\351\242\230.md" new file mode 100644 index 00000000..e0298525 --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\351\235\242\350\257\225\351\242\230.md" @@ -0,0 +1,162 @@ + + +## 面试题 + + +### iOS + +* 1、 [iOS为什么必须在主线程操作UI?](https://778477.github.io/2017/06/19/2017-06-19-为什么iOS更新UI操作必须在主线程/) +* 2、[Swift和OC的方法调用流程区别](https://kemchenj.github.io/2017-01-09/) +* 3、[iOS性能优化实践:头条抖音如何实现OOM崩溃率下降50%+](https://mp.weixin.qq.com/s/4-4M9E8NziAgshlwB7Sc6g) +* 4、[GCD](https://looseyi.github.io/post/notes/01-gcd/) +* 5、[runloop](https://blog.ibireme.com/2015/05/18/runloop/) +* 6、[Weak的实现原理](https://www.jianshu.com/p/f331bd5ce8f8) +* 7、[Block](https://www.jianshu.com/p/93813c293266) +* 8、[AutoreleasePool](https://www.jianshu.com/p/32265cbb2a26) +* 9、[编译过程](https://juejin.im/post/6844904040841641998) +* 10、[启动过程](https://www.jianshu.com/p/7096478ccbe7) +* 11、[设计一个线程池](https://www.jianshu.com/p/5bb4123e415c)、[iOS 任务调度器:为 CPU和内存减负](https://www.jianshu.com/p/f2a610c77d26) +* 12、[线程怎么保活](https://www.jianshu.com/p/2ffa6678f83d) +* 13、[内存满了之后如何处理](https://juejin.im/post/6844903550187733000) +* 14、[如何使用 Runtime 给现有的类添加 weak 属性](https://github.com/ChenYilong/iOSInterviewQuestions) +* 15、[加载一个图片经历那些过程](https://www.jianshu.com/p/72dd074728d8) +* 16、[Performselector方法里到底干了啥](https://www.jianshu.com/p/ac14e03d9345) +* 17、[C++虚函数我们一般哪里有用到啊,还有它开销了什么东西?为啥减少使用能优化启动速度?](https://www.dazhuanlan.com/2019/12/09/5dedcdb02c287/) +* 18、[内存缓存优化](https://www.jianshu.com/p/f7376a321c2e) +* 19、[autoreleasepool什么时候释放](https://www.jianshu.com/p/50bdd8438857) +* 20、[响应链机制](https://www.jianshu.com/p/2e074db792ba) +* 21、[instrment的Time profilter实现原理原理]() +* 22、[多线程之栅栏函数](https://zhuanlan.zhihu.com/p/142368783) +* 23、[iOS 图像渲染过程、离屏渲染](https://juejin.im/post/6844904162765832206#heading-18) +* 24、[iOS 架构模式--浅析MVC, MVP, MVVM 和 VIPER](https://zhangferry.com/2019/11/22/ios_architecture_patterns/) +* 25、[iOS面试备战-多线程](https://juejin.im/post/6854573211011514382) +* 26、[设计模式](https://www.jianshu.com/p/e5c69c7b8c00) +* 27、[组件化](https://www.jianshu.com/p/40060fa2a564) +* 28、[组件化采坑](https://juejin.im/post/6844903731738345479) +* 29、[md5实现原理](https://www.jianshu.com/p/82729c87ef68) +* 30、[NSTimer不准时](https://www.jianshu.com/p/d5845842b7d3) +* 31、[WKWebVie与JS通信的几种方式]() +* 32、[什么是泛型?Swift的字符串string与OC的NSString差别?对于Swift协议的理解?]() +* 33、[APP启动时长监控]() +* 34、[https相关](https://mp.weixin.qq.com/s/LV3cFpQtMUntMg6zn-01pQ) +* 35、[设计一个网络框架]() +* 36、[网络最大并发数]() +* 37、[分类和扩展区别](https://www.jianshu.com/p/ba35a975af9a) +* 38、[load和initializet](https://www.jianshu.com/p/c52d0b6ee5e9) +* 39、[UIView的绘制过程](https://www.jianshu.com/p/a81d48e0e44a) +* 40、[layoutSubviews什么时候执行](https://www.jianshu.com/p/a2acc4c7dc4b) +* 41、[iOS-UIView异步绘制](https://www.jianshu.com/p/1c1b3f7cf087) +* 42、[静态库和动态库的区别?怎么判断一个库是动态库还是静态库](https://www.jianshu.com/p/5069778e421a) +* 43、[AutoLayout的原理](https://www.jianshu.com/p/c6541ff0bdafv) +* 44、[iOS的Category添加 weak 属性](https://blog.csdn.net/devday/article/details/71130115) +* 45、[Runloop](https://www.jianshu.com/p/fcb271f69038) +* 46、[Runtime](https://www.jianshu.com/p/6ebda3cd8052) +* 47、[WKWebView 那些坑](https://zhuanlan.zhihu.com/p/24990222) +* 48、[NSString为用Copy和Strong有什么区别]() +* 49、[isKindOf和isMemberOf的区别,底层实现](https://www.jianshu.com/p/abad3809c7c1) +* 50、[多线程-现有10个上传任务,最大并发数是3,每完成一个任务要告诉用户此时剩余的任务数]() +* 51、[自己实现isEqual方法(hash算法](https://www.jianshu.com/p/915356e280fc) +* 52、[透彻理解block中weakSelf和strongSelf](https://www.jianshu.com/p/ae4f84e289b9) +* 53、[ios实现读写锁](https://blog.csdn.net/u014600626/article/details/102884794) +* 54、[UIView Animation的Block谁持有](http://saitjr.com/ios/ios-from-memory-leak-to-uiview-animation.html) +* 55、[watchdog 机制](https://juejin.cn/post/6844903683021340679) +* 56、[什么是依赖注入?](https://www.jianshu.com/p/0d72a945f2dd) +* 57、[ARC的原理,引用计数是怎么管理的,weak 关键字的原理](https://www.jianshu.com/p/e3690f3e4675) +* 58、[autoReleasePool的原理 autoReleasePool 的哨兵、自己创建的 @autoReleasePool是怎样运行的,什么对象是autoRelease的](https://www.jianshu.com/p/77eb9e0bcd70) +* 59、[RunLoop与AutoReleasepool的关系学习](https://www.jianshu.com/p/ae8a310457d7) +* 60、[子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处 理会内存泄漏吗?](https://zhuanlan.zhihu.com/p/26796146) +* 61、[Atomic为什么不安全,怎么实现一个安全的存取](https://www.jianshu.com/p/f7411c90a68a)、[iOS中如何设计多线程的读写安全](https://tech.souyunku.com/?p=31631) +* 62、[什么是@synchronized](https://blog.csdn.net/qq_28551705/article/details/86094764) +* 63、[线程和进程的区别](https://www.jianshu.com/p/68b274548069) +* 64、[对于大图片加载引起的oom问题,怎么处理](https://www.jianshu.com/p/52e3535dd37c) +* 65、[Crash堆栈和符号的对应解析符号化](https://www.jianshu.com/p/29051908c74b) +* 66、[IPA包签名原理](https://juejin.cn/post/6844903744233013262) +* 67、[load的详细过程](https://blog.csdn.net/Lea__DongYang/article/details/79702537) +* 68、[卡顿监测原理](https://www.jianshu.com/p/8123fc17fe0e) +* 69、[组件化过程中的坑](https://www.jianshu.com/p/a869650dd3b6) +* 70、[Mach-O文件](https://www.jianshu.com/p/7c87e115492d)、[深入iOS系统底层之程序映像](https://www.jianshu.com/p/3b83193ff851) +* 71、[深入理解Tagged Pointer](https://www.jianshu.com/p/c9089494fb6c) +* 72、[iOS 的渲染机制以及 UIView 的自动布局流程](https://www.dazhuanlan.com/2020/01/31/5e33cdfb28a2a/) +* 73、[MD5(哈希/摘要算法)、AES和DES(对称加密)、RSA(非对称加密)](https://blog.csdn.net/seoyundu/article/details/88112861) +* 74、[pod install和pod update的区别](https://blog.csdn.net/jhope/article/details/81535586) +* 75、[跨平台框架原理]() +* 76、[NSString的内部原理、taggedPointString](https://www.jianshu.com/p/627815e1996b) +* 77、[Mian函数启动前的流程,是谁加载的动态库(dyld),runtime在何时加载](https://www.jianshu.com/p/5efe327ac7ea) +* 78、[解释一下段⻚式的内存管理,段⻚式虚拟内存的含义是什么,怎么进行换⻚](https://blog.csdn.net/low5252/article/details/106075945) +* 79、[KVO存在哪些⻛险](https://www.cnblogs.com/wengzilin/p/4346775.html) +* 80、[大批量数据写入数据库怎么做](分表并发写入) +* 81、[从点击屏幕开始,runloop怎么进行工作的](https://www.colabug.com/2017/1225/2102566/) +* 82、[编译过程](https://blog.csdn.net/vincentiss/article/details/54617915) +* 83、[MVVM、MVC、MVP的区别]() +* 84、[iOS内存管理(MRC、ARC)深入浅出](https://www.jianshu.com/p/4f49c5c81021) +* 85、[APNS](https://www.jianshu.com/p/032bfc949917) +* 86、[AFN的二次封装]() +* 87、[聊聊CoreData、FMDB](https://www.jianshu.com/p/5a085a4fe2d7) +* 88、[多线程相关](https://www.cnblogs.com/orang123/p/12434774.html) +* 89、[http的header有什么字段?](https://blog.csdn.net/u013252047/article/details/80116742) +* 90、[TimeProfile的详细使用]() +* 91、[卡顿的形成原因?什么是离屏渲染?]() +* 92、[Rac、SDWebImage、YYKit、AFN的原理]() +* 93、[Js与na交互的原理]() +* 94、[设计一个下载/断点续传框架]() +* 95、[NSCopy/NSCoder](https://blog.csdn.net/u014205968/article/details/78260402) +* 96、[进程间通信的几种方式](https://www.jianshu.com/p/c60f4f3cc3d2) +* 97、[引用计数为0时,内存会立即释放吗?内存是什么时候释放的?]() +* 98、[iOS 底层 - dealloc 的底层实现](https://www.jianshu.com/p/5aee2e571f6b) +* 99、[git merge/rebase](https://blog.csdn.net/kuangdacaikuang/article/details/79619828) +* 100、[响应链的应用]() +* 101、[GCD Block会捕获self吗?]() +* 102、[Bounds和Frame的区别,改变Frame的Size会改变Bounds的size吗](https://blog.csdn.net/weixin_33920401/article/details/88027725) +* 103、[Swift和OC的区别?类和结构体的区别?]() +* 104、[pod install和pod pdate的区别](https://blog.csdn.net/u013538542/article/details/107932186) +* 105、[哈希冲突问题的解决办法]() + + +### 网络 + +* 1、[http1.0->1.1->2.2->3区别](https://www.cnblogs.com/riwang/p/12370785.html) +* 2、[HTTP1.1身份认证-摘要认证](https://blog.csdn.net/zhongshanxian/article/details/81294829) +* 3、[聊聊DNS劫持](https://www.jianshu.com/p/63a94cb46cd2) +* 4、[如何保障客户端与服务器交互中的数据安全](https://netsecurity.51cto.com/art/202002/610473.htm) +* 5、[iOS面试备战-网络篇](https://juejin.im/post/6844904202523639822#comment) +* 6、[在浏览器输入url后发生了什么](https://www.jianshu.com/p/7eea6fbc5fcd) +* 7、[TCP三次握手、四次挥手、https加密原理](https://www.cnblogs.com/imstudy/p/10669631.html) +* 8、[为什么TCP4次挥手时等待为2MSL?](https://www.zhihu.com/question/67013338) +* 9、[DNS的搜索过程](https://juejin.cn/post/6884183177926033416) +* 10、[tcp,为什么4次断开,tcp的拥塞机制,为什么这么设计]() +* 11、[TCP/UDP](https://juejin.cn/post/6844903889146216456) +* 12、[图解 | 什么是HTTP简史](https://mp.weixin.qq.com/s/LV3cFpQtMUntMg6zn-01pQ) +* 13、[Charles抓包原理](https://www.jianshu.com/p/f6f6a21e17c0) +* 14、[DNS的查找过程,什么是DNS劫持](https://juejin.cn/post/6884183177926033416) +* 15、[Get和Post](https://juejin.cn/post/6844904004707696648) +* 16、[http的响应状态码](https://juejin.cn/post/6844903940832641037) +* 17、[https抓包过程](https://www.jianshu.com/p/7a88617ce80b) + +### 算法 + +* 1、[最大子序和](https://leetcode-cn.com/problems/maximum-subarray/) +* 2、[最大子矩阵](https://leetcode-cn.com/problems/max-submatrix-lcci/) +* 3、[乘积最大子数组](https://leetcode-cn.com/problems/maximum-product-subarray/) +* 4、[爬楼梯]() +* 5、[快速排序]() +* 6、[整数反转]() +* 7、[链表反转]() +* 8、[链表是否循环]() +* 9、[两个有序数组合并]() +* 10、[删除链表的倒数第N个节点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) +* 11、[一个纯数字的字符串,调整顺序使之奇偶排列 “17485” ———>”14785 “]() +* 12、[实现一个LRU算法 不使用数组]() +* 13、[两个view的第一个公共父视图]() +* 14、[两个有序数组的中位数](https://leetcode-cn.com/problems/median-of-two-sorted-arrays/) +* 15、[字符串的全排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof//) +* 16、[二叉树的层级打印 计算层高](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/) +* 17、[用字符串实现大数加法]() +* 18、[二叉树 红黑树 二叉树的应用,以及数据库的数据结构以及优化]() +* 19、[找出无序数组中所有3个数字加起来是0 的数字(先排序,再双指针)]() +* 20、[最长回文子串]() +* 21、[找出无序数组中所有3个数字加起来是0 的数字]() +* 22、[8000W个字符串,每个长度在100以内,要求找到频率最高的前1000个](https://leetcode-cn.com/problems/top-k-frequent-elements) +* 23、[利用栈结构实现队列结构](https://www.cnblogs.com/lisen10/p/10892546.html) +* 24、[验证是否是对称二叉树](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/) +* 25、[统计所有小于非负整数 n 的质数的数量](https://leetcode-cn.com/problems/count-primes/) +* 26、[硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。](https://leetcode-cn.com/problems/coin-lcci/) diff --git "a/iOS_Tips/DarkMode/WorkIssues/\351\253\230\350\264\250\351\207\217\346\212\200\346\234\257\345\215\232\345\256\242.md" "b/iOS_Tips/DarkMode/WorkIssues/\351\253\230\350\264\250\351\207\217\346\212\200\346\234\257\345\215\232\345\256\242.md" new file mode 100644 index 00000000..b20f8a19 --- /dev/null +++ "b/iOS_Tips/DarkMode/WorkIssues/\351\253\230\350\264\250\351\207\217\346\212\200\346\234\257\345\215\232\345\256\242.md" @@ -0,0 +1,128 @@ + +## 高质量技术博客 + +* 1、[WeRead团队博客](https://wereadteam.github.io/) +* 2、[美团技术团队](https://tech.meituan.com/) +* 3、[Lyman's Blog](http://www.lymanli.com) +* 4、[sunnyxx孙源](http://blog.sunnyxx.com) +* 5、[波儿菜](https://www.jianshu.com/u/a89bf7b8bdd8) +* 6、[张不坏的博客](https://zhangbuhuai.com) +* 7、[微博@iOS程序犭袁](https://github.com/ChenYilong) +* 8、[小东邪](https://juejin.im/user/58ec343861ff4b00691b4f26) +* 9、[onevcat](https://onevcat.com/#blog) +* 10、[戴铭](https://www.jianshu.com/u/9a4903d7e3d1) +* 11、[casatwy](https://casatwy.com) +* 12、[bang](http://blog.cnbang.net) +* 13、[杭州小刘](https://github.com/FantasticLBP/knowledge-kit/blob/master/SUMMARY.md) +* 14、[收納箱](https://juejin.im/user/5ea7963b5188256da0323498) +* 15、[齐舞647](https://juejin.im/user/5979852b5188253df6575210/posts) +* 16、[性能优化](https://github.com/skyming/iOS-Performance-Optimization) +* 17、[阿里、字节 一套高效的iOS面试题解答](https://github.com/colourful987/bytedance-alibaba-interview) +* 18、[面试题、基础知识点](https://github.com/liberalisman/iOS-InterviewQuestion-collection) +* 19、[音视频笔记-雷霄骅](https://blog.csdn.net/leixiaohua1020) +* 20、[乐少joy0304](https://github.com/joy0304/Joy-Blog) +* 21、[maniac_kk](https://juejin.im/user/5aaf755cf265da23870ea3cf/posts) +* 22、[阿里巴巴淘系技术](https://juejin.im/user/5e8558f3518825738f2b1327) +* 23、[欧阳大哥2013](https://juejin.im/user/593fb40eda2f6000673bdc61) +* 24、[Wangkai](https://juejin.im/user/5bf20f696fb9a049fb4340b0) +* 25、[maniac_kk](https://juejin.im/user/5aaf755cf265da23870ea3cf/posts) +* 27、[汇总各大互联网公司容易考察的高频leetcode题🔥](https://github.com/afatcoder/LeetcodeTop) +* 28、[玉令天下-杨萧玉](http://yulingtianxia.com) +* 29、[唐巧](http://blog.devtang.com) +* 30、[Lision](https://juejin.im/user/2189882891443278) +* 31、[小鱼周凌宇](https://juejin.im/post/6844903616696844302) +* 32、[satanwoo](http://satanwoo.github.io/) +* 33、[景铭巴巴](https://www.jianshu.com/u/c3c893a27097) +* 34、[punmy](https://punmy.cn/) +* 35、[LearnOpengl](https://learnopengl-cn.github.io) +* 36、[everettjf](https://everettjf.github.io/) +* 37、[HybridPageKit](https://dequan1331.github.io/index.html) +* 38、[纳兰若水](https://www.xuyanlan.com/archives/) +* 39、[知识小集](https://juejin.im/user/1327865776308782) +* 40、[Cyandev](https://juejin.im/user/3298190611199415) +* 41、[RickeyBoy](https://juejin.im/user/2928754706626136) +* 42、[落影loyinglin](https://github.com/loyinglin) +* 43、[阿毛的蛋疼地](https://xiangwangfeng.com/) +* 44、[与佳期](gonghonglou.com) +* 45、[RoyCao](https://juejin.im/user/4019470241649550) +* 46、[gocy](https://blog.gocy.tech/) +* 47、[戴铭的博客](https://ming1016.github.io) +* 48、[iDog的精神时光屋](https://bigporo.github.io) +* 49、[Draveness](https://draveness.me/) +* 50、[NewPan](https://juejin.im/user/2506542239987454) +* 51、[没故事的卓同学 ](https://juejin.im/user/1926000099460664) +* 52、[薛定諤](https://juejin.im/user/325111170210045) +* 53、[Mr_Coder](https://juejin.im/user/3544481220795998) +* 54、[冬瓜逛逼乎](https://www.zhihu.com/people/desgard-duan) +* 55、[bestswifter](https://github.com/bestswifter) +* 56、[SilverFruity](https://silverfruity.github.io/) +* 57、[孤单的衬衣](https://juejin.im/user/2735240661962638) +* 58、[ShannonChenCHN](https://github.com/ShannonChenCHN) +* 59、[木子Lision](https://github.com/Lision) +* 60、[letqingbin](https://github.com/letqingbin) +* 61、[CainCamera](https://github.com/CainKernel/blog) +* 62、[行走少年郎](https://juejin.im/user/2488950054725101/posts) +* 63、[翻译、开发心得或学习笔记](https://github.com/nixzhu/dev-blog) +* 64、[778477](https://778477.github.io) +* 65、[ibireme-YYKit作者](https://blog.ibireme.com) +* 66、[mcuking](https://github.com/mcuking/blog) +* 67、[冬瓜争做全栈瓜](https://www.desgard.com) +* 68、[iOS一叶](https://juejin.im/user/1899557248829438) +* 69、[土土Edmond木](https://looseyi.github.io) +* 70、[iOSer逆向](https://iosre.com) +* 71、[GoldHouse-for-iOS](https://github.com/BiBoyang/GoldHouse-for-iOS) +* 72、[折腾范儿の味精](http://awhisper.github.io) +* 73、[zhangferry](https://juejin.im/user/2242659450368119) +* 74、[酷酷的哀殿](https://ai-chan.top/) +* 75、[陈满iOS](https://juejin.cn/user/3245414055161870) +* 76、[黑超熊猫zuik](https://juejin.cn/user/2594503170727399) +* 77、[卖萌凉](https://www.jianshu.com/u/5e309e5486c6) +* 78、[有赞技术团队](https://tech.youzan.com) +* 79、[Lewis](https://juejin.cn/user/3913917125896477) +* 80、[无夜之星辰 ](https://www.jianshu.com/u/4212f351f6b5) + + +## 高质量微信公众号 + +* 1、[一瓜技术](公众号:tech_gua) +* 2、[奔跑的程序猿iOSer](公众号:iOS2679114653) +* 3、[知识小集](公众号:zsxjtip) +* 4、[小浩算法](公众号:xuesuanfa) +* 5、[素燕](公众号:gh_a97f4df5b7b9) +* 6、[iOSTips](公众号:iostips) +* 7、[字节流动](公众号:google_developer) +* 8、[有赞coder](公众号:youzan_coder) +* 9、[58技术](公众号:architects_58) +* 10、[音视频开发进阶](公众号:glumes_blog) +* 11、[五分钟学算法](公众号:CXYxiaowu) +* 12、[iOS成长之路](公众号:gh_fa77b2df3538) +* 13、[京东零售技术](公众号:jd-sys) +* 14、[携程技术](公众号:ctriptech) +* 15、[字节跳动技术团队](公众号:toutiaotechblog) +* 16、[老司机技术周报](公众号:LSJCoding) +* 17、[淘系技术](公众号:AlibabaMTT) +* 18、[我是程序员小贱](公众号:Lanj1995Q) +* 19、[腾讯技术工程](公众号:Tencent_TEG) +* 20、[搜狐技术产品](公众号:sohu-tech) +* 21、[百度APP技术](公众号:gh_59f5931152fe) + +## 书籍 + +* 1、[iOS全埋点解决方案-神策数据](电子书) +* 2、[跟戴铭学iOS编程:理顺核心知识点 - 戴铭](电子书) +* 3、[程序员的自我修养:链接、装载与库](电子书) +* 4、[iOS动画学习手册2.0](电子书) +* 5、[Objective-C高级编程 iOS与OS X多线程和内存管理](电子书) +* 6、[App架构师实践指南](电子书) +* 7、[OpenGL ES 3.0编程指南 ](电子书) +* 8、[音视频开发进阶指南:基于Android与iOS平台的实践 ](电子书) +* 9、[AVFoundation开发秘籍 ](电子书) +* 10、[计算机网络 ](电子书) +* 11、[计算机操作系统 ](电子书) +* 12、[数据结构与算法 ](电子书) +* 13、[编译原理 ](电子书) + +## 找工作内推 + +* [2020:iOS 靠谱内推专题](https://www.yuque.com/iosalliance/article/bhutav) +* [谁在招人?](https://github.com/ruanyf/weekly/issues/1315) diff --git a/iOS_Tips/Podfile b/iOS_Tips/Podfile index ed3e8709..75bcda8a 100644 --- a/iOS_Tips/Podfile +++ b/iOS_Tips/Podfile @@ -1,6 +1,15 @@ source 'https://github.com/CocoaPods/Specs.git' +#source 'https://gitee.com/mirrors/CocoaPods-Specs.git' platform :ios,’10.0’ target "DarkMode" do + pod 'GPUImage' -pod 'MLeaksFinder' +#pod 'MLeaksFinder' +pod 'MBProgressHUD' +pod 'Masonry' +pod 'YYImage' +pod 'YYWebImage' +pod 'YYModel' +pod 'YYModel' +pod 'MJRefresh' end diff --git a/iOS_Tips/Podfile.lock b/iOS_Tips/Podfile.lock deleted file mode 100644 index d7412bc0..00000000 --- a/iOS_Tips/Podfile.lock +++ /dev/null @@ -1,24 +0,0 @@ -PODS: - - FBRetainCycleDetector (0.1.4) - - GPUImage (0.1.7) - - MLeaksFinder (1.0.0): - - FBRetainCycleDetector - -DEPENDENCIES: - - GPUImage - - MLeaksFinder - -SPEC REPOS: - https://github.com/cocoapods/specs.git: - - FBRetainCycleDetector - - GPUImage - - MLeaksFinder - -SPEC CHECKSUMS: - FBRetainCycleDetector: 46f8179bbb1c587deee3ea838a1a3ee02acf5015 - GPUImage: 733a5f0fab92df9de1c37ba9df520a833ccb406d - MLeaksFinder: 8c435bd2f6d070af18cff082b503b21adc130fc0 - -PODFILE CHECKSUM: b9760aa1ecd4bf12339cb7f0d7ce791b91ae50b7 - -COCOAPODS: 1.6.1 diff --git "a/\345\276\256\344\277\241\345\205\254\344\274\227\345\217\267.bmp" "b/\345\276\256\344\277\241\345\205\254\344\274\227\345\217\267.bmp" deleted file mode 100755 index 4d55bd8d..00000000 Binary files "a/\345\276\256\344\277\241\345\205\254\344\274\227\345\217\267.bmp" and /dev/null differ diff --git "a/\345\276\256\344\277\241\345\205\254\344\274\227\345\217\267.png" "b/\345\276\256\344\277\241\345\205\254\344\274\227\345\217\267.png" new file mode 100644 index 00000000..a1877423 Binary files /dev/null and "b/\345\276\256\344\277\241\345\205\254\344\274\227\345\217\267.png" differ