最近刚经历完苦逼的面试,抓紧吧技术面试题小结一下
1.tableView如何优化?
>1.cell重用
>2.减少透明度设置
>3.cell数据采用异步子线程网络请求
>4.复杂的cell可以采用异步绘图方式渲染图片到屏幕
>5.对cell的高度做计算并缓存起来方便以后直接取
>6.减少耗时操作
>7.异步加载图片方法最好写在scrollview的代理方法(当滑动减速时才进行加载图片操作)中
>8.tableView滚动时最好不要使用一些动画效果
2.框架设计模式—MVC/MVVM模式
MVC:
>1.Model,用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“ Model ”有对数据直接访问的权力,例如对数据库的访问。“Model”不依赖“View”和“Controller”,也就是说, Model 不关心它会被如何显示或是如何被操作。但是 Model 中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此 Model 的 View 必须事先在此 Model 上注册,从而,View 可以了解在数据 Model 上发生的改变。
>2.View,能够实现数据有目的的显示(理论上,这不是必需的)。在 View 中一般没有程序上的逻辑。为了实现 View 上的刷新功能,View 需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册
>3.Controller,起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据 Model 上的改变
>4.Controller中如果想对View进行更改,可以直接通过outlet拿到View,对其进行操作;View如果想把自己的一些想法告诉Controller,只能通过target,代理或数据源方法
>5.Controller中如果想对Model进行更改,可以直接拿到Model这个对象,对其进行更改;Model如果想把自己更改的一些数据信息,向Controller汇报,就需要通过广播的方式发送出去
>6.View和Model之间的通信是完全禁止的
MVVM:
它是将 Controller 中的展示逻辑抽取出来,放置到一个专门的地方,而这个地方就是 viewModel 。MVVM衍生于MVC,是对 MVC 的一种演进,它促进了 UI 代码与业务逻辑的分离。它正式规范了视图和控制器紧耦合的性质,并引入新的组件
3.Xib 和 Storyboard 的优缺点
>1.XIB:在编译前就提供了可视化界面,可以直接拖控件,也可以直接给控件添加约束,更直接一些,而且类文件中就少了创建控件的代码,确实简化不少,通常每个XIB对应一个类,
>2.Storyboard:在编译前提供了可视化界面,可拖控件,可加约束,在开发时比较直观,而且一个Storyboard可以有很多的界面,每个界面对应一个类文件,通过storyboard,可以直观地看出整个App的结构
>3.XIB:需求变动时,需要修改XIB很大,有时候甚至需要重新添加约束,导致开发周期变长,XIB载入相比纯代码自然要慢一些。对于比较复杂逻辑控制不同状态下 显示不同内容时,使用XIB是比较困难的。 当多人开发或者多团队开发时, 如果 XIB 文件被改动,极易导致冲突,而且解决冲突相对要困难很多;Storyboard也是如此.
>4.Xib是轻量级的,用来描述局部 UI界面;StoryBoard是重量级的,用来描述整个软件的多个界面,并且能展示多个界面之间的跳转关系.
4.通知,代理,block(修饰符,场景,循环引用问题)
>1.通知:当我们的View嵌套很深,无论使用代理还是block都比较麻烦或者两个控制器之间没有必然的联系时使用;一对一或者一对多,最后切记要移除通知
>2.代理(weak/assign):两者之间有一定联系,常见的有反向传值;只能一对一
>3.block(copy):适用于两者间的传值,回调;回调中使用self要注意循环引用问题(使用__weak修饰:__weak typeof (self) weakSelf = self;)
5.谈谈你对runtime的理解
>1.runtime底层都是C语言实现的,可以通过终端输入clang -rewrite-objc main.m生成main.cpp文件来查看.
>2.可以在程序运行过程中动态地生成一个类,成员变量和方法,动态地进行修改和删除.
6.定时器NSTimer使用及注意点?
>1.在ViewWillDisappear方法里执行[self.timer invalidate]; 和 self.timer = nil;这两步操作
>2.NSTimer要添加到运行循环中才能开启生效
>3.NSTimer被调用时会对调用对象做retain操作,所以不需要定时器时要销毁,默认执行一次的定时器会自动调用invalidate方法销毁,而重复执行的定时器要手动执行invalidate操作才能销毁,并且要做置空操作.
7.多线程创建方式以及他们的区别(NSThread / GCD / NSOperation)
>1.NSThread比其他两个轻量级,需要自己管理线程的生命周期,线程同步,线程同步对数据的加锁会有一定的系统开销
>2.GCD是基于C的底层API,NSOperation属于object-c类;iOS首先引入的是NSOperation,IOS4之后引入了GCD和NSOperationQueue并且其内部是用GCD实现的.NSOperation便是基于GCD的封装
>3.GCD能够开多个线程 但是并不能管理,但是NSOperation可以管理线程还可以设置最大操作并发数,而GCD可以使用延时方法 after 和sleep 还有一次性once方法 创建单例对象.
>4.GCD一般适用于单利创建和异步加载图片.GCD核心是队列管理,和NSOperationQueue很相似,但GCD是函数实现,效率高推荐使用.
>5.NSOperation的start方法默认是同步执行任务,只有将NSOperation与NSOperationQueue进行结合,才会发挥出这种多线程技术的最大功效.当NSOperation被添加到NSOperationQueue中后,就会全自动地执行异步操作.NSOperation还可以设置最大并发数和取消/暂停/恢复等操作.通过设置操作间的依赖,可以确定这些操作的执行顺序.
>6.处理大量并发数据,又追求性能效率的选择GCD,简单而安全的选择NSOperation实现多线程即可.
8. 谈谈你对单利的理解,来,写一下单利...
>1.单利对象的生命周期是和APP的整个生命周期是等同的
>2.单利对象所占的内存空间只有在程序结束时才会释放
>3.单利对象创建后,会不再被创建,放在静态区,节省了内存空间
>4.利用单利的设计模式,一些封装类可以使用单利创建模式
>5.虽然单利对象只创建一次,但是并不代表所有的对象都要使用单利创建方式,这样会占用过多的内存空间
+ (instancetype)shareInstance {
static Model *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
instance = [[Model alloc] init];
});
return instance;
}
9.说说你对AFN 和 SDWebImage的认识
>1.AFN是对NSURLConnection的封装,里面封装了最常用的GET/POST请求,支持文件上传和下载,还可以检测网络状态.
>2.AFN 3.0舍弃了NSURLConnection类
>3.AFN默认对服务器返回的json数据进行解析,如果不是会报错.如果返回数据格式不一致,需要添加Content-Type:text/xml属性.
>4.AFN使用中可以自己创建一个单利工具类继承自AFN,方便以后框架更改管理.
>5.AFN发送的所有请求默认都是异步,所以不会阻塞主线程.block回调默认是在主线程进行
>1.SDWebImage默认底层的缓存分三块,即内存图片缓存/内存操作缓存/磁盘沙盒缓存.
>2.SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片.
>3.SDWebImage内部有通知观察机制:内存接收到警告时要记得做清理缓存(clearMemory)的操作,接收到应用程序将要终止通知时,执行cleanDisk方法,程序进入后台后执行后台清理磁盘操作(backgroundCleanDisk).
>4.通过NSCache来实现缓存,图片文件缓存时长一周,最大并发数是 6.
>5.图片缓存思路:先检测内存中是否有缓存,如果有就返回image如果没有就去磁盘中检测有没有缓存,有就返回没有就去重新下载图片,然后把图片缓存到内存再保存到磁盘.
10.内存管理
ARC:
>1.系统自动帮我们对对象进行管理
>2.一般只需要注意循环引用问题即可:定时器导致的循环引用;代理导致的循环引用;block导致的循环引用.
MRC:
>1.在每次进行alloc,new,copy,retain等操作时,就要对应的匹配上release操作.
11.GET/POST的区别 Socket / TCP/IP / IM / HTTP ?
>1.GET请求是向服务器获取数据,效率比较高.POST请求是向服务器发送数据,效率较低.
>2.GET请求默认是做缓存的而POST请求是默认不会做缓存的.
>3.GET请求的参数暴露在URL中,相对不安全;而POST请求的参数封装在请求体中,相对GET请求要安全一些,但是仍然要对数据进行加密操作.
>4.GET请求参数长度有限制而POST请求参数长度无限制.
12.数据存储有哪些方式(归档接档 /偏好设置 /plist/SQLite/CoreData),以及各自的适用场景
>1.plist文件存储适用于存储基本数据类型,一般都是存取字典和数组,直接写成plist文件,把它存到应用沙盒当中
>2.偏好设置NSUserDefaults适用于存储基本数据类型,它的底层就是封装了一个字典,利用字典的方式生成plist文件不需要关心文件名(它会自动生成)快速进行键值对存储.
>3.归档一般适用于保存自定义对象,要遵守NSCoding协议.
>4.SQLite是一款轻型的嵌入式数据库,属于关系型数据库.
>5.Core Data是对SQLite数据库的封装,但是没有SQLite高效.
13.数据加密
>1.一般使用MD5加密,再做加盐操作,即在明文的固定位置插入字符串,然后再进行MD5.
>2.时间戳加密
14.功能原理(无限轮播/上拉加载/下拉刷新/抽屉效果)
>1.无限轮播采用UICollectionView + NSTimer来实现.
>2.上拉加载/下拉刷新,采用MJRefresh框架实现
>3.抽屉效果采用MMDrawerController框架实现
15.登录判断
>1.HTTP:短连接,通过token机制来验证用户的安全性.
>2.token值特点:是一个字符串/大整数,只需要保证唯一性.是服务器根据用户的信息(账号/密码/身份认证机制(电话号/身份证号/支付宝账号/银行卡信息)...)来生成的用于标识用户身份的值!当用户在首次登录成功后,服务器端就会生成一个token值,保存在服务器端数据库中,同时将它返回给客户端,客户端接收到token值后一般会保存在cookie中或者保存在沙盒中来作为一个公共参数传递,以后客户端再次发送网络请求(一般不是登录请求)的时候,就会将这个 token 值附带到参数中发送给服务器,服务器接收到客户端的请求之后,会取出token值与保存在本地(数据库)中的token值做对比,如果两个token值相同即说明用户登录成功过!当前用户处于登录状态,如果token值不同说明原来的登录信息已经失效,让用户重新登录.
16.说说响应链
定义:当用户的手指点击屏幕的时候,iOS操作系统通过触摸屏获取用户的点击行为,然后把这个点击信息包装成UITouch和UIEvent形式的实例,然后找到当前运行的程序,在这个程序中逐级寻找能够响应这个事件的所有对象,然后把这些对象放入一个链表,这个链表就是iOS的响应链。所以iOS中的对象想要响应事件都需要直接或间接的继承UIResponder,AppDelegate,UIApplication,UIWindow,ViewController都直接或者间接的继承了UIResponder,所以它们可以作为响应链中的对象来处理我们用户的点击事件
//示例情景:屏幕上有个大的AView,AView里面有BView和CView,CView里有DView;现在用户点击DView
AView上面添加了BView和CView,CView上面添加了DView,现在用户点击了DView,然后系统会接收到这个点击事件,然后调用 -(BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;方法,该方法是判断点击的点是够在本对象内,如果返回true则继续调用 -(nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;返回当前的这个对象,在我们上图的这歌机构中判断顺序是这样的:
1.触摸的这个点坐标在AView上吗?true,然后AView加入响应链,继续遍历AView的子页面BView和CView。
2.在BView上吗?false。该分支结束。
3.在CView上吗?true,CView加入响应链,继续遍历CView的子视图DView。
3.在DView上吗?在,DView加入响应链,DView没有子页面,这个检测结束。
经过以上检测就形成了这样一个链:AView -->CView
-->DView。需要注意的是,响应链的建立一定是在一个subview的关系,如果只是一个页面在另一个页面上面,没有包含关系的话,这个响应链就不会传递
总结:事件的传递是从上到下的,事件的响应是从下到上的。
响应链已经建立起来,那么下面就该响应用户刚才的那次点击了,首先找到第一响应者DView,看他有没有处理这次点击事件,如果DView不处理就通过响应链找到它的nextResponder-CView,CView如果也不处理就会一直向上寻找,如果最终找到响应链的最后一个响应者AppDelegate也不处理,就会丢弃这次点击事件。
17.假如现在有4个网络请求ABCD,当四个网络请求都完成之后再去去做任务E,应该如何去做?
//创建信号量
dispatch_semaphore_t
semaphore = dispatch_semaphore_create(0);
//创建全局并行
dispatch_queue_t
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_tgroup = dispatch_group_create();
dispatch_group_async(group,queue, ^{
NSLog(@"处理事件A");
for(int i = 0; i<10000;i++) {
NSLog(@"打印i %d",i); }
dispatch_semaphore_signal(semaphore);
});
dispatch_group_async(group,queue, ^{
NSLog(@"处理事件B");
for(int i = 0; i<10000;i++) {
NSLog(@"打印j %d",i);
}
dispatch_semaphore_signal(semaphore);
});
dispatch_group_async(group,queue, ^{
NSLog(@"处理事件C");
for(int i = 0; i<10000;i++) {
NSLog(@"打印k %d",i);
}
dispatch_semaphore_signal(semaphore);
});
dispatch_group_async(group,queue, ^{
NSLog(@"处理事件D");
for(int i = 0; i<10000;i++) {
NSLog(@"打印l %d",i);
}
dispatch_semaphore_signal(semaphore);
});
dispatch_group_notify(group,queue, ^{
/四个请求对应四次信号等待/ dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER); dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER); dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER); dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
NSLog(@"处理事件E");
});
注意:如果需求中改为:同时存在A,B,C,D四个网络请求任务,要求ABCD依次进行,当上一个完成时再进行下一个任务,当四个任务都完成时再处理事件E。这时只需要将队列改为串行队列即可不在需要信号量控制了.
18.有没有用过runtime?什么场景使用?
runtime 交换方法
runtime给分类动态添加属性
runtime字典转模型
runtime动态添加方法
runtime动态变量控制
runtime实现NSCoding的自动归档和解档
19.RunLoop了解吗?有几种mode?mode有什么作用?
当程序启动时,main入口代码会被调用,主线程也随之开始运行,RunLoop 也会随着启动.在UIApplicationMain()方法里面完成了程序初始化,并设置程序的Delegate任务,而且随之开启主线程的RunLoop,就开始接受事件处理.
RunLoop 是一个循环,在里面它接受线程的输入,通过事件处理函数来处理事件.你的代码中应该提供 while or for 循环来驱动 runloop.在你的循环中,用 runloop 对象驱动事件处理相关的内容,接受事件,并做响应的处理.
RunLoop 接受的事件源有两种大类: 异步的input sources, 同步的 Timer sources. 这两种事件的处理方法,系统所规定
RunLoopMode 可以理解成为一个集合, 包括所有要监视的事件源(前面提到的两种源)和要通知的 RunLoop 中注册的观察者.每次运行 RunLoop 时,都需要显示或者隐式的指定其运行在哪一种 Mode(RunLoop 每次只能运行在一个 mode中).在设置RunLoopMode 以后,你的 RunLoop 就会自动过滤和其他 Mode 相关的事件源,而只监视和当前设置 Mode 相关的源(以及通知相关的观察者).大多数时候 RunLoop 都运行在系统定义的默认的模式上.
在代码中,你可以通过mode 名称区分不同的 mode. Cocoa & CoreFoundation 框架通过不同名称(NSString,CFString)定义了缺省 mode 和一系列其他的 mode.你也可以使用不同的名称,定义自己的 mode,然后在这个 mode 中添加一些 source 以及 observer.使用这些 modes 可以从不想要的事件源中过滤事件.大多数情况下,我们都将 runloop 设置成default mode.
RunLoopMode 是基于事件的source源头,而不是事件的type类型去区分的.比如你不能通过 RunLoopMode 去只选择鼠标点击事件或者键盘输入事件.你可以使用 RunLoopMode 去监听端口,暂停计时器或者或者改变添加或删除一些 mode 中关注的sources or observers
Cocoa 和 CoreFoundation 为我们定义了默认和常用的 Mode.RunLoopMode 的名称可以使用字符串来标识,我们也可以使用字符串指定一个 Mode 名称来自定义Model.
下面列出iOS 中已经定义的RunLoopMode:
NSDefaultRunLoopMode,kCFRunLoopDefaultMode: 大多数工作中默认的运行方式。
NSConnectionReplyMode: 使用这个Mode去监听NSConnection对象的状态,我们很少需要自己使用这个Mode。
NSModalPanelRunLoopMode: 使用这个Mode在Model Panel情况下去区分事件(OS X开发中会遇到)。
UITrackingRunLoopMode: 使用这个Mode去跟踪来自用户交互的事件(比如UITableView上下滑动)。
GSEventReceiveRunLoopMode: 用来接受系统事件,内部的Run Loop Mode。
NSRunLoopCommonModes, kCFRunLoopCommomModes: 这是一个伪模式,其为一组run loop mode的集合。如果将Input source加入此模式,意味着关联Input source到Common Modes中包含的所有模式下。在iOS系统中NSRunLoopCommonMode包含NSDefaultRunLoopMode、NSTaskDeathCheckMode、UITrackingRunLoopMode.同时,我们可以使用 CoreFoundation 中的 CFRunLoopAddCommomMode()函数,将自定义的mode 加入其中。
注意 RunLoop 运行时只能以一种固定的 Mode运行,只会监控这个Mode 下添加的 Timer source 和 Input source.如果这个 Mode下没有添加时间源,RunLoop 就会立即返回.
RunLoop 不能运行在 NSRunLoopCommonModes,因为 NSRunLoopModes 是个 Mode 的集合,而不是一个具体的 Mode.我们可以在添加事件源的时候使用 NSRunLoopCommomModes,只要 RunLoop 运行在 NSRunLoopModes 中任何一个 Mode,这个事件源就会被触发.
20讲讲kvc和KVO
KVC是一种键值编码,利用key或者keypath进行属性赋值,或者是字典转模型的实现KVO是键值监听.
KVO底层实现:当一个类的属性被观察的时候,系统会通过runtime动态的创建一个该类的派生类,并且会在这个类中重写基类被观察的属性的setter方法,而且系统将这个类的isa指针指向了派生类,从而实现了给监听的属性赋值时调用的是派生类的setter方法。重写的setter方法会在调用原setter方法前后,通知观察对象值的改变
21.谈谈你对block的理解以及他的注意事项,为什么block内部捕获的变量不能修改?
局部变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值,需要使用__block进行修饰.
而静态变量,静态全局变量,全局变量是可以在block内部进行修改的, 静态变量传递给Block是内存地址值,所以能在Block里面直接改变值;静态全局变量和全局变量由于存储在全局区所以也是可以被block捕获并修改的.
22.为什么NSString 要用copy修饰?
因为:大部分声明一个属性都是用copy修饰字符串,因为系统实现set方法中,拷贝了一份这个属性的赋值,所以以后外界再次修改了这个属性的值,不会影响原来的值.
copy的作用:
1.产生一个副本(如果是不可变字符串,调用这个方法不产生新的对象,只是地址拷贝,如果是可变字符串调用这个方法,则一定会产生一个新的副本对象,地址不一样,可以进行打印验证)
2.修改了副本,并不会影响源对象;
23.为什么tableViewController的delegate属性都是assign而不是retain?
为了防止内存泄漏
24.你对OC内存管理是怎么理解的?需要注意什么?怎么检测的?
需要注意循环引用的情况也是会导致内存问题的
静态检测: 使用XCode分析功能,Product->Analyze
动态检测: 使用instruments里的Leaks工具
25. APP的崩溃原因一般有哪些?以及crash排查手段
EXC_CRASH:向不存在的示例发送消息
SIGSEGV: 引用了released对象 / 引用未init的对象 / 数组越界/ 试图往没有写权限的内存地址写数据,例如在子线程中刷新UI.
SIGABRT: 逻辑错误导致的Crash,比如尝试多次释放同一个内存
可以通过第三方如友盟的bug抓取定位.
如有不当之处,烦请多多指正,在下有礼了...