MVC 具有什么样的优势,各个模块之间怎么通信,比如点击 Button 后 怎么通知 Model?
MVC 是一种设计思想,一种框架模式,是一种把应用中所有类组织起来的策略,它把你的程序分为三块,分别是:
M(Model):实际上考虑的是“什么”问题,你的程序本质上是什么,独立于 UI 工作。是程序中用于处理应用程序逻辑的部分,通常负责存取数据。
C(Controller):控制你 Model 如何呈现在屏幕上,当它需要数据的时候就告诉 Model,你帮我获取某某数据;当它需要 UI 展示和更新的时候就告诉 View,你帮我生成一个 UI 显示某某数据,是 Model 和 View 沟通的桥梁。
V(View):Controller 的手下,是 Controller 要使用的类,用于构建视图,通常是根据 Model 来创建视图的。
要了解 MVC 如何工作,首先需要了解这三个模块间如何通信。
Controller to Model
可以直接单向通信。Controller 需要将 Model 呈现给用户,因此需要知道模型的一切,还需要有同 Model 完全通信的能力,并且能任意使用 Model 的公共 API。
Controller to View
可以直接单向通信。Controller 通过 View 来布局用户界面。
Model to View
永远不要直接通信。Model 是独立于 UI 的,并不需要和 View 直接通信,View 通过 Controller 获取 Model 数据。
View to Controller
View 不能对 Controller 知道的太多,因此要通过间接的方式通信。
Target action。首先 Controller 会给自己留一个 target,再把配套的 action 交给 View 作为联系方式。那么 View 接收到某些变化时,View 就会发送 action 给 target 从而达到通知的目的。这里 View 只需要发送 action,并不需要知道 Controller 如何去执行方法。
代理。有时候 View 没有足够的逻辑去判断用户操作是否符合规范,他会把判断这些问题的权力委托给其他对象,他只需获得答案就行了,并不会管是谁给的答案。
DataSoure。View 没有拥有他们所显示数据的权力,View 只能向 Controller 请求数据进行显示,Controller 则获取 Model 的数据整理排版后提供给 View。
Model 访问 Controller
同样的 Model 是独立于 UI 存在的,因此无法直接与 Controller 通信,但是当 Model 本身信息发生了改变的时候,会通过下面的方式进行间接通信。
Notification & KVO一种类似电台的方法,Model 信息改变时会广播消息给感兴趣的人 ,只要 Controller 接收到了这个广播的时候就会主动联系 Model,获取新的数据并提供给 View。
从上面的简单介绍中我们来简单概括一下 MVC 模式的优点。
1.低耦合性
2.有利于开发分工
3.有利于组件重用
4.可维护性
两个无限长度链表(也就是可能有环) 判断有没有交点
给定单链表,检测是否有环。如果有环,则求出进入环的第一个节点。
判断单向链表是否有环,可以采用快指针与慢指针的方式来解决。即定义一个快指针fast和一个慢指针slow,使得fast每次跳跃两个节点,slow每次跳跃一个节点。如果链表没有环的话,则slow与fast永远不会相遇(这里链表至少有两个节点);如果有环,则fast与slow将会在环中相遇。
然后获取环的入口点方法如图所示:
给定两个单链表(链表自身无环),检测两个链表是否有交点,如果有返回第一个交点。
检测两个单链表是否有交点是很容易的,因为如果两个链表有交点,那么这两个链表的最后一个节点必须相同,所以只需比较最后一个节点即可。但是这种方案是无法求出第一个交点的,所以需要另辟蹊径。另外,判断是否有交点可以转换成链表是否有环的问题。让一个链表的最后一个节点指向第二个链表的头结点,这样的话,如果相交,则新的链表是存在环的,并且交点便是环的入口点。
给定两个单链表(不确定是否带环),判断是否有交点
先判断两个链表是否带环;
i).如果两个都不带环,可以用:判断两个无环单链表是否有交点。
ii).两个都带环:找到两个入环点r1,r2,环1的入环点为r1,从r1开始遍历一圈,每个结点如r2比较
iii).一个带环一个不带环,则肯定不相交。
给定单链表头结点,删除链表中倒数第k个结点
这个问题的关键是需要获取倒数第k个节点。如何获取呢?这里,需要两个指针p和q,p指向头结点,q指向距离头结点为k的节点。这样p和q每次遍历一个节点,当q遍历到末尾的时候,p指向的节点即为倒数第k个节点,然后删除即可。
只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点;
只给定单链表中某个结点p(非空结点),在p前面插入一个结点。
上诉两种方法的原理都是一样的。
UITableView 的相关优化
1.基础的优化准则(高度缓存, cell 重用...)
滚动很快时,只加载目标范围内的Cell,这样按需加载,极大的提高流畅度。提前计算并缓存 好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法。异步绘制,遇到复 杂界面,遇到性能瓶颈时,可能就是突破口。滑动时按需加载,这个在大量图片展示,网络加 载的时候很管用!(SDWebImage已经实现异步加载,配合这条性能杠杠的)。
除了上面最主要的三个方面外,还有很多几乎大伙都很熟知的优化点:
正确使用reuseIdentifier来重用Cells
尽量使所有的viewopaque,包括Cell自身
尽量少用或不用透明图层
如果Cell内现实的内容来自web,使用异步加载,缓存请求结果
减少subviews的数量
在heightForRowAtIndexPath:中尽量不使用cellForRowAtIndexPath:,如果你需要用到 它,只用一次然后缓存结果
尽量少用addView给Cell动态添加View,可以初始化时就添加,然后通过hide来控制是否显
2.学会使用调试工具分析问题
//www.greatytc.com/p/af6b095aaaf3
http://blog.csdn.net/icetime17/article/details/46709975
KVO、Notification、delegate 各自的优缺点,效率还有使用场景
在开发ios应用的时候,我们会经常遇到一个常见的问题:在不过分耦合的前提下,controllers间怎么进行通信。在IOS应用不断的出现三种模式来实现这种通信:
1.委托delegation;
2.通知中心Notification Center;
3.键值观察key value observing,KVO
delegate的优势:
1.很严格的语法,所有能响应的时间必须在协议中有清晰的定义
2.因为有严格的语法,所以编译器能帮你检查是否实现了所有应该实现的方法,不容易遗忘和出错
3.使用delegate的时候,逻辑很清楚,控制流程可跟踪和识别
4.在一个controller中可以定义多个协议,每个协议有不同的delegate
5.没有第三方要求保持/监视通信过程,所以假如出了问题,那我们可以比较方便的定位错误代码。
6.能够接受调用的协议方法的返回值,意味着delegate能够提供反馈信息给controller
delegate的缺点:
需要写的代码比较多
有一个“Notification Center”的概念,他是一个单例对象,允许当事件发生的时候通知一些对象,满足控制器与一个任意的对象进行通信的目的,这种模式的基本特征就是接收到在该controller中发生某种事件而产生的消息,controller用一个key(通知名称),这样对于controller是匿名的,其他的使用同样地key来注册了该通知的对象能对通知的事件作出反应。
notification的优势:
1.不需要写多少代码,实现比较简单
2.一个对象发出的通知,多个对象能进行反应,一对多的方式实现很简单
缺点:
1.编译期不会接茬通知是否能被正确处理
2.释放注册的对象时候,需要在通知中心取消注册
3.调试的时候,程序的工作以及控制流程难跟踪
4.需要第三方来管理controller和观察者的联系
5.controller和观察者需要提前知道通知名称、UserInfo dictionary keys。如果这些没有在工作区间定义,那么会出现不同步的情况
6.通知发出后,发出通知的对象不能从观察者获得任何反馈。
KVO
KVO是一个对象能观察另一个对象属性的值,前两种模式更适合一个controller和其他的对象进行通信,而KVO适合任何对象监听另一个对象的改变,这是一个对象与另外一个对象保持同步的一种方法。KVO只能对属性做出反应,不会用来对方法或者动作做出反应。
优点:
1.提供一个简单地方法来实现两个对象的同步
2.能对非我们创建的对象做出反应
3.能够提供观察的属性的最新值和先前值
4.用keypaths 来观察属性,因此也可以观察嵌套对象
缺点:
1.观察的属性必须使用string来定义,因此编译器不会出现警告和检查
2.对属性的重构将导致观察不可用
3.复杂的“if”语句要求对象正在观察多个值,这是因为所有的观察都通过一个方法来指向
KVO有显著的使用场景,当你希望监视一个属性的时候,我们选用KVO
而notification和delegate有比较相似的用处,
当处理属性层的消息的事件时候,使用KVO,其他的尽量使用delegate,除非代码需要处理的东西确实很简单,那么用通知很方便
如何手动通知 KVO
重写Controller里面某个属性的setter方法,联动给View赋值,使用Controller监控Model里面某个值的变化,在controller的dealloc函数中用一行代码了结:removeObserver。
Objective-C 中的 copy 方法
Objective-c中对象的Copy、MutableCopy、浅拷贝、深拷贝
runtime 中,SEL 和 IMP 的区别
方法名 SEL – 表示该方法的名称;
一个 types – 表示该方法参数的类型;
一个IMP – 指向该方法的具体实现的函数指针,说白了IMP就是实现方法。
autoreleasepool 的使用场景和原理
Autorelease Pool全名叫做NSAutoreleasePool,是OC中的一个类。autorelease pool并不是天生就有的,你需要手动的去创建它。一般地,在新建一个iphone项目的时候,xcode会自动地为你创建一个Autorelease Pool,这个pool就写在Main函数里面。在NSAutoreleasePool中包含了一个可变数组,用来存储被声明为autorelease的对象。当NSAutoreleasePool自身被销毁的时候,它会遍历这个数组,release数组中的每一个成员(注意,这里只是release,并没有直接销毁对象)。若成员的retain count 大于1,那么对象没有被销毁,造成内存泄露。默认的NSAutoreleasePool 只有一个,你可以在你的程序中创建NSAutoreleasePool,被标记为autorelease的对象会跟最近的NSAutoreleasePool匹配。可以嵌套使用NSAutoreleasePool。
Objective-C Autorelease Pool 的实现原理
RunLoop 的实现原理和数据结构,什么时候会用到
block 为什么会有循环引用
产生原因:如下图所示,对象A和对象B相互引用了对方作为自己的成员变量,只有自己销毁的时候才能将成员变量的引用计数减1。对象A的销毁依赖于对象B的销毁,同时对象B销毁也依赖与对象A的销毁,从而形成循环引用,此时,即使外界没有任何指针访问它,它也无法释放。
循环引用示例图
多个对象间依然会存在循环引用问题,形成一个环,在编程中,形成的环越大越不容易察觉,如下图所示:
多个对象引用示例图
解决方法:
事先知道存在循环引用的地方,在合理的位置主动断开一个引用,是对象回收,使用弱引用的方法。
使用 GCD 如何实现这个需求:A、B、C 三个任务并发,完成后执行任务 D。
NSOperation 和 GCD 的区别
GCD是基于c的底层api,NSOperation属于object-c类。ios 首先引入的是NSOperation,IOS4之后引入了GCD和NSOperationQueue并且其内部是用gcd实现的。
相对于GCD:
1,NSOperation拥有更多的函数可用,具体查看api。
2,在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
3,有kvo,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)。
4,NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
GCD主要与block结合使用。代码简洁高效。
GCD也可以实现复杂的多线程应用,主要是建立个个线程时间的依赖关系这类的情况,但是需要自己实现相比NSOperation要复杂。
具体使用哪个,依需求而定。从个人使用的感觉来看,比较合适的用法是:除了依赖关系尽量使用GCD,因为苹果专门为GCD做了性能上面的优化。
CoreData 的使用,如何处理多线程问题
如何设计图片缓存?
有没有自己设计过网络控件?
怎么判断某个 cell 是否显示在屏幕上
进程和线程的区别
TCP 与 UDP 区别
这是两个工作在TCP/IP协议传输层的两个不同的协议,是用来传输数据用的。
TCP:Transfer Control Protocol,传输控制协议。
这是一个全双工的、面向连接的、可靠的并且是精确控制的协议。
主要是用在那些实时性不强、但要求不能出错的应用。比如说,网页的浏览、文件的下载(不是BT、电驴下载)、邮件的收发等场合,就需要TCP协议进行传输(因为不会出错)。
当然,它在网络方面的开销是昂贵的。
UDP:User Datagram Protocol,用户数据报协议。
这是一个不可靠的传输协议。因为它不排序所要发送的数据段、不关心这些数据段到达目的方的顺序(所以它才不可靠),所以它在网络的开销要比TCP小很多。因此UDP适合用在那些实时性强、允许出错的场合。
比如说:即时通信(MSN、QQ),视频,语音等方面
TCP 流量控制
滑动窗口机制
比如发送端能发送5个数据,接收端也能收到5个数据,给个确认(ack)给发送端,确认我收到5个数据。如果网络通信出现繁忙或者拥塞的时候,接收端只能收3个数据,接受端给个确认我只能收3个数据,那么发送端就自动调整发送的窗口为3,当线路又恢复通畅的时候,接受端又可以受到5个数据,那它会给确认给发送端,告诉它我的窗口为5,那发送端就把窗口又调整会5,这样进行流量控制的
2、比如说发送端窗口为3,发送到接收端,接收端的接收窗口为5的话,接受数据,并且会给发送端一个ack(确认)告诉发送端我的窗口为5,发送端收到确认后会把自己的发送端窗口调整为5~~这样就可以加速数据传输了
数组和链表的区别
从逻辑结构来看
1. 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;数组可以根据下标直接存取。
2. 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项,非常繁琐)链表必须根据next指针找到下一个元素
从内存存储来看
1. (静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小
2. 链表从堆中分配空间, 自由度大但是申请管理比较麻烦
从上面的比较可以看出,如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反, 如果需要经常插入和删除元素就需要用链表数据结构了。
链表是动态进行存储分配,不连续。数组是固定好数组长度,存储空间是静态连续的。
UIView 生命周期
一、 大体流程:
(loadView/nib)文件来加载view到内存-->viewDidLoad函数进一步初始化这些view-->内存不足时, 调用viewDidUnload函数释放views-->当需要使用view时又回到第一步
loadView:
永远不要主导调用这个函数。
viewController 会在view的property被请求并且当前view值为nil时调用这个函数。如果你手动创建view, 你应该重载这个函数,切不要在重载的时候调用[super loadView]。
viewDidload:
这个函数的作用主要是让你可以进一步的初始化你的views。
viewDidLoad通常负责的是view及其子view被加载进内存之后的数据初始化的工作,即视图的数据部分的初始化
viewDidUnLoad:
这个函数时viewDidLoad的对立函数。在程序内存欠缺时,这个函数被controller带哦用,来释放他的view以及view相关的对象。由于controller通常保存着view以及相关的object的引用,所以你必须使用这个函数来放弃这些对象的所有权以便内存回收,但不要释放那些难以重建的数据
viewWillAppear:
视图即将可见时调用,默认情况下不执行任何操作。
viewDidAppear:
视图已完全过渡到屏幕上时调用
viewWillDisappear:
视图被驳回时调用,覆盖或以其他方式隐藏,默认情况下不执行任何操作
viewDidDisappear:
视图被驳回后调用,覆盖或以其他方式隐藏。默认情况下不执行任何操作
didReceiveMemoryWarning:
当程序内存过度时,系统会调用该方法
二、Controller和View的生命周期
这里指的View是指Controller的View。它作为Controler的属性,生命周期在Controller的生命周期内。就是说你的Controller不能在view释放前就释放了。
当你alloc并init了一个ViewController时,这个ViewController应该是还没有创建view的。ViewController的view是使用了lazyInit方式创建,就是说你调用的view属性的getter:[self view]。在getter里会先判断view是否创建,如果没有创建,那么会调用loadView来创建view。loadView完成时会继续调用viewDidLoad。loadView和viewDidLoad的一个区别就是:loadView时还没有view。而viewDidLoad时view以及创建好了。
当view被添加其他view中之前时,会调用viewWillAppear,而之后会调用viewDidAppear。
当view从其他view中移出之前时,会调用viewWillDisAppear,而之后会调用viewDidDisappear。
当view不在使用,而且是disappeared,受到内存警告时,那么viewController会将view释放并将其指向nil。
三、代码组织(如何设计良好的viewcontroller)
ViewController生命周期中有那么多函数,一个重要问题就是什么代码该写在什么地方。
1、init里不要出现创建view的代码。良好的设计,在init里应该只有相关数据的初始化,而且这些数据都是比较关键的数据。init里不要掉self.view,否则会导致viewcontroller创建view。(因为view是lazyinit的)。
2、loadView中只初始化view,一般用于创建比较关键的view如tableViewController的tabView,UINavigationController的navgationBar,不可掉用view的getter(在掉super loadView前),最好也不要初始化一些非关键的view。如果你是从nib文件中创建的viewController在这里一定要首先调用super的loadView方法,但建议不要重载这个方法。
3、viewDidLoad 这时候view已经有了,最适合创建一些附加的view和控件了。
4、viewWillAppear 这个一般在view被添加到superview之前,切换动画之前调用。在这里可以进行一些显示前的处理。比如键盘弹出,一些特殊的过程动画(比如状态条和navigationbar颜色)。
5、viewDidAppear 一般用于显示后,在切换动画后,如果有需要的操作,可以在这里加入相关代码。
6、viewDidUnload 这时候viewController的view已经是nil了。由于这一般发生在内存警告时,所以在这里你应该将那些不在显示的view释放了。比如你在viewcontroller的view上加了一个label,而且这个label是viewcontroller的属性,那么你要把这个属性设置成nil,以免占用不必要的内存,而这个label在viewDidLoad时会重新创建。
7、接下来看看ViewController中的view是如何被卸载的:
当系统发出内存警告时,会调用didReceiveMemoeryWarning方法,如果当前有能被释放的view,系统会调用viewWillUnload方法来释放view,完成后调用viewDidUnload方法,至此,view就被卸载了。此时原本指向view的变量要被置为nil,具体操作是在viewDidUnload方法中调用self.myButton = nil;
小结一下:loadView和viewDidLoad的区别就是,loadView时view还没有生成,viewDidLoad时,view已经生成了,loadView只会被调用一次,而viewDidLoad可能会被调用多次(View可能会被多次加载),当view被添加到其他view中之前,会调用viewWillAppear,之后会调用viewDidAppear。当view从其他view中移除之前,调用viewWillDisAppear,移除之后会调用viewDidDisappear。当view不再使用时,受到内存警告时,ViewController会将view释放并将其指向为nil。
ViewController的生命周期中各方法执行流程如下:init—>loadView—>viewDidLoad—>viewWillApper—>viewDidApper—>viewWillDisapper—>viewDidDisapper—>viewWillUnload->viewDidUnload—>dealloc
如果页面 A 跳转到 页面 B,A 的 viewDidDisappear 方法和 B 的 viewDidAppear 方法哪个先调用?
1.A -->viewWillDisappear
2.B-->viewWillAppear
3.A-->viewDidDisappear
4.B-->viewDidAppear
ARC 的本质
ARC是编译器(时)特性,而不是运行时特性,更不是垃圾回收器(GC)。
Automatic Reference Counting (ARC) is a compiler-level feature that simplifies the process of managing object lifetimes (memory management) in Cocoa applications.
ARC只是相对于MRC(Manual Reference Counting或称为非ARC,下文中我们会一直使用MRC来指代非ARC的管理方式)的一次改进,但它和之前的技术本质上没有区别。具体信息可以参考ARC编译器官方文档。
如何找到字符串中第一个不重复的字符
如何在字符串里查找第一个不重复的字母,即只出现一次的最靠前的字母
哈希表如何处理冲突
介绍 block。
ARC 会对代码做什么优化?
——比如 NSString *s2 = s1; s2 = nil 这样的语句,可能就不会有 retain 和 release 方法了。
介绍一下 MVVM 和 RAC。
ViewModel层,就是View和Model层的粘合剂,他是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。说白了,就是把原来ViewController层的业务逻辑和页面逻辑等剥离出来放到ViewModel层。
View层,就是ViewController层,他的任务就是从ViewModel层获取数据,然后显示。
介绍自己用过哪些开源库。
——Masonry 和 SnapKit,AFNetWorking,MKNetworkKit,Alamofire,Mantle,SDWebImage
读过某个库的源码么?
——扯了一点 SDWebImage,后来被告知这个库用了 runloop 来保证滑动是加载数据的流畅性,自己看了源码后表示没有发现,唯一用到 runloop 地方是保证后台线程一直跑,也有可能是我理解错了,如果错误欢迎指正。
SDWebImage 下载了图片后为什么要解码?
——当时蒙住了,面试官很 nice 的解释了一下,说是要把 png 文件建立一个什么内存映射,目前还不太懂,有空研究一下。
不用临时变量怎么实现 swap(a, b)
——用加法或者异或都可以
二维有序数组查找数字
——剑指 offer 第 3题
亿级日志中,查找登陆次数最多的十个用户
——(不确定对不对,我的思路是)先用哈希表保存登陆次数和ID,然后用红黑树保存最大的十个数。剑指 offer 第 30题
简述排序算法
——快排,partion 函数的原理,堆排(不稳定),归并排序,基数排序。
说说你对 OC 中 load 方法和 initialize 方法的异同。
——主要说一下执行时间,各自用途,没实现子类的方法会不会调用父类的?
说说你对 block 的理解。
—— 三种 block,栈上的自动复制到堆上,block 的属性修饰符是 copy,循环引用的原理和解决方案。
说说你对 runtime 的理解。
——主要是方法调用时如何查找缓存,如何找到方法,找不到方法时怎么转发,对象的内存布局。
说说你对 MVC 和 MVVM 的理解。
—— MVC 的 C 太臃肿,可以和 V 合并,变成 MVVM 中的 V,而 VM 用来将 M 转化成 V 能用的数据。
野指针是什么,iOS 开发中什么情况下会有野指针?
——野指针是不为 nil,但是指向已经被释放的内存的指针,不知道什么时候会有,如果有知道的读者还望提醒。
很长一道题,读了很久才读懂,目测是 DFS,但是最后没时间了,写了个思路。
把 "www.zhidao.baidu.com" 这样的字符串改成 "com/baidu/zhidao/www"
——老题目了,剑指 offer 的,两次逆序排列即可。
求数组中和为某个值的所有子数组,比如数组是 [5,5,10,2,3] 一共有四个子数组的和是 15,比如 [5,10],[5,10],[10,2,3],[5,5,2,3]。
这个就是简单的递归了,分两种情况,当前位置的数字在子数组中,以及不在子数组中。