├── subjects
├── algorithm
│ ├── 09_爬楼梯.md
│ ├── 19_扔鸡蛋.md
│ ├── 02_大数相加.md
│ ├── 14_堆排序_栈排序.md
│ ├── 18_TopK.md
│ ├── 05_字母异位词_匹配子字符串.md
│ ├── 06_减绳子_求最大乘积.md
│ ├── 13_编辑距离_石子游戏.md
│ ├── 08_给定一个数_要求通过调整位置刚好大于该数的值.md
│ ├── 15_微软面试题集和.md
│ ├── create_list.sh
│ ├── 07_求立方根.md
│ ├── 20_实现sqrt.md
│ ├── 11_k组链表翻转.md
│ ├── README.md
│ ├── 01_二叉树求公共最低父节点.md
│ ├── 16_合并两个排序的数组.md
│ ├── 10_括号匹配.md
│ ├── 21_顺时针打印矩阵.md
│ ├── 04_1-n个每个数二进制1的个数.md
│ ├── 17_打印二叉树.md
│ ├── 03_乱序数组奇数偶数排序.md
│ └── 12_买卖股票求最大利润.md
├── 源码分析
│ ├── Aspects.md
│ └── fishhook.md
├── 视图渲染
│ ├── iOS 绘制过程.md
│ └── 扩展阅读.md
├── network
│ └── 弱网优化.md
├── 计算机基础
│ └── 进程和线程的区别.md
└── runtime
│ ├── AssociatedObjects.md
│ ├── load和initialize区别.md
│ ├── weak.md
│ └── block.md
├── res
├── kvo.png
├── 打赏.png
├── 三次握手.png
├── 四次挥手.png
├── 第一次握手.png
├── 第三次握手.png
├── 第二次握手.png
├── 7层网络协议图.jpeg
├── Darwin.png
├── afn_关键类.png
├── afn_总结.png
├── afn_整体架构.png
├── apns_1.jpg
├── apns_2.jpg
├── drawrect.png
├── runtime.png
├── drawrect2.png
├── message_forward.png
├── responderchain.png
├── responderhandle.png
├── weak_store_pic.png
├── array_copy_mutable.png
├── getter_procedure.jpg
├── responderhandle2.png
├── setkey_procedure.jpg
├── autoreleasepool_runloop.png
├── class_rw_t_class_ro_t.png
└── autoreleasepool_watchpoint.png
├── 阿里字节一套高效的iOS面试题解答.pdf
├── demos
├── TestLoad方法
│ ├── TestLoad方法
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── ViewController.h
│ │ ├── AppDelegate.h
│ │ ├── Teacher.h
│ │ ├── Professor.h
│ │ ├── Person+category1.h
│ │ ├── Person+category2.h
│ │ ├── Teacher+category1.h
│ │ ├── Teacher+category2.h
│ │ ├── Professor+category1.h
│ │ ├── Professor+category2.h
│ │ ├── SceneDelegate.h
│ │ ├── Teacher.m
│ │ ├── PTRuntimeUtil.h
│ │ ├── Person.m
│ │ ├── Professor.m
│ │ ├── Person+category1.m
│ │ ├── Person+category2.m
│ │ ├── Teacher+category1.m
│ │ ├── Teacher+category2.m
│ │ ├── Professor+category1.m
│ │ ├── Professor+category2.m
│ │ ├── main.m
│ │ ├── Person.h
│ │ ├── AppDelegate.m
│ │ ├── ViewController.m
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ ├── Info.plist
│ │ ├── SceneDelegate.m
│ │ └── PTRuntimeUtil.m
│ └── TestLoad方法.xcodeproj
│ │ ├── xcuserdata
│ │ └── pmst.xcuserdatad
│ │ │ ├── xcdebugger
│ │ │ └── Breakpoints_v2.xcbkptlist
│ │ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ │ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcuserdata
│ │ │ └── pmst.xcuserdatad
│ │ │ │ └── UserInterfaceState.xcuserstate
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── project.pbxproj
└── 04-28-DeepInBlock
│ ├── 04-28-DeepInBlock.xcodeproj
│ ├── xcuserdata
│ │ └── pmst.xcuserdatad
│ │ │ ├── xcdebugger
│ │ │ └── Breakpoints_v2.xcbkptlist
│ │ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcuserdata
│ │ │ └── pmst.xcuserdatad
│ │ │ │ └── UserInterfaceState.xcuserstate
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── project.pbxproj
│ └── 04-28-DeepInBlock
│ └── main.m
├── chapters
├── 7_数据结构与算法.md
├── 4_性能优化和开发证书.md
├── 5_架构设计_源码_其他问题.md
├── 6_系统基础知识.md
├── 2_多线程.md
├── 3_视图和图像相关.md
└── 1_runloop.md
└── interviews
└── iTeaTime技术清谈
├── 面试题集一_0520.md
└── 面试题集二_0520.md
/subjects/algorithm/09_爬楼梯.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/subjects/algorithm/19_扔鸡蛋.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/subjects/algorithm/02_大数相加.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/subjects/algorithm/14_堆排序_栈排序.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/subjects/algorithm/18_TopK.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/subjects/algorithm/05_字母异位词_匹配子字符串.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/subjects/algorithm/06_减绳子_求最大乘积.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/subjects/algorithm/13_编辑距离_石子游戏.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/subjects/源码分析/Aspects.md:
--------------------------------------------------------------------------------
1 | # Aspects 源码分析
--------------------------------------------------------------------------------
/subjects/algorithm/08_给定一个数_要求通过调整位置刚好大于该数的值.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/subjects/源码分析/fishhook.md:
--------------------------------------------------------------------------------
1 | # fishhook 源码分析
2 |
3 |
--------------------------------------------------------------------------------
/res/kvo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/kvo.png
--------------------------------------------------------------------------------
/res/打赏.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/打赏.png
--------------------------------------------------------------------------------
/res/三次握手.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/三次握手.png
--------------------------------------------------------------------------------
/res/四次挥手.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/四次挥手.png
--------------------------------------------------------------------------------
/res/第一次握手.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/第一次握手.png
--------------------------------------------------------------------------------
/res/第三次握手.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/第三次握手.png
--------------------------------------------------------------------------------
/res/第二次握手.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/第二次握手.png
--------------------------------------------------------------------------------
/subjects/algorithm/15_微软面试题集和.md:
--------------------------------------------------------------------------------
1 | 1. 字符串匹配
2 | 2. 二叉树后续遍历迭代
3 | 3. 最长回文子串
4 | 4. 最长字母数字个数相等子串
5 |
6 |
--------------------------------------------------------------------------------
/res/7层网络协议图.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/7层网络协议图.jpeg
--------------------------------------------------------------------------------
/res/Darwin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/Darwin.png
--------------------------------------------------------------------------------
/res/afn_关键类.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/afn_关键类.png
--------------------------------------------------------------------------------
/res/afn_总结.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/afn_总结.png
--------------------------------------------------------------------------------
/res/afn_整体架构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/afn_整体架构.png
--------------------------------------------------------------------------------
/res/apns_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/apns_1.jpg
--------------------------------------------------------------------------------
/res/apns_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/apns_2.jpg
--------------------------------------------------------------------------------
/res/drawrect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/drawrect.png
--------------------------------------------------------------------------------
/res/runtime.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/runtime.png
--------------------------------------------------------------------------------
/res/drawrect2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/drawrect2.png
--------------------------------------------------------------------------------
/阿里字节一套高效的iOS面试题解答.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/阿里字节一套高效的iOS面试题解答.pdf
--------------------------------------------------------------------------------
/res/message_forward.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/message_forward.png
--------------------------------------------------------------------------------
/res/responderchain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/responderchain.png
--------------------------------------------------------------------------------
/res/responderhandle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/responderhandle.png
--------------------------------------------------------------------------------
/res/weak_store_pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/weak_store_pic.png
--------------------------------------------------------------------------------
/res/array_copy_mutable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/array_copy_mutable.png
--------------------------------------------------------------------------------
/res/getter_procedure.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/getter_procedure.jpg
--------------------------------------------------------------------------------
/res/responderhandle2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/responderhandle2.png
--------------------------------------------------------------------------------
/res/setkey_procedure.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/setkey_procedure.jpg
--------------------------------------------------------------------------------
/res/autoreleasepool_runloop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/autoreleasepool_runloop.png
--------------------------------------------------------------------------------
/res/class_rw_t_class_ro_t.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/class_rw_t_class_ro_t.png
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/res/autoreleasepool_watchpoint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/res/autoreleasepool_watchpoint.png
--------------------------------------------------------------------------------
/subjects/algorithm/create_list.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ls | grep -v README.md | grep -v create_list.sh | xargs -I{} echo "* [ ] ["{}"](./"{}")" > README.md
4 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法.xcodeproj/xcuserdata/pmst.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/demos/04-28-DeepInBlock/04-28-DeepInBlock.xcodeproj/xcuserdata/pmst.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/demos/04-28-DeepInBlock/04-28-DeepInBlock.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法.xcodeproj/project.xcworkspace/xcuserdata/pmst.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/demos/TestLoad方法/TestLoad方法.xcodeproj/project.xcworkspace/xcuserdata/pmst.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/subjects/algorithm/07_求立方根.md:
--------------------------------------------------------------------------------
1 | # 求立方根
2 |
3 | ## 二分法
4 |
5 | ## 牛顿法
6 |
7 | > TODO:理论待补充
8 |
9 | ```c
10 | float fun(float guess,float x)
11 | {
12 | if(abs(guess*guess*guess-x)<0.0000001) return guess;
13 | else
14 | return fun((x/guess/guess+2*guess)/3,x);
15 | }
16 | ```
17 |
18 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/ViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface ViewController : UIViewController
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/demos/04-28-DeepInBlock/04-28-DeepInBlock.xcodeproj/project.xcworkspace/xcuserdata/pmst.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/colourful987/bytedance-alibaba-interview/HEAD/demos/04-28-DeepInBlock/04-28-DeepInBlock.xcodeproj/project.xcworkspace/xcuserdata/pmst.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/chapters/7_数据结构与算法.md:
--------------------------------------------------------------------------------
1 | # 数据结构与算法
2 |
3 | > pmst: 建议 [LeetCode](https://leetcode.com/)上刷题,面试是一方面,锻炼我们的*逻辑*思维、分析能力,就比如今天的每日一题[面试题 16.03. 交点](https://leetcode-cn.com/problems/intersection-lcci/),问题难度不高,就是初中的斜线问题,主要是分析多种情况下的交点,理顺逻辑很重要。
4 |
5 | 1. 八大排序算法
6 | 2. 栈&队列
7 | 3. 字符串处理
8 | 4. 链表
9 | 5. 二叉树相关操作
10 | 6. 深搜广搜
11 | 7. 基本的动态规划题、贪心算法、二分查找
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Teacher.h:
--------------------------------------------------------------------------------
1 | //
2 | // Teacher.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Person.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface Teacher : Person
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Professor.h:
--------------------------------------------------------------------------------
1 | //
2 | // Professor.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Teacher.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface Professor : Teacher
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Person+category1.h:
--------------------------------------------------------------------------------
1 | //
2 | // Person+category1.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Person.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface Person (category1)
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Person+category2.h:
--------------------------------------------------------------------------------
1 | //
2 | // Person+category2.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Person.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface Person (category2)
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Teacher+category1.h:
--------------------------------------------------------------------------------
1 | //
2 | // Teacher+category1.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Teacher.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface Teacher (category1)
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Teacher+category2.h:
--------------------------------------------------------------------------------
1 | //
2 | // Teacher+category2.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Teacher.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface Teacher (category2)
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Professor+category1.h:
--------------------------------------------------------------------------------
1 | //
2 | // Professor+category1.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Professor.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface Professor (category1)
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Professor+category2.h:
--------------------------------------------------------------------------------
1 | //
2 | // Professor+category2.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Professor.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface Professor (category2)
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/subjects/视图渲染/iOS 绘制过程.md:
--------------------------------------------------------------------------------
1 | # iOS 渲染过程
2 |
3 | > 待完善
4 |
5 | ## UIView & CALayer的区别
6 |
7 | * UIView 为 CALayer 提供内容,以及负责处理触摸等事件,参与响应链;
8 | * CALayer 负责显示内容 contents
9 | * 单一职责原则
10 |
11 | ## iOS 渲染过程
12 |
13 | 首先调用 `setNeedsDisplay` 给视图打上脏标记,等待 runloop 进入休眠前进行处理
14 |
15 | 
16 |
17 | 
18 |
19 | > 慕尚课程的总结图
--------------------------------------------------------------------------------
/demos/04-28-DeepInBlock/04-28-DeepInBlock.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/SceneDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface SceneDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow * window;
14 |
15 | @end
16 |
17 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Teacher.m:
--------------------------------------------------------------------------------
1 | //
2 | // Teacher.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Teacher.h"
10 |
11 | @implementation Teacher
12 | + (void)load {
13 | NSLog(@"%s",__FUNCTION__);
14 | }
15 |
16 | + (void)initialize {
17 | NSLog(@"%s",__FUNCTION__);
18 | }
19 | @end
20 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/PTRuntimeUtil.h:
--------------------------------------------------------------------------------
1 | //
2 | // PTRuntimeUtil.h
3 | // RuntimeUtil
4 | //
5 | // Created by pmst on 2020/3/15.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface PTRuntimeUtil : NSObject
14 | - (void)logClassInfo:(Class)cls;
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Person.m:
--------------------------------------------------------------------------------
1 | //
2 | // Person.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Person.h"
10 |
11 |
12 |
13 | @implementation Person
14 | + (void)load {
15 | NSLog(@"%s",__FUNCTION__);
16 | }
17 |
18 | + (void)initialize {
19 | NSLog(@"%s",__FUNCTION__);
20 | }
21 | @end
22 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Professor.m:
--------------------------------------------------------------------------------
1 | //
2 | // Professor.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Professor.h"
10 |
11 | @implementation Professor
12 | + (void)load {
13 | NSLog(@"%s",__FUNCTION__);
14 | }
15 |
16 | + (void)initialize {
17 | NSLog(@"%s",__FUNCTION__);
18 | }
19 | @end
20 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Person+category1.m:
--------------------------------------------------------------------------------
1 | //
2 | // Person+category1.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Person+category1.h"
10 |
11 | @implementation Person (category1)
12 | + (void)load {
13 | NSLog(@"%s",__FUNCTION__);
14 | }
15 |
16 | + (void)initialize {
17 | NSLog(@"%s",__FUNCTION__);
18 | }
19 | @end
20 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Person+category2.m:
--------------------------------------------------------------------------------
1 | //
2 | // Person+category2.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Person+category2.h"
10 |
11 | @implementation Person (category2)
12 | + (void)load {
13 | NSLog(@"%s",__FUNCTION__);
14 | }
15 |
16 | + (void)initialize {
17 | NSLog(@"%s",__FUNCTION__);
18 | }
19 | @end
20 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Teacher+category1.m:
--------------------------------------------------------------------------------
1 | //
2 | // Teacher+category1.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Teacher+category1.h"
10 |
11 | @implementation Teacher (category1)
12 | + (void)load {
13 | NSLog(@"%s",__FUNCTION__);
14 | }
15 |
16 | + (void)initialize {
17 | NSLog(@"%s",__FUNCTION__);
18 | }
19 | @end
20 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Teacher+category2.m:
--------------------------------------------------------------------------------
1 | //
2 | // Teacher+category2.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Teacher+category2.h"
10 |
11 | @implementation Teacher (category2)
12 | + (void)load {
13 | NSLog(@"%s",__FUNCTION__);
14 | }
15 |
16 | + (void)initialize {
17 | NSLog(@"%s",__FUNCTION__);
18 | }
19 | @end
20 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Professor+category1.m:
--------------------------------------------------------------------------------
1 | //
2 | // Professor+category1.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Professor+category1.h"
10 |
11 | @implementation Professor (category1)
12 | + (void)load {
13 | NSLog(@"%s",__FUNCTION__);
14 | }
15 |
16 | + (void)initialize {
17 | NSLog(@"%s",__FUNCTION__);
18 | }
19 | @end
20 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Professor+category2.m:
--------------------------------------------------------------------------------
1 | //
2 | // Professor+category2.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "Professor+category2.h"
10 |
11 | @implementation Professor (category2)
12 | + (void)load {
13 | NSLog(@"%s",__FUNCTION__);
14 | }
15 |
16 | + (void)initialize {
17 | NSLog(@"%s",__FUNCTION__);
18 | }
19 | @end
20 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法.xcodeproj/xcuserdata/pmst.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | TestLoad方法.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/demos/04-28-DeepInBlock/04-28-DeepInBlock.xcodeproj/xcuserdata/pmst.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | 04-28-DeepInBlock.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | NSString * appDelegateClassName;
14 | @autoreleasepool {
15 | // Setup code that might create autoreleased objects goes here.
16 | appDelegateClassName = NSStringFromClass([AppDelegate class]);
17 | }
18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName);
19 | }
20 |
--------------------------------------------------------------------------------
/subjects/network/弱网优化.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # 弱网优化
4 |
5 | > 初版,基本思路都是按照下面来的
6 |
7 | 1. 合适的超时时间,针对不同网络设定不同的超时时间,加快超时,尽快重试
8 | 2. 按子模块多请求去拉取数据,避免一次性加载,导致数据太多请求返回慢;
9 | 3. 缓存和增量请求
10 | 4. **优化DNS查询:**应尽量减少DNS查询,做DNS缓存,避免域名劫持、DNS污染,同时把用户调度到“最优接入点”。
11 | 5. 减小数据包大小和优化包量:通过压缩、精简包头、消息合并等方式,来减小数据包大小和包量。
12 | 6. **优化ACK包:**平衡冗余包和ACK包个数,达到降低延时,提高吞吐量的目的。(这些难度有点高)
13 | 7. 断线重连,因为我们是 socket 通信的,所以需要做断线重连,重连时间可以递增
14 | 8. 减少数据连接的创建次数,由于创建连接是一个非常昂贵的操作,所以应尽量**减少数据连接的创建次数**,且在一次请求中应尽量以批量的方式执行任务。如果多次发送小数据包,应该尽量保证在2秒以内发送出去。在短时间内访问不同服务器时,尽可能地复用无线连接。
15 | 9. 用户 UI 体验优化,加载一些动画什么的分散下注意力
16 | 10. **根据不同网络情况动态调节方案,比如图片下载和视频流就可以按照 3G 4G 和 WIFI 进行区分返回**
17 | 11. HTTP3.0
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Person.h:
--------------------------------------------------------------------------------
1 | //
2 | // Person.h
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface Person :NSObject
14 | {
15 | char c;
16 | NSInteger age1;
17 |
18 | int age;
19 | short age2;
20 | NSString *name;
21 | }
22 | //@property(nonatomic, strong)NSMutableArray *array;
23 | //@property (nonatomic, assign) int age;
24 | //@property(nonatomic, assign)short age1;
25 | //@property (nonatomic, copy) NSString *name;
26 | @end
27 |
28 | NS_ASSUME_NONNULL_END
29 |
--------------------------------------------------------------------------------
/subjects/视图渲染/扩展阅读.md:
--------------------------------------------------------------------------------
1 | # 文献资料
2 |
3 | [TOC]
4 |
5 | ## 视图&图像相关
6 |
7 | * [iOS Core Animation: Advanced Techniques](http://www.amazon.com/iOS-Core-Animation-Advanced-Techniques-ebook/dp/B00EHJCORC/ref=sr_1_1?ie=UTF8&qid=1423192842&sr=8-1&keywords=Core+Animation+Advanced+Techniques),译文[《ios核心动画高级技巧》](https://zsisme.gitbooks.io/ios-/content/index.html)
8 | * [《iOS 性能优化总结》](https://juejin.im/post/5ace078cf265da23994ee493),简单提及了 CPU 和 GPU 渲染知识以及优化点
9 | * [《深入理解 iOS Rendering Process》](https://juejin.im/post/5ad3f1cc6fb9a028d9379c5f),较为深入地介绍了渲染流程
10 | * [《GPU vs CPU in iOS》](https://www.jianshu.com/p/13a28228f25f)
11 | * [《浅谈图像格式 .bmp》](https://zhuanlan.zhihu.com/p/25119530)
12 |
13 |
--------------------------------------------------------------------------------
/subjects/algorithm/20_实现sqrt.md:
--------------------------------------------------------------------------------
1 | # 实现 sqrt
2 |
3 | 二分法:
4 |
5 | ```c
6 | #define Epsilon 0.0000001
7 |
8 | double mySqrt(int x){
9 | if(x == 0)return x;
10 |
11 | double l = Epsilon;
12 | double r = x;
13 |
14 | while(l + Epsilon < r){
15 | double mid = (r-l)/2.f + l;
16 | if(mid * mid > x){
17 | r = mid ;
18 | } else {
19 | l = mid+Epsilon;
20 | }
21 | }
22 | return l;
23 | }
24 | ```
25 |
26 | 牛顿法:
27 |
28 | ```c
29 | double mySqrt2(int x){
30 | if(x == 0)return x;
31 | double c = x;
32 | double x0 = x;
33 |
34 | while(1){
35 | double xi = 0.5 *(x0 + c/x0);
36 | if(ABS(x0-xi) < 1e-6)break;
37 | x0 = xi;
38 | }
39 |
40 | return x0;
41 | }
42 | ```
43 |
44 |
--------------------------------------------------------------------------------
/subjects/algorithm/11_k组链表翻转.md:
--------------------------------------------------------------------------------
1 | # [25. K 个一组翻转链表](https://leetcode-cn.com/problems/reverse-nodes-in-k-group/)
2 |
3 | > 难度:hard
4 |
5 | ```c
6 | /**
7 | * Definition for singly-linked list.
8 | * struct ListNode {
9 | * int val;
10 | * struct ListNode *next;
11 | * };
12 | */
13 |
14 | struct ListNode* reverseKGroup(struct ListNode* head, int k){
15 | struct ListNode *curr = head;
16 | int count = 0;
17 | while(curr != NULL && count != k){
18 | curr = curr->next;
19 | count++;
20 | }
21 |
22 | if(count == k){
23 | curr = reverseKGroup(curr,k);
24 | while(count-- > 0){
25 | struct ListNode *tmp = head->next;
26 | head->next = curr;
27 | curr = head;
28 | head = tmp;
29 | }
30 | head = curr;
31 | }
32 | return head;
33 | }
34 | ```
35 |
36 |
--------------------------------------------------------------------------------
/chapters/4_性能优化和开发证书.md:
--------------------------------------------------------------------------------
1 | # 性能优化
2 |
3 | 1. 如何做启动优化,如何监控
4 | 2. 如何做卡顿优化,如何监控
5 | 1. FPS 用 CADisplayLinker 来计数
6 | 2. 监听 runloop 的 source0 事件和进入休眠前,然后设定一个阈值,超过几次算卡顿
7 | 3. ping 方案,起一个子线程,while(1) 每次 async 一个 task 到主线程进行标志位置位,然后休眠或者等待一定时间在子线程检查是否这个task被执行了。
8 | 3. 如何做耗电优化,如何监控
9 | 4. 如何做网络优化,如何监控
10 |
11 | > [iOS面试题:简述性能优化](https://zhuanlan.zhihu.com/p/96963676)
12 |
13 | # 开发证书
14 |
15 | 1. 苹果使用证书的目的是什么
16 |
17 | 在 iOS 平台对第三方 APP 有绝对的控制权,一定要保证每一个安装到 iOS 上的 APP 都是经过苹果官方允许的,场景有如下三种
18 |
19 | 1. AppStore 下载应用验证,传 App 上 AppStore 时,苹果后台用私钥对 APP 数据进行签名,iOS 系统下载这个 APP 后,用公钥验证这个签名,若签名正确,这个 APP 肯定是由苹果后台认证的,并且没有被修改过,也就达到了苹果的需求:保证安装的每一个 APP 都是经过苹果官方允许的。
20 | 2. 开发 App 时可以直接把开发中的应用安装进手机进行调试。
21 | 3. In-House 企业内部分发,可以直接安装企业证书签名后的 APP。
22 | 4. AD-Hoc 相当于企业分发的限制版,限制安装设备数量,较少用。
23 |
24 | 2. AppStore安装app时的认证流程
25 |
26 | 略
27 |
28 | 3. 开发者怎么在debug模式下把app安装到设备呢
29 |
30 | 略
--------------------------------------------------------------------------------
/subjects/algorithm/README.md:
--------------------------------------------------------------------------------
1 | * [x] [01_二叉树求公共最低父节点.md](./01_二叉树求公共最低父节点.md)
2 | * [ ] [02_大数相加.md](./02_大数相加.md)
3 | * [x] [03_乱序数组奇数偶数排序.md](./03_乱序数组奇数偶数排序.md)
4 | * [x] [04_1-n个每个数二进制1的个数.md](./04_1-n个每个数二进制1的个数.md)
5 | * [ ] [05_字母异位词_匹配子字符串.md](./05_字母异位词_匹配子字符串.md)
6 | * [ ] [06_减绳子_求最大乘积.md](./06_减绳子_求最大乘积.md)
7 | * [x] [07_求立方根.md](./07_求立方根.md)
8 | * [ ] [08_给定一个数_要求通过调整位置刚好大于该数的值.md](./08_给定一个数_要求通过调整位置刚好大于该数的值.md)
9 | * [ ] [09_爬楼梯.md](./09_爬楼梯.md)
10 | * [x] [10_括号匹配.md](./10_括号匹配.md)
11 | * [ ] [11_k组链表翻转.md](./11_k组链表翻转.md)
12 | * [x] [12_买卖股票求最大利润.md](./12_买卖股票求最大利润.md)
13 | * [ ] [13_编辑距离_石子游戏.md](./13_编辑距离_石子游戏.md)
14 | * [ ] [14_堆排序_栈排序.md](./14_堆排序_栈排序.md)
15 | * [ ] [15_微软面试题集和.md](./15_微软面试题集和.md)
16 | * [x] [16_合并两个排序的数组.md](./16_合并两个排序的数组.md)
17 | * [x] [17_打印二叉树.md](./17_打印二叉树.md)
18 | * [ ] [18_TopK.md](./18_TopK.md)
19 | * [x] [19_扔鸡蛋.md](./19_扔鸡蛋.md)
20 | * [x] [20_实现sqrt.md](./20_实现sqrt.md)
21 | * [x] [21_顺时针打印矩阵.md](./21_顺时针打印矩阵.md)
22 |
23 |
--------------------------------------------------------------------------------
/chapters/5_架构设计_源码_其他问题.md:
--------------------------------------------------------------------------------
1 | # 架构设计
2 |
3 | ## 典型源码的学习
4 |
5 | 只是列出一些iOS比较核心的开源库,这些库包含了很多高质量的思想,源码学习的时候一定要关注每个框架解决的核心问题是什么,还有它们的优缺点,这样才能算真正理解和吸收
6 |
7 | > pmst: 源码分析博主不做分析了。
8 |
9 | 1. AFN
10 | 2. SDWebImage
11 | 3. JSPatch、Aspects(虽然一个不可用、另一个不维护,但是这两个库都很精炼巧妙,很适合学习)
12 | 4. Weex/RN, 笔者认为这种前端和客户端紧密联系的库是必须要知道其原理的
13 | 5. CTMediator、其他router库,这些都是常见的路由库,开发中基本上都会用到
14 |
15 | ## 架构设计
16 |
17 | 1. 手动埋点、自动化埋点、可视化埋点
18 |
19 | 2. `MVC、MVP、MVVM`设计模式
20 |
21 | 3. 常见的设计模式
22 |
23 |
24 |
25 | 4. 单例的弊端
26 |
27 | 5. 常见的路由方案,以及优缺点对比
28 |
29 | 6. 如果保证项目的稳定性
30 |
31 | 7. 设计一个图片缓存框架(LRU)
32 |
33 | 8. 如何设计一个`git diff`
34 |
35 | 9. 设计一个线程池?画出你的架构图
36 |
37 | 10. 你的app架构是什么,有什么优缺点、为什么这么做、怎么改进
38 |
39 | # 其他问题
40 |
41 | 1. `PerformSelector & NSInvocation`优劣对比
42 | 2. `oc`怎么实现多继承?怎么面向切面(可以参考[Aspects深度解析-iOS面向切面编程](https://juejin.im/post/5e13c4366fb9a047f42e6406))
43 | 3. 哪些`bug`会导致崩溃,如何防护崩溃
44 | 4. 怎么监控崩溃
45 | 5. `app`的启动过程(考察LLVM编译过程、静态链接、动态链接、runtime初始化)
46 | 6. 沙盒目录的每个文件夹划分的作用
47 | 7. 简述下`match-o`文件结构
--------------------------------------------------------------------------------
/subjects/algorithm/01_二叉树求公共最低父节点.md:
--------------------------------------------------------------------------------
1 | # 01_二叉树求公共最低父节点
2 |
3 | > source:
4 | >
5 | > * [面试题68 - II. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/),
6 | >
7 | > * [236. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)
8 | > * [面试题68 - I. 二叉搜索树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/)
9 | >
10 | > date: 2020/06/03
11 | >
12 | > note:
13 |
14 | ```c
15 | /**
16 | * Definition for a binary tree node.
17 | * struct TreeNode {
18 | * int val;
19 | * struct TreeNode *left;
20 | * struct TreeNode *right;
21 | * };
22 | */
23 |
24 | struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q){
25 | if(root == NULL || root == p || root == q){
26 | return root;
27 | }
28 | struct TreeNode *leftNode = lowestCommonAncestor(root->left, p,q);
29 | struct TreeNode *rightNode = lowestCommonAncestor(root->right,p,q);
30 |
31 | if(leftNode==NULL)
32 | return rightNode;
33 | if(rightNode==NULL)
34 | return leftNode;
35 |
36 | return root;
37 | }
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/subjects/algorithm/16_合并两个排序的数组.md:
--------------------------------------------------------------------------------
1 | # 合并两个排序的数组
2 |
3 | > source :[88. 合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/)
4 |
5 | 逐个插入,额外分配了 N 空间,复杂度 O(n)。
6 |
7 | ```c
8 | void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
9 |
10 | int i = 0;
11 | int j = 0;
12 | int k = 0;
13 | int *ans = (int *)malloc(sizeof(int) *(m+n));
14 | memset(ans, 0, m+n);
15 |
16 | while(i < m && j < n){
17 | if(nums1[i] < nums2[j]){
18 | ans[k++] = nums1[i++];
19 | } else {
20 | ans[k++] = nums2[j++];
21 | }
22 | }
23 |
24 | while(i < m){
25 | ans[k++] = nums1[i++];
26 | }
27 |
28 | while(j < n){
29 | ans[k++] = nums2[j++];
30 | }
31 | for(int i = 0; i < m+n;i++){
32 | nums1[i] = ans[i];
33 | }
34 | }
35 | ```
36 |
37 | > 限制条件:不能额外分配内存解题,从后往前插数字,反而这种是最简单的解法。
38 |
39 | ```c
40 | void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
41 | // 从后往前塞数字
42 | int len1 = m-1;
43 | int len2 = n-1;
44 | int len = m+n-1;
45 |
46 | while(len1 >= 0 && len2 >= 0){
47 | nums1[len--] = nums1[len1] > nums2[len2] ? nums1[len1--] : nums2[len2--];
48 | }
49 | while(len2 >= 0){
50 | nums1[len--] = nums2[len2--];
51 | }
52 | }
53 | ```
54 |
55 |
--------------------------------------------------------------------------------
/subjects/algorithm/10_括号匹配.md:
--------------------------------------------------------------------------------
1 | ## 括号匹配
2 |
3 | 括号匹配题目有多题,可以以辅助栈来解答。
4 |
5 | ### [20. 有效的括号](https://leetcode-cn.com/problems/valid-parentheses/)
6 |
7 | ```c
8 | int isPair(char a,char b){
9 | printf("匹配:%c,%c \n",a,b);
10 | if(a == '(' && b == ')') return 1;
11 |
12 | if(a == '{' && b == '}') return 1;
13 |
14 | if(a == '[' && b == ']') return 1;
15 | printf("匹配:%c,%c 失败",a,b);
16 | return 0;
17 | }
18 |
19 | bool isValid(char * s){
20 | int len = strlen(s);
21 | if(len == 0)return 1;
22 |
23 | printf("len :%d\n",len);
24 |
25 | char *stack = (char *)malloc(sizeof(char) * len);
26 | int count = 1;
27 | stack[count-1] = s[0]; // 入栈一个
28 |
29 | for(int i = 1;i < len;i++){
30 | char cur = s[i]; // 当前栈
31 |
32 | // 如果栈中没有可以匹配的 且不符合消除机制的三种情况
33 | if(count == 0 && (cur==')' || cur==']' || cur=='}')){
34 | return 0;
35 | }
36 |
37 | // printf("i:%d count :%d\n",i,count);
38 |
39 | if(count >= 1 && isPair(stack[count-1], cur) == 1){
40 | // 匹配成功了
41 | count--;
42 | } else {
43 | count++;
44 | stack[count-1] = cur;// 入栈
45 | }
46 | }
47 |
48 | free(stack);
49 | if (count == 0)return 1;
50 |
51 | return 0;
52 |
53 | }
54 | ```
55 |
56 |
--------------------------------------------------------------------------------
/subjects/计算机基础/进程和线程的区别.md:
--------------------------------------------------------------------------------
1 | # 进程和线程的区别:
2 |
3 | ## 解答一:
4 |
5 | > link: https://www.zhihu.com/question/25532384/answer/81152571
6 | >
7 | > author: zhonyong
8 |
9 | 首先来一句概括的总论:**进程和线程都是一个时间段的描述,是CPU工作时间段的描述。**
10 |
11 | **下面细说背景**:
12 | CPU+RAM+各种资源(比如显卡,光驱,键盘,GPS, 等等外设)构成我们的电脑,但是电脑的运行,实际就是CPU和相关寄存器以及RAM之间的事情。
13 |
14 | **一个最最基础的事实**:CPU太快,太快,太快了,寄存器仅仅能够追的上他的脚步,RAM和别的挂在各总线上的设备完全是望其项背。那当多个任务要执行的时候怎么办呢?轮流着来?或者谁优先级高谁来?不管怎么样的策略,一句话就是在CPU看来就是轮流着来。
15 |
16 | **一个必须知道的事实**:执行一段程序代码,实现一个功能的过程介绍 ,当得到CPU的时候,相关的资源必须也已经就位,就是显卡啊,GPS啊什么的必须就位,然后CPU开始执行。这里除了CPU以外所有的就构成了这个程序的执行环境,也就是我们所定义的程序上下文。当这个程序执行完了,或者分配给他的CPU执行时间用完了,那它就要被切换出去,等待下一次CPU的临幸。在被切换出去的最后一步工作就是保存程序上下文,因为这个是下次他被CPU临幸的运行环境,必须保存。
17 |
18 | **串联起来的事实**:前面讲过在CPU看来所有的任务都是一个一个的轮流执行的,具体的轮流方法就是:**先加载程序A的上下文,然后开始执行A,保存程序A的上下文,调入下一个要执行的程序B的程序上下文,然后开始执行B,保存程序B的上下文。。。**。
19 |
20 | ========= 重要的东西出现了========
21 | **进程和线程就是这样的背景出来的**,两个名词不过是对应的CPU时间段的描述,名词就是这样的功能。
22 |
23 | - **进程就是包换上下文切换的程序执行时间总和** = **CPU加载上下文+CPU执行+CPU保存上下文**
24 |
25 | **线程是什么呢?**
26 |
27 | 进程的颗粒度太大,每次都要有上下的调入,保存,调出。如果我们把进程比喻为一个运行在电脑上的软件,那么一个软件的执行不可能是一条逻辑执行的,必定有多个分支和多个程序段,就好比要实现程序A,实际分成 a,b,c等多个块组合而成。那么这里具体的执行就可能变成:
28 |
29 | 程序A得到CPU =》CPU加载上下文,开始执行程序A的a小段,然后执行A的b小段,然后再执行A的c小段,最后CPU保存A的上下文。
30 |
31 | 这里a,b,c的执行是共享了A的上下文,CPU在执行的时候没有进行上下文切换的。这**里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境,的更为细小的CPU时间段。
32 |
33 | 到此全文结束,再一个总结:
34 | **进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同。**
35 |
36 | > ❓"开始执行程序A的a小段,然后执行A的b小段,然后再执行A的c小段" 这里的描述有个疑问: a、b、c 三个小段执行顺序详细是怎么样的,实际上也是”二次“分时间片给各个线程去执行吗?进程时间片结束后,记录每个线程执行的指令位置?
--------------------------------------------------------------------------------
/subjects/algorithm/21_顺时针打印矩阵.md:
--------------------------------------------------------------------------------
1 | # 顺时针打印矩阵
2 |
3 | > source :[面试题29. 顺时针打印矩阵](https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/)
4 |
5 | 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
6 |
7 | 示例 1:
8 |
9 | ```c
10 | 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
11 | 输出:[1,2,3,6,9,8,7,4,5]
12 | ```
13 | 示例 2:
14 | ```c
15 | 输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
16 | 输出:[1,2,3,4,8,12,11,10,9,5,6,7]
17 | ```
18 |
19 |
20 |
21 | ```c
22 | /**
23 | * Note: The returned array must be malloced, assume caller calls free().
24 | */
25 | int* spiralOrder(int** matrix, int matrixSize, int* matrixColSize, int* returnSize){
26 | if(matrixSize == 0){
27 | *returnSize = 0;
28 | return NULL;
29 | }
30 |
31 | int l = 0;
32 | int r = *matrixColSize - 1;
33 | int t = 0;
34 | int b = matrixSize - 1;
35 | *returnSize = (r+1) * (b+1);
36 | int *res = (int *)malloc(sizeof(int) * (*returnSize));
37 | memset(res, 0 , (*returnSize));
38 | int x = 0;
39 | while(1){
40 | for(int i = l;i <= r; i++)res[x++] = matrix[t][i]; // left -> right;
41 | if(++t > b)break;
42 | for(int i = t;i <= b; i++)res[x++] = matrix[i][r]; // top -> bottom;
43 | if(l > --r)break;
44 | for(int i = r;i >= l; i--)res[x++] = matrix[b][i]; // right->left;
45 | if(t > --b)break;
46 | for(int i = b;i >= t; i--)res[x++] = matrix[i][l]; // bottom -> top;
47 | if(++l > r)break;
48 | }
49 | return res;
50 | }
51 | ```
52 |
53 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 |
11 | @interface AppDelegate ()
12 |
13 | @end
14 |
15 | @implementation AppDelegate
16 |
17 |
18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
19 | // Override point for customization after application launch.
20 | return YES;
21 | }
22 |
23 |
24 | #pragma mark - UISceneSession lifecycle
25 |
26 |
27 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
28 | // Called when a new scene session is being created.
29 | // Use this method to select a configuration to create the new scene with.
30 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
31 | }
32 |
33 |
34 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions {
35 | // Called when the user discards a scene session.
36 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
37 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
38 | }
39 |
40 |
41 | @end
42 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/ViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "ViewController.h"
10 | #import "Person.h"
11 | #import "Teacher.h"
12 | #import "Professor.h"
13 | #import
14 | #import "PTRuntimeUtil.h"
15 | @interface ViewController ()
16 | @property(nonatomic, strong)Person *person;
17 | @end
18 |
19 | @implementation ViewController
20 |
21 | - (void)viewDidLoad {
22 | [super viewDidLoad];
23 | self.person = [Person new];
24 | NSLog(@"NSObject实例对象的成员变量所占用的大小为:%zd",class_getInstanceSize([self.person class]));
25 | [[PTRuntimeUtil new] logClassInfo:[self.person class]];
26 | // self.person = [Person new];
27 | // self.person.array = [NSMutableArray array];
28 | //
29 | // [self.person addObserver:self forKeyPath:@"array" options:NSKeyValueObservingOptionNew context:nil];
30 | //
31 | // [[self.person mutableArrayValueForKey:@"array"] addObject:@"ff"];
32 | }
33 |
34 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
35 | NSNumber *kind = change[NSKeyValueChangeKindKey];
36 | NSArray *students = change[NSKeyValueChangeNewKey];
37 | NSArray *oldStudent = change[NSKeyValueChangeOldKey];
38 | NSIndexSet *changedIndexs = change[NSKeyValueChangeIndexesKey];
39 |
40 | NSLog(@"kind: %@, students: %@, oldStudent: %@, changedIndexs: %@", kind, students, oldStudent, changedIndexs);
41 | }
42 |
43 |
44 | @end
45 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/subjects/algorithm/04_1-n个每个数二进制1的个数.md:
--------------------------------------------------------------------------------
1 | # 1-n个每个数二进制1的个数
2 |
3 | > 没找到该题,leetcode 有类似的题目,先写一些
4 |
5 |
6 |
7 |
8 |
9 | ## 1. [1~n整数中1出现的次数](https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/)
10 |
11 | 写下[题解](https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/mian-shi-ti-43-1n-zheng-shu-zhong-1-chu-xian-de-2/) 中一些理解,方便后面回顾。
12 |
13 | > 2304 举例,high = 23,cur = 0,low = 4;
14 |
15 | * cur = 0 的时候,那么高位的变化只能是 0 - 22 共计23种可能,伴随着个位数 0 - 9 的10种可能(这里衍生为 digit ,10 ,100,1000...),试想高位为0的时候, [0~22]1[0~9],除了中间固定了1,计算公式就是 hight x digit ;
16 |
17 | > 2314 举例,high = 23,cur = 1,low = 4;
18 |
19 | * cur = 1的时候,**错误理解是觉得高位有24种情况(0~23),然后24 x 10 得出240**;造成这种错觉的原因是,高位24种情况中,23 这个数值稍有不同,想要高位为23,且十位是1的情况,得出是这样的【 **23 1 X **】,而 X 的范围是 0 ~4,所以应该是 23 x 10 + low+1;
20 |
21 | > 23X4 举例, 其中 X >= 2;
22 |
23 | * cur >= 2 的情况,首先高位 24 种情况,因为现在十位大于等于2,所以当该位固定为1的时候,低位范围可以是 0 ~ 9 十种情况,因此公式为 (high+1) x digit
24 |
25 | ```c
26 | int countDigitOne(int n){
27 | //求每个位的数字所用
28 | long index = 1;
29 | //记录1的个数
30 | long count = 0;
31 | long high = n,cur = 0,low = 0;
32 |
33 | while(high > 0){
34 | high /= 10; // 取得高位
35 | cur = (n / index) % 10; // 当前位
36 | low = n - (n / index) * index; // 余数
37 | //以下是计算的公式
38 | if(cur == 0) count += high * index;
39 | if(cur == 1) count += high * index + low + 1;
40 | if(cur > 1) count += (high+1) * index;
41 | index *= 10;
42 | }
43 | return count;
44 |
45 | }
46 | ```
47 |
48 |
49 |
50 | ## 2. [二进制中1的个数](https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/)
51 |
52 | ```c
53 | int hammingWeight(uint32_t n) {
54 | int ans = 0;
55 |
56 | while(n){
57 | if(n & 0x1){
58 | ans++;
59 | }
60 | n = n>>1;
61 | }
62 | return ans;
63 | }
64 | ```
65 |
66 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/SceneDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.m
3 | // TestLoad方法
4 | //
5 | // Created by pmst on 2020/4/21.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "SceneDelegate.h"
10 |
11 | @interface SceneDelegate ()
12 |
13 | @end
14 |
15 | @implementation SceneDelegate
16 |
17 |
18 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
19 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
20 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
21 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
22 | }
23 |
24 |
25 | - (void)sceneDidDisconnect:(UIScene *)scene {
26 | // Called as the scene is being released by the system.
27 | // This occurs shortly after the scene enters the background, or when its session is discarded.
28 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
29 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
30 | }
31 |
32 |
33 | - (void)sceneDidBecomeActive:(UIScene *)scene {
34 | // Called when the scene has moved from an inactive state to an active state.
35 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
36 | }
37 |
38 |
39 | - (void)sceneWillResignActive:(UIScene *)scene {
40 | // Called when the scene will move from an active state to an inactive state.
41 | // This may occur due to temporary interruptions (ex. an incoming phone call).
42 | }
43 |
44 |
45 | - (void)sceneWillEnterForeground:(UIScene *)scene {
46 | // Called as the scene transitions from the background to the foreground.
47 | // Use this method to undo the changes made on entering the background.
48 | }
49 |
50 |
51 | - (void)sceneDidEnterBackground:(UIScene *)scene {
52 | // Called as the scene transitions from the foreground to the background.
53 | // Use this method to save data, release shared resources, and store enough scene-specific state information
54 | // to restore the scene back to its current state.
55 | }
56 |
57 |
58 | @end
59 |
--------------------------------------------------------------------------------
/subjects/algorithm/17_打印二叉树.md:
--------------------------------------------------------------------------------
1 | # 打印二叉树
2 |
3 | ## [面试题32 - I. 从上到下打印二叉树](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/)
4 |
5 | ```java
6 | public class TreeNode {
7 | int val;
8 | TreeNode left;
9 | TreeNode right;
10 | TreeNode(int x) { val = x;}
11 | }
12 |
13 | class Solution {
14 | public int[] levelOrder(TreeNode root) {
15 | if(root == null)return new int[0];
16 | Queue queue = new LinkedList();
17 | ArrayList ans = new ArrayList<>();
18 |
19 | queue.offer(root);
20 | while(!queue.isEmpty){
21 | int size = queue.size();
22 | for(int i = 0; i < size;i ++){
23 | if(queue.peek().left != null)queue.offer(queue.peek().left);
24 | if(queue.peek().right != null)queue.offer(queue.peek().right);
25 | ans.add(queue.poll().val);
26 | }
27 | }
28 | int[] res = new int[ans.size()];
29 | for(int i = 0; i < ans.size(); i++)
30 | res[i] = ans.get(i);
31 | return res;
32 | }
33 | }
34 | ```
35 |
36 |
37 |
38 | ## [面试题32 - II. 从上到下打印二叉树 II](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/)
39 |
40 | ```java
41 | class Solution {
42 | public List> levelOrder(TreeNode root) {
43 | Queue queue = new LinkedList();
44 | List> wrapperList = new LinkedList>();
45 |
46 | if(root == null)return wrapperList;
47 |
48 | queue.offer(root);
49 | while(!queue.isEmpty()){
50 | int levelNum = queue.size();
51 | List subList = new LinkedList();
52 | for(int i =0 ; i< levelNum;i++){
53 | if(queue.peek().left != null) queue.offer(queue.peek().left);
54 | if(queue.peek().right != null) queue.offer(queue.peek().right);
55 | subList.add(queue.poll().val);
56 | }
57 | wrapperList.add(subList);
58 | }
59 | return wrapperList;
60 | }
61 | }
62 | ```
63 |
64 |
65 |
66 | ## [面试题32 - III. 从上到下打印二叉树 III](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/)
67 |
68 | ```java
69 | class Solution {
70 | public List> levelOrder(TreeNode root) {
71 | if(root == null)return new LinkedList>();
72 |
73 | Queue queue = new LinkedList();
74 | List> wrapList = new LinkedList>();
75 |
76 | queue.offer(root);
77 |
78 | while(!queue.isEmpty()) {
79 | int k = queue.size();
80 | LinkedList subList = new LinkedList();
81 |
82 | for(int i = 0;i < k;i++){
83 | if(queue.peek().left != null) queue.offer(queue.peek().left);
84 | if(queue.peek().right != null) queue.offer(queue.peek().right);
85 |
86 | if(wrapList.size() % 2 == 0){
87 | subList.addLast(queue.poll().val);
88 | } else {
89 | subList.addFirst(queue.poll().val);
90 | }
91 |
92 | }
93 | wrapList.add(subList);
94 | }
95 | return wrapList;
96 | }
97 | }
98 | ```
99 |
100 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法/PTRuntimeUtil.m:
--------------------------------------------------------------------------------
1 | //
2 | // PTRuntimeUtil.m
3 | // RuntimeUtil
4 | //
5 | // Created by pmst on 2020/3/15.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import "PTRuntimeUtil.h"
10 | #import
11 |
12 | @implementation PTRuntimeUtil
13 |
14 | - (void)printMethods:(Class)cls {
15 | NSLog(@"==== OUTPUT:%@ Method ====",NSStringFromClass(cls));
16 | unsigned int count ;
17 | Method *methods = class_copyMethodList(cls, &count);
18 |
19 | for (int i = 0; i < count; i++) {
20 | Method method = methods[i];
21 | NSString *name = NSStringFromSelector(method_getName(method));
22 | NSLog(@"method name:%@\n",name);
23 | }
24 | free(methods);
25 | }
26 |
27 | - (void)printProperties:(Class)cls {
28 | NSLog(@"==== OUTPUT:%@ properties ====",NSStringFromClass(cls));
29 |
30 | unsigned int count ;
31 | objc_property_t *properties = class_copyPropertyList(cls, &count);
32 |
33 | for (int i = 0; i < count; i++) {
34 | objc_property_t prop = properties[i];
35 | const char *name = property_getName(prop);
36 | const char *attributes = property_getAttributes(prop);
37 | // TODO: attributes 转成 human read
38 | NSLog(@"property name:%s 属性:%s\n",name,attributes);
39 | }
40 | free(properties);
41 | }
42 |
43 | - (void)printIvars:(Class)cls {
44 | NSLog(@"==== OUTPUT:%@ Ivars ====",NSStringFromClass(cls));
45 |
46 | unsigned int count ;
47 | Ivar *ivars = class_copyIvarList(cls, &count);
48 |
49 | for (int i = 0; i < count; i++) {
50 | Ivar var = ivars[i];
51 | const char *name = ivar_getName(var);
52 | const char *encode = ivar_getTypeEncoding(var);
53 | // 类似 int32_t
54 | ptrdiff_t offset = ivar_getOffset(var);
55 | // TODO: attributes 转成 human read
56 | NSLog(@"ivar name:%s encode:%s 偏移量:%lu\n",name,encode,offset);
57 | }
58 | free(ivars);
59 | }
60 |
61 | - (void)printProtocols:(Class)cls {
62 | NSLog(@"==== OUTPUT:%@ Protocols ====",NSStringFromClass(cls));
63 |
64 | unsigned int count ;
65 | Protocol * __unsafe_unretained _Nonnull *protocols = class_copyProtocolList(cls, &count);
66 |
67 | for (int i = 0; i < count; i++) {
68 | Protocol * __unsafe_unretained _Nonnull protocol = protocols[i];
69 | const char *name = protocol_getName(protocol);
70 | NSLog(@"Protocol:%s 方法声明如下:",name);
71 |
72 | unsigned int methodcnt;
73 | struct objc_method_description * methodlist = protocol_copyMethodDescriptionList(protocol, YES, YES, &methodcnt);
74 | for (int j =0; j < methodcnt; j++) {
75 | struct objc_method_description desc = methodlist[j];
76 | NSLog(@"SEL %@ 类型:%s\n",NSStringFromSelector(desc.name), desc.types);
77 | }
78 | free(methodlist);
79 |
80 | NSLog(@"Protocol:%s 属性声明如下:",name);
81 | unsigned int protcnt;
82 | objc_property_t *properties = protocol_copyPropertyList(protocol, &protcnt);
83 |
84 | for (int i = 0; i < count; i++) {
85 | objc_property_t prop = properties[i];
86 | const char *name = property_getName(prop);
87 | const char *attributes = property_getAttributes(prop);
88 | // TODO: attributes 转成 human read
89 | NSLog(@"property name:%s 属性:%s\n",name,attributes);
90 | }
91 | free(properties);
92 |
93 | }
94 | free(protocols);
95 | }
96 |
97 | - (void)logClassInfo:(Class)cls {
98 | NSLog(@"LOG:(%@) INFO",NSStringFromClass(cls));
99 | // [self printProperties:cls];
100 | [self printIvars:cls];
101 | // [self printMethods:cls];
102 | // [self printProtocols:cls];
103 | NSLog(@"=========================\n");
104 | }
105 | @end
106 |
--------------------------------------------------------------------------------
/subjects/algorithm/03_乱序数组奇数偶数排序.md:
--------------------------------------------------------------------------------
1 | # 乱序数组奇偶数排序
2 |
3 | ## 1. [面试题21. 调整数组顺序使奇数位于偶数前面](https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/)
4 |
5 | ```c
6 | bool isOdd(int num){
7 | return num %2 == 1 ? true:false;
8 | }
9 |
10 | int* exchange(int* nums, int numsSize, int* returnSize){
11 | if(numsSize <= 1){
12 | *returnSize = numsSize;
13 | return nums;
14 | }
15 | int i = 0;
16 | int j = numsSize-1;
17 |
18 | int temp = nums[0];
19 | while(i < j) {
20 | while(i < j && isOdd(nums[j]) == false)j--;
21 | nums[i] = nums[j];
22 |
23 | while(i < j && isOdd(nums[i]) == true)i++;
24 | nums[j] = nums[i];
25 | }
26 | nums[j] = temp;
27 |
28 | *returnSize = numsSize;
29 | return nums;
30 | }
31 | ```
32 |
33 |
34 |
35 | ## 2. 其他
36 |
37 | ```c
38 | void swap(int *a,int *b){
39 | int temp = *a;
40 | *a = *b;
41 | *b = temp;
42 | }
43 | #pragma mark - 快排 ✅
44 | void quickSort(int *arr,int begin,int end){
45 | if(begin>=end)return;
46 |
47 | int i = begin;
48 | int j = end;
49 | int tmp = arr[i];
50 | // {1,2,-3,9,3,-6,-7}
51 | while(i < j){
52 | while(i < j && arr[j] >= tmp){
53 | j--;
54 | }
55 |
56 | if(i < j){
57 | arr[i++] = arr[j];
58 | }
59 |
60 | while(i < j && arr[i] < tmp){
61 | i++;
62 | }
63 |
64 | if(i < j){
65 | arr[j--] = arr[i];
66 | }
67 | }
68 | arr[i] = tmp;
69 | quickSort(arr, begin, i-1);
70 | quickSort(arr, i+1, end);
71 | }
72 |
73 | #pragma mark - 球排序 0 1 2 表示 红 白 蓝 三色球 ✅
74 | void sortBall(int *array, int size){
75 | int begin = 0, cur = 0;
76 | int end = size-1;
77 |
78 | while(array[begin] == 0)begin++;
79 | cur = begin+1; // 直到找到非0的球
80 | while(array[end] == 2)end--;
81 |
82 | while( cur<=end ) {
83 | // 只为追寻 0 和 2 ,b 和 e 标识指向两者的坑
84 | if( array[cur] ==0 ) {
85 | swap(&array[cur], &array[begin]);
86 | cur++;
87 | begin++;
88 | } else if( array[cur] == 1 ) {
89 | cur++;
90 | } else {//When array[current] =2
91 | swap(&array[cur], &array[end]);
92 | end--;
93 | }
94 | }
95 | }
96 |
97 | #pragma mark - 正负数交替排序 保持原有顺序
98 | // // int array[8] = {3,4,2,-3,12,-7,-6,10};
99 | void sortPosAndNeg01(int *a, int length)
100 | {
101 | int cur = 0, find = 0;
102 | while(cur < length){
103 | if (cur % 2== 0) {
104 | // ========== 偶数位置插入一个正数 ============
105 | // 当前位置小于零 则往后找一个正数进行冒泡
106 | if (a[cur] < 0){
107 | find = cur;
108 | // 找一个负数
109 | int founded = 0;
110 | while (find < length) {
111 | if (a[find] >= 0) {
112 | founded = 1;
113 | break;
114 | }
115 | find++;
116 | }
117 |
118 | if (founded == 0)break;
119 |
120 | // 冒泡
121 | int k = find-1;
122 | while (k >= cur) {
123 | swap(&a[k+1], &a[k]);
124 | k--;
125 | }
126 | }
127 | cur++;
128 | } else {
129 | // ========== 奇数位置插入一个负数 ============
130 | // 当前位置是正数 则往后找一个负数进行冒泡
131 | if (a[cur] >= 0) {
132 | find = cur;
133 | int founded = 0;
134 | // 找一个正数
135 | while (find < length) {
136 | if (a[find] < 0) {
137 | founded = 1;
138 | break;
139 | }
140 | find++;
141 | }
142 |
143 | if (founded == 0)break;
144 |
145 | // 冒泡
146 | int k = find-1;
147 | while (k >= cur) {
148 | swap(&a[k+1], &a[k]);
149 | k--;
150 | }
151 | }
152 | cur++;
153 | }
154 | }
155 | }
156 |
157 | #pragma mark - 正负数排序(负数在左,正数在右侧) 保持原有顺序 ✅
158 | // int array[8] = {3,4,2,-3,12,-7,-6,10};
159 | void sortPosAndNeg(int *a, int length)
160 | {
161 | int i,temp;
162 | for(i=0;i=0 && a[temp]>=0){
169 | swap(&a[temp+1],&a[temp]);
170 | temp--;
171 | }
172 | }
173 | }
174 | }
175 |
176 | #pragma mark - 所有0放最前面 ✅
177 | void sortZero(int *arr,int begin,int end){
178 |
179 | int i = begin;
180 | int j = end;
181 | int tmp = arr[i];
182 | // {1,2,-3,9,3,-6,-7}
183 | while(i < j){
184 | while(i < j && arr[j] != 0){
185 | j--;
186 | }
187 |
188 | if(i < j){
189 | arr[i++] = arr[j];
190 | }
191 |
192 | while(i < j && arr[i] == 0){
193 | i++;
194 | }
195 |
196 | if(i < j){
197 | arr[j--] = arr[i];
198 | }
199 | }
200 | arr[i] = tmp;
201 | }
202 |
203 | ```
204 |
205 |
--------------------------------------------------------------------------------
/chapters/6_系统基础知识.md:
--------------------------------------------------------------------------------
1 | # 系统基础知识
2 |
3 | ### 进程和线程的区别
4 |
5 | > 这个解答很多,暂时引自[进程和线程的区别介绍](https://baijiahao.baidu.com/s?id=1611925141861592999&wfr=spider&for=pc) 一文。
6 |
7 | 1、首先是定义
8 |
9 | 进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。
10 |
11 | 线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。
12 |
13 | 2、一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。
14 |
15 | 3、线程是一种轻量级的进程,与进程相比,线程给操作系统带来侧创建、维护、和管理的负担要轻,意味着线程的代价或开销比较小。
16 |
17 | 4、线程没有地址空间,线程包含在进程的地址空间中。线程上下文只包含一个堆栈、一个寄存器、一个优先权,线程文本包含在他的进程 的文本片段中,进程拥有的所有资源都属于线程。所有的线程共享进程的内存和资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段, 寄存器的内容,栈段又叫运行时段,用来存放所有局部变量和临时变量。
18 |
19 | 5、父和子进程使用进程间通信机制,同一进程的线程通过读取和写入数据到进程变量来通信。
20 |
21 | 6、进程内的任何线程都被看做是同位体,且处于相同的级别。不管是哪个线程创建了哪一个线程,进程内的任何线程都可以销毁、挂起、恢复和更改其它线程的优先权。线程也要对进程施加控制,进程中任何线程都可以通过销毁主线程来销毁进程,销毁主线程将导致该进程的销毁,对主线程的修改可能影响所有的线程。
22 |
23 | 7、子进程不对任何其他子进程施加控制,进程的线程可以对同一进程的其它线程施加控制。子进程不能对父进程施加控制,进程中所有线程都可以对主线程施加控制。
24 |
25 | 相同点:
26 |
27 | 进程和线程都有ID/寄存器组、状态和优先权、信息块,创建后都可更改自己的属性,都可与父进程共享资源、都不鞥直接访问其他无关进程或线程的资源。
28 |
29 | ### `HTTPS`的握手过程
30 |
31 | 密钥协商过程:
32 |
33 | 1. 客户端将TLS版本,支持的加密算法,ClientHello random C 发给服务端【客户端->服务端】
34 | 2. 服务端从加密算法中pick一个加密算法, ServerHello random S,server 证书返回给客户端;【服务端->客户单】
35 | 3. 客户端验证 server 证书【客户端】
36 | 4. 客户端生成一个 48 字节的预备主密钥,其中前2个字节是 Protocol Version,后46个字节是随机数,客户端用证书中的公钥对预备主密钥进行非对称加密后通过 client key exchange 子消息发给服务端【客户端->服务端】
37 | 5. 服务端用私钥解密得到预备主密钥;【服务端】
38 | 6. 服务端和客户端都可以通过预备主密钥、ClientHello random C 和 ServerHello random S 通过 PRF 函数生成主密钥;会话密钥由主密钥、SecurityParameters.server_random 和 SecurityParameters.client_random 数通过 PRF 函数来生成会话密钥里面包含对称加密密钥、消息认证和 CBC 模式的初始化向量,对于非 CBC 模式的加密算法来说,就没有用到这个初始化向量。
39 |
40 | > Session ID 缓存和 Session Ticket 里面保存的也是主密钥,而不是会话密钥,这样每次会话复用的时候再用双方的随机数和主密钥导出会话密钥,从而实现每次加密通信的会话密钥不一样,即使一个会话的主密钥泄露了或者被破解了也不会影响到另一个会话。
41 |
42 | ### 什么是`中间人攻击`?怎么预防
43 |
44 | HTTP 明文传输,客户端和服务端进行通信时,中间人即指夹在客户端和服务端之间的第三者,对于客户端来说,中间人就是 **服务端**,对于服务端来说,中间人就是 **客户端**。中间人拦截客户端消息,然后再发送给服务端;服务端发发送消息给中间人,中间人再返还给客户端。
45 |
46 | 使用 HTTPS,单双向认证,
47 |
48 | ### `TCP`的握手过程?为什么进行三次握手,四次挥手
49 |
50 | 三次握手:
51 |
52 | > 为了确认服务端和客户端双方的收发能力。
53 |
54 | * 客户端发送 SYN = 1,seq=x 给服务端
55 | * 服务端接收发送 SYN = 1,ACK = 1,ack=x+1, seq = y 给客户端
56 | * 客户端发送 ACK = 1,ack = y+1 ,seq = z 给服务端
57 |
58 | 
59 |
60 | 
61 |
62 | 
63 |
64 | 四次挥手:
65 |
66 | * 主动方发送 FIN = 1,seq = u 给被动方;
67 | * 被动方 ACK = 1,ack = u+1,seq = v;
68 | * 被动方继续传输数据给主动方;
69 | * 被动方没有更多数据了,发送 FIN = 1,ACK=1,seq = w,ack=u+1;
70 | * 主动方 ACK = 1,seq = u + 1,ack = w +1;
71 |
72 | ### `堆和栈`区的区别?谁的占用内存空间大
73 |
74 | > 解答出自:[堆区(heap)和栈区(stack)的区别](https://blog.csdn.net/shanshanhi/article/details/50904706)
75 |
76 | (1)申请方式
77 |
78 | 栈区:由编译器自动分配释放,存放函数的参数值,局部变量值等;
79 |
80 | 堆区:一般由程序员分配释放(使用new/delete或malloc/free),若程序员不释放,程序结束时可能由OS回收;
81 |
82 | (2)操作方式
83 |
84 | 栈区:操作方式类似于数据结构中的栈;
85 |
86 | 堆区:不同于数据结构中的堆,分配方式类似于链表。
87 |
88 | (3)申请后系统的响应
89 |
90 | 栈区:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出;
91 |
92 | 堆区:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
93 |
94 | (4)申请大小的限制
95 |
96 | 栈区:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
97 |
98 | 堆区:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
99 |
100 | (5)申请效率的比较
101 |
102 | 栈区:系统自动分配,速度较快。但程序员是无法控制的。
103 |
104 | 堆区:由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
105 |
106 | 注意:在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。
107 |
108 | (6)堆和栈中的存储内容
109 |
110 | 栈区:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
111 |
112 | 堆区:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
113 |
114 | ### 加密算法:`对称加密算法和非对称加密算法`区别
115 |
116 | ### 常见的`对称加密和非对称加密`算法有哪些
117 |
118 | #### 对称加密
119 |
120 | 对称加密指的就是加密和解密使用同一个秘钥,所以叫做对称加密。对称加密只有一个秘钥,作为私钥。
121 |
122 | 具体算法有:DES,3DES,TDEA,Blowfish,RC5,IDEA。常见的有:DES,AES,3DES等等。
123 |
124 | > 优点:算法公开、计算量小、加密速度快、加密效率高。 缺点:秘钥的管理和分发非常困难,不够安全。在数据传送前,发送方和接收方必须商定好秘钥,然后双方都必须要保存好秘钥,如果一方的秘钥被泄露,那么加密信息也就不安全了。另外,每对用户每次使用对称加密算法时,都需要使用其他人不知道的唯一秘钥,这会使得收、发双方所拥有的钥匙数量巨大,密钥管理成为双方的负担。
125 |
126 | #### 非对称加密
127 |
128 | 非对称加密指的是:加密和解密使用不同的秘钥,一把作为公开的公钥,另一把作为私钥。公钥加密的信息,只有私钥才能解密。私钥加密的信息,只有公钥才能解密。 私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。
129 |
130 | 我们常见的数字证书、加密狗即是采用非对称加密来完成安全验证的。
131 |
132 | > 优点:安全性更高,公钥是公开的,秘钥是自己保存的,不需要将私钥给别人。 缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
133 |
134 | 主要算法:RSA、Elgamal、背包算法、Rabin、HD,ECC(椭圆曲线加密算法)。常见的有:RSA,ECC
135 |
136 | #### 银行动态令牌
137 |
138 | 网银比较流行的时候,银行给我们发一个动态令牌。这个令牌并不使用任何对称或者非对称加密的算法,在整个银行的认证体系中,动态令牌只是一个一次性口令的产生器,它是基于时间同步方式,每隔60秒产生一个随机6位动态密码在其中运行的主要计算仅包括时间因子的计算和散列值的计算。
139 |
140 | 在用户从银行手中拿到动态口令令牌卡的时候,在令牌卡的内部已经存储了一份种子文件(即图中钥匙所代表的seed),这份种子文件在银行的服务器里保存的完全一样的一份,所以对于动态口令令牌来说,这种方式是share secret的。另外在令牌硬件上的设置中,假使有人打开了这个令牌卡,种子文件将会从令牌卡的内存上擦除(待考证)。 令牌卡中有了种子文件,并实现了TOTP算法,在预先设置的间隔时间里它就能不断产生不同的动态口令,并显示到屏幕上,而银行服务器上跟随时间做同样的计算,也会得到和令牌卡同样的口令,用作认证。 那么TOTP算法具体做了什么操作呢?在RFC6238中有详细的算法描述,这里也会做简单的叙述。
141 |
142 | > TOTP是来自 HOTP [RFC4226] 的变形,从统筹上看,他们都是将数据文件进行散列计算,只是HOTP的因子是事件因子,TOTP将因子换成了时间因子,具体的TOTP计算公式(其中的HMAC-SHA-256也可能是 HMAC-SHA-512): TOTP = Truncate(HMAC-SHA-256(K,T))
143 |
144 | 其中: K 为这里的种子文件内容; T 为计算出来的时间因子 公式中的 HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。而公式中给出的哈希算法是 SHA-256,这种哈希算法目前并没有好的破解办法。 令牌卡中预先设置了要显示的口令长度,TOTP 中的 Truncate 操作剪切获得口令。 以上就是动态口令令牌卡的内部原理。
145 |
146 | > 解答出自 [对称加密算法与非对称加密算法的优缺点](https://zhuanlan.zhihu.com/p/38307899)
147 |
148 | ### `MD5、Sha1、Sha256`区别
149 |
150 | 签名算法,SHA(Security Hash Algorithm) ,貌似 MD5 更高效,花费时间更少,但相对较容易**碰撞**。SHA1 已经被攻破,所以安全性不行。
151 |
152 | ### `charles`抓包过程?不使用`charles`,`4G`网络如何抓包
153 |
154 | 中间人攻击原理,easy。
--------------------------------------------------------------------------------
/demos/04-28-DeepInBlock/04-28-DeepInBlock/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // 04-28-DeepInBlock
4 | //
5 | // Created by pmst on 2020/4/28.
6 | // Copyright © 2020 pmst. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | typedef NS_OPTIONS(int,PTBlockFlags) {
12 | PTBlockFlagsHasCopyDisposeHelpers = (1 << 25),
13 | PTBlockFlagsHasSignature = (1 << 30)
14 | };
15 | typedef struct PTBlock {
16 | __unused Class isa;
17 | PTBlockFlags flags;
18 | __unused int reserved;
19 | void (__unused *invoke)(struct PTBlock *block, ...);
20 | struct {
21 | unsigned long int reserved;
22 | unsigned long int size;
23 | // requires PTBlockFlagsHasCopyDisposeHelpers
24 | void (*copy)(void *dst, const void *src);
25 | void (*dispose)(const void *);
26 | // requires PTBlockFlagsHasSignature
27 | const char *signature;
28 | const char *layout;
29 | } *descriptor;
30 | // imported variables
31 | } *PTBlockRef;
32 |
33 | typedef struct PTBlock_byref {
34 | void *isa;
35 | struct PTBlock_byref *forwarding;
36 | volatile int flags; // contains ref count
37 | unsigned int size;
38 | // 下面两个函数指针是不定的 要根据flags来
39 | // void (*byref_keep)(struct PTBlock_byref *dst, struct PTBlock_byref *src);
40 | // void (*byref_destroy)(struct PTBlock_byref *);
41 | // long shared[0];
42 | } *PTBlock_byref_Ref;
43 |
44 | #define DEMO 6
45 |
46 | int main(int argc, const char * argv[]) {
47 | @autoreleasepool {
48 | #if DEMO == 1
49 | void (^blk)(void) = ^{
50 | NSLog(@"hello world");
51 | };
52 | PTBlockRef block = (__bridge PTBlockRef)blk;
53 | block->invoke(block);
54 | #elif DEMO == 2
55 | void (^blk)(int, short, NSString *) = ^(int a, short b, NSString *str){
56 | NSLog(@"a:%d b:%d str:%@",a,b,str);
57 | };
58 | PTBlockRef block = (__bridge PTBlockRef)blk;
59 | if (block->flags & PTBlockFlagsHasSignature) {
60 | void *desc = block->descriptor;
61 | desc += 2 * sizeof(unsigned long int);
62 | if (block->flags & PTBlockFlagsHasCopyDisposeHelpers) {
63 | desc += 2 * sizeof(void *);
64 | }
65 |
66 | const char *signature = (*(const char **)desc);
67 | NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:signature];
68 | NSLog(@"方法 signature:%s",signature);
69 | }
70 | #elif DEMO == 3
71 | int a = 0x11223344;
72 | int b = 0x55667788;
73 | NSString *str = @"pmst";
74 | void (^blk)(void) = ^{
75 | NSLog(@"a:%d b:%d str:%@",a,b, str);
76 | };
77 | PTBlockRef block = (__bridge PTBlockRef)blk;
78 | void *pt = (void *)block + sizeof(struct PTBlock);
79 | long long *ppt = pt;
80 | NSString *str_ref = (__bridge id)((void *)(*ppt));
81 | int *a_ref = pt + sizeof(NSString *);
82 | int *b_ref = pt + sizeof(NSString *) + sizeof(int);
83 |
84 | NSLog(@"a:0x%x b:0x%x str:%@",*a_ref, *b_ref, str_ref);
85 | #elif DEMO == 4
86 | __block int a = 0x99887766;
87 | __unsafe_unretained void (^blk)(void) = ^{
88 | NSLog(@"__block a :%d",a);
89 | };
90 | NSLog(@"Block 类型 %@",[blk class]);
91 | PTBlockRef block = (__bridge PTBlockRef)blk;
92 | void *pt = (void *)block + sizeof(struct PTBlock);
93 | long long *ppt = pt;
94 | void *ref = (PTBlock_byref_Ref)(*ppt);
95 | void *shared = ref + sizeof(struct PTBlock_byref);
96 | int *a_ref = (int *)shared;
97 | NSLog(@"a 指针:%p block a 指针:%p block a value:0x%x",&a, a_ref,*a_ref);
98 | NSLog(@"PTBlock_byref 指针:%p",ref);
99 | NSLog(@"PTBlock_byref forwarding 指针:%p",((PTBlock_byref_Ref)ref)->forwarding);
100 | #elif DEMO == 5
101 | __block int a = 0x99887766;
102 | __unsafe_unretained void (^blk)(NSString *) = ^(NSString *flag){
103 | NSLog(@"[%@] 中 a 地址:%p",flag, &a);
104 | };
105 | NSLog(@"blk 类型 %@",[blk class]);
106 | blk(@"origin block");
107 | void (^copyblk)(NSString *) = [blk copy];
108 | NSLog(@"copyblk 类型 %@",[copyblk class]);
109 | copyblk(@"copy block");
110 | blk(@"origin block 二次打印");
111 |
112 | // int *a_ref = (int *)shared;
113 | // NSLog(@"a 指针:%p block a 指针:%p block a value:0x%x",&a, a_ref,*a_ref);
114 | // NSLog(@"PTBlock_byref 指针:%p",ref);
115 | // NSLog(@"PTBlock_byref forwarding 指针:%p",((PTBlock_byref_Ref)ref)->forwarding);
116 | #elif DEMO == 6
117 | __block int a = 0x99887766;
118 | __unsafe_unretained void (^blk)(NSString *,id) = ^(NSString *flag, id bblk){
119 | NSLog(@"[%@] a address:%p",flag, &a); // a 取值都是 ->forwarding->a 方式
120 | PTBlockRef block = (__bridge PTBlockRef)bblk;
121 | void *pt = (void *)block + sizeof(struct PTBlock);
122 | long long *ppt = pt;
123 | void *ref = (PTBlock_byref_Ref)(*ppt);
124 | NSLog(@"[%@] PTBlock_byref_Ref 指针:%p",flag,ref);
125 | NSLog(@"[%@] PTBlock_byref_Ref forwarding 指针:%p",flag,((PTBlock_byref_Ref)ref)->forwarding);
126 | void *shared = ref + sizeof(struct PTBlock_byref);
127 | int *a_ref = (int *)shared;
128 | NSLog(@"[%@] a value : 0x%x a adress:%p", flag, *a_ref, a_ref);
129 |
130 | };
131 | NSLog(@"blk 类型 %@",[blk class]);
132 | blk(@"origin block", blk);
133 | void (^copyblk)(NSString *,id) = [blk copy];
134 | NSLog(@"copy之后 a address:%p", &a);
135 | NSLog(@"copyblk 类型 %@",[copyblk class]);
136 | copyblk(@"copy block",copyblk);
137 | blk(@"origin block after copy", blk);
138 | #endif
139 |
140 |
141 |
142 |
143 |
144 | }
145 | return 0;
146 | }
147 |
--------------------------------------------------------------------------------
/subjects/runtime/AssociatedObjects.md:
--------------------------------------------------------------------------------
1 | ## 1. 关联对象需要手动处理生命周期吗?释放时机是什么时候?
2 |
3 | > 出现频率:1
4 |
5 | 不需要,在 dealloc 方法中会进行关联对象的释放。
6 |
7 | > 知识点一:关联对象是如何管理的?
8 |
9 | 底层通过 `AssociationsManager` 全局维护一个哈希表,当前对象指针通过取反操作作为取值的 KEY,然后从哈希表中找到对应的 `ObjectAssociationMap`,当前对象所有的关联对象都存储在这个 Map 中,键是我们方法中设定的,值是通过 `ObjcAssociation` 二次封装,数据结构包括了 `id value` 和 `uintptr_t _policy`。
10 |
11 | 底层关联对象的getter和setter实现代码如下:
12 |
13 | ```objective-c
14 | void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
15 | // retain the new value (if any) outside the lock.
16 | ObjcAssociation old_association(0, nil);
17 | // 如果value对象存在,则进行retain or copy 操作
18 | id new_value = value ? acquireValue(value, policy) : nil;
19 | {
20 | AssociationsManager manager;
21 | // manager.associations() 返回的是一个 `AssociationsHashMap` 对象(*_map)
22 | // 所以这里 `&associations` 中用了 `&`
23 | AssociationsHashMap &associations(manager.associations());
24 | // intptr_t 是为了兼容平台,在64位的机器上,intptr_t和uintptr_t分别是long int、unsigned long int的别名;在32位的机器上,intptr_t和uintptr_t分别是int、unsigned int的别名
25 | // DISGUISE 内部对指针做了 ~ 取反操作,“伪装”
26 | disguised_ptr_t disguised_object = DISGUISE(object);
27 | if (new_value) {
28 | // break any existing association.
29 | /*
30 | AssociationsHashMap 继承自 unordered_map,存储 key-value 的组合
31 | iterator find ( const key_type& key ),如果 key 存在,则返回key对象的迭代器,
32 | 如果key不存在,则find返回 unordered_map::end;因此可以通过 `map.find(key) == map.end()`
33 | 判断 key 是否存在于当前 map 中。
34 | */
35 | AssociationsHashMap::iterator i = associations.find(disguised_object);
36 | // 这里和get操作不同,set操作时如果查询到对象没有关联对象,那么这一次设值是第一次,
37 | // 所以会创建一个新的 ObjectAssociationMap 用来存储实例对象的所有关联属性
38 | if (i != associations.end()) {
39 | // secondary table exists
40 | /*
41 | unordered_map 的键值分别是迭代器的first和second属性。
42 | 所以说上面先通过 object 对象(实例对象or类对象) 找到其所有关联对象
43 | i->second 取到又是一个 ObjectAssociationMap
44 | 此刻再通过我们自己设定的 key 来查找对应的关联属性值,不过使用
45 | `ObjcAssociation` 封装的
46 | */
47 | ObjectAssociationMap *refs = i->second;
48 | ObjectAssociationMap::iterator j = refs->find(key);
49 | // 关联属性用 ObjcAssociation 结构体封装
50 | if (j != refs->end()) {
51 | old_association = j->second;
52 | j->second = ObjcAssociation(policy, new_value);
53 | } else {
54 | (*refs)[key] = ObjcAssociation(policy, new_value);
55 | }
56 | } else {
57 | // create the new association (first time).
58 | ObjectAssociationMap *refs = new ObjectAssociationMap;
59 | associations[disguised_object] = refs;
60 | (*refs)[key] = ObjcAssociation(policy, new_value);
61 | // 知识点是:newisa.has_assoc = true;
62 | object->setHasAssociatedObjects();
63 | }
64 | } else {
65 | // setting the association to nil breaks the association.
66 | AssociationsHashMap::iterator i = associations.find(disguised_object);
67 | if (i != associations.end()) {
68 | ObjectAssociationMap *refs = i->second;
69 | ObjectAssociationMap::iterator j = refs->find(key);
70 | if (j != refs->end()) {
71 | old_association = j->second;
72 | refs->erase(j);
73 | }
74 | }
75 | }
76 | }
77 | // release the old value (outside of the lock).
78 | if (old_association.hasValue()) ReleaseValue()(old_association);
79 | }
80 | ```
81 |
82 | getter 实现:
83 |
84 | ```objective-c
85 |
86 | id _object_get_associative_reference(id object, void *key) {
87 | id value = nil;
88 | uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
89 | {
90 | AssociationsManager manager;
91 | // manager.associations() 返回的是一个 `AssociationsHashMap` 对象(*_map)
92 | // 所以这里 `&associations` 中用了 `&`
93 | AssociationsHashMap &associations(manager.associations());
94 | // intptr_t 是为了兼容平台,在64位的机器上,intptr_t和uintptr_t分别是long int、unsigned long int的别名;在32位的机器上,intptr_t和uintptr_t分别是int、unsigned int的别名
95 | // DISGUISE 内部对指针做了 ~ 取反操作,“伪装”?
96 | disguised_ptr_t disguised_object = DISGUISE(object);
97 | /*
98 | AssociationsHashMap 继承自 unordered_map,存储 key-value 的组合
99 | iterator find ( const key_type& key ),如果 key 存在,则返回key对象的迭代器,
100 | 如果key不存在,则find返回 unordered_map::end;因此可以通过 `map.find(key) == map.end()`
101 | 判断 key 是否存在于当前 map 中。
102 | */
103 | AssociationsHashMap::iterator i = associations.find(disguised_object);
104 | if (i != associations.end()) {
105 | /*
106 | unordered_map 的键值分别是迭代器的first和second属性。
107 | 所以说上面先通过 object 对象(实例对象or类对象) 找到其所有关联对象
108 | i->second 取到又是一个 ObjectAssociationMap
109 | 此刻再通过我们自己设定的 key 来查找对应的关联属性值,不过使用
110 | `ObjcAssociation` 封装的
111 | */
112 | ObjectAssociationMap *refs = i->second;
113 | ObjectAssociationMap::iterator j = refs->find(key);
114 | if (j != refs->end()) {
115 | ObjcAssociation &entry = j->second;
116 | value = entry.value();
117 | policy = entry.policy();
118 | // 如果策略是 getter retain ,注意这里留个坑
119 | // 平常 OBJC_ASSOCIATION_RETAIN = 01401
120 | // OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8)
121 | if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
122 | // TODO: 有学问
123 | objc_retain(value);
124 | }
125 | }
126 | }
127 | }
128 | if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
129 | objc_autorelease(value);
130 | }
131 | return value;
132 | }
133 | ```
134 |
135 | > 知识点二:关联对象dealloc释放过程
136 |
137 | ```objective-c
138 | void *objc_destructInstance(id obj)
139 | {
140 | if (obj) {
141 | // Read all of the flags at once for performance.
142 | bool cxx = obj->hasCxxDtor();
143 | bool assoc = obj->hasAssociatedObjects();
144 |
145 | // This order is important.
146 | if (cxx) object_cxxDestruct(obj);
147 | if (assoc) _object_remove_assocations(obj);
148 | obj->clearDeallocating();
149 | }
150 |
151 | return obj;
152 | }
153 |
154 | void _object_remove_assocations(id object) {
155 | vector< ObjcAssociation,ObjcAllocator > elements;
156 | {
157 | AssociationsManager manager;
158 | AssociationsHashMap &associations(manager.associations());
159 | if (associations.size() == 0) return;
160 | disguised_ptr_t disguised_object = DISGUISE(object);
161 | AssociationsHashMap::iterator i = associations.find(disguised_object);
162 | if (i != associations.end()) {
163 | // copy all of the associations that need to be removed.
164 | ObjectAssociationMap *refs = i->second;
165 | for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
166 | elements.push_back(j->second);
167 | }
168 | // remove the secondary table.
169 | delete refs;
170 | associations.erase(i);
171 | }
172 | }
173 | // the calls to releaseValue() happen outside of the lock.
174 | for_each(elements.begin(), elements.end(), ReleaseValue());
175 | }
176 | ```
177 |
178 |
179 |
180 |
--------------------------------------------------------------------------------
/subjects/runtime/load和initialize区别.md:
--------------------------------------------------------------------------------
1 | ## 1. +load 与 +initialize 区别,initialize 是否会覆盖父类方法,为什么?
2 |
3 | * `load` 方法是在读取 image 镜像的时候调用;
4 |
5 | * 主类较之分类会先调用,会按照基类->父类->子类依次调用 `+load` 方法;分类是按照**编译顺序**决定的,也就是 BuildPhase 中的编译顺序,先编译的先调用,后编译的后调用,和类的继承关系无关;
6 | * 多个主类的 `+load` 会按照编译顺序调用;
7 | * `+load` 方法是系统自动调用,所以禁止在内部调用 `[super load]` 会引发未知、奇怪的现象(其实也是可以说的通的,`[super load]` 实际上还涉及到 category 方法覆盖问题,以及第一次类被调用时会调用 initialize 方法);
8 | * `load` 方法在main函数之前执行,`initialize `在main函数之后且被用到的时候才会初始化;
9 |
10 | * `+initialize` 方法只有当类第一次被使用到的时候才会去加载,且会触发父类调用;
11 |
12 | * runtime 底层会去调用父类的方法,如下图的 `_class_initialize(supercls)`:
13 |
14 | ```objective-c
15 | void _class_initialize(Class cls)
16 | {
17 | Class supercls;
18 | bool reallyInitialize = NO;
19 |
20 | // Make sure super is done initializing BEFORE beginning to initialize cls.
21 | // See note about deadlock above.
22 | supercls = cls->superclass;
23 | if (supercls && !supercls->isInitialized()) {
24 | _class_initialize(supercls);
25 | }
26 | // 省略.....
27 | }
28 | ```
29 |
30 | * `+initialize` 可以简单理解为方法调用,所以分类的方法会“覆盖”主类的方法,且后编译的分类方法会被调用;
31 |
32 | * `+initialize` 有且仅会被调用一次;
33 |
34 | * `+initialize` 会覆盖父类方法,和普通的方法调用是一样的,且内部隐式地会判断super 是否被初始化过,如果没有则会去调用 super 的 initialize 方法,
35 |
36 | * **Note**: 千万不要在 load 和 initialize 方法中调用 [super xxx],除非你能理顺关系
37 |
38 | ## 2. 为什么有调用顺序的区别?
39 |
40 | 从方法名上来推测,`+load` 就是加载,不同的分类可能加载要处理的事情不同,`+initialize` 则是初始化,类初始化一次就够了。
41 |
42 | ## 完整源码
43 |
44 | load 方法调用时机,而且只调用当前类本身,不会调用superClass 的 `+load` 方法,如果你使用 `[super load]` 那是在走消息发送,runtime 底层调用实现如下:
45 |
46 | ```c
47 | void
48 | load_images(const char *path __unused, const struct mach_header *mh)
49 | {
50 | // Return without taking locks if there are no +load methods here.
51 | if (!hasLoadMethods((const headerType *)mh)) return;
52 |
53 | recursive_mutex_locker_t lock(loadMethodLock);
54 |
55 | // Discover load methods
56 | {
57 | mutex_locker_t lock2(runtimeLock);
58 | prepare_load_methods((const headerType *)mh);
59 | }
60 |
61 | // Call +load methods (without runtimeLock - re-entrant)
62 | call_load_methods();
63 | }
64 |
65 | void call_load_methods(void)
66 | {
67 | static bool loading = NO;
68 | bool more_categories;
69 |
70 | loadMethodLock.assertLocked();
71 |
72 | // Re-entrant calls do nothing; the outermost call will finish the job.
73 | if (loading) return;
74 | loading = YES;
75 |
76 | void *pool = objc_autoreleasePoolPush();
77 |
78 | do {
79 | // 1. Repeatedly call class +loads until there aren't any more
80 | while (loadable_classes_used > 0) {
81 | call_class_loads();
82 | }
83 |
84 | // 2. Call category +loads ONCE
85 | more_categories = call_category_loads();
86 |
87 | // 3. Run more +loads if there are classes OR more untried categories
88 | } while (loadable_classes_used > 0 || more_categories);
89 |
90 | objc_autoreleasePoolPop(pool);
91 |
92 | loading = NO;
93 | }
94 | ```
95 |
96 | runtime 中对 `+initialize` 调用,判断父类是否 `isInitialized` ,
97 |
98 | ```c
99 | void _class_initialize(Class cls)
100 | {
101 | assert(!cls->isMetaClass());
102 |
103 | Class supercls;
104 | bool reallyInitialize = NO;
105 |
106 | // Make sure super is done initializing BEFORE beginning to initialize cls.
107 | // See note about deadlock above.
108 | supercls = cls->superclass;
109 | if (supercls && !supercls->isInitialized()) {
110 | _class_initialize(supercls);
111 | }
112 |
113 | // Try to atomically set CLS_INITIALIZING.
114 | {
115 | monitor_locker_t lock(classInitLock);
116 | if (!cls->isInitialized() && !cls->isInitializing()) {
117 | cls->setInitializing();
118 | reallyInitialize = YES;
119 | }
120 | }
121 |
122 | if (reallyInitialize) {
123 | // We successfully set the CLS_INITIALIZING bit. Initialize the class.
124 |
125 | // Record that we're initializing this class so we can message it.
126 | _setThisThreadIsInitializingClass(cls);
127 |
128 | if (MultithreadedForkChild) {
129 | // LOL JK we don't really call +initialize methods after fork().
130 | performForkChildInitialize(cls, supercls);
131 | return;
132 | }
133 |
134 | // Send the +initialize message.
135 | // Note that +initialize is sent to the superclass (again) if
136 | // this class doesn't implement +initialize. 2157218
137 | if (PrintInitializing) {
138 | _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
139 | pthread_self(), cls->nameForLogging());
140 | }
141 |
142 | // Exceptions: A +initialize call that throws an exception
143 | // is deemed to be a complete and successful +initialize.
144 | //
145 | // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
146 | // bootstrapping problem of this versus CF's call to
147 | // objc_exception_set_functions().
148 | #if __OBJC2__
149 | @try
150 | #endif
151 | {
152 | callInitialize(cls);
153 |
154 | if (PrintInitializing) {
155 | _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
156 | pthread_self(), cls->nameForLogging());
157 | }
158 | }
159 | #if __OBJC2__
160 | @catch (...) {
161 | if (PrintInitializing) {
162 | _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
163 | "threw an exception",
164 | pthread_self(), cls->nameForLogging());
165 | }
166 | @throw;
167 | }
168 | @finally
169 | #endif
170 | {
171 | // Done initializing.
172 | lockAndFinishInitializing(cls, supercls);
173 | }
174 | return;
175 | }
176 |
177 | else if (cls->isInitializing()) {
178 | // We couldn't set INITIALIZING because INITIALIZING was already set.
179 | // If this thread set it earlier, continue normally.
180 | // If some other thread set it, block until initialize is done.
181 | // It's ok if INITIALIZING changes to INITIALIZED while we're here,
182 | // because we safely check for INITIALIZED inside the lock
183 | // before blocking.
184 | if (_thisThreadIsInitializingClass(cls)) {
185 | return;
186 | } else if (!MultithreadedForkChild) {
187 | waitForInitializeToComplete(cls);
188 | return;
189 | } else {
190 | // We're on the child side of fork(), facing a class that
191 | // was initializing by some other thread when fork() was called.
192 | _setThisThreadIsInitializingClass(cls);
193 | performForkChildInitialize(cls, supercls);
194 | }
195 | }
196 |
197 | else if (cls->isInitialized()) {
198 | // Set CLS_INITIALIZING failed because someone else already
199 | // initialized the class. Continue normally.
200 | // NOTE this check must come AFTER the ISINITIALIZING case.
201 | // Otherwise: Another thread is initializing this class. ISINITIALIZED
202 | // is false. Skip this clause. Then the other thread finishes
203 | // initialization and sets INITIALIZING=no and INITIALIZED=yes.
204 | // Skip the ISINITIALIZING clause. Die horribly.
205 | return;
206 | }
207 |
208 | else {
209 | // We shouldn't be here.
210 | _objc_fatal("thread-safe class init in objc runtime is buggy!");
211 | }
212 | }
213 |
214 | void callInitialize(Class cls)
215 | {
216 | ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
217 | asm("");
218 | }
219 | ```
220 |
221 |
222 |
223 |
--------------------------------------------------------------------------------
/interviews/iTeaTime技术清谈/面试题集一_0520.md:
--------------------------------------------------------------------------------
1 | > - 面试题出自技术群分享
2 | > - 欢迎转载,转载请注明出处:[pmst-swiftgg](https://links.jianshu.com/go?to=[https%3A%2F%2Fwww.jianshu.com%2Fp%2Fc1765a6305ab](https%3A%2F%2Fwww.jianshu.com%2Fp%2Fc1765a6305ab))
3 | > - 调试好可运行的源码 [objc-runtime](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fcolourful987%2F2020-Read-Record%2Ftree%2Fmaster%2FAnnotated source code%2Fobjc4-750),官网找 [objc4](https://links.jianshu.com/go?to=https%3A%2F%2Fopensource.apple.com%2Ftarballs%2Fobjc4%2F)
4 | > - 完成度:0%
5 | > - 最后修订:2020/05/24
6 |
7 | ## 一面
8 |
9 | ### 1. 你们 hybrid APP 技术方案跨端方案(weex、RN、flutter)选择? 与 h5 hybrid 方案相比优缺点, 哪些场景需要用到native、哪些页面h5、哪些页面用跨端方案(weex、RN、flutter) ?
10 |
11 | ### 2. 如果你做的是一个超级 APP (微信、淘宝), 里面有一个引擎可以运行不同的小程序, 你如何设计保证小程序之间的安全性?
12 | ### 3. 介绍一下你们APP的架构设计
13 | ### 4. H5 与 跨端方案(weex、RN、flutter) 如何进行代码复用、适配?
14 | ### 5. 进程和线程的区别? (不说到CPU调度, 会一直追问)
15 |
16 | [进程和线程的区别](../../subjects/计算机基础/进程和线程的区别.md)
17 |
18 | ### 6. OC 是否支持重载? 为什么?
19 | ### 7. 分类是否能够添加属性
20 |
21 | 关联对象基本使用方法:
22 |
23 | ```objective-c
24 | #import
25 |
26 | static NSString * const kKeyOfImageProperty;
27 |
28 | @implementation UIView (Image)
29 |
30 | - (UIImage *)pt_image {
31 | return objc_getAssociatedObject(self, &kKeyOfImageProperty);
32 | }
33 |
34 | - (void)setPTImage:(UIImage *)image {
35 | objc_setAssociatedObject(self, &kKeyOfImageProperty, image,OBJC_ASSOCIATION_RETAIN);
36 | }
37 | @end
38 | ```
39 |
40 |
41 |
42 | **更多问题:**
43 |
44 | * [关联对象需要手动处理生命周期吗?释放时机是什么时候?](../../subjects/runtime/AssociatedObjects.md)
45 | * 关联对象的如何进行内存管理的?关联对象如何实现weak属性?
46 | * 使用了 `policy` 设置内存管理策略,具体见上。凡是加一层呗。。。搞一个类,然后内部封装一个 `weak` 变量持有;或者不用 weak,但是还是封装一层,但是在 dealloc 中进行置为 nil操作
47 |
48 | ### 8. OC 内存泄漏的原因
49 |
50 | 1. 存在 retainCycle 循环引用导致对象相互持有无法释放造成内存泄露;
51 |
52 | 1. Block ,内部使用了 `self` 或直接 `_instance` 访问实例变量也会隐式捕获 `self`;
53 |
54 | 2. delegate 循环引用,比如使用 `strong` 关键字去修饰;
55 |
56 | 3. NSTimer 和 CADisplayLink,两者内存泄露原因差不多,以 NSTimer 为例,这里 timer 会强持有 target (通常就是 self 调用者本身),然后 self 会用一个属性去持有 timer,这里就形成了retainCycle,另外timer 一旦加入到 runloop 就会被持有,直至被 invalidated 才会被移除;考虑 **repeats = NO** 的情况,一旦触发过一次 timer 就会 invalidated,也就会从 runloop 中移除。
57 |
58 | ```objective-c
59 | + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
60 | ```
61 |
62 | 2. 单例缓存视图或其他对象
63 |
64 | 3. 非OC对象内存处理,比如:
65 |
66 | ```objective-c
67 | CGImageRef ref = [context createCGImage:outputImage fromRect:outputImage.extent];
68 | UIImage *endImg = [UIImage imageWithCGImage:ref];
69 | _imageView.image = endImg;
70 | CGImageRelease(ref);//非OC对象需要手动内存释放
71 |
72 | // 其他一
73 | Ivar *ivars = class_copyIvarList(cls, &count);
74 | free(ivars);
75 |
76 | // 其他二、
77 | CFUUIDRef puuid = CFUUIDCreate( kCFAllocatorDefault );
78 | CFStringRef uuidString = CFUUIDCreateString( kCFAllocatorDefault, puuid );
79 | NSString *uuid = [(NSString *)CFBridgingRelease(CFStringCreateCopy(NULL, uuidString)) uppercaseString];
80 | // 使用完后释放 puuid 和 uuidString 对象
81 | CFRelease(puuid);
82 | CFRelease(uuidString);
83 | ```
84 |
85 | ### 9. 堆和栈的区别是什么?
86 |
87 | > link:[堆和栈的区别](https://blog.csdn.net/hairetz/article/details/4141043)
88 |
89 | #### 一、预备知识—程序的内存分配
90 |
91 | 一个由C/C++编译的程序占用的内存分为以下几个部分
92 |
93 | 1. 栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其
94 | 操作方式类似于数据结构中的栈。
95 | 2. 堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回
96 | 收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
97 | 3. 全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的
98 | 全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另
99 | 一块区域。 - 程序结束后由系统释放。
100 | 4. 文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
101 | 5. 程序代码区—存放函数体的二进制代码;
102 |
103 | ```c
104 | //main.cpp
105 | int a = 0; //全局初始化区
106 | char *p1; //全局未初始化区
107 | main()
108 | {
109 | int b; //栈
110 | char s[] = "abc"; //栈
111 | char *p2; //栈
112 | char *p3 = "123456"; //123456/0在常量区,p3在栈上。
113 | static int c =0; //全局(静态)初始化区
114 | p1 = (char *)malloc(10);
115 | p2 = (char *)malloc(20);
116 | //分配得来得10和20字节的区域就在堆区。
117 | strcpy(p1, "123456"); // 123456/0放在常量区,编译器可能会将它与p3所指向的"123456"
118 | 优化成一个地方。
119 | }
120 | ```
121 |
122 | #### 二、堆和栈的理论知识
123 |
124 | ##### 2.1申请方式
125 | stack:
126 | 由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
127 | heap:
128 | 需要程序员自己申请,并指明大小,在c中malloc函数
129 | 如`p1 = (char *)malloc(10) `
130 | 在C++中用new运算符
131 | 如 `p2 = new char[10]`
132 | 但是注意p1、p2本身是在栈中的。
133 |
134 | ##### 2.2 申请后系统的响应
135 |
136 | 栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢
137 | 出。
138 | 堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
139 | 会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表
140 | 中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的
141 | 首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。
142 | 另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部
143 | 分重新放入空闲链表中。
144 |
145 | ##### 2.3 申请大小的限制
146 | 栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意
147 | 思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有
148 | 的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将
149 | 提示overflow。因此,能从栈获得的空间较小。
150 | 堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储
151 | 的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小
152 | 受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
153 |
154 | ##### 2.4 申请效率的比较:
155 | 栈由系统自动分配,速度较快。但程序员是无法控制的。
156 | 堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
157 | 另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是
158 | 直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
159 |
160 | ##### 2.5 堆和栈中的存储内容
161 | 栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可
162 | 执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈
163 | 的,然后是函数中的局部变量。注意静态变量是不入栈的。
164 | 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地
165 | 址,也就是主函数中的下一条指令,程序由该点继续运行。
166 | 堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。
167 |
168 | ##### 2.6存取效率的比较
169 |
170 | ```c
171 | char s1[] = "aaaaaaaaaaaaaaa";
172 | char *s2 = "bbbbbbbbbbbbbbbbb";
173 | aaaaaaaaaaa是在运行时刻赋值的;
174 | 而bbbbbbbbbbb是在编译时就确定的;
175 | 但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
176 | 比如:
177 | #include
178 | void main()
179 | {
180 | char a = 1;
181 | char c[] = "1234567890";
182 | char *p ="1234567890";
183 | a = c[1];
184 | a = p[1];
185 | return;
186 | }
187 | 对应的汇编代码
188 | 10: a = c[1];
189 | 00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
190 | 0040106A 88 4D FC mov byte ptr [ebp-4],cl
191 | 11: a = p[1];
192 | 0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
193 | 00401070 8A 42 01 mov al,byte ptr [edx+1]
194 | 00401073 88 45 FC mov byte ptr [ebp-4],al
195 | 第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到
196 | edx中,再根据edx读取字符,显然慢了。
197 | ```
198 |
199 | ##### 2.7小结:
200 | 堆和栈的区别可以用如下的比喻来看出:
201 | 使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就
202 | 走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自
203 | 由度小。
204 | 使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由
205 | 度大。 (经典!)
206 |
207 | ### 10. 栈、堆分别是否会被线程所共享?
208 | ### 11. 内存空间中除了堆和栈还有什么内容?
209 |
210 | 1. 栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其
211 | 操作方式类似于数据结构中的栈。
212 | 2. 堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回
213 | 收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
214 | 3. 全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的
215 | 全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另
216 | 一块区域。 - 程序结束后由系统释放。
217 | 4. 文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
218 | 5. 程序代码区—存放函数体的二进制代码;
219 |
220 | ### 12. 讲一下你做过的性能优化?
221 | ### 13. HTTP 请求方法种类有哪些?(别忘记HEAD)
222 |
223 | * GET:幂等性、安全性、可缓存性
224 | * POST
225 | * PUT
226 | * DELETE
227 | * HEAD
228 |
229 | ### 14. 如何实现一个网络监控? --追问--> 如果拦截 NSURLSession 具体会怎么做? 涉及哪些方法,方法名字说下?如何保证线程安全(并发场景下, 监控的线程和正常业务方的线程, 如何保证正常回调?) (参考Notification的回调逻辑)
230 |
231 | 可查看 FLEX 源码
232 |
233 | ### 15. Swift、OC 如何相互调用? Swift-->OC 、OC -->Swift? 我开发一个Swift的SDK,(API都是Swift的), 内部需要调用到 OC 的库, 要怎么做?.
234 | ### 16. 动态库和静态库区别, 优缺点辨析? --追问-->包大小的差异的原因?为什么会有差异?
235 |
236 | [Q&A 八问: 静态库和动态库、armv7,armv7s,i386,x86_64架构](https://www.jianshu.com/p/107375bd9ca7)
--------------------------------------------------------------------------------
/subjects/runtime/weak.md:
--------------------------------------------------------------------------------
1 | ### 1. weak 如何实现的
2 |
3 | 底层源码实现:
4 |
5 | ```objective-c
6 | id
7 | objc_initWeak(id *location, id newObj)
8 | {
9 | if (!newObj) {
10 | *location = nil;
11 | return nil;
12 | }
13 |
14 | return storeWeak
15 | (location, (objc_object*)newObj);
16 | }
17 |
18 | ```
19 |
20 | 借助了 SideTable 数据结构,也称之为引用计数和弱引用表,底层维护了一个全局的 SideTables 对象,是 `StripedMap` 哈希表,通过对象指针作为 KEY 去找到对应的 SideTable。
21 |
22 | > key 就是对象指针进行哈希算法后的值,本来全局的 SideTables 表个数为 8 或 64,因此必定存在多个对象共用同一个 SideTable 的情况,不过SideTable中的 `weak_table_t` 和 `RefcountMap` 又是个哈希表,此时的 key 是对指针进行取反操作,另外还做了哈希碰撞处理。
23 |
24 | ```c++
25 | // objc-private.h
26 | #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
27 | enum { StripeCount = 8 };
28 | #else
29 | enum { StripeCount = 64 };
30 | #endif
31 |
32 | static unsigned int indexForPointer(const void *p) {
33 | uintptr_t addr = reinterpret_cast(p);
34 | return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
35 | }
36 | ```
37 |
38 | 数据结构如下
39 |
40 | ```c++
41 | struct SideTable {
42 | spinlock_t slock;
43 | RefcountMap refcnts;
44 | weak_table_t weak_table;
45 | };
46 | ```
47 |
48 | 接着通过 `weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating)` 将 weak 修饰的指针变量地址 location 和对象进行注册,源码实现:
49 |
50 | ```c++
51 | id
52 | weak_register_no_lock(weak_table_t *weak_table, id referent_id,
53 | id *referrer_id, bool crashIfDeallocating)
54 | {
55 | objc_object *referent = (objc_object *)referent_id;
56 | objc_object **referrer = (objc_object **)referrer_id;
57 |
58 | if (!referent || referent->isTaggedPointer()) return referent_id;
59 |
60 | // ...省略
61 |
62 | // ===========================!!!!!! Core !!!!!!!!===========================
63 | // now remember it and where it is being stored
64 | weak_entry_t *entry;
65 | if ((entry = weak_entry_for_referent(weak_table, referent))) {
66 | append_referrer(entry, referrer);
67 | }
68 | else {
69 | // Notice
70 | weak_entry_t new_entry(referent, referrer);
71 | weak_grow_maybe(weak_table);
72 | weak_entry_insert(weak_table, &new_entry);
73 | }
74 | // ===========================!!!!!! Core !!!!!!!!===========================
75 | return referent_id;
76 | }
77 | ```
78 |
79 | 正如源码所示,此处将每一个指向对象的 weak 变量,都使用 `weak_entry_t` 进行封装,数据结构如下:
80 |
81 | ```c++
82 | struct weak_entry_t {
83 | DisguisedPtr referent;
84 | union {
85 | struct {
86 | weak_referrer_t *referrers;
87 | uintptr_t out_of_line_ness : 2;
88 | uintptr_t num_refs : PTR_MINUS_2;
89 | uintptr_t mask;
90 | uintptr_t max_hash_displacement;
91 | };
92 | struct {
93 | // out_of_line_ness field is low bits of inline_referrers[1]
94 | weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
95 | };
96 | };
97 | };
98 | ```
99 |
100 | 在插入一个新的 `weak_entry_t` 的代码实现,同样是通过 key-value 形式去查询,键就是通过当前对象进行 `hash_pointer(referent)` 得到
101 |
102 | ```c++
103 | static weak_entry_t *
104 | weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
105 | {
106 | assert(referent);
107 |
108 | weak_entry_t *weak_entries = weak_table->weak_entries;
109 |
110 | if (!weak_entries) return nil;
111 |
112 | size_t begin = hash_pointer(referent) & weak_table->mask;
113 | size_t index = begin;
114 | size_t hash_displacement = 0;
115 | while (weak_table->weak_entries[index].referent != referent) {
116 | index = (index+1) & weak_table->mask;
117 | if (index == begin) bad_weak_table(weak_table->weak_entries);
118 | hash_displacement++;
119 | if (hash_displacement > weak_table->max_hash_displacement) {
120 | return nil;
121 | }
122 | }
123 |
124 | return &weak_table->weak_entries[index];
125 | }
126 |
127 | #if __LP64__
128 | static inline uint32_t ptr_hash(uint64_t key)
129 | {
130 | key ^= key >> 4;
131 | key *= 0x8a970be7488fda55;
132 | key ^= __builtin_bswap64(key);
133 | return (uint32_t)key;
134 | }
135 | #else
136 | static inline uint32_t ptr_hash(uint32_t key)
137 | {
138 | key ^= key >> 4;
139 | key *= 0x5052acdb;
140 | key ^= __builtin_bswap32(key);
141 | return key;
142 | }
143 | #endif
144 | ```
145 |
146 | 最后一步就是 `weak_entry_insert` 将封装好的 entry 添加到 SideTable 的 `weak_table` 中。
147 |
148 | ```c++
149 | static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
150 | {
151 | weak_entry_t *weak_entries = weak_table->weak_entries;
152 | assert(weak_entries != nil);
153 |
154 | size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
155 | size_t index = begin;
156 | size_t hash_displacement = 0;
157 | while (weak_entries[index].referent != nil) {
158 | index = (index+1) & weak_table->mask;
159 | if (index == begin) bad_weak_table(weak_entries);
160 | hash_displacement++;
161 | }
162 |
163 | weak_entries[index] = *new_entry;
164 | weak_table->num_entries++;
165 |
166 | if (hash_displacement > weak_table->max_hash_displacement) {
167 | weak_table->max_hash_displacement = hash_displacement;
168 | }
169 | }
170 | ```
171 |
172 | ### 2. weak修饰的属性释放后会被变成nil,怎么实现的
173 |
174 | 全局的 SideTables,通过key-value,用指针获取到对应的SideTable,引用计数和weak表
175 |
176 | ```objective-c
177 | struct SideTable {
178 | spinlock_t slock;
179 | RefcountMap refcnts;
180 | weak_table_t weak_table;
181 | }
182 |
183 | struct weak_table_t {
184 | weak_entry_t *weak_entries;
185 | size_t num_entries;
186 | uintptr_t mask;
187 | uintptr_t max_hash_displacement;
188 | };
189 | ```
190 |
191 | 做成不同的SideTable,是为了提升效率,涉及到资源竞争,所以加锁,但是加锁又很耗时,如果只有一个全局表,那么不同线程都访问的话,效率极低,而现在就ok拉。
192 |
193 | `weak_entries` 保存了指向某个实例对象的 weak objects,因为存储的是 `referrers`,说白了就是指针的指针,在 dealloc 的时候会被置为 nil。
194 |
195 | ```objective-c
196 | // 1
197 | _objc_rootDealloc
198 |
199 | // 2
200 | obj->rootDealloc()
201 |
202 | // 3
203 | inline void
204 | objc_object::rootDealloc()
205 | {
206 | if (isTaggedPointer()) return; // fixme necessary?
207 |
208 | if (fastpath(isa.nonpointer &&
209 | !isa.weakly_referenced &&
210 | !isa.has_assoc &&
211 | !isa.has_cxx_dtor &&
212 | !isa.has_sidetable_rc))
213 | {
214 | assert(!sidetable_present());
215 | free(this);
216 | }
217 | else {
218 | object_dispose((id)this);
219 | }
220 | }
221 |
222 | // 4
223 | id
224 | object_dispose(id obj)
225 | {
226 | if (!obj) return nil;
227 |
228 | objc_destructInstance(obj);
229 | free(obj);
230 |
231 | return nil;
232 | }
233 |
234 | // 5
235 | void *objc_destructInstance(id obj)
236 | {
237 | if (obj) {
238 | // Read all of the flags at once for performance.
239 | bool cxx = obj->hasCxxDtor();
240 | bool assoc = obj->hasAssociatedObjects();
241 |
242 | // This order is important.
243 | if (cxx) object_cxxDestruct(obj);
244 | if (assoc) _object_remove_assocations(obj);
245 | obj->clearDeallocating();
246 | }
247 |
248 | return obj;
249 | }
250 |
251 | // 6
252 | inline void
253 | objc_object::clearDeallocating()
254 | {
255 | if (slowpath(!isa.nonpointer)) {
256 | // Slow path for raw pointer isa.
257 | sidetable_clearDeallocating();
258 | }
259 | else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
260 | // Slow path for non-pointer isa with weak refs and/or side table data.
261 | clearDeallocating_slow();
262 | }
263 |
264 | assert(!sidetable_present());
265 | }
266 |
267 | // 7
268 | void
269 | objc_object::sidetable_clearDeallocating()
270 | {
271 | SideTable& table = SideTables()[this];
272 |
273 | // clear any weak table items
274 | // clear extra retain count and deallocating bit
275 | // (fixme warn or abort if extra retain count == 0 ?)
276 | table.lock();
277 | RefcountMap::iterator it = table.refcnts.find(this);
278 | if (it != table.refcnts.end()) {
279 | if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
280 | // 判断是否有weak指向当前对象
281 | weak_clear_no_lock(&table.weak_table, (id)this);
282 | }
283 | table.refcnts.erase(it);
284 | }
285 | table.unlock();
286 | }
287 | ```
288 |
289 | ###
--------------------------------------------------------------------------------
/interviews/iTeaTime技术清谈/面试题集二_0520.md:
--------------------------------------------------------------------------------
1 | >- 面试题出自技术群分享
2 | >- 欢迎转载,转载请注明出处:[pmst-swiftgg](https://links.jianshu.com/go?to=[https%3A%2F%2Fwww.jianshu.com%2Fp%2Fc1765a6305ab](https%3A%2F%2Fwww.jianshu.com%2Fp%2Fc1765a6305ab))
3 | >- 调试好可运行的源码 [objc-runtime](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fcolourful987%2F2020-Read-Record%2Ftree%2Fmaster%2FAnnotated source code%2Fobjc4-750),官网找 [objc4](https://links.jianshu.com/go?to=https%3A%2F%2Fopensource.apple.com%2Ftarballs%2Fobjc4%2F)
4 | >- 完成度:60%
5 | >- 最后修订:2020/05/24
6 |
7 | ## 一面
8 |
9 | ### 1. `__block`的作用和原理? 怎么做到block内部修改的?如何做变量提升的?怎么修改的内存位置?
10 |
11 | [block 面试题解答](../subjects/runtime/block.md)
12 |
13 | ### 2. block在内存层面是如何分配的? 多个场景解释. (借助 clang 查看)
14 |
15 | [block 面试题解答](../subjects/runtime/block.md)
16 |
17 | ### 3. load 和 initial 方法的相同点与不同点? 调用方式的区别? (方法调用方式请与 messageSend 进行辨析? )如果父类、本类、子类分别都实现了Load、initial 方法, 调用顺序分别是什么?
18 |
19 | [load和initialize区别](../subjects/runtime/load和initialize区别.md)。
20 |
21 | ### 4. 发送消息的流程?
22 |
23 | OC中的方法调用,编译后的代码最终都会转成 `objc_msgSend(id , SEL, ...)` 方法进行调用,这个方法第一个参数是一个消息接收者对象,`runtime` 通过这个对象的 isa 指针找到这个对象的类对象,从类对象中的cache中查找(**哈希查找,bucket 桶实现**)是否存在 `SEL` 对应的 IMP,若不存在,则会在 `method_list` 中查找(二分查找或者顺序查找),如果还是没找到,则会到 `super_class` 中查找,仍然没找到的话,就会调用 `_objc_msgForward(id, SEL, ...)` 进行消息转发。
24 |
25 | ### 5. 自动释放池工作原理?什么时候会释放?
26 |
27 | > autoreleasePool 就是一个基于栈节点的双向链表,@autoreleasePool{} 分别对应 [autoreleasePool push] 和 [autoreleasePool pop],前者就是将一个哨兵对象(nil) push 到栈中,花括号内所有发送一次 autorelease 消息的对象都会push到当前栈中,等到作用域结束 [autoreleasePool pop] 调用之时,会批量的pop对象直至碰到哨兵对象,对于pop出来的对象发送一次 release 消息。
28 |
29 | ### 6. 消息转发具体经过几步?具体到方法名, 参数,返回值
30 |
31 | 
32 |
33 | 各个阶段的应用:
34 |
35 | `resolveInstanceMethod:`
36 |
37 | 1. CoreData 数据模型属性动态创建,允许动态添加方法;
38 |
39 | `forwardingTargetForSelector:`
40 |
41 | 1. 解决 NSTimer,CADisplayLink 循环引用问题;
42 |
43 | `forwardInvocation:`
44 |
45 | 1. Aspects 库的实现;
46 | 2. NSProxy 多代理、多继承,解决 NSTimer,CADisplayLink 循环引用问题;
47 | 3. UIAppearance 实现;
48 |
49 | ### 7. 分类、扩展的区别?应用场景有哪些?
50 |
51 | category:
52 |
53 | * 运行时添加分类属性/协议/方法
54 | * 分类添加的方法会“覆盖”原类方法,因为方法查找的话是从头至尾,一旦查找到了就停止了
55 | * 同名分类方法谁生效取决于编译顺序,image 读取的信息是倒叙的,所以编译越靠后的越先读入
56 | * 名字相同的分类会引起编译报错;
57 |
58 | extension:
59 |
60 | * 编译时决议
61 | * 只以声明的形式存在,多数情况下就存在于 .m 文件中;
62 | * 不能为系统类添加扩展
63 |
64 | ### 8. 算法:字符串中最大不重复子串 abcdcde -> abcd、abcdcdefg -> cdefg、abcdecfgh -> decfgh
65 |
66 | ```c
67 | #define MAX(a,b) ((a) < (b) ? (b) : (a))
68 |
69 | char * maxWindow(char *s) {
70 | int dict[128] = {0};
71 | for(int i = 0; i < 128;i++)dict[i] = -1;
72 |
73 | int ansL = 0,ansR = 0;
74 | int head = 0;
75 | int res = 0;
76 | int len = strlen(s);
77 |
78 | for(int i = 0;i < len;i++){
79 | char ss = s[i];
80 | int k = dict[ss];
81 | if(k != -1){
82 | head = MAX(head, k);
83 | }
84 | dict[s[i]] = i + 1; // 存的是当前字符的下一个序号作为 head
85 |
86 | if (res < i - head +1) {
87 | ansL = head;
88 | ansR = i;
89 | res = i - head +1;
90 | }
91 | }
92 |
93 | char *ans = (char *)malloc(sizeof(char) * (ansR-ansL+2));
94 | int k = 0;
95 | for(int i = ansL; i < ansR+1;i++){
96 | ans[k++] = s[i];
97 | }
98 | ans[ansR-ansL+1] = '\0';
99 | return ans;
100 | }
101 | ```
102 |
103 |
104 |
105 | ### 9. assign与weak的辨析? weak实现原理? (map 的key value 具体是谁? 考虑如果有多个weak 指针指向同一个对象的场景, 怎么进行map设计, 这么存?)
106 |
107 | [weak 面试题解答](../subjects/runtime/weak.md)
108 |
109 | > key 就是对象指针进行哈希算法后的值,本来全局的 SideTables 表个数为 8 或 64,因此必定存在多个对象共用同一个 SideTable 的情况,不过SideTable中的 `weak_table_t` 和 `RefcountMap` 又是个哈希表,此时的 key 是对指针进行取反操作,另外还做了哈希碰撞处理。
110 |
111 | ```c++
112 | // objc-private.h
113 | #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
114 | enum { StripeCount = 8 };
115 | #else
116 | enum { StripeCount = 64 };
117 | #endif
118 |
119 | static unsigned int indexForPointer(const void *p) {
120 | uintptr_t addr = reinterpret_cast(p);
121 | return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
122 | }
123 | ```
124 |
125 |
126 |
127 | ### 10. 对于结构体,如何访问成员变量.比如:类中成员变量(ivar)
128 |
129 | 通过实例对象首地址+偏移量的方式取值
130 |
131 | ```objective-c
132 | @interface Person : NSObject
133 | {
134 | NSString *_name;
135 | NSUInteger _age;
136 | NSDate *_birthDay;
137 | NSUInteger _value;
138 | }
139 | @end
140 |
141 | @implementation Person
142 |
143 | + (instancetype)personWithName:(NSString *)name
144 | age:(NSUInteger)age
145 | birthday:(NSDate *)day
146 | value:(NSUInteger)value {
147 | Person *p = [Person new];
148 | p->_name = name;
149 | p->_age = age;
150 | p->_birthDay = day;
151 | p->_value = value;
152 | return p;
153 | }
154 | @end
155 |
156 | @implementation ViewController
157 |
158 | - (void)viewDidLoad {
159 | [super viewDidLoad];
160 | Person *p = [Person personWithName:@"pmst" age:18 birthday:[NSDate now] value:0x12345678];
161 | Ivar name_ivar = class_getInstanceVariable(Person.class, "_name");
162 | ptrdiff_t name_offset = ivar_getOffset(name_ivar);
163 | void **pp = (__bridge void *)p + name_offset;
164 | NSLog(@"name : %@",(__bridge id)(*pp));
165 |
166 | Ivar age_ivar = class_getInstanceVariable(Person.class, "_age");
167 | ptrdiff_t age_offset = ivar_getOffset(age_ivar);
168 | void **ppp = (__bridge void *)p + age_offset;
169 | NSLog(@"name : %d",(__bridge id)(*ppp));
170 |
171 | Ivar day_ivar = class_getInstanceVariable(Person.class, "_birthDay");
172 | ptrdiff_t day_offset = ivar_getOffset(day_ivar);
173 | void **pppp = (__bridge void *)p + day_offset;
174 | NSLog(@"name : %@",(__bridge id)(*pppp));
175 |
176 | Ivar value_ivar = class_getInstanceVariable(Person.class, "_value");
177 | ptrdiff_t value_offset = ivar_getOffset(value_ivar);
178 | void **ppppp = (__bridge void *)p + value_offset;
179 | NSLog(@"name : 0x%x",(__bridge id)(*ppppp));
180 | }
181 |
182 |
183 | @end
184 | ```
185 |
186 | runtime 提供了 `object_getIvar` 方法返回是对象,注意 `id *location = (id *)((char *)obj + offset)` 其实也就是对象起始位置+偏移量,不过 setIvar 同样是针对oc对象,两者都没有获取基本数据类型的接口:
187 |
188 | ```c
189 | id object_getIvar(id obj, Ivar ivar)
190 | {
191 | if (!obj || !ivar || obj->isTaggedPointer()) return nil;
192 |
193 | ptrdiff_t offset;
194 | objc_ivar_memory_management_t memoryManagement;
195 | _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
196 |
197 | id *location = (id *)((char *)obj + offset);
198 |
199 | if (memoryManagement == objc_ivar_memoryWeak) {
200 | return objc_loadWeak(location);
201 | } else {
202 | return *location;
203 | }
204 | }
205 |
206 | static ALWAYS_INLINE
207 | void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
208 | {
209 | if (!obj || !ivar || obj->isTaggedPointer()) return;
210 |
211 | ptrdiff_t offset;
212 | objc_ivar_memory_management_t memoryManagement;
213 | _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
214 |
215 | if (memoryManagement == objc_ivar_memoryUnknown) {
216 | if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
217 | else memoryManagement = objc_ivar_memoryUnretained;
218 | }
219 |
220 | id *location = (id *)((char *)obj + offset);
221 |
222 | switch (memoryManagement) {
223 | case objc_ivar_memoryWeak: objc_storeWeak(location, value); break;
224 | case objc_ivar_memoryStrong: objc_storeStrong(location, value); break;
225 | case objc_ivar_memoryUnretained: *location = value; break;
226 | case objc_ivar_memoryUnknown: _objc_fatal("impossible");
227 | }
228 | }
229 | ```
230 |
231 |
232 |
233 | ### 11. 评价下, 如何代码:
234 |
235 | ```objective-c
236 | @interface homeViewControler : UIViewController
237 | {
238 | someManager *_manager;
239 | }
240 | @property (nonatomic, assign) NSNumber *flag;
241 | @property (nonatomic, strong) NSString *name;
242 | @property (nonatomic, strong) UIButton *button;
243 | @end
244 |
245 | @implementation homeViewControler
246 |
247 | - (void)viewDidLoad
248 | {
249 | self.button.onClick = ^{
250 | if (self.flag) {
251 | self.name = @"the name";
252 | [_manager reloadData:self.name];
253 | }
254 | else
255 | {
256 | self.name = nil;
257 | [_manager clearData];
258 | }
259 | };
260 | }
261 |
262 | @end
263 | ```
264 |
265 | ## 二面
266 |
267 | ### 1. 方法交换和分类同时去hook同一个方法, 结果会怎么样? 具体交换的是什么? 交换时是如何处理传参数? 如果使用 NSInvocation 的话, 是否能处理方法有返回值的场景?具体怎么处理的?
268 | ### 2. 介绍Weex/RN的渲染原理? 与 Flutter 渲染的区别? 目录树的步骤是在哪里的? iOS/android渲染出来的树是否一致?
269 | ### 3. Weex JS 运行环境是多个还是一个?
270 | ### 4. Weex/RN 与 H5、native 相比的优缺点?
271 | ### 5. Weex/RN 与 H5 白屏的原因?
272 | ### 6. 你认为c++、与大前端相关的语言,比如objc、swift、js相比它的优缺点?
273 |
274 | > 关于 Weex / RN / Flutter 可以提 pr 到仓库,请在 rn_flutter_weex 目录下创建对应的解答,然后修改当前 md 文档附上链接即可
275 |
276 | ### 三面
277 |
278 | ### websocket 协议 与 MQTT 协议的区别? MQTT 是否支持搭配 websocket 实现聊天功能?
279 | ### 弱网情况下 IM 即时通讯的优化? 比如: 网络抖动.
280 |
281 | [弱网环境下整体优化方案](../subjects/network/弱网优化.md)
282 |
283 | ### 什么是 SNI? SNI 是什么的缩写? iOS上想设置 SNI , 怎么设?
284 | ### 排序算法, 字母和数字排序, 字母优先级高于数字: abc123.
--------------------------------------------------------------------------------
/subjects/algorithm/12_买卖股票求最大利润.md:
--------------------------------------------------------------------------------
1 | # 12_买卖股票求最大利润
2 |
3 | > source : [买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/)
4 | >
5 | > 推荐:[Most-consistent-ways-of-dealing-with-the-series-of-stock-problems](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108870/Most-consistent-ways-of-dealing-with-the-series-of-stock-problems) 以及 labuladong 的[一个方法团灭 6 道股票问题](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/yi-ge-fang-fa-tuan-mie-6-dao-gu-piao-wen-ti-by-l-3/)
6 | >
7 | > date:2020/06/03
8 |
9 | ### 题解:
10 |
11 | 状态转移方程
12 |
13 | ```c
14 | i - 第 i 天
15 | k - 交易次数(买入卖出才算一次交易)
16 | m - 0 or 1 0表示没有持有 1表示持有
17 |
18 | // 持有到卖出,是赚钱的行为,所以加上此刻prices[i]
19 | dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
20 |
21 | // 没有到持有,说明要花钱去买入,交易次数也会少一
22 | dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0] - prices[i])
23 | ```
24 |
25 | ### 1. 买卖股票的最佳时机一
26 |
27 | 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
28 |
29 | 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
30 |
31 | 注意你不能在买入股票前卖出股票。
32 |
33 | > 套模型:此处 k = 1。
34 |
35 | ```c
36 | #define MAX(a,b) ((a)<(b)?(b):(a))
37 | int maxProfit(int* prices, int pricesSize){
38 | if(pricesSize == 0)return 0;
39 |
40 | int **dp = (int **)malloc(sizeof(int *) * pricesSize);
41 | for(int i = 0;i 套模型:此处 k = infinity。
94 |
95 | ```c
96 | #define MAX(a,b) ((a)<(b)?(b):(a))
97 | int maxProfit(int* prices, int pricesSize){
98 | if(pricesSize == 0)return 0;
99 |
100 | int **dp = (int **)malloc(sizeof(int *) * pricesSize);
101 | for(int i = 0;i 套模型:此处 k = 2。
212 |
213 | ```c
214 |
215 | int maxProfit_with_fee(int[] prices) {
216 | int max_k = 2;
217 |
218 | int[][][] dp = new int[n][max_k + 1][2];
219 | for (int i = 0; i < n; i++) {
220 | for (int k = max_k; k >= 1; k--) {
221 | if (i - 1 == -1) {
222 | /* 处理 base case */
223 | dp[i][k][0] = 0;
224 | dp[i][k][1] = -prices[i];
225 | continue;
226 | }
227 | dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]);
228 | dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]);
229 | }
230 | }
231 | // 穷举了 n × max_k × 2 个状态,正确。
232 | return dp[n - 1][max_k][0];
233 | }
234 | // 简写
235 | #define MAX(a,b) ((a)<(b)?(b):(a))
236 |
237 | int maxProfit(int* prices, int pricesSize){
238 | if(pricesSize == 0) return 0;
239 |
240 | int dp_i_2_0 = 0;
241 | int dp_i_2_1 = INT_MIN;
242 | int dp_i_1_0 = 0;
243 | int dp_i_1_1 = INT_MIN;
244 |
245 | for(int i = 0;i < pricesSize;i++){
246 | dp_i_2_0 = MAX(dp_i_2_0,dp_i_2_1 + prices[i]);
247 | dp_i_2_1 = MAX(dp_i_2_1,dp_i_1_0 - prices[i]);
248 | dp_i_1_0 = MAX(dp_i_1_0,dp_i_1_1 + prices[i]);
249 | dp_i_1_1 = MAX(dp_i_1_1, - prices[i]);
250 | }
251 | return dp_i_2_0;
252 | }
253 | ```
254 |
255 | ### 6. 买卖股票的最佳时机
256 |
257 | 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
258 |
259 | 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
260 |
261 | 注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
262 |
263 | ```c
264 | #define MAX(a,b) ((a)<(b)?(b):(a))
265 |
266 | int maxProfit_inf(int* prices, int pricesSize){
267 | if(pricesSize == 0)return 0;
268 |
269 | int **dp = (int **)malloc(sizeof(int *) * pricesSize);
270 | for(int i = 0;i pricesSize/2){
291 | return maxProfit_inf(prices,pricesSize);
292 | }
293 |
294 | if(pricesSize == 0) return 0;
295 |
296 | int ***dp = (int ***)malloc(sizeof(int **) * pricesSize);
297 |
298 | for(int i = 0 ;i < pricesSize;i++) {
299 | dp[i] = (int **)malloc(sizeof(int *) * (max_k + 1));
300 | for(int j = 0;j < max_k+1;j++){
301 | dp[i][j] = (int *)malloc(sizeof(int) * 2);
302 | for(int p = 0;p < 2; p++){
303 | dp[i][j][p] = 0;
304 | }
305 | }
306 | }
307 |
308 | for(int i = 0;i < pricesSize;i++){
309 | for(int k = max_k;k >= 1;k--){
310 | if(i == 0){
311 | dp[i][k][0] = 0;
312 | dp[i][k][1] = -prices[i];// 第一天就买入股票
313 | continue;
314 | }
315 | dp[i][k][0] = MAX(dp[i-1][k][0],dp[i-1][k][1] + prices[i]);
316 | dp[i][k][1] = MAX(dp[i-1][k][1],dp[i-1][k-1][0] - prices[i]);
317 | }
318 | }
319 | return dp[pricesSize-1][max_k][0];
320 | }
321 | ```
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
--------------------------------------------------------------------------------
/demos/04-28-DeepInBlock/04-28-DeepInBlock.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4F16010B2458809600C4D5F3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F16010A2458809600C4D5F3 /* main.m */; };
11 | /* End PBXBuildFile section */
12 |
13 | /* Begin PBXCopyFilesBuildPhase section */
14 | 4F1601052458809600C4D5F3 /* CopyFiles */ = {
15 | isa = PBXCopyFilesBuildPhase;
16 | buildActionMask = 2147483647;
17 | dstPath = /usr/share/man/man1/;
18 | dstSubfolderSpec = 0;
19 | files = (
20 | );
21 | runOnlyForDeploymentPostprocessing = 1;
22 | };
23 | /* End PBXCopyFilesBuildPhase section */
24 |
25 | /* Begin PBXFileReference section */
26 | 4F1601072458809600C4D5F3 /* 04-28-DeepInBlock */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "04-28-DeepInBlock"; sourceTree = BUILT_PRODUCTS_DIR; };
27 | 4F16010A2458809600C4D5F3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
28 | /* End PBXFileReference section */
29 |
30 | /* Begin PBXFrameworksBuildPhase section */
31 | 4F1601042458809600C4D5F3 /* Frameworks */ = {
32 | isa = PBXFrameworksBuildPhase;
33 | buildActionMask = 2147483647;
34 | files = (
35 | );
36 | runOnlyForDeploymentPostprocessing = 0;
37 | };
38 | /* End PBXFrameworksBuildPhase section */
39 |
40 | /* Begin PBXGroup section */
41 | 4F1600FE2458809600C4D5F3 = {
42 | isa = PBXGroup;
43 | children = (
44 | 4F1601092458809600C4D5F3 /* 04-28-DeepInBlock */,
45 | 4F1601082458809600C4D5F3 /* Products */,
46 | );
47 | sourceTree = "";
48 | };
49 | 4F1601082458809600C4D5F3 /* Products */ = {
50 | isa = PBXGroup;
51 | children = (
52 | 4F1601072458809600C4D5F3 /* 04-28-DeepInBlock */,
53 | );
54 | name = Products;
55 | sourceTree = "";
56 | };
57 | 4F1601092458809600C4D5F3 /* 04-28-DeepInBlock */ = {
58 | isa = PBXGroup;
59 | children = (
60 | 4F16010A2458809600C4D5F3 /* main.m */,
61 | );
62 | path = "04-28-DeepInBlock";
63 | sourceTree = "";
64 | };
65 | /* End PBXGroup section */
66 |
67 | /* Begin PBXNativeTarget section */
68 | 4F1601062458809600C4D5F3 /* 04-28-DeepInBlock */ = {
69 | isa = PBXNativeTarget;
70 | buildConfigurationList = 4F16010E2458809600C4D5F3 /* Build configuration list for PBXNativeTarget "04-28-DeepInBlock" */;
71 | buildPhases = (
72 | 4F1601032458809600C4D5F3 /* Sources */,
73 | 4F1601042458809600C4D5F3 /* Frameworks */,
74 | 4F1601052458809600C4D5F3 /* CopyFiles */,
75 | );
76 | buildRules = (
77 | );
78 | dependencies = (
79 | );
80 | name = "04-28-DeepInBlock";
81 | productName = "04-28-DeepInBlock";
82 | productReference = 4F1601072458809600C4D5F3 /* 04-28-DeepInBlock */;
83 | productType = "com.apple.product-type.tool";
84 | };
85 | /* End PBXNativeTarget section */
86 |
87 | /* Begin PBXProject section */
88 | 4F1600FF2458809600C4D5F3 /* Project object */ = {
89 | isa = PBXProject;
90 | attributes = {
91 | LastUpgradeCheck = 1140;
92 | ORGANIZATIONNAME = pmst;
93 | TargetAttributes = {
94 | 4F1601062458809600C4D5F3 = {
95 | CreatedOnToolsVersion = 11.4.1;
96 | };
97 | };
98 | };
99 | buildConfigurationList = 4F1601022458809600C4D5F3 /* Build configuration list for PBXProject "04-28-DeepInBlock" */;
100 | compatibilityVersion = "Xcode 9.3";
101 | developmentRegion = en;
102 | hasScannedForEncodings = 0;
103 | knownRegions = (
104 | en,
105 | Base,
106 | );
107 | mainGroup = 4F1600FE2458809600C4D5F3;
108 | productRefGroup = 4F1601082458809600C4D5F3 /* Products */;
109 | projectDirPath = "";
110 | projectRoot = "";
111 | targets = (
112 | 4F1601062458809600C4D5F3 /* 04-28-DeepInBlock */,
113 | );
114 | };
115 | /* End PBXProject section */
116 |
117 | /* Begin PBXSourcesBuildPhase section */
118 | 4F1601032458809600C4D5F3 /* Sources */ = {
119 | isa = PBXSourcesBuildPhase;
120 | buildActionMask = 2147483647;
121 | files = (
122 | 4F16010B2458809600C4D5F3 /* main.m in Sources */,
123 | );
124 | runOnlyForDeploymentPostprocessing = 0;
125 | };
126 | /* End PBXSourcesBuildPhase section */
127 |
128 | /* Begin XCBuildConfiguration section */
129 | 4F16010C2458809600C4D5F3 /* Debug */ = {
130 | isa = XCBuildConfiguration;
131 | buildSettings = {
132 | ALWAYS_SEARCH_USER_PATHS = NO;
133 | CLANG_ANALYZER_NONNULL = YES;
134 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
135 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
136 | CLANG_CXX_LIBRARY = "libc++";
137 | CLANG_ENABLE_MODULES = YES;
138 | CLANG_ENABLE_OBJC_ARC = YES;
139 | CLANG_ENABLE_OBJC_WEAK = YES;
140 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
141 | CLANG_WARN_BOOL_CONVERSION = YES;
142 | CLANG_WARN_COMMA = YES;
143 | CLANG_WARN_CONSTANT_CONVERSION = YES;
144 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
145 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
146 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
147 | CLANG_WARN_EMPTY_BODY = YES;
148 | CLANG_WARN_ENUM_CONVERSION = YES;
149 | CLANG_WARN_INFINITE_RECURSION = YES;
150 | CLANG_WARN_INT_CONVERSION = YES;
151 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
152 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
153 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
154 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
155 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
156 | CLANG_WARN_STRICT_PROTOTYPES = YES;
157 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
158 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
159 | CLANG_WARN_UNREACHABLE_CODE = YES;
160 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
161 | COPY_PHASE_STRIP = NO;
162 | DEBUG_INFORMATION_FORMAT = dwarf;
163 | ENABLE_STRICT_OBJC_MSGSEND = YES;
164 | ENABLE_TESTABILITY = YES;
165 | GCC_C_LANGUAGE_STANDARD = gnu11;
166 | GCC_DYNAMIC_NO_PIC = NO;
167 | GCC_NO_COMMON_BLOCKS = YES;
168 | GCC_OPTIMIZATION_LEVEL = 0;
169 | GCC_PREPROCESSOR_DEFINITIONS = (
170 | "DEBUG=1",
171 | "$(inherited)",
172 | );
173 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
174 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
175 | GCC_WARN_UNDECLARED_SELECTOR = YES;
176 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
177 | GCC_WARN_UNUSED_FUNCTION = YES;
178 | GCC_WARN_UNUSED_VARIABLE = YES;
179 | MACOSX_DEPLOYMENT_TARGET = 10.15;
180 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
181 | MTL_FAST_MATH = YES;
182 | ONLY_ACTIVE_ARCH = YES;
183 | SDKROOT = macosx;
184 | };
185 | name = Debug;
186 | };
187 | 4F16010D2458809600C4D5F3 /* Release */ = {
188 | isa = XCBuildConfiguration;
189 | buildSettings = {
190 | ALWAYS_SEARCH_USER_PATHS = NO;
191 | CLANG_ANALYZER_NONNULL = YES;
192 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
193 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
194 | CLANG_CXX_LIBRARY = "libc++";
195 | CLANG_ENABLE_MODULES = YES;
196 | CLANG_ENABLE_OBJC_ARC = YES;
197 | CLANG_ENABLE_OBJC_WEAK = YES;
198 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
199 | CLANG_WARN_BOOL_CONVERSION = YES;
200 | CLANG_WARN_COMMA = YES;
201 | CLANG_WARN_CONSTANT_CONVERSION = YES;
202 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
203 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
204 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
205 | CLANG_WARN_EMPTY_BODY = YES;
206 | CLANG_WARN_ENUM_CONVERSION = YES;
207 | CLANG_WARN_INFINITE_RECURSION = YES;
208 | CLANG_WARN_INT_CONVERSION = YES;
209 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
210 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
211 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
212 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
213 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
214 | CLANG_WARN_STRICT_PROTOTYPES = YES;
215 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
216 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
217 | CLANG_WARN_UNREACHABLE_CODE = YES;
218 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
219 | COPY_PHASE_STRIP = NO;
220 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
221 | ENABLE_NS_ASSERTIONS = NO;
222 | ENABLE_STRICT_OBJC_MSGSEND = YES;
223 | GCC_C_LANGUAGE_STANDARD = gnu11;
224 | GCC_NO_COMMON_BLOCKS = YES;
225 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
226 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
227 | GCC_WARN_UNDECLARED_SELECTOR = YES;
228 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
229 | GCC_WARN_UNUSED_FUNCTION = YES;
230 | GCC_WARN_UNUSED_VARIABLE = YES;
231 | MACOSX_DEPLOYMENT_TARGET = 10.15;
232 | MTL_ENABLE_DEBUG_INFO = NO;
233 | MTL_FAST_MATH = YES;
234 | SDKROOT = macosx;
235 | };
236 | name = Release;
237 | };
238 | 4F16010F2458809600C4D5F3 /* Debug */ = {
239 | isa = XCBuildConfiguration;
240 | buildSettings = {
241 | CLANG_ENABLE_OBJC_ARC = NO;
242 | CODE_SIGN_STYLE = Automatic;
243 | DEVELOPMENT_TEAM = 66AQ356GA4;
244 | ENABLE_HARDENED_RUNTIME = YES;
245 | PRODUCT_NAME = "$(TARGET_NAME)";
246 | };
247 | name = Debug;
248 | };
249 | 4F1601102458809600C4D5F3 /* Release */ = {
250 | isa = XCBuildConfiguration;
251 | buildSettings = {
252 | CLANG_ENABLE_OBJC_ARC = NO;
253 | CODE_SIGN_STYLE = Automatic;
254 | DEVELOPMENT_TEAM = 66AQ356GA4;
255 | ENABLE_HARDENED_RUNTIME = YES;
256 | PRODUCT_NAME = "$(TARGET_NAME)";
257 | };
258 | name = Release;
259 | };
260 | /* End XCBuildConfiguration section */
261 |
262 | /* Begin XCConfigurationList section */
263 | 4F1601022458809600C4D5F3 /* Build configuration list for PBXProject "04-28-DeepInBlock" */ = {
264 | isa = XCConfigurationList;
265 | buildConfigurations = (
266 | 4F16010C2458809600C4D5F3 /* Debug */,
267 | 4F16010D2458809600C4D5F3 /* Release */,
268 | );
269 | defaultConfigurationIsVisible = 0;
270 | defaultConfigurationName = Release;
271 | };
272 | 4F16010E2458809600C4D5F3 /* Build configuration list for PBXNativeTarget "04-28-DeepInBlock" */ = {
273 | isa = XCConfigurationList;
274 | buildConfigurations = (
275 | 4F16010F2458809600C4D5F3 /* Debug */,
276 | 4F1601102458809600C4D5F3 /* Release */,
277 | );
278 | defaultConfigurationIsVisible = 0;
279 | defaultConfigurationName = Release;
280 | };
281 | /* End XCConfigurationList section */
282 | };
283 | rootObject = 4F1600FF2458809600C4D5F3 /* Project object */;
284 | }
285 |
--------------------------------------------------------------------------------
/chapters/2_多线程.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [TOC]
4 |
5 | # 多线程
6 |
7 | ## `iOS`开发中有多少类型的线程?分别对比
8 |
9 | * NSThread,每个 NSThread对象对应一个线程,量级较轻,通常我们会起一个 runloop 保活,然后通过添加自定义source0源或者 perform onThread 来进行调用,优点轻量级,使用简单,缺点:需要自己管理线程的生命周期,保活,另外还会线程同步,加锁、睡眠和唤醒。
10 | * GCD:Grand Central Dispatch(派发) 是基于C语言的框架,可以充分利用多核,是苹果推荐使用的多线程技术
11 | * 优点:GCD更接近底层,而NSOperationQueue则更高级抽象,所以GCD在追求性能的底层操作来说,是速度最快的,有待确认
12 | * 缺点:操作之间的事务性,顺序行,依赖关系。GCD需要自己写更多的代码来实现
13 | * NSOperation
14 | * 优点: 使用者的关注点都放在了 operation 上,而不需要线程管理。
15 | * 支持在操作对象之间依赖关系,方便控制执行顺序。
16 | * 支持可选的完成块,它在操作的主要任务完成后执行。
17 | * 支持使用KVO通知监视操作执行状态的变化。
18 | * 支持设定操作的优先级,从而影响它们的相对执行顺序。
19 | * 支持取消操作,允许您在操作执行时暂停操作。
20 | * 缺点:高级抽象,性能方面相较 GCD 来说不足一些;
21 |
22 | ## `GCD`有哪些队列,默认提供哪些队列
23 |
24 | > [Grand Central Dispatch(GCD) 深入浅出](https://www.jianshu.com/p/8cb4f395d2c4)
25 |
26 | | 队列 | 队列类型 | 说明 |
27 | | -------------------------------------------------- | ------------ | ------------------------------------------------------------ |
28 | | 主队列(main queue) | 串行 | 保证所有的任务都在主线程执行,而主线程是唯一用于 UI 更新的线程。此外还用于发送消息给视图或发送通知。 |
29 | | 四个全局调度队列(high、default、low、background) | 并发 | Apple 的接口也会使用这些队列,所以你添加的任何任务都不会是这些队列中唯一的任务 |
30 | | 自定义队列 | 串行 or 并发 | 1. 多个任务以串行方式执行,但又不想在主线程中;2. 多个任务以并行方式执行,但不希望队列中有其他系统的任务干扰。 |
31 |
32 | ## `GCD`有哪些方法api
33 |
34 | ```objective-c
35 | // 接口过多,罗列几个关键的
36 | // 1. dispatch_sync
37 | // 线程 A 调用 someMethod 方法
38 | - (void)someMethod {
39 | // 同步
40 | dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
41 |
42 | // 由于是同步,线程A会被阻塞
43 | [self doOtherThing];
44 | }
45 |
46 | // 2. dispatch_async
47 | // 线程 A 调用 someMethod 方法
48 | - (void)someMethod {
49 | // 异步
50 | dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
51 |
52 | // 由于是异步,线程A不会被阻塞
53 | [self doOtherThing];
54 | }
55 |
56 | // 3. dispatch_after
57 | // 线程 A 调用 someMethod 方法
58 | - (void)someMethod {
59 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
60 | <#code to be executed after a specified delay#>
61 | });
62 | }
63 |
64 | // 4. dispatch_once
65 | static dispatch_once_t onceToken;
66 | dispatch_once(&onceToken, ^{
67 | <#code to be executed once#>
68 | });
69 |
70 | // 5. dispatch_barrier_sync 和 dispatch_barrier_async
71 | // 这个可以实现读写锁
72 | // 线程 A 调用 someMethod 方法
73 | - (void)someMethod {
74 | // 同步
75 | dispatch_barrier_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
76 |
77 | // 由于是同步,线程A会被阻塞
78 | [self doOtherThing];
79 | }
80 |
81 | // 6. dispatch_apply
82 | // 串行方式做事情
83 | - (void)serialDoSomething {
84 | for(int idx=0; idx < 3; idx++) {
85 | // 这里你可以处理事情 比如下载图片
86 | downloadPic(idx);
87 | }
88 | }
89 |
90 | // 并发方式做事情
91 | - (void)concurrencyDoSomething {
92 | dispatch_apply(3, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t idx) {
93 | // 由于下载图片之间没有任何关系,允许并发的去下载
94 | downloadPic(idx);
95 | })
96 | }
97 | ```
98 |
99 | 7. Dispatch Groups 集合
100 |
101 | 这里引入“**组(group)**”的概念,与队列不同,任何加入到组中的任务(task),可以是串行执行或并行执行,可以来自任何其他队列,当组中所有任务完成之时,会通知你这个消息。下面是几个常用接口:
102 |
103 | - `dispatch_group_t group_name = dispatch_group_create();` 实例化一个组
104 | - `dispatch_group_enter(<#dispatch_group_t _Nonnull group#>)` 和 `dispatch_group_leave(<#dispatch_group_t _Nonnull group#>)` ,“加入”和“离开”是一对,就好比Objective-C 内存管理一样,谁持有(`retain`)谁释放(`release`)
105 | - `dispatch_group_wait(<#dispatch_group_t _Nonnull group#>,DISPATCH_TIME_FOREVER)` 阻塞当前线程,等待任务组中的所有任务执行完毕。
106 | - `dispatch_group_notify(<#dispatch_group_t _Nonnull group#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)` 和3不同,当组中的全部执行完毕,将 `block` 任务加入到队列 `queue` 执行。
107 |
108 | 8. Semaphores 信号量
109 |
110 | ```objective-c
111 | dispatch_group_t group = dispatch_group_create(); // 1
112 | dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); // 2
113 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 3
114 | for (int i = 0; i < 100; i++)
115 | {
116 | dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 4
117 | dispatch_group_async(group, queue, ^{
118 | NSLog(@"%i",i); // 5
119 | sleep(2);
120 | dispatch_semaphore_signal(semaphore); // 6
121 | });
122 | }
123 | dispatch_group_wait(group, DISPATCH_TIME_FOREVER);// 7
124 | NSLog(@"所有任务完成");
125 | ```
126 |
127 | ## `GCD`主线程 & 主队列的关系
128 |
129 | 队列其实就是一个数据结构体,主队列由于是串行队列,所以入队列中的 task 会逐一派发到主线程中执行;但是其他队列也可能会派发到主线程执行
130 |
131 | ## 如何实现同步,有多少方式就说多少
132 |
133 | 1. dispatch_sync
134 | 2. dispatch_group,
135 | 3. dispatch_semaphore
136 |
137 | ## `dispatch_once`实现原理
138 |
139 | ```c
140 | void
141 | dispatch_once(dispatch_once_t *val, dispatch_block_t block)
142 | {
143 | dispatch_once_f(val, block, _dispatch_Block_invoke(block));
144 | }
145 |
146 | void
147 | dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
148 | {
149 | #if !DISPATCH_ONCE_INLINE_FASTPATH
150 | if (likely(os_atomic_load(val, acquire) == DLOCK_ONCE_DONE)) {
151 | return;
152 | }
153 | #endif // !DISPATCH_ONCE_INLINE_FASTPATH
154 | return dispatch_once_f_slow(val, ctxt, func);
155 | }
156 |
157 | static void
158 | dispatch_once_f_slow(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
159 | {
160 | _dispatch_once_waiter_t volatile *vval = (_dispatch_once_waiter_t*)val;
161 | struct _dispatch_once_waiter_s dow = { };
162 | _dispatch_once_waiter_t tail = &dow, next, tmp;
163 | dispatch_thread_event_t event;
164 |
165 | if (os_atomic_cmpxchg(vval, NULL, tail, acquire)) {
166 | dow.dow_thread = _dispatch_tid_self();
167 | _dispatch_client_callout(ctxt, func);
168 |
169 | next = (_dispatch_once_waiter_t)_dispatch_once_xchg_done(val);
170 | while (next != tail) {
171 | tmp = (_dispatch_once_waiter_t)_dispatch_wait_until(next->dow_next);
172 | event = &next->dow_event;
173 | next = tmp;
174 | _dispatch_thread_event_signal(event);
175 | }
176 | } else {
177 | _dispatch_thread_event_init(&dow.dow_event);
178 | next = *vval;
179 | for (;;) {
180 | if (next == DISPATCH_ONCE_DONE) {
181 | break;
182 | }
183 | if (os_atomic_cmpxchgv(vval, next, tail, &next, release)) {
184 | dow.dow_thread = next->dow_thread;
185 | dow.dow_next = next;
186 | if (dow.dow_thread) {
187 | pthread_priority_t pp = _dispatch_get_priority();
188 | _dispatch_thread_override_start(dow.dow_thread, pp, val);
189 | }
190 | _dispatch_thread_event_wait(&dow.dow_event);
191 | if (dow.dow_thread) {
192 | _dispatch_thread_override_end(dow.dow_thread, val);
193 | }
194 | break;
195 | }
196 | }
197 | _dispatch_thread_event_destroy(&dow.dow_event);
198 | }
199 | }
200 | ```
201 |
202 | 上面是 libdispatch-913.60.2 的实现,稍显负责,所以找了下旧版本的[其他大佬的解答摘抄下](http://lingyuncxb.com/2018/02/01/GCD源码分析2%20——%20dispatch-once篇/):
203 |
204 | ```c
205 | void dispatch_once_f(dispatch_once_t *val, void *ctxt, void (*func)(void *)){
206 |
207 | volatile long *vval = val;
208 | if (dispatch_atomic_cmpxchg(val, 0l, 1l)) {
209 | func(ctxt); // block真正执行
210 | dispatch_atomic_barrier();
211 | *val = ~0l;
212 | }
213 | else
214 | {
215 | do
216 | {
217 | _dispatch_hardware_pause();
218 | } while (*vval != ~0l);
219 | dispatch_atomic_barrier();
220 | }
221 | }
222 | ```
223 |
224 | - 1、在开篇中已经讲过`dispatch_atomic_cmpxchg`,它是一个宏定义,原型为`__sync_bool_compare_and_swap((p), (o), (n))` ,这是LockFree给予CAS的一种原子操作机制,原理就是 **如果p==o,那么将p设置为n,然后返回true;否则,不做任何处理返回false**
225 | - 2、在多线程环境中,如果某一个线程A首次进入`dispatch_once_f`,*val==0,这个时候直接将其原子操作设为1,然后执行传入`dispatch_once_f`的block,然后调用`dispatch_atomic_barrier`,最后将*val的值修改为~0。
226 | - 3、`dispatch_atomic_barrier`是一种内存屏障,所谓内存屏障,从处理器角度来说,是用来串行化读写操作的,从软件角度来讲,就是用来解决顺序一致性问题的。编译器不是要打乱代码执行顺序吗,处理器不是要乱序执行吗,你插入一个内存屏障,就相当于告诉编译器,屏障前后的指令顺序不能颠倒,告诉处理器,只有等屏障前的指令执行完了,屏障后的指令才能开始执行。所以这里`dispatch_atomic_barrier`能保证只有在block执行完毕后才能修改*val的值。
227 | - 4、在首个线程A执行block的过程中,如果其它的线程也进入`dispatch_once_f`,那么这个时候if的原子判断一定是返回false,于是走到了else分支,于是执行了do~while循环,其中调用了`_dispatch_hardware_pause`,这有助于提高性能和节省CPU耗电,pause就像nop,干的事情就是延迟空等的事情。直到首个线程已经将block执行完毕且将*val修改为~0,调用`dispatch_atomic_barrier`后退出。这么看来其它的线程是无法执行block的,这就保证了在`dispatch_once_f`的block的执行的唯一性,生成的单例也是唯一的。
228 |
229 | dispatch_once死锁
230 |
231 | - 死锁方式1:
232 | 1、某线程T1()调用单例A,且为应用生命周期内首次调用,需要使用dispatch_once(&token, block())初始化单例。
233 | 2、上述block()中的某个函数调用了dispatch_sync_safe,同步在T2线程执行代码
234 | 3、T2线程正在执行的某个函数需要调用到单例A,将会再次调用dispatch_once。
235 | 4、这样T1线程在等block执行完毕,它在等待T2线程执行完毕,而T2线程在等待T1线程的dispatch_once执行完毕,造成了相互等待,故而死锁
236 | - 死锁方式2:
237 | 1、某线程T1()调用单例A,且为应用生命周期内首次调用,需要使用dispatch_once(&token, block())初始化单例;
238 | 2、block中可能掉用到了B流程,B流程又调用了C流程,C流程可能调用到了单例A,将会再次调用dispatch_once;
239 | 3、这样又造成了相互等待。
240 |
241 | 所以在使用写单例时要注意:
242 |
243 | - 1、初始化要尽量简单,不要太复杂;
244 | - 2、尽量能保持自给自足,减少对别的模块或者类的依赖;
245 | - 3、单例尽量考虑使用场景,不要随意实现单例,否则这些单例一旦初始化就会一直占着资源不能释放,造成大量的资源浪费。
246 |
247 | > [**深入浅出 GCD 之 dispatch_once**](https://xiaozhuanlan.com/topic/7916538240)
248 |
249 | ## 什么情况下会死锁
250 |
251 | 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
252 |
253 |
254 |
255 | ## 有哪些类型的线程锁,分别介绍下作用和使用场景
256 |
257 | [[iOS] 谈谈iOS多线程的锁](https://juejin.im/post/5a0a92996fb9a0451f307479) 一文直接吃透所有锁。
258 |
259 | ## `NSOperationQueue`中的`maxConcurrentOperationCount`默认值
260 |
261 | 默认值为 -1,默认的最大操作数由NSOperationQueue对象根据当前系统条件动态确定。
262 |
263 | ## `NSTimer、CADisplayLink、dispatch_source_t` 的优劣
264 |
265 | ### NSTimer
266 |
267 | 方式一:
268 |
269 | > 接口文档:Creates a timer and schedules it on the current run loop in the default mode.
270 |
271 | ```objective-c
272 | self.timer = [NSTimer scheduledTimerWithTimeInterval:2
273 | target:self
274 | selector:@selector(timerTest)
275 | userInfo:nil
276 | repeats:YES];
277 | // 我们可以通过如下接口添加到commonMode中
278 | [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
279 | ```
280 |
281 | 方式二:
282 |
283 | > You must add the new timer to a run loop, using [addTimer:forMode:](apple-reference-documentation://hcocJkO-uk). Then, after `ti` seconds have elapsed, the timer fires, sending the message `aSelector` to `target`. (If the timer is configured to repeat, there is no need to subsequently re-add the timer to the run loop.)
284 |
285 | ```objective-c
286 | self.timer = [NSTimer timerWithTimeInterval:2
287 | target:self
288 | selector:@selector(timerTest)
289 | userInfo:nil
290 | repeats:YES];
291 | [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
292 | ```
293 |
294 | > 关于 NSTimer 的 retainCycle 问题和解决方案可参考其他文章。解决方案有:
295 | >
296 | > 1. NSTimer 加一个 category,然后以 block 的方式注入定时器触发时的执行任务,Timer 的 target 此处是用 Timer 类对象,而它是常驻内存中的,所以vc->timer->Timer类对象 没有构成环,但是注意闭包中要用 weakSelf;
297 | > 2. 封装一个中间对象 WeakProxy, 内部使用一个 weak 属性变量持有 self,所以现在持有关系式 vc->timer->weakProxy ---->vc,所以也没有形成 retainCycle。
298 | >
299 | > NSTimer repeats 等于 NO 的时候,执行完任务,定时器自动 invalidated 就会释放对 self 的 strong reference ,
300 |
301 | 销毁的时候使用:
302 |
303 | ```objective-c
304 | /// 文档接口说明:The timer maintains a strong reference to this object until it (the timer) is invalidated.
305 | [self.timer invalidate];
306 | self.timer = nil;
307 | ```
308 |
309 | **缺点**
310 |
311 | **计时不精确**:不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的**RunLoop**和**RunLoop Mode**有关,如果此**RunLoop**正在执行一个连续性的运算,**timer**就会被延时出发。重复性的**timer**遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行。
312 |
313 | ### CADisplayLink
314 |
315 | > Apple 专门提供的一个类,主要的优势在于他的执行频率是根据设备屏幕的刷新频率来计算的,也即是时间间隔最准确的定时器。用法和 NSTimer 差不多,当然也存在 retainCycle 问题,解决方式同上。
316 |
317 | 暂停继续:
318 |
319 | ```objective-c
320 | - (void)displayLinkStart {
321 | self.displayLink.paused = !self.displayLink.paused;
322 | }
323 | ```
324 |
325 | #### 优缺点
326 |
327 | - **优点:** 依托于设备屏幕刷新频率触发事件,所以其触发时间上是最准确的。也是最适合做UI不断刷新的事件,过渡相对流畅,无卡顿感。
328 | - **缺点:**
329 | 1. 由于依托于屏幕刷新频率,若果CPU不堪重负而影响了屏幕刷新,那么我们的触发事件也会受到相应影响。
330 | 2. selector触发的时间间隔只能是duration的整倍数
331 | 3. selector事件如果大于其触发间隔就会造成掉帧现象。
332 |
333 | ### dispatch_source_t
334 |
335 | ```objective-c
336 | - (void)createGCDTimer {
337 | /// 创建定时器
338 | self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
339 | /// 设置触发间隔时间
340 | dispatch_source_set_timer(self.timer, dispatch_walltime(NULL,0 * NSEC_PER_SEC), 1 * NSEC_PER_SEC, 0);
341 |
342 | /// 设置回调
343 | dispatch_source_set_event_handler(self.timer, ^{
344 | NSLog(@"triggered");
345 | });
346 | }
347 | ```
348 |
349 | **参数:**
350 |
351 | **dispatch_source_create()相关参数**
352 |
353 | | 参数 | 意义 |
354 | | :----: | ---------------------------------------------------------- |
355 | | type | dispatch源可处理的事件 |
356 | | handle | 可以理解为句柄、索引或id,假如要监听进程,需要传入进程的ID |
357 | | mask | 可以理解为描述,提供更详细的描述,让它知道具体要监听什么 |
358 | | queue | 自定义源需要的一个队列,用来处理所有的响应句柄(block) |
359 |
360 | **dispatch_source_set_timer()相关参数**
361 |
362 | | 参数 | 意义 |
363 | | :------: | ---------------------- |
364 | | source | dispatch_source_t |
365 | | start | 事件首次触发的延迟时间 |
366 | | interval | 时间间隔 |
367 | | leeway | 误差范围 |
368 |
369 | #### 开始 暂停
370 |
371 | ```objectivec
372 | //开始
373 | dispatch_resume(self.disTimer);
374 | //暂停
375 | dispatch_suspend(self.disTimer);
376 | ```
377 |
378 | #### 销毁
379 |
380 | ```css
381 | dispatch_source_cancel(self.disTimer);
382 | ```
383 |
384 | **注意:**dispatch_source_t 一定要被设置为成员变量,否则将会立即被释放。
385 |
386 | #### 优缺点
387 |
388 | - **优点:**不受当前runloopMode的影响;
389 | - **缺点:**虽然不受runloopMode的影响,但是其计时效应仍不是百分之百准确的;
--------------------------------------------------------------------------------
/chapters/3_视图和图像相关.md:
--------------------------------------------------------------------------------
1 | [TOC]
2 |
3 | # 视图&图像相关
4 |
5 | ## 1. AutoLayout的原理,性能如何
6 |
7 | 参考文章:[AutoLayout 的原理性能](https://www.dazhuanlan.com/2019/10/05/5d97e8f15427d/)
8 |
9 | ## 2. UIView & CALayer的区别
10 |
11 | * UIView 为 CALayer 提供内容,以及负责处理触摸等事件,参与响应链;
12 |
13 | * CALayer 负责显示内容 contents
14 | * 单一职责原则
15 |
16 | ## 3. 事件响应链
17 |
18 | 
19 |
20 | > 慕尚课程的总结图
21 |
22 | 
23 |
24 |
25 |
26 | 
27 |
28 | ## drawrect & layoutsubviews调用时机
29 |
30 | 
31 |
32 | 
33 |
34 |
35 |
36 | ### layoutSubviews
37 |
38 | > 参考文章:[layoutSubviews和drawRect调用时机的探究](https://blog.csdn.net/wangyanchang21/article/details/50774522)
39 |
40 | 1. init初始化不会触发layoutSubviews。
41 | 2. addSubview会触发layoutSubviews。
42 | 3. 改变一个UIView的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
43 | 4. 滚动一个UIScrollView引发UIView的重新布局会触发layoutSubviews。
44 | 5. 旋转Screen会触发父UIView上的layoutSubviews事件。
45 | 6. 直接调用 setNeedsLayout 或者 layoutIfNeeded。
46 |
47 | * `setNeedsLayout`
48 | 标记为需要重新布局,异步调用`layoutIfNeeded`刷新布局,不立即刷新,在下一轮runloop结束前刷新,对于这一轮`runloop`之内的所有布局和UI上的更新只会刷新一次,`layoutSubviews`一定会被调用。
49 |
50 | * `layoutIfNeeded`
51 | 如果有需要刷新的标记,立即调用`layoutSubviews`进行布局(如果没有标记,不会调用`layoutSubviews`)。
52 |
53 |
54 |
55 | ## 4. 隐式动画 & 显示动画区别
56 |
57 | > 解答出自 [iOS动画-CALayer隐式动画原理与特性](https://cloud.tencent.com/developer/article/1418000)。
58 |
59 | 隐式动画,指我们可以在不设定任何动画类型的情况下,仅仅改变CALayer的一个可做动画的属性,就能实现动画效果。
60 |
61 | ### 1. 事务
62 |
63 | **事务**,其实是Core Animation用来包含一系列属性动画集合的机制,通过指定事务来改变图层的可动画属性,这些变化都不是立刻发生变化的,而是在事务被提交的时候才启动一个动画过渡到新值。任何可以做动画的图层属性都会被添加到栈顶的事务。
64 |
65 | ```objective-c
66 | //1.动画属性的入栈
67 | + (void)begin;
68 |
69 | //2.动画属性出栈
70 | + (void)commit;
71 |
72 | //3.设置当前事务的动画时间
73 | + (void)setAnimationDuration:(CFTimeInterval)dur;
74 |
75 | //4.获取当前事务的动画时间
76 | + (CFTimeInterval)animationDuration;
77 |
78 | //5.在动画结束时提供一个完成的动作
79 | + (void)setCompletionBlock:(nullable void (^)(void))block;
80 | ```
81 |
82 | 现在再来考虑隐式动画,其实是Core Animation在每个RunLoop周期中会自动开始一次新的事务,即使你不显式的使用[CATranscation begin]开始一次事务,任何在一次RunLoop运行时循环中属性的改变都会被集中起来,执行默认0.25秒的动画。
83 |
84 | 通过事务来设置动画:
85 |
86 | ```objective-c
87 | [CATransaction begin]; //入栈
88 | //1.设置动画执行时间
89 | [CATransaction setAnimationDuration:3];
90 | //2.设置动画执行完毕后的操作:颜色渐变之后再旋转90度
91 | [CATransaction setCompletionBlock:^{
92 | CGAffineTransform transform = self.colorLayer.affineTransform;
93 | transform = CGAffineTransformRotate(transform, M_PI_2);
94 | self.colorLayer.affineTransform = transform;
95 | }];
96 |
97 | CGFloat red = arc4random() % 255 / 255.0;
98 | CGFloat green = arc4random() % 255 / 255.0;
99 | CGFloat blue = arc4random() % 255 / 255.0;
100 | UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
101 | _colorLayer.backgroundColor = randomColor.CGColor;
102 | [CATransaction commit]; //出栈
103 | ```
104 |
105 | ### 2. 图层行为
106 |
107 | 我们上述的实验对象是一个独立图层,如果直接对UIView或者CALayer关联的图层layer改变动画属性,这样是没有隐式动画效果的,这说明虽然Core Animation对所有的CALayer动画属性设置了隐式动画,但UIView把它关联的图层的这个特性给关闭了。 为了更好的理解中一点,我们需要知道隐式动画是如何实现的: 我们把改变属性时CALayer自动执行的动画称作行为,当CALayer的属性被修改时,它会调用-actionForKey:方法传递属性名称,我们可以找到这个方法的具体说明如下:
108 |
109 | ```objective-c
110 | /* Returns the action object associated with the event named by the
111 | * string 'event'. The default implementation searches for an action
112 | * object in the following places:
113 | *
114 | * 1. if defined, call the delegate method -actionForLayer:forKey:
115 | * 2. look in the layer's `actions' dictionary
116 | * 3. look in any `actions' dictionaries in the `style' hierarchy
117 | * 4. call +defaultActionForKey: on the layer's class
118 | *
119 | * If any of these steps results in a non-nil action object, the
120 | * following steps are ignored. If the final result is an instance of
121 | * NSNull, it is converted to `nil'. */
122 |
123 | - (nullable id)actionForKey:(NSString *)event;
124 | ```
125 |
126 | 翻译过来大概就是说:
127 |
128 | 1. 图层会首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法;如果有,就直接调用并返回结果。
129 | 2. 如果没有委托或者委托没有实现-actionForLayer:forKey方法,图层将会检查包含属性名称对应行为映射的actions字典
130 | 3. 如果actions字典没有包含对应的属性,图层接着在它的style字典里搜索属性名.
131 | 4. 最后,如果在style也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的+defaultActionForKey:方法
132 |
133 | 从流程上分析来看,经过一次完整的搜索动画之后,-actionForKey:要么返回空(这种情况不会有动画发生),要么返回遵循CAAction协议的对象(CALayer拿这个结果去对先前和当前的值做动画)。现在我们再来考虑UIKit是如何禁用隐式动画的: 每个UIView对它关联的图层都遵循了CALayerDelegate协议,并且实现了-actionForLayer:forKey方法。当不在一个动画块中修改动画属性时,UIView对所有图层行为都返回了nil,但是在动画Block范围就返回了非空值,下面通过一段代码来验证:
134 |
135 | ```objective-c
136 | @interface TestLayerAnimationVC ()
137 |
138 | @property (nonatomic,weak)IBOutlet UIView *layerView;
139 |
140 | @end
141 |
142 | - (void)viewDidLoad {
143 | [super viewDidLoad];
144 | //测试图层行为:UIKit默认关闭了自身关联图层的隐式动画
145 | NSLog(@"OutSide:%@",[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
146 |
147 | [UIView beginAnimations:nil context:nil];
148 | NSLog(@"InSide:%@",[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
149 | [UIView commitAnimations];
150 | }
151 |
152 | //打印:
153 | OutSide:
154 | InSide:
155 | ```
156 |
157 | 由此得出结论:当属性在动画块之外发生变化,UIView直接通过返回nil来禁用隐式动画。但是如果在动画块范围内,UIView则会根据动画具体类型返回响应的属性,
158 |
159 | ### 3. 关闭和开启隐式动画
160 |
161 | 当然,返回nil并不是禁用隐式动画的唯一方法,CATransaction也为我们提供了具体的方法,可以用来对所有属性打开或者关闭隐式动画,方法如下:
162 |
163 | ```javascript
164 | + (void)setDisableActions:(BOOL)flag;
165 | ```
166 |
167 | UIView关联的图层禁用了隐式动画,那么对这种图层做动画的方法有有了以下几种方式:
168 |
169 | 1. 使用UIView的动画函数(而不是依赖CATransaction)
170 | 2. 继承UIView,并覆盖-actionforLayer:forkey:方法
171 | 3. 直接创建显式动画
172 |
173 | 其实,对于单独存在的图层,我们也可以通过实现图层的-actionforLayer:forkey:方法,或者提供一个actions字典来控制隐式动画
174 |
175 | ### 4. 自定义图层行为
176 |
177 | 通过对事务和图层行为的了解,我们可以这样思考,图层行为其实是被Core Animation隐式调用的显式动画对象。我们可以发现改变隐式动画的这种图层行为有两种方式: 1.给layer设置自定义的actions字典 2.实现委托代理,返回遵循CAAction协议的动画对象 现在,我们尝试使用第一种方法来自定义图层行为,这里用到的是一个推进过渡的动画(也是遵循了CAAction的动画类),具体的代码如下:
178 |
179 | ```javascript
180 | @interface TestLayerAnimationVC ()
181 | @property (nonatomic,strong) CALayer *colorLayer;
182 | @end
183 |
184 | - (void)viewDidLoad {
185 | [super viewDidLoad];
186 |
187 | _colorLayer = [[CALayer alloc] init];
188 | _colorLayer.frame = CGRectMake(30, 30, kDeviceWidth - 60, 60);
189 | _colorLayer.backgroundColor = [UIColor orangeColor].CGColor;
190 | //自定义动画对象
191 | CATransition *transition = [CATransition animation];
192 | transition.type = kCATransitionPush;
193 | transition.subtype = kCATransitionFromLeft;
194 | _colorLayer.actions = @{@"backgroundColor":transition};
195 | [self.view.layer addSublayer:_colorLayer];
196 | }
197 |
198 | - (IBAction)changeColor:(UIButton *)sender{
199 | CGFloat red = arc4random() % 255 / 255.0;
200 | CGFloat green = arc4random() % 255 / 255.0;
201 | CGFloat blue = arc4random() % 255 / 255.0;
202 | UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
203 | _colorLayer.backgroundColor = randomColor.CGColor;
204 | }
205 | ```
206 |
207 |
208 |
209 | ## 5. 什么是离屏渲染
210 |
211 | [离屏渲染专题](https://github.com/colourful987/2020-Read-Record/tree/master/topics/离屏渲染专题)
212 |
213 | ## 6. imageNamed & imageWithContentsOfFile区别
214 |
215 | iOS加载本地图片有两种方式:imageNamed 和 imageWithContentOfFile 两种:
216 |
217 | #### imageNamed
218 |
219 | 前者Apple官方文档有说到:
220 |
221 | > This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method locates and loads the image data from disk or asset catelog, and then returns the resulting object. You can not assume that this method is thread safe.
222 |
223 | 首先从系统缓存中根据图片名称寻找图片,如果找到了就返回。如果没有在缓存中找到图片,该方法会从指定的文件中加载图片数据,并将其缓存起来,然后再把结果返回,下次再使用该名称图片的时候就省去了从硬盘中加载图片的过程。对于相同名称的图片,**系统只会把它Cache到内存一次,**如果相应的图片数据不存在,返回nil。
224 |
225 | 关于加载:`imageNamed` 方法可以加载 `Assets.xcassets` 和 bundle 中的图片。
226 |
227 | 关于缓存:加载到内存当中会一直存在内存当中,(图片)不会随着对象的销毁而销毁;加载进去图片后,占用的内存归系统管理,我们是无法管理的;相同的图片是不会重复加载的,加载到内存中占据的内存较大。
228 |
229 | ```objective-c
230 | UIImage *img = [UIImage imageNamed:@"pic"];
231 | ```
232 |
233 | #### imageWithContentsOfFile
234 |
235 | `imageWithContentsOfFile` 方法只是简单的加载图片,并不会将图片缓存起来,图像会被系统以数据方式加载到程序。
236 |
237 | 关于加载:`imageWithContentsOfFile` 只能加载 mainBundle 中图片。
238 |
239 | 关于缓存:加载到内存中占据的内存较小,相同的图片会被重复加载到内存当中,加载的图片会随着对象的销毁而销毁;
240 |
241 | #### 两者应用场景
242 |
243 | * 如果图片较小,并且使用频繁的图片使用 imageName:方法来加载
244 | * 如果图片较大,并且使用较少,使用imageWithContentOfFile:来加载。
245 | * 当你不需要重用该图像,或者你需要将图像以数据方式存储到数据库,又或者你要通过网络下载一个很大的图像时,使用 `imageWithContentsOfFile`;
246 | * 如果在程序中经常需要重用的图片,比如用于UITableView的图片,那么最好是选择imageNamed方法。这种方法可以节省出每次都从磁盘加载图片的时间;
247 |
248 | > * [《iOS之图片加载》](https://www.jianshu.com/p/ea2a2ba3cd97)
249 | > * [IOS如何选择图片加载方式:imageNamed和imageWithContentsOfFile的区别](https://blog.csdn.net/wzzvictory/article/details/9053813)
250 |
251 | ## 7. 多个相同的图片,会重复加载吗
252 |
253 | 答案见上
254 |
255 | ## 8. 图片是什么时候解码的,如何优化
256 |
257 | * [iOS 图片解码(decode)笔记](https://www.jianshu.com/p/4da6981a746c)
258 | * [探讨iOS 中图片的解压缩到渲染过程](https://www.jianshu.com/p/72dd074728d8)
259 |
260 | ## 9. 图片渲染怎么优化
261 |
262 | > 出自 swift.gg 文章 [图像渲染优化技巧](https://swift.gg/2019/11/01/image-resizing/)
263 |
264 | ####9.1 图片尺寸**明显大于** **`UIImageView`** 显示尺寸的场景
265 |
266 | 1. [绘制到 UIGraphicsImageRenderer 上](https://swift.gg/2019/11/01/image-resizing/#technique-1-drawing-to-a-uigraphicsimagerenderer)
267 |
268 | ```swift
269 | import UIKit
270 |
271 | // 技巧 #1
272 | func resizedImage(at url: URL, for size: CGSize) -> UIImage? {
273 | guard let image = UIImage(contentsOfFile: url.path) else {
274 | return nil
275 | }
276 |
277 | let renderer = UIGraphicsImageRenderer(size: size)
278 | return renderer.image { (context) in
279 | image.draw(in: CGRect(origin: .zero, size: size))
280 | }
281 | }
282 | ```
283 |
284 | [`UIGraphicsImageRenderer`](https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer) 是一项相对较新的技术,在 iOS 10 中被引入,用以取代旧版本的 `UIGraphicsBeginImageContextWithOptions` / `UIGraphicsEndImageContext` API。你通过指定以 `point` 计量的 `size` 创建了一个 `UIGraphicsImageRenderer`。`image` 方法带有一个闭包参数,返回的是一个经过闭包处理后的位图。最终,原始图像便会在缩小到指定的范围内绘制。
285 |
286 | > 在不改变图像原始纵横比(aspect ratio)的情况下,缩小图像原始的尺寸来显示通常很有用。[`AVMakeRect(aspectRatio:insideRect:)`](https://developer.apple.com/documentation/avfoundation/1390116-avmakerect) 是在 AVFoundation 框架中很方便的一个函数,负责帮你做如下的计算:
287 |
288 | ```
289 | import func AVFoundation.AVMakeRect
290 | let rect = AVMakeRect(aspectRatio: image.size, insideRect: imageView.bounds)
291 | ```
292 |
293 | 2. [绘制到 Core Graphics Context 上](https://swift.gg/2019/11/01/image-resizing/#technique-2-drawing-to-a-core-graphics-context)
294 |
295 | ```swift
296 | import UIKit
297 | import CoreGraphics
298 |
299 | // 技巧 #2
300 | func resizedImage(at url: URL, for size: CGSize) -> UIImage? {
301 | guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
302 | let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
303 | else {
304 | return nil
305 | }
306 |
307 | let context = CGContext(data: nil,
308 | width: Int(size.width),
309 | height: Int(size.height),
310 | bitsPerComponent: image.bitsPerComponent,
311 | bytesPerRow: image.bytesPerRow,
312 | space: image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!,
313 | bitmapInfo: image.bitmapInfo.rawValue)
314 | context?.interpolationQuality = .high
315 | context?.draw(image, in: CGRect(origin: .zero, size: size))
316 |
317 | guard let scaledImage = context?.makeImage() else { return nil }
318 |
319 | return UIImage(cgImage: scaledImage)
320 | }
321 | ```
322 |
323 |
324 |
325 | 3. [使用 Image I/O 创建缩略图像](https://swift.gg/2019/11/01/image-resizing/#technique-3-creating-a-thumbnail-with-image-io)
326 |
327 | Image I/O 是一个强大(却鲜有人知)的图像处理框架。抛开 Core Graphics 不说,它可以读写许多不同图像格式,访问图像的元数据,还有执行常规的图像处理操作。这个框架通过先进的缓存机制,提供了平台上最快的图片编码器和解码器,甚至可以增量加载图片。
328 |
329 | 这个重要的 `CGImageSourceCreateThumbnailAtIndex` 提供了一个带有许多不同配置选项的 API,比起在 Core Graphics 中等价的处理操作要简洁得多:
330 |
331 | 4. [使用 Core Image 进行 Lanczos 重采样](https://swift.gg/2019/11/01/image-resizing/#technique-4-lanczos-resampling-with-core-image)
332 |
333 | Core Image 内置了 [Lanczos 重采样(resampling)](https://en.wikipedia.org/wiki/Lanczos_resampling) 功能,它是以 `CILanczosScaleTransform` 的同名滤镜命名的。虽然可以说它是在 UIKit 层级之上的 API,但无处不在的 key-value 编写方式导致它使用起来很不方便。
334 |
335 | 即便如此,它的处理模式还是一致的。
336 |
337 | 创建转换滤镜,对滤镜进行配置,最后渲染输出图像,这样的步骤和其他任何 Core Image 的工作流没什么不同。
338 |
339 | ```
340 | import UIKit
341 | import CoreImage
342 |
343 | let sharedContext = CIContext(options: [.useSoftwareRenderer : false])
344 |
345 | // 技巧 #4
346 | func resizedImage(at url: URL, scale: CGFloat, aspectRatio: CGFloat) -> UIImage? {
347 | guard let image = CIImage(contentsOf: url) else {
348 | return nil
349 | }
350 |
351 | let filter = CIFilter(name: "CILanczosScaleTransform")
352 | filter?.setValue(image, forKey: kCIInputImageKey)
353 | filter?.setValue(scale, forKey: kCIInputScaleKey)
354 | filter?.setValue(aspectRatio, forKey: kCIInputAspectRatioKey)
355 |
356 | guard let outputCIImage = filter?.outputImage,
357 | let outputCGImage = sharedContext.createCGImage(outputCIImage,
358 | from: outputCIImage.extent)
359 | else {
360 | return nil
361 | }
362 |
363 | return UIImage(cgImage: outputCGImage)
364 | }
365 | ```
366 |
367 | 5. [使用 vImage 优化图片渲染](https://swift.gg/2019/11/01/image-resizing/#technique-5-image-scaling-with-vimage)
368 |
369 | 最后一个了,它是古老的 [Accelerate 框架](https://developer.apple.com/documentation/accelerate) —— 更具体点来说,它是 `vImage` 的图像处理子框架。
370 |
371 | vImage 附带有 [一些不同的功能](https://developer.apple.com/documentation/accelerate/vimage/vimage_operations/image_scaling),可以用来裁剪图像缓冲区大小。这些底层 API 保证了高性能同时低能耗,但会导致你对缓冲区的管理操作增加(更不用说要编写更多的代码了)
372 |
373 | - **UIKit**, **Core** **Graphics**, 和 **Image** **I/O** 都能很好地用于大部分图片的优化操作。如果(在 iOS 平台,至少)要选择一个的话,`UIGraphicsImageRenderer` 是你最佳的选择。
374 | - **Core** **Image** 在图像优化渲染操作方面性能表现优越。实际上,根据 Apple 官方 [*Core* *Image* *编程规范中的性能最佳实践单元*](https://developer.apple.com/library/mac/documentation/graphicsimaging/Conceptual/CoreImaging/ci_performance/ci_performance.html#//apple_ref/doc/uid/TP30001185-CH10-SW1),你应该使用 Core Graphics 或 Image I/O 对图像进行裁剪和下采样,而不是用 Core Image。
375 |
376 | ## 10. 如果GPU的刷新率超过了iOS屏幕60Hz刷新率是什么现象,怎么解决
--------------------------------------------------------------------------------
/chapters/1_runloop.md:
--------------------------------------------------------------------------------
1 | # Runloop
2 |
3 | `runloop`对于一个标准的iOS开发来说都不陌生,应该说熟悉`runloop`是标配,下面就随便列几个典型问题吧
4 |
5 | ### app如何接收到触摸事件的
6 |
7 | > [iOS Touch Event from the inside out](https://www.jianshu.com/p/70ba981317b6)
8 |
9 | #### 1 Touch Event 的生命周期
10 |
11 | ##### 1.1 物理层面事件的生成
12 |
13 | iPhone 采用电容触摸传感器,利用人体的电流感应工作,由一块四层复合玻璃屏的内表面和夹层各涂有一层导电层,最外层是一层矽土玻璃保护层。当我们手指触摸感应屏的时候,人体的电场让手指和触摸屏之间形成一个耦合电容,对高频电流来说电容是直接导体。于是手指从接触点吸走一个很小的电流,这个电流分从触摸屏的四脚上的电极流出,并且流经这四个电极的电流和手指到四个电极的距离成正比。控制器通过对这四个电流的比例做精确的计算,得出触摸点的距离。
14 |
15 | > 更多文献:
16 | > * [OLED发光原理、面板结构及OLED关键技术深度解析](https://www.hangjianet.com/v5/topicDetail?id=15422809007930000)
17 | > * [光学触摸屏原理](http://www.51touch.com/technology/principle/201308/29-24678.html)
18 | > * [电容触摸屏原理](http://www.51touch.com/technology/principle/201308/14-24323.html)
19 | > * [电阻式触摸屏原理(FLASH演示版)](http://www.51touch.com/technology/principle/201309/04-24800.html)
20 | > * [iPhone这十年在传感器上的演进](https://zhuanlan.zhihu.com/p/22677100)
21 |
22 | ##### 1.2 iOS 操作系统下封装和分发事件
23 |
24 | iOS 操作系统看做是一个处理复杂逻辑的程序,不同进程之间彼此通信采用消息发送方式,即 IPC (Inter-Process Communication)。现在继续说上面电容触摸传感器产生的 Touch Event,它将交由 IOKit.framework 处理封装成 IOHIDEvent 对象;下一步很自然想到通过消息发送方式将事件传递出去,至于发送给谁,何时发送等一系列的判断逻辑又该交由谁处理呢?
25 |
26 | 答案是 **SpringBoard.app**,它接收到封装好的 **IOHIDEvent** 对象,经过逻辑判断后做进一步的调度分发。例如,它会判断前台是否运行有应用程序,有则将封装好的事件采用 mach port 机制传递给该应用的主线程。
27 |
28 | Port 机制在 IPC 中的应用是 Mach 与其他传统内核的区别之一,在 Mach 中,用户进程调用内核交由 IPC 系统。与直接系统调用不同,用户进程首先向内核申请一个 port 的访问许可;然后利用 IPC 机制向这个 port 发送消息,本质还是系统调用,而处理是交由其他进程完成的。
29 |
30 | ##### 1.3 IOHIDEvent -> UIEvent
31 |
32 | 应用程序主线程的 runloop 申请了一个 mach port 用于监听 `IOHIDEvent` 的 `Source1` 事件,回调方法是 `__IOHIDEventSystemClientQueueCallback()`,内部又进一步分发 `Source0` 事件,而 `Source0`事件都是自定义的,非基于端口 port,包括触摸,滚动,selector选择器事件,它的回调方法是 `__UIApplicationHandleEventQueue()`,将接收到的 `IOHIDEvent` 事件对象封装成我们熟悉的 `UIEvent` 事件;然后调用 `UIApplication` 实例对象的 `sendEvent:` 方法,将 `UIEvent` 传递给 `UIWindow` 做一些逻辑判断工作:比如触摸事件产生于哪些视图上,有可能有多个,那又要确定哪个是最佳选项呢? 等等一系列操作。这里先按下不表。
33 |
34 | ##### 1.4 Hit-Testing 寻找最佳响应者
35 |
36 | `Source0` 回调中将封装好的触摸事件 UIEvent(里面有多个UITouch 即手势点击对象),传递给视图 `UIWindow`,其目的在于找到最佳响应者,这个过程称之为 `Hit-Testing`,字面上理解:hit 即触碰了屏幕某块区域,这个区域可能有多个视图叠加而成,那么这个触摸讲道理响应者有多个喽,那么“最佳”又该如何评判?这里要牢记几个规则:
37 |
38 | 1. 事件是自下而上传递,即 `UIApplication -> UIWindow -> 子视图 -> ...->子视图中的子视图`;
39 | 2. 后加的视图响应程度更高,即更靠近我们的视图;
40 | 3. 如果某个视图不想响应,则传递给比它响应程度稍低一级的视图,若能响应,你还得继续往下传递,若某个视图能响应了,但是没有子视图 它就是最佳响应者。
41 | 4. 寻找最佳响应者的过程中, UIEvent 中的 UITouch 会不断打上标签:比如 `HitTest View` 是哪个,`superview` 是哪个?关联了什么 `Gesture Recognizer`?
42 |
43 | 那么如何判定视图为响应者?由于 OC 中的类都继承自 `NSObject` ,因此默认判断逻辑已经在`hitTest:withEvent`方法中实现,它有两个作用: 1.询问当前视图是否能够响应事件 2.事件传递的桥梁。若当前视图无法响应事件,返回 nil 。代码如下:
44 |
45 | ```swift
46 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
47 | // 1. 前置条件要满足
48 | if (self.userInteractionEnabled == NO ||
49 | self.hidden == YES ||
50 | self.alpha <= 0.01) return nil;
51 |
52 | // 2. 判断点是否在视图内部 这是最起码的 note point 是在当前视图坐标系的点位置
53 | if ([self pointInside:point withEvent:event] == NO) return nil;
54 |
55 | // 3. 现在起码能确定当前视图能够是响应者 接下去询问子视图
56 | int count = (int)self.subviews.count;
57 | for (int i = count - 1; i >= 0; i--)
58 | {
59 | // 子视图
60 | UIView *childView = self.subviews[i];
61 |
62 | // 点需要先转换坐标系
63 | CGPoint childP = [self convertPoint:point toView:childView];
64 | // 子视图开始询问
65 | UIView *fitView = [childView hitTest:childP withEvent:event];
66 | if (fitView)
67 | {
68 | return fitView;
69 | }
70 | }
71 |
72 | return self;
73 | }
74 | ```
75 |
76 | 1. 首先满足几个前置条件,可交互`userInteractionEnabled=YES`;没有隐藏`self.hidden == NO`;非透明 `self.alpha <= 0.01` ———— 注意一旦不满足上述三个条件,当前视图及其子视图都不能作为响应者,Hit-Testing 判定也止步于此
77 | 2. 接着判断触摸点是否在视图内部 ———— 这个是最基本,无可厚非的判定规则
78 | 3. 此时已经能够说当前视图为响应者,但是不是**最佳**还不能下定论,因此需要进一步传递给子视图判定;注意 `pointInside` 也是默认实现的。
79 |
80 | ##### 1.5 UIResponder Chain 响应链
81 |
82 | `Hit-Testing` 过程中我们无法确定当前视图是否为“最佳”响应者,此时自然还不能处理事件。因此处理机制应该是找到所有响应者以及最佳响应者(**自下而上**),由它们构成了一条响应链;接着将事件沿着响应链**自上而下**传递下去 ——最顶端自然是最佳响应者,事件除了被响应者消耗,还能被手势识别器或是 `target-action` 模式捕获并消耗。有时候,最佳响应者可能对处理 `Event` “毫无兴趣”,它们不会重写 `touchBegan` `touchesMove`..等四个方法;也不会添加任何手势;但如果是 `control(控件)` 比如 UIButton ,那么事件还是会被消耗掉的。
83 |
84 | ##### 1.6 UITouch 、 UIEvent 、UIResponder
85 |
86 | IOHIDEvent 前面说到是在 IOKit.framwork 中生成的然后经过一系列的分别才到达前台应用,然后应用主线程runloop处理source1回调中又进行source0事件分发,这里有个封装UIEvent的过程,那么 UITouch 呢? 是不是也是那时候呢?换种思路:一个手指一次触摸屏幕 生成一个 UITouch 对象,内部应该开始进行识别了,因为可能是多个 Touch,并且触摸的先后顺序也不同,这样识别出来的 UIEvent 也不同。所以 UIEvent 对象中包含了触发该事件的触摸对象的集合,通过 allTouches 属性获取。
87 |
88 | 每个响应者都派生自 UIResponder 类,本身具有相应事件的能力,响应者默认实现 `touchesBegin` `touchesMove` `touchesEnded` `touchesCancelled`四个方法。
89 |
90 | 事件在未截断的情况下沿着响应链传递给最佳响应者,伪代码如下:
91 |
92 | ```swift
93 | 0 - [AView touchesBegan:withEvent
94 | 1 - [UIWindow _sendTouchesForEvent]
95 | 2 - [UIWindow sendEvent]
96 | 3 - [UIApplication sendEvent]
97 | 4 __dispatchPreprocessEventFromEventQueue
98 | 5 __handleEventQueueInternal
99 | 6 _CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION_
100 | 7 _CFRunLOOPDoSource0
101 | 8 _CFRunLOOPDoSources0
102 | 9 _CFRunLoopRun
103 | 10 _CFRunLoopRunSpecific
104 | 11 GSEventRunModal
105 | 12 UIApplication
106 | 13 main
107 | 14 start
108 |
109 | // UIApplication.m
110 | - (void)sendEvent {
111 | [window sendEvent];
112 | }
113 |
114 | // UIWindow.m
115 | - (void)sendEvent{
116 | [self _sendTouchesForEvent];
117 | }
118 |
119 | - (void)_sendTouchesForEvent{
120 | //find AView Because we know hitTest View
121 | [AView touchesBegan:withEvent];
122 | }
123 | ```
124 |
125 | ### 为什么只有主线程的`runloop`是开启的
126 |
127 | > 这个问题很奇葩,要回答的会就是其他线程并没有调用 `NSRunLoop *runloop = [NSRunLoop currentRunLoop]`,其他还能说啥呢?更多源码分析见网上教程。
128 |
129 | ```c
130 | CFRunLoopRef CFRunLoopGetCurrent(void) {
131 | CHECK_FOR_FORK();
132 | CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
133 | if (rl) return rl;
134 | return _CFRunLoopGet0(pthread_self());
135 | }
136 | ```
137 |
138 | ### 为什么只在主线程刷新UI
139 |
140 | > 引用掘金文章[《iOS拾遗——为什么必须在主线程操作UI》](https://juejin.im/post/5c406d97e51d4552475fe178)。
141 |
142 | UIKit并不是一个 **线程安全** 的类,UI操作涉及到渲染访问各种View对象的属性,如果异步操作下会存在读写问题,而为其加锁则会耗费大量资源并拖慢运行速度。另一方面因为整个程序的起点`UIApplication`是在主线程进行初始化,所有的用户事件都是在主线程上进行传递(如点击、拖动),所以 view 只能在主线程上才能对事件进行响应。而在渲染方面由于图像的渲染需要以60帧的刷新率在屏幕上 **同时** 更新,在非主线程异步化的情况下无法确定这个处理过程能够实现同步更新。
143 |
144 | ### `PerformSelector`和`runloop`的关系
145 |
146 | perform 有几种方式,如 `[self performSelector:@selector(perform) withObject:nil]` 同步执行的,等同于 objc_msgSend 方法执行调用方法。
147 |
148 | 而`[self performSelector:@selector(perform) withObject:nil afterDelay:0]` 则是会在当前 runloop 中起一个 timer,如果当前线程没有起runloop(也就是上面说的没有调用 `[NSRunLoop currentRunLoop]` 方法的话),则不会有输出
149 |
150 | ```objective-c
151 | - (IBAction)test01:(id)sender {
152 | dispatch_async(self.concurrencyQueue2, ^{
153 | NSLog(@"[1] 线程:%@",[NSThread currentThread]);
154 | // 当前线程没有开启 runloop 所以改方法是没办法执行的
155 | [self performSelector:@selector(perform) withObject:nil afterDelay:0];
156 | NSLog(@"[3]");
157 | });
158 | }
159 |
160 | - (void)perform {
161 | NSLog(@"[2] 线程:%@",[NSThread currentThread]);
162 | }
163 | ```
164 |
165 | > This method sets up a timer to perform the `aSelector` message on the current thread’s run loop. The timer is configured to run in the default mode (`NSDefaultRunLoopMode`). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.
166 | >
167 | > If you want the message to be dequeued when the run loop is in a mode other than the default mode, use the [performSelector:withObject:afterDelay:inModes:](apple-reference-documentation://hcVXEPtYGA)method instead. If you are not sure whether the current thread is the main thread, you can use the [performSelectorOnMainThread:withObject:waitUntilDone:](apple-reference-documentation://hcChQUeJuZ) or [performSelectorOnMainThread:withObject:waitUntilDone:modes:](apple-reference-documentation://hcNxrNoLOP) method to guarantee that your selector executes on the main thread. To cancel a queued message, use the [cancelPreviousPerformRequestsWithTarget:](apple-reference-documentation://hcVCAfekYt) or [cancelPreviousPerformRequestsWithTarget:selector:object:](apple-reference-documentation://hc3jvcW4n6) method.
168 |
169 | 修改代码:
170 |
171 | ```objective-c
172 | - (IBAction)test01:(id)sender {
173 | dispatch_async(self.concurrencyQueue2, ^{
174 | NSLog(@"[1] 线程:%@",[NSThread currentThread]);
175 | // 当前线程没有开启 runloop 所以改方法是没办法执行的
176 | NSRunLoop *runloop = [NSRunLoop currentRunLoop];
177 | [self performSelector:@selector(perform) withObject:nil afterDelay:0];// 这里打断点,po runloop ,查看执行这条语句后runloop添加了啥
178 | [runloop run];
179 | NSLog(@"[3]");
180 | });
181 | }
182 | ```
183 |
184 | 输出如下内容,注意 `[3]` 被输出了,所以说 runloop 并没有被起来,至于原因见下。
185 |
186 | ```objective-c
187 | 2020-04-07 00:16:43.146357+0800 04-06-performSelector-RunLoop[15486:585028] [1] 线程:{number = 6, name = (null)}
188 | 2020-04-07 00:16:43.146722+0800 04-06-performSelector-RunLoop[15486:585028] [2] 线程:{number = 6, name = (null)}
189 | 2020-04-07 00:16:43.146932+0800 04-06-performSelector-RunLoop[15486:585028] [3]
190 | ```
191 |
192 | 所以为了保活可以加个定时器,repeat=YES 的那种。(ps: repeat=NO 的那种复习下 timer 的循环引用会自动打破!)
193 |
194 | ```objective-c
195 | - (IBAction)test01:(id)sender {
196 | dispatch_async(self.concurrencyQueue2, ^{
197 | NSLog(@"[1] 线程:%@",[NSThread currentThread]);
198 | NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
199 | NSLog(@"timer 定时任务");
200 | }];
201 |
202 | // 当前线程没有开启 runloop 所以改方法是没办法执行的
203 | NSRunLoop *runloop = [NSRunLoop currentRunLoop];
204 | [runloop addTimer:timer forMode:NSDefaultRunLoopMode];
205 | [self performSelector:@selector(perform) withObject:nil afterDelay:0];
206 | [runloop run];
207 | NSLog(@"[3]");
208 | });
209 | }
210 | ```
211 |
212 | RunLoop 添加观察者代码:
213 |
214 | ```objective-c
215 | /*
216 | kCFRunLoopEntry = (1UL << 0),1
217 | kCFRunLoopBeforeTimers = (1UL << 1),2
218 | kCFRunLoopBeforeSources = (1UL << 2), 4
219 | kCFRunLoopBeforeWaiting = (1UL << 5), 32
220 | kCFRunLoopAfterWaiting = (1UL << 6), 64
221 | kCFRunLoopExit = (1UL << 7),128
222 | kCFRunLoopAllActivities = 0x0FFFFFFFU
223 | */
224 | static void addRunLoopObserver() {
225 | CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
226 | switch (activity) {
227 | case kCFRunLoopEntry: {
228 | NSLog(@"即将进入 runloop");
229 | }
230 | break;
231 | case kCFRunLoopBeforeTimers: {
232 | NSLog(@"定时器 timers 之前");
233 | }
234 | break;
235 | case kCFRunLoopBeforeSources: {
236 | NSLog(@"Sources 事件前");
237 | }
238 | break;
239 | case kCFRunLoopBeforeWaiting: {
240 | NSLog(@"RunLoop 即将进入休眠");
241 | }
242 | break;
243 | case kCFRunLoopAfterWaiting: {
244 | NSLog(@"RunLoop 唤醒后");
245 | }
246 | break;
247 | case kCFRunLoopExit: {
248 | NSLog(@"退出");
249 | }
250 | break;
251 | default:
252 | break;
253 | }
254 | });
255 | // 这里 CFRunLoopGetCurrent 创建了当前线程的 runloop 对象
256 | CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
257 | CFRelease(observer);
258 | }
259 |
260 | - (IBAction)test01:(id)sender {
261 | dispatch_async(self.concurrencyQueue2, ^{
262 | NSLog(@"[1] 线程:%@",[NSThread currentThread]);
263 | NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) {
264 | NSLog(@"timer 定时任务");
265 | }];
266 | addRunLoopObserver();
267 | // 当前线程没有开启 runloop 所以改方法是没办法执行的
268 | NSRunLoop *runloop = [NSRunLoop currentRunLoop];
269 | [runloop addTimer:timer forMode:NSDefaultRunLoopMode];
270 | [self performSelector:@selector(perform) withObject:nil afterDelay:0];
271 | [runloop run];
272 | NSLog(@"[3]");
273 | });
274 | }
275 | ```
276 |
277 | > 参考文献:
278 | >
279 | > * [Runloop与performSelector](https://juejin.im/post/5c70b391e51d451646267db1)
280 |
281 | ### 如何使线程保活
282 |
283 | > 线程保活就是不让线程退出,所以往简单说就是搞个 “while(1)” 自己实现一套处理流程,事件派发就可以了;但 iOS 中有 runloop,所以我们就无须大费周章。 TODO: C 语言嵌入式如何实现线程池和保活。
284 |
285 | runloop 线程保活前提就是有事情要处理,这里指 timer,source0,source1 事件。
286 |
287 | 所以有如下几种方式,方式一:
288 |
289 | ```objective-c
290 | NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
291 | NSLog(@"timer 定时任务");
292 | }];
293 | NSRunLoop *runloop = [NSRunLoop currentRunLoop];
294 | [runloop addTimer:timer forMode:NSDefaultRunLoopMode];
295 | [runloop run];
296 | ```
297 |
298 | 方式二:
299 |
300 | ```objective-c
301 | NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
302 | [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
303 | [runLoop run];
304 | ```
305 |
306 | 方式三:
307 |
308 | ```objectivec
309 | - (IBAction)testRunLoopKeepAlive:(id)sender {
310 | self.myThread = [[NSThread alloc] initWithTarget:self selector:@selector(start) object:nil];
311 | [self.myThread start];
312 | }
313 |
314 | - (void)start {
315 | self.finished = NO;
316 | do {
317 | // Runs the loop until the specified date, during which time it processes data from all attached input sources.
318 | [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
319 | } while (!self.finished);
320 | }
321 | - (IBAction)closeRunloop:(id)sender {
322 | self.finished = YES;
323 | }
324 |
325 | - (IBAction)executeTask:(id)sender {
326 | [self performSelector:@selector(doTask) onThread:self.myThread withObject:nil waitUntilDone:NO];
327 | }
328 |
329 | - (void)doTask {
330 | NSLog(@"执行任务在线程:%@",[NSThread currentThread]);
331 | }
332 | ```
333 |
334 | > If no input sources or timers are attached to the run loop, this method exits immediately and returns `NO`; otherwise, it returns after either the first input source is processed or `limitDate` is reached. Manually removing all known input sources and timers from the run loop does not guarantee that the run loop will exit immediately. macOS may install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.
335 |
336 | 所以上面的方式并非完美,只要没有源,runloop 直接就被退出了,但是因为包了一个while (!self.finished),所以相当于退出->起->退出-> 起。
337 |
338 | > Note: 如果runloop中没有处理事件,这里一直会退出然后起runloop,就算设置了 `[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];` 也没用,但是执行过一次 `[self performSelector:@selector(doTask) onThread:self.myThread withObject:nil waitUntilDone:NO]`,那么程序就卡在 `[NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]` 这一行了。
339 | >
340 | > TODO: 看下源码实现。
--------------------------------------------------------------------------------
/subjects/runtime/block.md:
--------------------------------------------------------------------------------
1 | # Block 底层原理和 `_block` 关键字作用和原理
2 | ## 1 Block 基础知识
3 |
4 | ### 1.1 `block`的内部实现,结构体是什么样的
5 |
6 | block 也是一个对象,一个捕获了上下文以及函数指针的结构体,数据结构上表现为 Imp 结构体 和 Desc 结构体(比如 block 入参类型),两个结构体之后跟着捕获的变量,用 `clang -rewrite-objc` 命令将 oc 代码重写成 c++:
7 |
8 | ```c++
9 | struct __block_impl {
10 | void *isa;
11 | int Flags;
12 | int Reserved;
13 | void *FuncPtr;
14 | };
15 |
16 | struct __main_block_impl_0 {
17 | struct __block_impl impl;
18 | struct __main_block_desc_0* Desc;
19 | __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
20 | impl.isa = &_NSConcreteStackBlock;
21 | impl.Flags = flags;
22 | impl.FuncPtr = fp;
23 | Desc = desc;
24 | }
25 | };
26 | ```
27 |
28 | > 实际上 runtime 源码定义的 block 数据类型和上面还是有一定出路的,建议以下面的数据结构为准,另外强烈建议看下文中的《Block 原理探究代码篇》
29 |
30 | ```c
31 | typedef NS_OPTIONS(int,PTBlockFlags) {
32 | PTBlockFlagsHasCopyDisposeHelpers = (1 << 25),
33 | PTBlockFlagsHasSignature = (1 << 30)
34 | };
35 | typedef struct PTBlock {
36 | __unused Class isa;
37 | PTBlockFlags flags;
38 | __unused int reserved;
39 | void (__unused *invoke)(struct PTBlock *block, ...);
40 | struct {
41 | unsigned long int reserved;
42 | unsigned long int size;
43 | // requires PTBlockFlagsHasCopyDisposeHelpers
44 | void (*copy)(void *dst, const void *src);
45 | void (*dispose)(const void *);
46 | // requires PTBlockFlagsHasSignature
47 | const char *signature;
48 | const char *layout;
49 | } *descriptor;
50 | // imported variables
51 | // Block 捕获的实例变量都在次
52 | } *PTBlockRef;
53 |
54 | typedef struct PTBlock_byref {
55 | void *isa;
56 | struct PTBlock_byref *forwarding;
57 | volatile int flags; // contains ref count
58 | unsigned int size;
59 | // 下面两个函数指针是不定的 要根据flags来
60 | // void (*byref_keep)(struct PTBlock_byref *dst, struct PTBlock_byref *src);
61 | // void (*byref_destroy)(struct PTBlock_byref *);
62 | // long shared[0];
63 | } *PTBlock_byref_Ref;
64 | ```
65 |
66 | ### 1.2 block是类吗,有哪些类型
67 |
68 | block 有三种类型:`_NSConcreteGlobalBlock`、`_NSConcreteStackBlock`、`_NSConcreteMallocBlock`,根据Block对象创建时所处数据区不同而进行区别。
69 |
70 | 1. 栈上 Block,引用了栈上变量,生命周期由系统控制的,一旦所属作用域结束,就被系统销毁了,ARC 下由于默认是 `_strong` 属性,所以打印的可能都是 `_NSConcreteMallocBlock`,**这里使用 unretained** 关键字去修饰下就行了,或者 MRC 下去验证
71 | 2. 堆上 Block,使用 copy 或者 strong(ARC)下就从栈Block 拷贝到堆上;
72 | 3. 全局 Block,未引用任何栈上变量时就是全局Block;
73 |
74 | ### 1.3 一个`int`变量被 `__block` 修饰与否的区别?block的变量截获
75 |
76 | > 关于 `__block` 建议看 《Block 原理探究代码篇》
77 |
78 | 值拷贝和指针拷贝,`__block` 修饰的话允许在 block 内部修改变量,因为传入的是 int变量的指针。
79 |
80 | 外部变量有四种类型:自动变量、静态变量、静态全局变量、全局变量。(**ps: 面试常问**)
81 |
82 | 全局变量和静态全局变量在 block 中是直接引用的,不需要通过结构去传入指针;
83 |
84 | 函数/方法中的 static 静态变量是直接在block中保存了指针,如下测试代码:
85 |
86 | ```c++
87 | int a = 1;
88 | static int b = 2;
89 |
90 | int main(int argc, const char * argv[]) {
91 |
92 | int c = 3;
93 | static int d = 4;
94 | NSMutableString *str = [[NSMutableString alloc]initWithString:@"hello"];
95 | void (^blk)(void) = ^{
96 | a++;
97 | b++;
98 | d++;
99 | [str appendString:@"world"];
100 | NSLog(@"1----------- a = %d,b = %d,c = %d,d = %d,str = %@",a,b,c,d,str);
101 | };
102 |
103 | a++;
104 | b++;
105 | c++;
106 | d++;
107 | str = [[NSMutableString alloc]initWithString:@"haha"];
108 | NSLog(@"2----------- a = %d,b = %d,c = %d,d = %d,str = %@",a,b,c,d,str);
109 | blk();
110 |
111 | return 0;
112 | }
113 | ```
114 |
115 | 转成 c++ 代码:
116 |
117 | ```objective-c
118 | struct __block_impl {
119 | void *isa;
120 | int Flags;
121 | int Reserved;
122 | void *FuncPtr;
123 | };
124 |
125 | int a = 1; // <------------------- NOTE
126 | static int b = 2; // <------------------- NOTE
127 | struct __main_block_impl_0 {
128 | struct __block_impl impl;
129 | struct __main_block_desc_0* Desc;
130 | int *d; // <------------------- NOTE
131 | NSMutableString *str; // <------------------- NOTE
132 | int c; // <------------------- NOTE
133 | __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_d, NSMutableString *_str, int _c, int flags=0) : d(_d), str(_str), c(_c) {
134 | impl.isa = &_NSConcreteStackBlock;
135 | impl.Flags = flags;
136 | impl.FuncPtr = fp;
137 | Desc = desc;
138 | }
139 | };
140 |
141 | static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
142 | int *d = __cself->d; // bound by copy
143 | NSMutableString *str = __cself->str; // bound by copy
144 | int c = __cself->c; // bound by copy
145 |
146 | a++;
147 | b++;
148 | (*d)++;
149 | ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_1);
150 | NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_2,a,b,c,(*d),str);
151 | }
152 | static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
153 |
154 | static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
155 |
156 | static struct __main_block_desc_0 {
157 | size_t reserved;
158 | size_t Block_size;
159 | void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
160 | void (*dispose)(struct __main_block_impl_0*);
161 | } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
162 |
163 | int main(int argc, const char * argv[]) {
164 | int c = 3;
165 | static int d = 4;
166 | NSMutableString *str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_0);
167 | void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &d, str, c, 570425344));
168 |
169 | a++;
170 | b++;
171 | c++;
172 | d++;
173 | str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_3);
174 | NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_4,a,b,c,d,str);
175 | ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
176 |
177 | return 0;
178 | }
179 | ```
180 |
181 | ### 1.4 `block`在修改`NSMutableArray`,需不需要添加`__block`
182 |
183 | 不需要,本身 block 内部就捕获了 NSMutableArray 指针,除非你要修改指针指向的对象,而这里明显只是修改内存数据,这个可以类比 NSMutableString。
184 |
185 | ### 1.5 怎么进行内存管理的
186 |
187 | `static void *_Block_copy_internal(const void *arg, const int flags)` 和 `void _Block_release(void *arg) `
188 |
189 | > 推荐[iOS Block原理探究以及循环引用的问题](https://www.jianshu.com/p/9ff40ea1cee5) 一文。
190 |
191 | ### 1.6 `block`可以用`strong`修饰吗
192 |
193 | ARC 貌似是可以的, strong 和 copy 的操作都是将栈上block 拷贝到堆上。TODO:确认下。
194 |
195 | ### 1.7 解决循环引用时为什么要用`__strong、__weak`修饰
196 |
197 | `__weak` 就是为了避免 retainCycle,而block 内部 `__strong` 则是在作用域 retain 持有当前对象做一些操作,结束后会释放掉它。
198 |
199 | ### 1.8 `block`发生`copy`时机
200 |
201 | block 从栈上拷贝到堆上几种情况:
202 |
203 | * 调用Block的copy方法
204 |
205 | * 将Block作为函数返回值时
206 |
207 | * 将Block赋值给__strong修饰的变量或Block类型成员变量时
208 |
209 | * 向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时
210 |
211 | ### 1.9 `Block`访问对象类型的`auto变量`时,在`ARC和MRC`下有什么区别
212 |
213 | ## 2 Block 原理探究代码篇
214 |
215 | 首先明确 Block 底层数据结构,之后所有的 demos 都基于此来学习知识点:
216 |
217 | ```c
218 | typedef NS_OPTIONS(int,PTBlockFlags) {
219 | PTBlockFlagsHasCopyDisposeHelpers = (1 << 25),
220 | PTBlockFlagsHasSignature = (1 << 30)
221 | };
222 | typedef struct PTBlock {
223 | __unused Class isa;
224 | PTBlockFlags flags;
225 | __unused int reserved;
226 | void (__unused *invoke)(struct PTBlock *block, ...);
227 | struct {
228 | unsigned long int reserved;
229 | unsigned long int size;
230 | // requires PTBlockFlagsHasCopyDisposeHelpers
231 | void (*copy)(void *dst, const void *src);
232 | void (*dispose)(const void *);
233 | // requires PTBlockFlagsHasSignature
234 | const char *signature;
235 | const char *layout;
236 | } *descriptor;
237 | // imported variables
238 | // Block 捕获的实例变量都在次
239 | } *PTBlockRef;
240 |
241 | typedef struct PTBlock_byref {
242 | void *isa;
243 | struct PTBlock_byref *forwarding;
244 | volatile int flags; // contains ref count
245 | unsigned int size;
246 | // 下面两个函数指针是不定的 要根据flags来
247 | // void (*byref_keep)(struct PTBlock_byref *dst, struct PTBlock_byref *src);
248 | // void (*byref_destroy)(struct PTBlock_byref *);
249 | // long shared[0];
250 | } *PTBlock_byref_Ref;
251 | ```
252 |
253 | ### 2.1 调用 block
254 |
255 | ```c
256 | void (^blk)(void) = ^{
257 | NSLog(@"hello world");
258 | };
259 | PTBlockRef block = (__bridge PTBlockRef)blk;
260 | block->invoke(block);
261 | ```
262 |
263 | ### 2.2 block 函数签名
264 |
265 | ```c
266 | void (^blk)(int, short, NSString *) = ^(int a, short b, NSString *str){
267 | NSLog(@"a:%d b:%d str:%@",a,b,str);
268 | };
269 | PTBlockRef block = (__bridge PTBlockRef)blk;
270 | if (block->flags & PTBlockFlagsHasSignature) {
271 | void *desc = block->descriptor;
272 | desc += 2 * sizeof(unsigned long int);
273 | if (block->flags & PTBlockFlagsHasCopyDisposeHelpers) {
274 | desc += 2 * sizeof(void *);
275 | }
276 |
277 | const char *signature = (*(const char **)desc);
278 | NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:signature];
279 | NSLog(@"方法 signature:%s",signature);
280 | }
281 |
282 | // 打印内容如下:
283 | // v24 @?0 i8 s12 @"NSString"16
284 | // 其中 ? 是 An unknown type (among other things, this code is used for function pointers)
285 | ```
286 |
287 | ### 2.3 block 捕获栈上局部变量
288 |
289 | 捕获的变量都会按照顺序放置在 `PTBlock` 结构体后面,如此看来就是个变长结构体。
290 |
291 | 也就是说我们可以通过如下方式知道 block 捕获了哪些外部变量(全局变量除外)。
292 |
293 | ```c
294 | int a = 0x11223344;
295 | int b = 0x55667788;
296 | NSString *str = @"pmst";
297 | void (^blk)(void) = ^{
298 | NSLog(@"a:%d b:%d str:%@",a,b, str);
299 | };
300 | PTBlockRef block = (__bridge PTBlockRef)blk;
301 | void *pt = (void *)block + sizeof(struct PTBlock);
302 | long long *ppt = pt;
303 | NSString *str_ref = (__bridge id)((void *)(*ppt));
304 | int *a_ref = pt + sizeof(NSString *);
305 | int *b_ref = pt + sizeof(NSString *) + sizeof(int);
306 |
307 | NSLog(@"a:0x%x b:0x%x str:%@",*a_ref, *b_ref, str_ref);
308 | ```
309 |
310 | > TODO:`NSString` layout 布局为何在第一位?
311 |
312 | ### 2.4 `__block` 变量(栈上)
313 |
314 | ```c
315 | __block int a = 0x99887766;
316 | __unsafe_unretained void (^blk)(void) = ^{
317 | NSLog(@"__block a :%d",a);
318 | };
319 | NSLog(@"Block 类型 %@",[blk class]);
320 | PTBlockRef block = (__bridge PTBlockRef)blk;
321 | void *pt = (void *)block + sizeof(struct PTBlock);
322 | long long *ppt = pt;
323 | void *ref = (PTBlock_byref_Ref)(*ppt);
324 | void *shared = ref + sizeof(struct PTBlock_byref);
325 | int *a_ref = (int *)shared;
326 | NSLog(@"a 指针:%p block a 指针:%p block a value:0x%x",&a, a_ref,*a_ref);
327 | NSLog(@"PTBlock_byref 指针:%p",ref);
328 | NSLog(@"PTBlock_byref forwarding 指针:%p",((PTBlock_byref_Ref)ref)->forwarding);
329 | /*
330 | 输出如下:
331 | Block 类型 __NSStackBlock__
332 | a 指针:0x7ffeefbff528 block a 指针:0x7ffeefbff528 block a value:0x99887766
333 | PTBlock_byref 指针:0x7ffeefbff510
334 | PTBlock_byref forwarding 指针:0x7ffeefbff510
335 | */
336 | ```
337 |
338 | 可以看到 `__block int a` 已经变成了另外一个数据结构了,打印地址符合预期,此刻 block 以及其他的变量结构体都在栈上。
339 |
340 | ### 2.5 `__block` 变量,[block copy] 后的内存变化
341 |
342 | ```c
343 | __block int a = 0x99887766;
344 | __unsafe_unretained void (^blk)(NSString *) = ^(NSString *flag){
345 | NSLog(@"[%@] 中 a 地址:%p",flag, &a);
346 | };
347 | NSLog(@"blk 类型 %@",[blk class]);
348 | blk(@"origin block");
349 | void (^copyblk)(NSString *) = [blk copy];
350 | copyblk(@"copy block");
351 | blk(@"origin block 二次调用");
352 | /**
353 | 输出如下:
354 | blk 类型 __NSStackBlock__
355 | [origin block] 中 a 地址:0x7ffeefbff528
356 | copyblk 类型 __NSMallocBlock__
357 | [copy block] 中 a 地址:0x102212468
358 | [origin block 二次调用] 中 a 地址:0x102212468
359 | */
360 | ```
361 |
362 | 很明显对 blk 进行 copy 操作后,copyblk 已经“移驾”到堆上,随着拷贝的还有 `__block` 修饰的a变量(`PTBlock_byref_Ref `类型);
363 |
364 | ### 2.6 `__block` 变量中 forwarding 指针
365 |
366 | ```c
367 | __block int a = 0x99887766;
368 | __unsafe_unretained void (^blk)(NSString *,id) = ^(NSString *flag, id bblk){
369 | NSLog(@"[%@] a address:%p",flag, &a); // a 取值都是 ->forwarding->a 方式
370 | PTBlockRef block = (__bridge PTBlockRef)bblk;
371 | void *pt = (void *)block + sizeof(struct PTBlock);
372 | long long *ppt = pt;
373 | void *ref = (PTBlock_byref_Ref)(*ppt);
374 | NSLog(@"[%@] PTBlock_byref_Ref 指针:%p",flag,ref);
375 | NSLog(@"[%@] PTBlock_byref_Ref forwarding 指针:%p",flag,((PTBlock_byref_Ref)ref)->forwarding);
376 | void *shared = ref + sizeof(struct PTBlock_byref);
377 | int *a_ref = (int *)shared;
378 | NSLog(@"[%@] a value : 0x%x a adress:%p", flag, *a_ref, a_ref);
379 |
380 | };
381 | NSLog(@"blk 类型 %@",[blk class]);
382 | blk(@"origin block", blk);
383 | void (^copyblk)(NSString *,id) = [blk copy];
384 | NSLog(@"copyblk 类型 %@",[copyblk class]);
385 | copyblk(@"copy block",copyblk);
386 | blk(@"origin block after copy", blk);
387 | /**
388 | MRC 模式下输出:
389 | blk 类型 __NSStackBlock__
390 | [origin block] a address:0x7ffeefbff528
391 | [origin block] PTBlock_byref_Ref 指针:0x7ffeefbff510
392 | [origin block] PTBlock_byref_Ref forwarding 指针:0x7ffeefbff510
393 | [origin block] a value : 0x99887766 a adress:0x7ffeefbff528
394 | copyblk 类型 __NSMallocBlock__
395 | [copy block] a address:0x1032041d8
396 | [copy block] PTBlock_byref_Ref 指针:0x1032041c0
397 | [copy block] PTBlock_byref_Ref forwarding 指针:0x1032041c0
398 | [copy block] a value : 0x99887766 a adress:0x1032041d8
399 | [origin block after copy] a address:0x1032041d8
400 | [origin block after copy] PTBlock_byref_Ref 指针:0x7ffeefbff510
401 | [origin block after copy] PTBlock_byref_Ref forwarding 指针:0x1032041c0
402 | [origin block after copy] a value : 0x99887766 a adress:0x7ffeefbff528
403 |
404 | ARC 模式下输出(这个稍有出路):
405 | blk 类型 __NSStackBlock__
406 | [origin block] a address:0x100604cc8
407 | [origin block] PTBlock_byref_Ref 指针:0x100604cb0
408 | [origin block] PTBlock_byref_Ref forwarding 指针:0x100604cb0
409 | [origin block] a value : 0x99887766 a adress:0x100604cc8
410 | copyblk 类型 __NSMallocBlock__
411 | [copy block] a address:0x100604cc8
412 | [copy block] PTBlock_byref_Ref 指针:0x100604cb0
413 | [copy block] PTBlock_byref_Ref forwarding 指针:0x100604cb0
414 | [copy block] a value : 0x99887766 a adress:0x100604cc8
415 | */
416 | ```
417 |
418 | 这里可以看到 forwarding 指针确实指向了结构体本身,随着 copy 行为确实进行了一次栈->堆的赋值——`block`和 `__block` 变量。
419 |
420 | > 建议用 lldb 命令去看内存布局。
421 |
422 | ## 3. `_block` 关键字修饰底层实现
423 |
424 | 《Block原理研究代码篇》中见 2.3 - 2.6 小节对其代码验证。
425 |
426 | `__block` 关键字修饰的变量最终都会变成如下结构体,内容是存储在 `long shared[0]` ,即仅跟结构体后面的内存中。
427 |
428 | ```objective-c
429 | typedef struct PTBlock_byref {
430 | void *isa;
431 | struct PTBlock_byref *forwarding;
432 | volatile int flags; // contains ref count
433 | unsigned int size;
434 | // 下面两个函数指针是不定的 要根据flags来
435 | // void (*byref_keep)(struct PTBlock_byref *dst, struct PTBlock_byref *src);
436 | // void (*byref_destroy)(struct PTBlock_byref *);
437 | // long shared[0];
438 | } *PTBlock_byref_Ref;
439 | ```
440 |
441 | 如下简单的调用:
442 |
443 | ```objective-c
444 | __block int a = 0x99887766;
445 | __unsafe_unretained void (^blk)(NSString *) = ^(NSString *flag){
446 | a = 0x11223344;
447 | };
448 | a = 0x55667788;
449 | blk();
450 | ```
451 |
452 | 一旦用 `__block` 修饰后, a 就已经变成了上面的 `PTBlock_byref` 结构体了(**ps:PT是我加的前缀,但是数据结构和底层保持一致**),因此所有对变量的 a 的修改实际上都是使用 `a_block_byref_Ref->forwarding->a ` 进行修改的,由于此刻 block 对象还在栈上,因此其捕获的 `__block` 修饰的变量(`PTBlock_byref` 的结构体)也同样是在栈上,且 `forwarding` 指针是指向该结构体本身。
453 |
454 | 上面的代码实际上应该是这样的:
455 |
456 | ```objective-c
457 | PTBlock_byref a_block_byref_Ref = PTBlock_byref(0x99887766); // 伪代码
458 |
459 | __unsafe_unretained void (^blk)(NSString *) = ^(NSString *flag){
460 | a_block_byref_Ref->forwarding->a = 0x11223344;
461 | };
462 |
463 | a_block_byref_Ref->forwarding->a = 0x55667788;
464 |
465 | blk();
466 | ```
467 |
468 | 一旦 Block 发生了从栈到堆的拷贝操作,那么拷贝的同时,也会将 `__block` 修饰的所有变量( `PTBlock_byref` 类型)拷贝到堆上,然后将`forwarding` 指针指向自身头部;而栈上的 `a_block_byref_Ref->forwarding` 修正为指向堆上的变量头部,所以保证了数据在 block 拷贝后修改一致性:
469 |
470 | ```objective-c
471 | PTBlock_byref a_block_byref_Ref = PTBlock_byref(0x99887766); // 伪代码
472 |
473 | __unsafe_unretained void (^blk)(NSString *) = ^(NSString *flag){
474 | a_block_byref_Ref->forwarding->a = 0x11223344;
475 | };
476 |
477 | // 此刻 forwarding 还是指向栈上的自身,也就是 a_block_byref_Ref
478 | a_block_byref_Ref->forwarding->a = 0x55667788;
479 |
480 | void (^copyblk)(NSString *) = [blk copy];
481 |
482 | // 此刻 forwarding 已经指向一个堆上新的 PTBlock_byref a 变量了,修改的也是堆上的数据
483 | a_block_byref_Ref->forwarding->a = 0x99887766;
484 |
485 | blk();
486 | ```
487 |
488 |
489 |
490 |
--------------------------------------------------------------------------------
/demos/TestLoad方法/TestLoad方法.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4FD71090244F4A2900747686 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD7108F244F4A2900747686 /* AppDelegate.m */; };
11 | 4FD71093244F4A2900747686 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD71092244F4A2900747686 /* SceneDelegate.m */; };
12 | 4FD71096244F4A2900747686 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD71095244F4A2900747686 /* ViewController.m */; };
13 | 4FD71099244F4A2900747686 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4FD71097244F4A2900747686 /* Main.storyboard */; };
14 | 4FD7109B244F4A2B00747686 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4FD7109A244F4A2B00747686 /* Assets.xcassets */; };
15 | 4FD7109E244F4A2B00747686 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4FD7109C244F4A2B00747686 /* LaunchScreen.storyboard */; };
16 | 4FD710A1244F4A2B00747686 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710A0244F4A2B00747686 /* main.m */; };
17 | 4FD710A9244F4BA300747686 /* Person.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710A8244F4BA300747686 /* Person.m */; };
18 | 4FD710AC244F4BCB00747686 /* Person+category1.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710AB244F4BCB00747686 /* Person+category1.m */; };
19 | 4FD710AF244F4BE700747686 /* Person+category2.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710AE244F4BE700747686 /* Person+category2.m */; };
20 | 4FD710B2244F4C0800747686 /* Teacher.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710B1244F4C0800747686 /* Teacher.m */; };
21 | 4FD710B5244F4C1D00747686 /* Teacher+category1.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710B4244F4C1D00747686 /* Teacher+category1.m */; };
22 | 4FD710B8244F4C2700747686 /* Teacher+category2.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710B7244F4C2700747686 /* Teacher+category2.m */; };
23 | 4FD710BB244F4C3B00747686 /* Professor.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710BA244F4C3B00747686 /* Professor.m */; };
24 | 4FD710BE244F4C4E00747686 /* Professor+category1.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710BD244F4C4E00747686 /* Professor+category1.m */; };
25 | 4FD710C1244F4C5800747686 /* Professor+category2.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710C0244F4C5800747686 /* Professor+category2.m */; };
26 | 4FD710C42450A18700747686 /* PTRuntimeUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD710C32450A18700747686 /* PTRuntimeUtil.m */; };
27 | /* End PBXBuildFile section */
28 |
29 | /* Begin PBXFileReference section */
30 | 4FD7108B244F4A2900747686 /* TestLoad方法.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TestLoad方法.app"; sourceTree = BUILT_PRODUCTS_DIR; };
31 | 4FD7108E244F4A2900747686 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
32 | 4FD7108F244F4A2900747686 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
33 | 4FD71091244F4A2900747686 /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; };
34 | 4FD71092244F4A2900747686 /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; };
35 | 4FD71094244F4A2900747686 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; };
36 | 4FD71095244F4A2900747686 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; };
37 | 4FD71098244F4A2900747686 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
38 | 4FD7109A244F4A2B00747686 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
39 | 4FD7109D244F4A2B00747686 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
40 | 4FD7109F244F4A2B00747686 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
41 | 4FD710A0244F4A2B00747686 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
42 | 4FD710A7244F4BA300747686 /* Person.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Person.h; sourceTree = ""; };
43 | 4FD710A8244F4BA300747686 /* Person.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Person.m; sourceTree = ""; };
44 | 4FD710AA244F4BCB00747686 /* Person+category1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Person+category1.h"; sourceTree = ""; };
45 | 4FD710AB244F4BCB00747686 /* Person+category1.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "Person+category1.m"; sourceTree = ""; };
46 | 4FD710AD244F4BE700747686 /* Person+category2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Person+category2.h"; sourceTree = ""; };
47 | 4FD710AE244F4BE700747686 /* Person+category2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "Person+category2.m"; sourceTree = ""; };
48 | 4FD710B0244F4C0800747686 /* Teacher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Teacher.h; sourceTree = ""; };
49 | 4FD710B1244F4C0800747686 /* Teacher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Teacher.m; sourceTree = ""; };
50 | 4FD710B3244F4C1D00747686 /* Teacher+category1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Teacher+category1.h"; sourceTree = ""; };
51 | 4FD710B4244F4C1D00747686 /* Teacher+category1.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "Teacher+category1.m"; sourceTree = ""; };
52 | 4FD710B6244F4C2700747686 /* Teacher+category2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Teacher+category2.h"; sourceTree = ""; };
53 | 4FD710B7244F4C2700747686 /* Teacher+category2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "Teacher+category2.m"; sourceTree = ""; };
54 | 4FD710B9244F4C3B00747686 /* Professor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Professor.h; sourceTree = ""; };
55 | 4FD710BA244F4C3B00747686 /* Professor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Professor.m; sourceTree = ""; };
56 | 4FD710BC244F4C4E00747686 /* Professor+category1.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Professor+category1.h"; sourceTree = ""; };
57 | 4FD710BD244F4C4E00747686 /* Professor+category1.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "Professor+category1.m"; sourceTree = ""; };
58 | 4FD710BF244F4C5800747686 /* Professor+category2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Professor+category2.h"; sourceTree = ""; };
59 | 4FD710C0244F4C5800747686 /* Professor+category2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "Professor+category2.m"; sourceTree = ""; };
60 | 4FD710C22450A18700747686 /* PTRuntimeUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTRuntimeUtil.h; sourceTree = ""; };
61 | 4FD710C32450A18700747686 /* PTRuntimeUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PTRuntimeUtil.m; sourceTree = ""; };
62 | /* End PBXFileReference section */
63 |
64 | /* Begin PBXFrameworksBuildPhase section */
65 | 4FD71088244F4A2900747686 /* Frameworks */ = {
66 | isa = PBXFrameworksBuildPhase;
67 | buildActionMask = 2147483647;
68 | files = (
69 | );
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | /* End PBXFrameworksBuildPhase section */
73 |
74 | /* Begin PBXGroup section */
75 | 4FD71082244F4A2900747686 = {
76 | isa = PBXGroup;
77 | children = (
78 | 4FD7108D244F4A2900747686 /* TestLoad方法 */,
79 | 4FD7108C244F4A2900747686 /* Products */,
80 | );
81 | sourceTree = "";
82 | };
83 | 4FD7108C244F4A2900747686 /* Products */ = {
84 | isa = PBXGroup;
85 | children = (
86 | 4FD7108B244F4A2900747686 /* TestLoad方法.app */,
87 | );
88 | name = Products;
89 | sourceTree = "";
90 | };
91 | 4FD7108D244F4A2900747686 /* TestLoad方法 */ = {
92 | isa = PBXGroup;
93 | children = (
94 | 4FD710C22450A18700747686 /* PTRuntimeUtil.h */,
95 | 4FD710C32450A18700747686 /* PTRuntimeUtil.m */,
96 | 4FD7108E244F4A2900747686 /* AppDelegate.h */,
97 | 4FD7108F244F4A2900747686 /* AppDelegate.m */,
98 | 4FD71091244F4A2900747686 /* SceneDelegate.h */,
99 | 4FD71092244F4A2900747686 /* SceneDelegate.m */,
100 | 4FD71094244F4A2900747686 /* ViewController.h */,
101 | 4FD71095244F4A2900747686 /* ViewController.m */,
102 | 4FD71097244F4A2900747686 /* Main.storyboard */,
103 | 4FD7109A244F4A2B00747686 /* Assets.xcassets */,
104 | 4FD7109C244F4A2B00747686 /* LaunchScreen.storyboard */,
105 | 4FD7109F244F4A2B00747686 /* Info.plist */,
106 | 4FD710A0244F4A2B00747686 /* main.m */,
107 | 4FD710B0244F4C0800747686 /* Teacher.h */,
108 | 4FD710B1244F4C0800747686 /* Teacher.m */,
109 | 4FD710B9244F4C3B00747686 /* Professor.h */,
110 | 4FD710BA244F4C3B00747686 /* Professor.m */,
111 | 4FD710BC244F4C4E00747686 /* Professor+category1.h */,
112 | 4FD710BD244F4C4E00747686 /* Professor+category1.m */,
113 | 4FD710BF244F4C5800747686 /* Professor+category2.h */,
114 | 4FD710C0244F4C5800747686 /* Professor+category2.m */,
115 | 4FD710B6244F4C2700747686 /* Teacher+category2.h */,
116 | 4FD710B7244F4C2700747686 /* Teacher+category2.m */,
117 | 4FD710B3244F4C1D00747686 /* Teacher+category1.h */,
118 | 4FD710B4244F4C1D00747686 /* Teacher+category1.m */,
119 | 4FD710A7244F4BA300747686 /* Person.h */,
120 | 4FD710A8244F4BA300747686 /* Person.m */,
121 | 4FD710AA244F4BCB00747686 /* Person+category1.h */,
122 | 4FD710AB244F4BCB00747686 /* Person+category1.m */,
123 | 4FD710AD244F4BE700747686 /* Person+category2.h */,
124 | 4FD710AE244F4BE700747686 /* Person+category2.m */,
125 | );
126 | path = "TestLoad方法";
127 | sourceTree = "";
128 | };
129 | /* End PBXGroup section */
130 |
131 | /* Begin PBXNativeTarget section */
132 | 4FD7108A244F4A2900747686 /* TestLoad方法 */ = {
133 | isa = PBXNativeTarget;
134 | buildConfigurationList = 4FD710A4244F4A2B00747686 /* Build configuration list for PBXNativeTarget "TestLoad方法" */;
135 | buildPhases = (
136 | 4FD71087244F4A2900747686 /* Sources */,
137 | 4FD71088244F4A2900747686 /* Frameworks */,
138 | 4FD71089244F4A2900747686 /* Resources */,
139 | );
140 | buildRules = (
141 | );
142 | dependencies = (
143 | );
144 | name = "TestLoad方法";
145 | productName = "TestLoad方法";
146 | productReference = 4FD7108B244F4A2900747686 /* TestLoad方法.app */;
147 | productType = "com.apple.product-type.application";
148 | };
149 | /* End PBXNativeTarget section */
150 |
151 | /* Begin PBXProject section */
152 | 4FD71083244F4A2900747686 /* Project object */ = {
153 | isa = PBXProject;
154 | attributes = {
155 | LastUpgradeCheck = 1140;
156 | ORGANIZATIONNAME = pmst;
157 | TargetAttributes = {
158 | 4FD7108A244F4A2900747686 = {
159 | CreatedOnToolsVersion = 11.4.1;
160 | };
161 | };
162 | };
163 | buildConfigurationList = 4FD71086244F4A2900747686 /* Build configuration list for PBXProject "TestLoad方法" */;
164 | compatibilityVersion = "Xcode 9.3";
165 | developmentRegion = en;
166 | hasScannedForEncodings = 0;
167 | knownRegions = (
168 | en,
169 | Base,
170 | );
171 | mainGroup = 4FD71082244F4A2900747686;
172 | productRefGroup = 4FD7108C244F4A2900747686 /* Products */;
173 | projectDirPath = "";
174 | projectRoot = "";
175 | targets = (
176 | 4FD7108A244F4A2900747686 /* TestLoad方法 */,
177 | );
178 | };
179 | /* End PBXProject section */
180 |
181 | /* Begin PBXResourcesBuildPhase section */
182 | 4FD71089244F4A2900747686 /* Resources */ = {
183 | isa = PBXResourcesBuildPhase;
184 | buildActionMask = 2147483647;
185 | files = (
186 | 4FD7109E244F4A2B00747686 /* LaunchScreen.storyboard in Resources */,
187 | 4FD7109B244F4A2B00747686 /* Assets.xcassets in Resources */,
188 | 4FD71099244F4A2900747686 /* Main.storyboard in Resources */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | /* End PBXResourcesBuildPhase section */
193 |
194 | /* Begin PBXSourcesBuildPhase section */
195 | 4FD71087244F4A2900747686 /* Sources */ = {
196 | isa = PBXSourcesBuildPhase;
197 | buildActionMask = 2147483647;
198 | files = (
199 | 4FD710C42450A18700747686 /* PTRuntimeUtil.m in Sources */,
200 | 4FD710AF244F4BE700747686 /* Person+category2.m in Sources */,
201 | 4FD710BE244F4C4E00747686 /* Professor+category1.m in Sources */,
202 | 4FD710C1244F4C5800747686 /* Professor+category2.m in Sources */,
203 | 4FD710B5244F4C1D00747686 /* Teacher+category1.m in Sources */,
204 | 4FD710B8244F4C2700747686 /* Teacher+category2.m in Sources */,
205 | 4FD710AC244F4BCB00747686 /* Person+category1.m in Sources */,
206 | 4FD710B2244F4C0800747686 /* Teacher.m in Sources */,
207 | 4FD710BB244F4C3B00747686 /* Professor.m in Sources */,
208 | 4FD710A9244F4BA300747686 /* Person.m in Sources */,
209 | 4FD71096244F4A2900747686 /* ViewController.m in Sources */,
210 | 4FD71090244F4A2900747686 /* AppDelegate.m in Sources */,
211 | 4FD710A1244F4A2B00747686 /* main.m in Sources */,
212 | 4FD71093244F4A2900747686 /* SceneDelegate.m in Sources */,
213 | );
214 | runOnlyForDeploymentPostprocessing = 0;
215 | };
216 | /* End PBXSourcesBuildPhase section */
217 |
218 | /* Begin PBXVariantGroup section */
219 | 4FD71097244F4A2900747686 /* Main.storyboard */ = {
220 | isa = PBXVariantGroup;
221 | children = (
222 | 4FD71098244F4A2900747686 /* Base */,
223 | );
224 | name = Main.storyboard;
225 | sourceTree = "";
226 | };
227 | 4FD7109C244F4A2B00747686 /* LaunchScreen.storyboard */ = {
228 | isa = PBXVariantGroup;
229 | children = (
230 | 4FD7109D244F4A2B00747686 /* Base */,
231 | );
232 | name = LaunchScreen.storyboard;
233 | sourceTree = "";
234 | };
235 | /* End PBXVariantGroup section */
236 |
237 | /* Begin XCBuildConfiguration section */
238 | 4FD710A2244F4A2B00747686 /* Debug */ = {
239 | isa = XCBuildConfiguration;
240 | buildSettings = {
241 | ALWAYS_SEARCH_USER_PATHS = NO;
242 | CLANG_ANALYZER_NONNULL = YES;
243 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
244 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
245 | CLANG_CXX_LIBRARY = "libc++";
246 | CLANG_ENABLE_MODULES = YES;
247 | CLANG_ENABLE_OBJC_ARC = YES;
248 | CLANG_ENABLE_OBJC_WEAK = YES;
249 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
250 | CLANG_WARN_BOOL_CONVERSION = YES;
251 | CLANG_WARN_COMMA = YES;
252 | CLANG_WARN_CONSTANT_CONVERSION = YES;
253 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
254 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
255 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
256 | CLANG_WARN_EMPTY_BODY = YES;
257 | CLANG_WARN_ENUM_CONVERSION = YES;
258 | CLANG_WARN_INFINITE_RECURSION = YES;
259 | CLANG_WARN_INT_CONVERSION = YES;
260 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
261 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
262 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
264 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
265 | CLANG_WARN_STRICT_PROTOTYPES = YES;
266 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
267 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
268 | CLANG_WARN_UNREACHABLE_CODE = YES;
269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
270 | COPY_PHASE_STRIP = NO;
271 | DEBUG_INFORMATION_FORMAT = dwarf;
272 | ENABLE_STRICT_OBJC_MSGSEND = YES;
273 | ENABLE_TESTABILITY = YES;
274 | GCC_C_LANGUAGE_STANDARD = gnu11;
275 | GCC_DYNAMIC_NO_PIC = NO;
276 | GCC_NO_COMMON_BLOCKS = YES;
277 | GCC_OPTIMIZATION_LEVEL = 0;
278 | GCC_PREPROCESSOR_DEFINITIONS = (
279 | "DEBUG=1",
280 | "$(inherited)",
281 | );
282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
284 | GCC_WARN_UNDECLARED_SELECTOR = YES;
285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
286 | GCC_WARN_UNUSED_FUNCTION = YES;
287 | GCC_WARN_UNUSED_VARIABLE = YES;
288 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
289 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
290 | MTL_FAST_MATH = YES;
291 | ONLY_ACTIVE_ARCH = YES;
292 | SDKROOT = iphoneos;
293 | };
294 | name = Debug;
295 | };
296 | 4FD710A3244F4A2B00747686 /* Release */ = {
297 | isa = XCBuildConfiguration;
298 | buildSettings = {
299 | ALWAYS_SEARCH_USER_PATHS = NO;
300 | CLANG_ANALYZER_NONNULL = YES;
301 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
302 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
303 | CLANG_CXX_LIBRARY = "libc++";
304 | CLANG_ENABLE_MODULES = YES;
305 | CLANG_ENABLE_OBJC_ARC = YES;
306 | CLANG_ENABLE_OBJC_WEAK = YES;
307 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
308 | CLANG_WARN_BOOL_CONVERSION = YES;
309 | CLANG_WARN_COMMA = YES;
310 | CLANG_WARN_CONSTANT_CONVERSION = YES;
311 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
313 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
314 | CLANG_WARN_EMPTY_BODY = YES;
315 | CLANG_WARN_ENUM_CONVERSION = YES;
316 | CLANG_WARN_INFINITE_RECURSION = YES;
317 | CLANG_WARN_INT_CONVERSION = YES;
318 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
319 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
320 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
322 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
323 | CLANG_WARN_STRICT_PROTOTYPES = YES;
324 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
325 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
326 | CLANG_WARN_UNREACHABLE_CODE = YES;
327 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
328 | COPY_PHASE_STRIP = NO;
329 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
330 | ENABLE_NS_ASSERTIONS = NO;
331 | ENABLE_STRICT_OBJC_MSGSEND = YES;
332 | GCC_C_LANGUAGE_STANDARD = gnu11;
333 | GCC_NO_COMMON_BLOCKS = YES;
334 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
335 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
336 | GCC_WARN_UNDECLARED_SELECTOR = YES;
337 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
338 | GCC_WARN_UNUSED_FUNCTION = YES;
339 | GCC_WARN_UNUSED_VARIABLE = YES;
340 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
341 | MTL_ENABLE_DEBUG_INFO = NO;
342 | MTL_FAST_MATH = YES;
343 | SDKROOT = iphoneos;
344 | VALIDATE_PRODUCT = YES;
345 | };
346 | name = Release;
347 | };
348 | 4FD710A5244F4A2B00747686 /* Debug */ = {
349 | isa = XCBuildConfiguration;
350 | buildSettings = {
351 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
352 | CODE_SIGN_STYLE = Automatic;
353 | DEVELOPMENT_TEAM = 66AQ356GA4;
354 | INFOPLIST_FILE = "TestLoad方法/Info.plist";
355 | LD_RUNPATH_SEARCH_PATHS = (
356 | "$(inherited)",
357 | "@executable_path/Frameworks",
358 | );
359 | PRODUCT_BUNDLE_IDENTIFIER = "com.pmst.TestLoad--";
360 | PRODUCT_NAME = "$(TARGET_NAME)";
361 | TARGETED_DEVICE_FAMILY = "1,2";
362 | };
363 | name = Debug;
364 | };
365 | 4FD710A6244F4A2B00747686 /* Release */ = {
366 | isa = XCBuildConfiguration;
367 | buildSettings = {
368 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
369 | CODE_SIGN_STYLE = Automatic;
370 | DEVELOPMENT_TEAM = 66AQ356GA4;
371 | INFOPLIST_FILE = "TestLoad方法/Info.plist";
372 | LD_RUNPATH_SEARCH_PATHS = (
373 | "$(inherited)",
374 | "@executable_path/Frameworks",
375 | );
376 | PRODUCT_BUNDLE_IDENTIFIER = "com.pmst.TestLoad--";
377 | PRODUCT_NAME = "$(TARGET_NAME)";
378 | TARGETED_DEVICE_FAMILY = "1,2";
379 | };
380 | name = Release;
381 | };
382 | /* End XCBuildConfiguration section */
383 |
384 | /* Begin XCConfigurationList section */
385 | 4FD71086244F4A2900747686 /* Build configuration list for PBXProject "TestLoad方法" */ = {
386 | isa = XCConfigurationList;
387 | buildConfigurations = (
388 | 4FD710A2244F4A2B00747686 /* Debug */,
389 | 4FD710A3244F4A2B00747686 /* Release */,
390 | );
391 | defaultConfigurationIsVisible = 0;
392 | defaultConfigurationName = Release;
393 | };
394 | 4FD710A4244F4A2B00747686 /* Build configuration list for PBXNativeTarget "TestLoad方法" */ = {
395 | isa = XCConfigurationList;
396 | buildConfigurations = (
397 | 4FD710A5244F4A2B00747686 /* Debug */,
398 | 4FD710A6244F4A2B00747686 /* Release */,
399 | );
400 | defaultConfigurationIsVisible = 0;
401 | defaultConfigurationName = Release;
402 | };
403 | /* End XCConfigurationList section */
404 | };
405 | rootObject = 4FD71083244F4A2900747686 /* Project object */;
406 | }
407 |
--------------------------------------------------------------------------------