iOS面试(三)
1.MVC具有什么样的优势,各个模块之间怎么通信,比如Button后怎么通知Model
MVC是一种设计思想,是一种架构模式,是一种把应用所有类组织起来的策略。他们把程序分成3块。
M: 存储数据,处理程序的逻辑部分。
C: 控制你的model如何呈现在屏幕上。他需要数据的时候会去Model获取数据。需要视图显示的时候会通知视图显示。它是model和view之间的桥梁。它使model和view解耦。
V:视图的展示。根据model来创建视图。
Controller to Model
可以单向通信。控制器需要知道model中的一切。还要同model完全的通信的能力。
Controller to View
可以直接进行单向通信,Controller通过View来布局界面
Model to View
永远不要直接通信。Model是独立于UI的存在的。View 通过Controller获取Model数据。
View to Controller
View不能对Controller知道的太多。隐藏他们使用间接通信的方式
- targer action
- delegate
- dataSource
Model to Controller
Model独立于UI存在的。Model无法直接访问Controller这样就绑死了。通过Notification&&KVO
优点:
- 低耦合性
- 开发分工
- 有利于组件重用
- 可维护性
2. UITableView 的相关优化
从几个角度去分析
-
基础优化(高度缓存,cell重用)
- 正确使用TableViewCell的缓存机制
- 提前计算机cell的高度。把cell的高度缓存起来。因为height forRow 这个方法会频繁的调用所以不适合存在大量的计算。
- 下载图片避免阻塞主线程
- 按需求加载。设置一个需要加载的Arr。超过加载范围的cell清除显示的内容。会使得滚动的范围内出现大量的留白。但是会仅绘制目标的cell
- 减少subViews的数量
- 尽量重用开销比较大的对象
- 减少复杂的计算
- 不要动态add或者remove子控件
-
学会使用分析工具
instruments中的Core Animation instrument
instruments中的OpenGL ES Drive instrument
Xcode的debug的一些列的调试工具
-
异步绘制
- 开启一条异步线程绘制cell
- 绘制Cell使用的是CALayer而不是UIView
- UIView的绘制是建立在Core Graphic上,使用的是CPU。而CALayer的绘制是建立在Core Animation上,CPU,GPU均可。UIView的绘制是从下到上一层层的绘制。然后渲染。CALayer处理的是Texture,利用GPU的Texture Cache和独立的浮点数计算单元加速文理的处理。
KVO,Notification,delegate各自的优缺点
- delegate 委托
- 通知中心 NotificationCenter
- KVO键值观察
delegate的优势:
- 严格的语法,协议清晰
- 不会遗忘。
- 可以有返回值,delegate可以反馈信息给Controller
- 出现问题可以很好的定位
缺点:
代码较多
NotificationCenter优点:
- 1对多
- 使用简单
缺点:
- 无法对发出通知的路径进行追踪
- 当监听者销毁的时候需要取消监听
- 发送者无法确定监听者收到了消息
KVO
- 使用简单可以实现2个对象同步
- 因为使用字符串可以嵌套观察
确定 - 字符串不会报警告
- 如果一个controller观察多个属性 需要写很多的if else{}
如何手动通知 KVO
控制器中重写某个属性的set方法。在控制器中监听。
Objective-C 中的copy方法
对象的复制就是复制一个对象作为副本。它会开辟一块的内存来存储副本对象。就像文件复制一样。源对象和副本对象是两块内存。对象如果想要实现复制功能。必须遵循NSCopying协议或者NSMutableCopying协议。
copy:产生的对象是不可变的
mutableCopy产生的对象是可以变的。
深拷贝和浅拷贝:
浅拷贝指的是复制对象本身,对象的属性,包含的对象不做复制
深拷贝既指复制对象本身,对象的熟悉也会复制一份
Foundation中支持复制的类,默认是浅复制
深浅拷贝和retain之间的关系
- 对一个不可变对象指向copy操作,相当于retain
- 当我们使用mutableCopy得到的都是一个可变对象
- copy一个可变对象得到的对象不可变。
runtime 中,SEL 和 IMP 的区别
方法名 SEL -- 方法的名称
一个IMP 指向该方法的具体实现的函数指针,说白了IMP就是方法的实现。
autoreleasePool的使用场景和原理
autoreleasePool 是OC的一个类,他不是天生就有的,而是我们手动创建的。当我们创建iPhone工程的时候,系统会为我们创建一个AutoreleasePool.这个pool写在main函数中。它的内部有一个可变数组 用来存储被标记autorelease的对象。
内存管理一直是重中之重,尽管现在已经是ARC时代,但是理解ARC依然非常重要,只有了解了Autorelease的原理,我们才能算是理解了Objective-C的内存管理机制。
autoreleased 对象什么时候释放
autorelease本质就是延迟调用release,那autoreleased的对象到底什么时候释放
我们都注意到了AutoreleasePool release本质是AutorelesePage::pop。那么本质执行了什么。
- 每个线程的autoreleasePool本质就是一个指针的堆栈。
- 每一个指针代表一个需要release对象或者POOL_SENTINEL(哨兵对象,代表AutoreleasePool的边界)
- 一个pool token 就是这个pool对于的POOL_SENTINEL对应的内存地址。当这个pool被pop之后,所有内存地址在 pool token 之后的对象都会被 release
- 这个堆栈 被划分成一个以page为节点的双向链表。pages会动态的增加和删除
- Thread-local storage(线程局部存储)指向hotPage,即新增加的autorelease所在的page.
AutoreleasePage内部结构
- magic AutoreleasePoolPage 的结构是否完整
- next指向新增的autorelease对象的下一个地址。初始化的时候指向begin
- thread 当前线程
- parent 指向父结点,第一个结点的 parent 值为 nil
- child指向子节点,最后一个节点的child值为nil
next = begin() 代表autoreleasePoolPage为空。当 next == end() 时,表示 AutoreleasePoolPage 已满。
@autoreleasepool{}
转换为C++代码
extern "C" __declspec(dllimport)
void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport)
void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
__AtAutoreleasePool()
{atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
本质就是结构体构造的时候调用构造方法执行push操作。析构的时候执行pop函数
因此autoreleasepool运行的过程可以理解为objc_autoreleasePoolPush()
,[obj autorelease]
,objc_autoreleasePoolPop
AutoreleasePoolPage 的 push 函数的作用和执行过程。一个Push操作其实就是创建一个新的autoreleasepool。对应的就是往AutoreleasePage的next插入一个哨兵对象。并且返回这个哨兵对象的地址做为pop时候的返回值来使用。
push函数通过调用autoreleaseFast 函数来执行具体的插入操作。
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
分为3种情况。
- 存在&&不满 直接插入当前AutorelesePage的next位置
- 满了 创建一个新的autoreleasePage
- 不存在 新创建一个
每调用一次 push 操作就会创建一个新的 autoreleasepool ,即往 AutoreleasePoolPage 中插入一个 POOL_SENTINEL ,并且返回插入的 POOL_SENTINEL 的内存地址
Autorelease操作
与push操作类型。Push操作是插入一个哨兵对象并且返回哨兵对象的地址供给pop对象使用。而autorelease插入的则是一个具体的autorelease对象。
pop操作
同理,前面提到的objc_autoreleasePoolPop(void*)也是调用的AutoreleasePoolPage的pop函数。Pop函数的入参就是Push对象的返回值。也就是POOL_SENTINEL的内存地址,即pool token。这个地址之后的所有的对象进行release操作。知道pool token所在的page的next指向pool token为止。
可能会用到autoreleasePool对象的地方
- 编写的程序不是基于UI框架
- 创建了一个辅助线程
- 编写的循环中创建了大量的临时变量
block 为什么会有循环引用
Block块会对其中的所有成员进行强引用,如果里面的成员也对它进行强引用的话,会形成一个闭合的环。形成循环引用
NSOperation 和 GCD 的区别
GCD是基于C的底层Api,NSOpertaion属于Objective-C类。iOS首先引入的NSOperation,iOS4之后引入的GCD和NSOpertaionQueue。相对GCD。NSOperation优势
- NSOperation拥有多个函数可以调用
- NSOperationQueue可以设置多个Operation的依赖关系
- 通过KVO可以监听NSOperation的isExecuted(是否执行),isCancel(是否取消),isFinish(是否完成)
- NSOperationQueue可以方便的管理并发。Operation之间的优先级。
GCD主要与block结合使用。代码简洁高效。
GCD也可以实现复杂的多线程应用,主要是建立个个线程时间的依赖关系这类的情况,但是需要自己实现相比NSOperation要复杂。
具体使用哪个,依需求而定。从个人使用的感觉来看,比较合适的用法是:除了依赖关系尽量使用GCD,因为苹果专门为GCD做了性能上面的优化。
GCD技术是一个轻量级的,底层实现隐藏的神奇技术。通过gcd和block轻松实现多线程编程。有时间gcd比其他的多线程编程更有效当然,有时候GCD不是最佳选择,另一个多线程编程的技术 NSOprationQueue 让我们能够将后台线程以队列的形式依序执行,并提供了更多操作的入口。这和GCD的实现有些类似。
两者的区别
- GCD是C语言构筑的APi.而NSOpertaionQueue是NSObject对象。GCD由Block构建成任务,这是一个轻量级的数据结构。而Opertaion作为对象为我们提供了更多的选择,
- 在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务
- NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行
- KVO监听NSOperation的完成取消执行的情况。
- 在NSOperation中,我们能够设置任务的优先级。GCD只能设计队列的优先级。
- 我们可以写一个自定义的类继承自NSOperation 提高代码的复用度。
如何设计图片缓存
- 提供相应速度
- 节省流量
- 提高用户体验。
提高响应速度:因为图片一旦缓存在本地之后,那么本地IO数据的读取,远比网络中得IO读取效率要高的多。所以可以提高响应速度
节省流量: 一张图片在某些情况下,只加载一次,之后便不会重新加载,减少了网络流量。减少流量肯定是必然的。介于国内的流量费用这么贵,是肯定必要的
提高用户体验: 本地存储。
如何设计一个网络控件
交互方式
显示样式
数据使用
-
选择正确的初始化方式
- 分别在awakeFromNib和initWithFrame分别调用setUp
-
调整布局的时机
- frame的话在layoutSubView/viewDidLayoutSubView时更改
- 如果使用AutoLayout可以直接写
-
正确的使用touches方法
- 尽量使用手势
-
drawRect和CALayer的使用与动画
- 绘制完成之后调用setNeedDisplay完成绘制
UIControl与UIButton
更友好的支持xib
IBInspectable 。IB_DESIGNABLE方便xib可视化-
不规范图形和事件的触摸范围
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
-
合理使用KVO
- 自定义控件内部使用KVO,监听自身各种属性的变化