/** 事件传递
hidden
userInteractionEabled
alpha > 0.1
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
}
*/
@implementation EventView
/** CPU
layout: UI布局, 文本计算
display: 绘制 - drawRect
prepare: 解码
commit: 提交 bitmap
*/
/** GPU 渲染管线
顶点着色
图元装配
光栅化
片段着色
片段处理
-> FrameBuffer
*/
/** 滑动优化方案
- CPU
对象的创建, 调整, 销毁放到子线程去操作
预排版(布局, 文本计算)
预渲染(文本的异步绘制, 图片编解码)
*GPU
纹理渲染(离屏渲染, maskToBounce, shadow)
视图混合(视图层级复杂性, 合成位图)
*/
/** 异步绘制
判断 -[layer.delegate displayLayer:]
layer的代理遵守了代理,
步骤:
// [self setNeedsDisplay];
// [self.layer setNeedsDisplay];
// [layer.delegate displayLayer:];
--> 切换到子线程, 生成 bitmap
-> 返回主线程, 显示
** 当前屏渲染(On-Screen Rendering)
GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行的
** 离屏渲染(Off-Screen Rendering)
GPU在当前屏幕缓冲区以外, 新开辟一个缓冲区进行渲染操作
圆角属性(与 maskToBounce一起使用), 图层蒙版,阴影遮罩, 光栅化! 当有些控件设置某些属性, 标记为在为合成之前, 不能直接在当前屏幕显示时, 就会触发
- 为何要避免离屏渲染?
- 创建新的渲染缓冲区, 上下文切换
- CPU 和 GPU在图像显示上做了大量工作, 离屏渲染是触发了 GPU 多通道渲染管线, 产生了额外的开销!
*-> 增加 GPU 工作量, 可能导致CPU+GPU>16.7s, 导致掉帧或者卡顿.
UIView 和 CALayer 设计原则-->单一职责
*/
pragma mark - OC 原理面试题
/** 1. OC 分类原理 category
你用分类都做了那些事 ?
** 声明私有方法
** 分解体积庞大的类文件
** 把 framework 的私有方法公开化特点
** 运行时决议(通过 runtime给宿主类添加方法)
** 可以为系统类添加分类(UIVIew 添加获取坐标)分类中可以添加哪些内容?
** 实例方法
** 类方法
** 协议
** 属性(只是添加了set get 方法, 并不是添加了实例变量, 通过关联对象可添加变量)
struct objc_category {
char * _Nonnull category_name
char * _Nonnull class_name
struct objc_method_list * _Nullable instance_methods
struct objc_method_list * _Nullable class_methods
struct objc_protocol_list * _Nullable protocols
}
分类加载调用栈
_objc_init
map_2_images
map_images_nolock
_read_images
remethodizeClass ***
-> 多个分类重写同一个方法, 最后编译分类中的方法, 会生效!
method.attachLists(mlist, mcount)
memmove() 将分类的方法, 插入到宿主类方法列表前面
*** 能否给分类添加成员变量?
** 不能, 但是可以通过关联对象方法, 达到添加成员变量的效果
objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>)
*** 关联对象本质
- 关联对象共有AssociationsManager 管理并在 AssociationHashMap 存储
- 所有对象的关联内容都在同一个全局容器中
- 根据 对象的地址作为 key, value+policy 封装成AssociationMap作为值, 添加到 AssociationHashMap 中统一管理.
---> value 设置为 nil, 可以清除关联对象的值
// objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>)
// objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>)
// objc_removeAssociatedObjects(<#id _Nonnull object#>)
*/
/** 2. 类的扩展(Extention)
** a.一半用扩展做什么
- 声明私有属性
- 声明私有方法
- 声明私有成员变量
** b.分类和扩展的区别
- 编译是决定
- 只以声明的形式存在, 多数情况寄生在宿主类的 .m中
- 不能为系统类添加扩展
*/
/** 3. 代理(delegate)
** 设计模式
- @ protocol 方式实现
- 一对一
*/
/** 4.通知(NSNoticefication)
** 基于观察者模式实现的, 跨层消息传递机制.
- 传递方式: 一对多
*** 如何实现通知机制?
- Noticefication_map(name, observer_list(observer1, observer2...))
*/
/** 5. KVO 机制
** 观察者设计模式
** apple采用 isa 混写(isa_swizzing)来实现 kvo
-
addObserverA -> NSKVONotifying_A(运行时动态创建子类) -> 重写了 setter
- (void)didChangeValueForKey:(NSString *)key
- (void)willChangeValueForKey:(NSString *)key
???
** a.通过 KVC 设置 value 能否生效
- setMValue:(id)value forKey:
- YES
- 调用了 set 方法
** b.通过成员变量直接赋值value, 能否生效
- NO
- 可以手动添加 willChangeValueForKey,didChangeValueForKey 就可以生效 (手动 KVO)
*/
// KVO运行时动态生成的子类, 重写了 set 方法, 达到监听效果
- (void)setMValue:(id)value forKey:(NSString *)key{
[self willChangeValueForKey:key];
[super setValue:value forKey:key];
[self didChangeValueForKey:key];
}
/** 6. KVC 底层实现
** 键值编码技术
- 相关方法 valueForKey setValueForKey
- valueForKey
Accessor Method is Exit? -> YES (Invoke end)
-> NO
Instance var is exit? (+ (BOOL)accessInstanceVariablesDirectly{})
->YES (Invoke end)
-> NO -> valueForUndefinedKey ,抛出异常 exception - Accessor Method
- 成员变量判断 Instance var <_key> <_iskey>
** setValueForKey
Setter Method is exit ? -> YES (Invoke end) ->NO Instance var is exit ?(+ (BOOL)accessInstanceVariablesDirectly{}) -> YES (Invoke end) -> NO -> setValueForUndefinedKey: 抛出异常 exception
*/
/** 7. 属性关键字
** 读写权限
readOnly readWrite
** 原子性
nonatomic atomic(读, 写 保证线程安全->对一个数组进行插入删除元素, 不能保证线程安全)
** 引用技术
retain/stong
assign : a.能够修饰基本数据类型; b.修饰对象类型时,不改变其应用计数; 会产生悬垂指针
weak : a.不改变修饰对象的引用计数; b.所指向对象在被释放之后会自动设置为 nil
copy : a.;;
- 浅拷贝 : 增加对象的引用计数, 不会增加内存空间 未开辟内存空间 增加引用计数
- 深拷贝 : 让目标对象指针和源对象指针, 指向两片相同的内存 开辟内存空间 不会增加引用计数
mutableOBJ copy 不可变 深拷贝
mutableOBJ mutableCopy 可变 深拷贝
immutableOBJ copy 不可变 浅拷贝
immutableOBJ mutableCopy 可变 深拷贝
可变对象的 copy, 都是深 copy
不可变对象的copy, 是浅 copy
@property(copy) NSMutableArray *array;
copy 出来的是 NSArray, 不可变(addObj 报错)
*/
/** 8. MRC 如何重写 retain 修饰变量的 setter
*/
//- (void)setObj:(id)obj {
// if(_obj != obj) {
// [_obj release];
// _obj = [obj retain];
// }
//}
pragma mark - 内存管理
/** 01. 内存
- stack: 方法调用
- heap : allo 创建的对象
- .bss : 未初始化的全局变量
- .data: 已初始化的全局变量
- .text: 程序代码
*** 内存管理方案
- TaggedPointer (NSNumber)
- NONPOINTER_ISA (非指针类 isa)
- 散列表(引用计数表, 弱引用)
*** NONPOINTER_ISA arm64
- 64bit
- 0-15 : indexed =1, 存储了内存管理相关数据
*** 散列表(side Tables 结构)
- spinlock_t(自旋锁)
- RefcountMap(引用计数表)
- weak_table_t(弱引用表)
??? 为什么不是一个 sidetable
分离锁:
用一个表, 不同线程操作的时候存在效率问题.
用多个表, 可以并发处理不同对象引用计数.
??? 怎样实现快速分流
sideTable本质是一张 Hash 表
key(对象指针) ->(hash 函数)-> Value(sideTable)
查找元素, 不需要遍历, 根据 hash函数取对应index!
*** spinlock_t(自旋锁)
- "忙等"(当前线程不断去获取锁的状态)
- 适用于轻量级访问.
*/
/*** RefcountMap(引用计数表)
- size_t unsigned long
- 计算时需要 右移两位
*/
/*** weak_table_t(弱引用表)
- 实现也是 hash 表
*/
pragma mark - 网络
/** 0. 请求报文
方法 url 协议版本 crlf
首部字段名: 值
实体主题
*** 响应报文
版本 状态码 短语 crlf
首部字段名: 值
实体主题
**** http 请求方式
GET POST HEAD PUT DELETE OPTIONS
??? GET/POST 区别
GET请求参数拼接到 URL 后面, POST 请求参数放在 Body 中
GET参数长度限制2048个字符, POST 一般没有限制
GET 请求不安全, POST 请求安全
-> 语义角度分析
GET: 用来获取资源, 安全, 幂等, 可缓存
POST: 处理资源, 不安全, 不幂等, 不可缓存
安全性: 不应该引起 sever 端任何状态变化 // GET HEAD OPTIONS
幂等性: 同一个请求方法执行多次和执行一次的效果完全相同 // PUT DELETE
可缓存: 请求是否可以被缓存 // GET HEAD
??? 创建的状态码, 含义
1...
2...正常
3...转发
4...请求错误
5...服务器错误
??? http 三次握手 四次挥手
// 三次握手, 建立连接
syn 2.syn, ACK
ACK
http 请求报文 5. http 响应报文
// 四次挥手断开连接
6.FIN 7.ACK
8.FIN,ACK
9.ACK
??? http特点
- 无连接, 通过 http 持久连接来解决无连接特点
- 无状态, cookie/session
** 持久连接
头部字段
connection: keep-alive
time: 20 // 持续时间
max:10 // http请求最多发送次数
?? 怎样判断一个请求结束
content-length:1024 // 根据接受到数据长度
chunked: 最后有个空的 chunked
?? charles 抓包原理
中间人攻击
**** HTTPS
https = http + ssl
??? https 建立流程
Client -> TLS 版本号, 支持的加密算法, random C -> Server
<- 商定的加密算法, random S, server 证书 <-
验证证书
组装会话秘钥
-> 通过 sever 的公钥对预主秘钥进行加密传输 ->
通过私钥解密得到预主秘钥
组装会话秘钥
-> 发送加密的握手消息 ->
<- 发送加密的握手消息 <-
会话秘钥 = random S + random C + 预主秘钥 // 对称加密
?? https 使用了哪些加密手段
连接过程使用的非对称加密
后续传输使用对称加密
??? TCP / UDP
TCP:
特点: 面向连接, 可靠传输, 面向字节流, 流量控制, 拥塞控制
面向连接: 三次握手(防超时, 误建立连接), 四次挥手
可靠传输: 无差错, 不丢失, 不重复, 按序到达
UDP:
特点: 无连接, 尽最大努力交互(不可靠), 面向报文(既不合并, 也不拆分, 将应用层的报文直接传输到)
功能: 复用, 分用
*** DNS 解析
??? DNS 劫持
钓鱼 DNS 返回错误的 IP
DNS 请求使用 UDP 数据包, 端口号53; 发生在 http 连接之前.
怎样解决 DNS 劫持?
使用 http 协议向 httpDNS 80 端口 服务器请求
使用长连接
**** cookie
cookie 主要用来记录用户状态, 区分用户, 状态保存在客户端;
?? 怎样修改 cookie
新的 cookie 覆盖旧的 cookie;
覆盖规则: name, path, domain 等需要跟原 cookie 一致;
?? 怎样删除 cookie
新的 cookie 覆盖旧的 cookie; 设置 expires= 过期的时间 或者 maxAge=0
?? 怎样保证 cookie 安全
对 cookie 进行加密;
只在 https 上携带 cookie;
设置httpOnly;
?? session
session存放在服务端
*/
/**
??? 什么是 ARC
ARC 是由 LLVM 和 runtime 共同协作的自动引用计数内存管理机制
??? weak 指针指向的对象, 释放后, 至 nil
对象被废弃之后, 会调用一个清除弱引用的方法, 方法会通过哈希函数查找对象在哈希表中的位置, 来提取弱引用指针的数组, 然后遍历置为nil
??? 苹果如果实现 autoreleasepool
autoreleasepool是以栈为节点, 通过双向链表实现的结构
??? 循环引用
runloo -> timer 中间对象(弱引用) 对象<- VC
??? 什么是 block
block 是将 函数 及其 执行上下文 封装器来的 对象
??? block 截获变量
局部变量
基本数据类型: 截获局部变量对应的值
对象类型: 连同所有权修饰符一起截获
静态局部变量: 以指针形式
全局变量, 静态全局变量: 不截获
对被截获变量进行 赋值 操作, 需要使用__block 修饰变量
对静态局部变量, 全局变量, 静态全局变量赋值, 不需要使用__block 修饰
*/
pragma mark - 算法
/** 0. 字符串反转算法
hash 算法
求无需数组中的中位数
*/
// 0. 字符串反转算法
void char_reverse(char* cha)
{
// 指向第一个字符
char* begin = cha;
// 指向最后一个字符
char* end = cha + strlen(cha) - 1;
while (begin < end) {
// 交换前后两个字符,同时移动指针
char temp = *begin;
*(begin++) = *end;
*(end--) = temp;
}
}
// 1. 链表反转
struct Node* reverseList(struct Node *head)
{
// 定义遍历指针,初始化为头结点
struct Node *p = head;
// 反转后的链表头部
struct Node *newH = NULL;
// 遍历链表
while (p != NULL) {
// 记录下一个结点
struct Node *temp = p->next;
// 当前结点的next指向新链表头部
p->next = newH;
// 更改新链表头部为当前结点
newH = p;
// 移动p指针
p = temp;
}
// 返回反转后的链表头结点
return newH;
}
// 有序数组合并
void mergeList(int a[], int aLen, int b[], int bLen, int result[])
{
int p = 0; // 遍历数组a的指针
int q = 0; // 遍历数组b的指针
int i = 0; // 记录当前存储位置
// 任一数组没有到达边界则进行遍历
while (p < aLen && q < bLen) {
// 如果a数组对应位置的值小于b数组对应位置的值
if (a[p] <= b[q]) {
// 存储a数组的值
result[i] = a[p];
// 移动a数组的遍历指针
p++;
}
else{
// 存储b数组的值
result[i] = b[q];
// 移动b数组的遍历指针
q++;
}
// 指向合并结果的下一个存储位置
i++;
}
// 如果a数组有剩余
while (p < aLen) {
// 将a数组剩余部分拼接到合并结果的后面
result[i] = a[p++];
i++;
}
// 如果b数组有剩余
while (q < bLen) {
// 将b数组剩余部分拼接到合并结果的后面
result[i] = b[q++];
i++;
}
}
// hash 算法 - 在一个字符串中找到第一个只出现一次的字符
char findFirstChar(char* cha)
{
char result = '\0';
// 定义一个数组 用来存储各个字母出现次数
int array[256];
// 对数组进行初始化操作
for (int i=0; i<256; i++) {
array[i] =0;
}
// 定义一个指针 指向当前字符串头部
char* p = cha;
// 遍历每个字符
while (*p != '\0') {
// 在字母对应存储位置 进行出现次数+1操作
array[*(p++)]++;
}
// 将P指针重新指向字符串头部
p = cha;
// 遍历每个字母的出现次数
while (*p != '\0') {
// 遇到第一个出现次数为1的字符,打印结果
if (array[*p] == 1)
{
result = *p;
break;
}
// 反之继续向后遍历
p++;
}
return result;
}
// 查找两个自视图共同父视图算法
- (NSArray *)findCommonSuperView:(UIView *)viewOne other:(UIView *)viewOther
{
NSMutableArray *result = [NSMutableArray array];
// 查找第一个视图的所有父视图
NSArray *arrayOne = [self findSuperViews:viewOne];
// 查找第二个视图的所有父视图
NSArray *arrayOther = [self findSuperViews:viewOther];
int i = 0;
// 越界限制条件
while (i < MIN((int)arrayOne.count, (int)arrayOther.count)) {
// 倒序方式获取各个视图的父视图
UIView *superOne = [arrayOne objectAtIndex:arrayOne.count - i - 1];
UIView *superOther = [arrayOther objectAtIndex:arrayOther.count - i - 1];
// 比较如果相等 则为共同父视图
if (superOne == superOther) {
[result addObject:superOne];
i++;
}
// 如果不相等,则结束遍历
else{
break;
}
}
return result;
}
- (NSArray *)findSuperViews:(UIView *)view
{
// 初始化为第一父视图
UIView *temp = view.superview;
// 保存结果的数组
NSMutableArray *result = [NSMutableArray array];
while (temp) {
[result addObject:temp];
// 顺着superview指针一直向上查找
temp = temp.superview;
}
return result;
}
//求一个无序数组的中位数
int findMedian(int a[], int aLen)
{
int low = 0;
int high = aLen - 1;
int mid = (aLen - 1) / 2;
int div = PartSort(a, low, high);
while (div != mid)
{
if (mid < div)
{
//左半区间找
div = PartSort(a, low, div - 1);
}
else
{
//右半区间找
div = PartSort(a, div + 1, high);
}
}
//找到了
return a[mid];
}
int PartSort(int a[], int start, int end)
{
int low = start;
int high = end;
//选取关键字
int key = a[end];
while (low < high)
{
//左边找比key大的值
while (low < high && a[low] <= key)
{
++low;
}
//右边找比key小的值
while (low < high && a[high] >= key)
{
--high;
}
if (low < high)
{
//找到之后交换左右的值
int temp = a[low];
a[low] = a[high];
a[high] = temp;
}
}
int temp = a[high];
a[high] = a[end];
a[end] = temp;
return low;
}