从有道云笔记复制过来的,没有任何格式了.不知道用markdown记录的笔记会不会附带格式,我还不想重新再写一遍,这就很尴尬
UIView和CALayer
calayer 负责显示内容 contents 实际显示的东西都是bitmap
面试题 为什么要讲显示和事件分开 设计模式中的单一模式原则
响应连
事件传递链 点击屏幕 -> 事件传递到UIApplication -> 传递到UIWindow -> 然后同过hittestwithEvent -> 这里又调用pointInsideWithevent来判断是否点击在UIwindow范围内 ->是就会遍历window的所有子视图通过hittest值不nil,倒叙遍历
图片渲染流程
1定点数据 2顶点着色器 3图元(确定形状 三角形还是畸形) 4裁剪空间 5光栅化(确定这个图元的像素) 6片元着色器(上色)
滑动优化方案
同时加载多张大图 可以监听runloop.一次runloop只加载一张图,这样内存和性能都会变好
cpu 可以降低对象的频繁创建销毁,或者是创建放到子线程,还有排版什么也放到子线程
gpu 异步绘制 离屏渲染 图层避免太过复杂
1 异步绘制
calayer 有个代理 displayalyer 是子线程,在这个代理里面设置bitmap的一些相关的问题,最后用bitmap合成CGImage 提交给calayer的content 回到主线程
2 避免离屏渲染 离屏渲染指的是GPU层面,在当前屏幕缓冲区外新开辟一个缓冲区来进行渲染操作
是指视图设置了一些属性,再未操作之前不能直接显示到屏幕上,就会触发离屏渲染, 圆角与masktobounds同时设置,设置阴影,mask,光栅化
之所以要避免是因为增加了GPu的工作,这样在cpu+gpu的工作量就大于每秒60次的刷新,就会卡顿
分类可以做什么
- 在不创建继承类的情况下实现对已有类的扩展
- 能对大型的类有效分解
- 将常用的相关方法进行分组
- 当已知某个类库的某个方法有BUG,我们无法直接修改源代码的时候,可以使用Category来替代这个已有类中某个方法的实体,从而达到修复BUG的目的
多个分类中有相同名字的函数方法,运行取决于最后编译的分类,
分类会覆盖掉宿主类的方法 ,因为运行的时候,所有的函数名在一个数组里,分类的函数靠前,
相同名字的分类 会编译报错,类似于宿主类两个同名属性
扩展
与分类区别 分类是运行时确定,扩展是编译时确定
扩展不能为系统类添加方法
关联对象
所有的关联对象都存放在一个全局的容器当中 在AssociationsHashMap中存储
通知
通知是广播形式 一对多
Post一个通知后,经由通知中心广播给所有的observe
实现一个通知方法, 单利 维护 一个字典, key为通知name ,value存obsver对象以及相应通知的方法名和一些其他参数
KVO观察者模式
观察者模式的内部实现
在对一个对象的属性实现监听之后,这个对象,会动态生成一个派生类,然后将源对象的isa指针指向新生成的派生类,然后重写派生类的set方法.然后来达到通知观察者的模式
当对象走了addobserver之后,类名就发生改变
通过kvc方式将对象属性进项改变,kvo依然能够监听到 setValue””forKey”keypath”
用_(成员变量)下划线方式修改属性 监听不到,因为下划线不会setter
修饰符
原子性 线程安全 赋值取值不会被线程调度器中断
assign 可以修饰基本类型与对象类型 assign 引用计数不加1 weak不能修饰基本类型
copy 只有当被拷贝对象为不可变,拷贝出对象为不可变的时候才是浅拷贝
RunTime相关
Objc_object
所有的id对象在runtiome中 都是Objc_Object
Class类在runtime中是Objc_class这样数据结构 objc_class 继承与Objc_objec 所以class也是一个对象
isa指针
isa指针是isa_t这个结构体
面试题: isa 含义 ,答案如下
isa_t 有指针型的 isa 和非指针的isa
指针型的isa代表class的地址 非指针型的isa 部分带表class地址(其余多出来的部分来存储别的东西来达到节约内存的目的 寻址过程中用不到64位 用不满,还有一部分空余)
然后class的指针指向其元类对象 (MetaClass)
类对象和元类对象
类对象存储实例方法列表信息
元类对象存储类方法列表信息
catch_t
用于快速查找方法函数(一个缓存,可以不用去方法列表里去逐一便利,提高运行速度)
是一个增量扩展的哈希表结构(哈希表实现数据结构是为了提高查找效率)
是局部性原理的最佳应用 -> 局部性原理是 把调用率最高的方法放到缓存里
缓存查找 哈希查找 通过方法选择器 sel 然后通过算出key找到IMP函数指针返回给调用者
Class_data_bits_t
主要是对Class_rw_t的封装 (rw读写)代表了对类的读写的信息还有对class_ro_t的封装
Method_t
就是一个函数
函数四要素 名称 参数 返回值 函数体
metdthod_t结构体中含有 SEL name : 代表函数名称 const char*type参数与返回值的组合 IMP:imp 函数体(一个无类型的函数指针)
Runtime整体数据结构
方法调用顺序
实例方法,当调用一个对象的实例方法时,会通过isa指针找到对象的类对象,然后类对象查缓存方法列表catch_t,没有就去找全部方法列表,如果没有,就去类对象的父类查找,一直到基类.
类方法会通过isa指针找到元类对象,然后同上,最后区别是,当元类对象基类也没有此方法时,会通过isa指针找到类对象的基类,查找是否有对应的实例方法
方法查找:缓存查找是哈希查找通过sel方法选择器生成一个key然后找到对应imp的函数指针返回,如果缓存中没有就去方法列表中找,对于已经排序的方法列表通过二分查找,未排序的通过遍历查找,都没有就通过superclass指针找到父类对象,然后在父类对象中重复上一操作
面试题
phone继承于mobile 在phone中调用[self class]和调用[super class] 打印是什么
打印结果都是phone 在[self class]会通过函数objcMessageSend这个函数调用,消息传递过程中 self为类对象objc_Object ,objcet isa指针指向类对象 在类对象catch方法中找对应的函数,找不到就找superclass中的方法,然后一直到基类nsobject中找到方法
[super closs] 会调用objcMessageSendSuper函数,调用者虽然是super 在运行时中表现为objc_super 然后super中有一个recever,表示为消息的接受者,其实还是self ,然后查找方法是会直接找objc_objcet的类对象的super class ,也就是跳过当前类对象去向上查找,所以还是phone.
Method_Swizzling 方法交换
动态添加方法
classAddMethod 消息转发机制 resolveInstanceMethodSel 参数是方法选择器@selector 如果有函数体返回值,就结束消息转发 没有就就可以动态添加方法,
Dynamic 动态方法
就是在编译的时候不确定某个方法的函数体,运行时才决议
面试题
1 [obc func]和objc_sendMessage有什么关系
在编译后 这个方法就变成了objc_sendMessage 参数就是obj和func选择器
2runtime如果通过@selector找到对应的IMP地址的
通过实例对象拿到类对象,在类对象的方法缓存中用哈希查找发查找,没有就通过super………
3能否向编译后的类添加实例变量(不能,这个时候),可以在动态添加类的时候添加实例变量(动态添加类,点一个按钮创建一个类)
内存管理
- isa 64位用一部分存地址,一部分存else
- 散列表
散列表
sideTables内含多个sidetable(64个) 是一个哈希表 具有多个sideTable原因是性能问题 因为会有成千上万个对象,如果在一个sideTable中操作,会加锁解锁保证线程安全,性能问题 所以用分离(多个表)
sideTable 结构 自旋锁 引用计数表 弱引用表
引用计数表和弱引用表 也都是一个哈希表 使用哈希提高查找效率
引用计数通过对指针的函数查找找到对应的引用计数
弱引用通过指针的哈希函数查找到对象的地址
面试题: retain实现原理 通过对sidetables 哈希查找对象引用计数所在的sideTable,然后在sideTable中同样用哈希查找找到对应的引用计数
对象释放 weak指针的变量会自动置为nil 因为dealloc中会调用 weak_clear(弱引用清除)的方法 通过对象指针哈希查找到"弱引用数组",如果这个数组中有,遍历所有对象 ,将指针置为nil
ARC
arc是有编译器llvm在自动位置插入代码和runtime协作管理内存 arc 不能手动调用引用计数的方法 mrc可以
自动释放池
1 组成结构 是以栈为节点的双线链表的形式组合而成
@autoreleasePool{}
编译之后的形式为
objc_autoreleasePoolPush
{中间为代码块
}
objc_autoreleasePoolPop
每一个自动释放池都是有多个AutoreleasePoolPage 以双向链表形式组成的
AutoreleasePoolPage是一个栈,当自动释放池初始化的时候objc_autoreleasePoolPush方法 会自动向栈顶插入一个哨兵对象(nil的别称) 然后再陆续添加{}中的需要自动释放的对象 然后 调用objc_autoreleasePoolPop ,回从栈顶查找一直找到上一个哨兵对象的位置,对着中间的所有对象执行release操作
2 和线程一一对应
问题1 在viewdidiload中创建一个数组,这个数组什么时候回被释放掉 runloop将要结束的时候
2 autoRelease 多层嵌套就是底层 增加一个哨兵对象
3 什么时候手动创建autoReleasePool 在循环创建大量临时对象的时候
https://www.jianshu.com/p/32265cbb2a26 自动释放池
循环引用
一共有三种循环引用
1自循环 一个对象有一个强引用成员变量 这时将这个成员变量赋值为对象自身,就造成的自循环
2相互循环 对象A有个强引用B ,B里还有个强引用A
3多循环 A->B->C->A
如何破除循环引用
1 主动避开循环引用 weak关键字 2 在适当的时机破环
具体实现方法 __weak ( __block 在arc下依然会增加引用计数 所以还需要手动破环)
项目中遇到的循环引用问题 以及解决方案 调用timerInvalid
NSTImer 一个VC里弱引用timeer , timer的定时器回调会对对象强引用,再有Runloop回对timer强引用这样就相当于runloop也强引用了对象 所以问题不是timer和vc之间的强引用,在于runloop一直存在所以timer一直存在所以对象释放不掉
https://www.jianshu.com/p/a1e0dd905a69 NSTimer
解决方法1 选择合适的时机手动释放timer 调用[self.timer invalidate];
2. timer使用block方式添加Action 避免timertarger强引用vc
3 用一个中间对象 VC弱引用中间对象 中间对象持有Timer 这样 中间对象释放 指针会自动置为nil
Block相关
什么是Block? Block是将函数 上下文封装起来的对象
变量截获 问题
局部变量 block编译后的结构体中会自动新增类型名称相同的成员变量 相当于swift值类型,静态的局部变量(static)是对其指针截获
静态全局变量、全局变量 不产生截获
block截获象类型的变量所有权和修饰符一并截获的,并存储自身的结构体中作为成员变量
如下图
面试题
此题 将multiplier加上__block后 结果为8
__block关键字什么时候使用? 一般情况下对被截获的变量进行赋值的操作需要加__block修饰符 (赋值操作 array = [new arr]属于赋值 array.append(E)不是赋值操作)
这道题在于__Block在ARC下会造成引用计数+1 所以 blockSelf 强引用了self 是一个大闭环引用, 所以在 block内部将blockSelf置位nil
block 为什么会造成循环引用 block截获当前对象实例变量的时候会对当前对象造成强引用,然后当前对象也强引用block
https://www.jianshu.com/p/c36f1bb92b03
多线程相关
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新线程中执行任务,具备开启新线程的能力。不一定开辟新线程 (并发队列开多条,系统决定,穿行开一条)
并发: 多个任务同事执行(只有在异步才有效果) 串行:任务顺序执行
只有异步函数+并发队列才会并发执行 其余都是串行执行
viewdidload 中调用sync…dispathcGetmain 会造成死锁 循环等待 viewdidload在主线程执行中 会调用block,等block调用完成之后,viewdidiload 才会结束,而block也是在主队列中,队列的性质就是先进先出,必须要等didload结束才会走,所以相互等待 如果队列不是主队列是不会死锁的,因为两者不在同一队列.
栅栏调用 sync 和async 的区别是sync是执行完在返回 async是先返回再执行
dispatch_apply 快速迭代 就是异步线程. 一般io操作文件移动什么的耗时操作 会用到
Ios中的锁
@synchronized 互斥锁 一般在创建单利对象的时候 来保证在多线程环境下 所创建的对象是惟一的 (缺点比较慢)
atomic 属性关键字 对修饰的对象进行原子操作(赋值可以,使用无效) 就是对setter方法枷锁
OSSpinLock 自旋锁 循环等待访问,不释放自身资源
NSLOck
NSRecursiveLock 递归锁
semaphore信号量
面试题 ios几种多线程特点
GCD 实现简单的线程同步 子线程分配 ,多读单写
NSOperation 特点是对任务的状态进行控制 sdweb和af都用的这个
NSThread 常驻线程
Runloop
什么是runloop 是通过内部维护的 "事件循环" 来对 消息/事件处理的一个对象
没有消息处理时休眠以节省资源*(内核态) 有消息需要处理时 唤醒(用户态) 用户态时应用层面 内核态时系统层面
数据结构
NSRunloop 包装了CFFunloop,提供了面向对象的api
rp循环机制
RL与线程关系
线程与runloop是一一对应的,但是自己创建的线程是默认没有创建的
问题1 main函数为什么能保持不退出
main函数中 会调用uiapplactionMian函数 会启动主线程的runloop ,runloop是事件循环的一个对象 ,可以做到内核态到用户态的切换
2 tableview滑动的时候 定时器 怎样才能一直起作用 调用吃饭runloop addtimer 因为rl中有好多个mode mode里面有source timer observer ,同一时期只执行一个mode ,我们需要将这个timer加入到这个虚 拟的commonmode中
3如何实现常驻线程
创建一个线程对应的runloop 第二 向mode中添加source timer obser等内容 第三部 调用run ,释放的时候只要remove scorce…就可以 注意!运行模式和资源添加模式要是同一个
4 子线程数据回来更新UI不打断用户滑动操作
将子线程数据返回给子线程的时候,将数据提交到default模式,滑动式track模式,这样用户停止滑动的时候就会自动切换为def 就会处理数据
https://www.jianshu.com/p/f3079ea36775
nsoperation block等只有加入operationquene里面才会并发执行开辟线程,或者追加addExecution
operation队列可以暂停恢复取消最大并发等特点 操作依赖,operA依赖于operB ,B执行后再执行A .还可以监听operation 的执行完毕
网络
http 请求行: 方法字段 (post get) URL 协议版本 头部字段 (key value ) 实体主题
响应报文 版本 状态码 状态码描述 首部字段 响应主体
HTTP请求方式有几种
GET POST HEAD PUT DELETE OPTIONS
GET POST 区别
get参数是?后拼接到url里面 post是在body里 get参数有长度限制 post没有
get 不会对server端进行操作 幂等性(执行一次和多次效果是一样的) 可缓存 post全返
HTTP链接建立流程 三次握手四次挥手
抓包原理 中间人共计 代理服务
对称加密 加密和解密是同一个秘钥 非对称加密 加密和解密要分开为公钥和私钥.或者私钥公钥
设计模式
六大设计原则 : 1 单一模式原则 比如view和layer 各分其职 一个类只负责一件事,否则就代码臃肿,可读性差
2 依赖倒置原则 是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合
3 开闭原则 对组件功能的扩展式开放的,对组件内容的修改式关闭的
4 里氏设计原则 父类可以被子类替换,而原有功能不受任何影响 (KVO)
5 接口隔离原则 使用多个专门的协议,而不是一个臃肿的协议 比如UITable的collec和datasource
6迪米特法则 一个对象应该对其他对象尽可能少了解 高内聚低耦合
责任链模式: 链表结构,参考famey滤镜实现
桥接模式: 将抽象部分与它实现部分分离,使它们都可以独立地变化。https://www.jianshu.com/p/7d8a2aae0041
适配器模式: 将一个接口转换希望的另一个接口,使接口不兼容的那些类可以一起工作 也叫包装模式 有一个古老的类,进行修改的时候, new一个新类,将老类作为成员变量.然后对老类操作,封装啥的
单例模式: 要重写allocwithzone 返回shareinstance 来防止外界不使用share方法初始化 重写copy 防止外界copy单利
命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化 撤销重做
设计一个图片缓存框架 manager 内存 磁盘 网络三方面 然后解码相关,压缩解压缩.注意事项 缓存条数,淘汰策略(时长,切换后台等,lru算法(最近多少分钟没有用过)) 网络方面最大并发任务啥的
时长统计
解耦 依赖注入 是一个将行为从依赖中分离的技术 一般就是将A的实力b不在A中创建 而是在A的构造函数中将b作为参数传入.