IOS面试题
我有过不少面试和被面试的经历,作为面试官出这份面试题从来就不是为了难倒面试者,而是为了多角度全面的了解面试者从而建立信任。面试的时候最担心的是冷场,面试题只不过个引子,我心底里最希望遇到的面试者是能够举一反三,除了回答问题本身之外,还能自信的旁征博引,深谈其背后原理或者相关的知识理论的。问题本身反而并不怎么重要。这份清单里的问题也并不难,这里我列下我的回答以及从我的角度所期望的答案。
什么是arc?(arc是为了解决什么问题诞生的?)
现在有不少程序员是直接从arc上手的,从没接触过mrc,对arc的理解仅仅停留在apple帮助管理内存的层面。这个问题真正想了解的是对内存管理的理解,retain release虽然不用写了,但arc下还是会有内存泄漏野指针crash的bug存在。如果能从retain count这种内存管理策略的角度去阐述arc诞生的意义就算答对了。如果还能扯下其他类型的策略,比如java里的mark and sweep,那就加分点赞。
请解释以下keywords的区别: assign vs weak, __block vs __weak
这道题属于基础语法题,可以网上搜到答案。不过真有不少同学不知道weak在对象释放后会置为nil。__block关键字的理解稍微难点,因为在arc和mrc下含义(对retain count的影响)完全不同。理解了这几个关键字就能应付使用block时引入retain cycle的风险了。这题还在内存管理的范畴之内。
使用atomic一定是线程安全的吗?
看这题的问法不用想答案肯定是NO。有些人说不出所以然,有些人知道通过property的方式使用才能保证安全,还有人知道这个用来做多线程安全会有性能损耗,更有出色的候选人能谈atomic,synchronized,NSLock,pthread mutex,OSSpinLock的差别。好奇宝宝点我
描述一个你遇到过的retain cycle例子。(别撒谎,你肯定遇到过)
block中的循环引用一个ViewController
@property (nonatomic,strong) HttpRequestHandler * handler;
@property (nonatomic,strong) NSData *data;
_handler = [httpRequestHandler sharedManager];
[ downloadData:^(id responseData){
_data = responseData;
}];
self 拥有_handler, _handler 拥有block, block拥有self(因为使用了self的_data属性,block会copy 一份self)
解决方法:
__weak typedof(self)weakSelf = self
[ downloadData:^(id responseData){
weakSelf.data = responseData;
}];
+load和+initialize有什么用处?
这题属于runtime范畴,我遇到过能说出对runtime的理解却不知道这两个方法的候选人。所以答不出来也没关系,这属于细节知识点,是加分项,能答出两个message各在什么阶段接收就可以了。
- +load是在类载入时就会调用。通常我们在Method-Swizzling时,会选择在+load方法中处理。因为只要类存在,就会载入,而只要载入就会调用。
- +initialize是在类首次被调用时才会调用,所以即使类存在并不一定会调用
为什么其他语言里叫函数调用, objective c里则是给对象发消息(或者谈下对runtime的理解)
这题考查的是objective c这门语言的dynamic特性,需要对比c++这类传统静态方法调用才能理解。最好能说出一个对象收到message之后的完整的流程是如何的。对runtime有完整理解的候选人还能说出oc的对象模型。
- 首先,函数调用通常是在面向过程的语言中叫法。
- 其次,ObjC是扩展C,因此具备C的特性,也就有函数调用
- 最后,ObjC中有面向对象特性,在编译时会将方法调用转成成objc_msgSend,其实就是给消息接收者发送消息。
什么是method swizzling?
说了解runtime但没听过method swizzling是骗人的。这题很容易搜到答案。定位一些疑难杂症bug,hack老项目实现,阅读第三方源码都有机会接触到这个概念。
Method-Swizzling其实就是方法实现体交换。在ObjC中,方法名与实现体是一一对应的关系,只要知道方法名,就能找到实现体。
笔者专门学习研究了一下runtime的Method-Swizzling,大家可以参考阅读Runtime Method Swizzling
UIView和CALayer是啥关系?
1、view支持很多手势的交互,你所操作iphone的各种点击,拖动等等。
2、layer来至于mac os,是可以跨平台的东西。这里就是个很要学问的东西,系统的可变部分和不可变部分,可变部分越多,系统越不稳定,但是功能就更加丰富。layer就是作为一种不可变的东西存在,view作为一种可变的东西存在,所有我们每次在ios更新时候获得了大量的view新特性,但是整个view系统底层很多东西依旧可以接着使用,各种动画等。这就是分开view和layer的功劳。一言以蔽之
能答出UIView是CALayer的delegate就及格了,能说出UIView主要处理事件,CALayer负责绘制就更好,再聊下二者在使用过程中对动画流畅性影响的注意点就更好了,UI流畅性是个大话题。
一个UIView默认会有一个layer,当然一个UIView可以有很多个layer。UIView负责处理事件,而CALayer负责绘制,而绘制渲染又分为离屏渲染和当前屏渲染。我们知道,离屏渲染是要付出很大的代价的,因此在性能方面是需要好好考虑的。大家可以阅读笔者写的Offscreen-Rendered(离屏渲染)
如何高性能的给UIImageView加个圆角?
使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制,而离
屏绘制就会给性能带来负面影响,会有卡顿的现象出现
self.view.layer.cornerRadius=5;
self.view.layer.masksToBounds=YES;
正确的解决方案:使用绘图技术
- (UIImage*)circleImage
{
// NO代表透明
UIGraphicsBeginImageContextWithOptions(self.size,NO,0.0);
//获得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//添加一个圆
CGRectrect = CGRectMake(0,0,self.size.width,self.size.height);
CGContextAddEllipseInRect(ctx, rect);
//裁剪CGContextClip(ctx);
//将图片画上去
[selfdrawInRect:rect];
UIImage*image = UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文UIGraphicsEndImageContext();
return image;
}
还有一种方案:使用了贝塞尔曲线"切割"个这个图片,给UIImageView添加了的圆角,其实也是通过绘图技术来实现的
UIImageView*imageView = [[UIImageViewalloc]
initWithFrame:CGRectMake(0,0,100,100)];
imageView.center= CGPointMake(200,300);
UIImage*anotherImage = [UIImageimageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.
size,NO,1.0);
[[UIBezierPathbezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.viewaddSubview:imageView];
答主在项目里做过圆角头像的处理,里面的坑还真不少。cornerRadius会导致offscreen drawing有性能问题,美工切图无法适用有背景图的场景,即使加上shouldRasterize也有cache实效问题。正确的做法是切换到工作线程利用CoreGraphic API生成一个offscreen UIImage,再切换到main thread赋值给UIImageView。这里还涉及到UIImageView复用,圆角头像cache缓存(不能每次都去绘制),新旧头像替换等等逻辑。还有其他的实现方式,但思路离不开工作线程与主线程切换。
笔者开源了一个专门处理圆角问题的库:开源高性能处理圆角
使用drawRect有什么影响?(这个可深可浅,你至少得用过。。)
不少同学都用过drawRect或者看别人用过,但不知道这个api存在的含义。这不仅仅是另一种做UI的方式。drawRect会利用CPU生成offscreen bitmap,从而减轻GPU的绘制压力,用这种方式绘制UI可以将动画流畅性优化到极致,但缺点是绘制api复杂,offscreen cache增加内存开销。UI动画流畅性的优化主要平衡CPU和GPU的工作压力。
这个方法的影响在于有touch event的时候之后,会重新绘制,很多这样的按钮的话就会比较影响效率。以下都会被调用
1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect 掉用是在Controller->loadView, Controller->viewDidLoad 两方法之后掉用的.所以不用担心在 控制器中,这些View的drawRect就开始画了.这样可以在控制器中设置一些值给View(如果这些View draw的时候需要用到某些变量 值).
2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。
ASIHttpRequest或者SDWebImage里面给UIImageView加载图片的逻辑是什么样的?
很多同学没有读源码的习惯,别人的轮子拿来只是用用却不知道真正的营养都在源代码里面。这两个经典的framework代码并不复杂,很值得一读。能对一个UIImageView怎么通过url展示一张图片有完整的理解。涉及到的知识点也非常多,UITableViewCell的复用,memory cache, disk cache, 多线程切换,甚至http协议本身都需要有一定的涉及。
把UIImageView放到UITableViewCell里面问更赞。
麻烦你设计个简单的图片内存缓存器(移除策略是一定要说的)
内存缓存是个通用话题,每个平台都会涉及到。cache算法会影响到整个app的表现。候选人最好能谈下自己都了解哪些cache策略及各自的特点。常见的有FIFO,LRU,LRU-2,2Q等等。由于NSCache的缓存策略不透明,一些app开发者会选择自己做一套cache机制,其实并不难。
讲讲你用Instrument优化动画性能的经历吧(别问我什么是Instrument)
Apple的instrument为开发者提供了各种template去优化app性能和定位问题。很多公司都在赶feature,并没有充足的时间来做优化,导致不少开发者对instrument不怎么熟悉。但这里面其实涵盖了非常完整的计算机基础理论知识体系,memory,disk,network,thread,cpu,gpu等等,顺藤摸瓜去学习,是一笔巨大的知识财富。动画性能只是其中一个template,重点还是理解上面问题当中CPU GPU如何配合工作的知识。
loadView是干嘛用的?
loadView在View为nil时调用,早于ViewDidLoad,通常用于代码实现控件,收到内存警告时会再次调用。loadView默认做的事情是:如果此VIewcontroller存在一个对应的nib文件,那么就加载这个nib。否则,就创建一个UIView对象。如果你用Interface BVuilder来创建界面,那么不应该重载这个方法。
如果你想自己创建View对象,那么可以重载这个方法,此时你需要自己给View属性赋值。你自定义的方法不应该调用super。如果你需要对View做一些其他定制操作,在ViewDidload中去做
根据上面的文档可以知道,有两种情况:
1、如果你用了nib文件,重载这个方法就没有太大意义。因为loadView的作用就是加载nib。如果你重载了这个方法不调用super,那么nib文件就不会被加载。如果调用了super,那么view已经加载完了,你需要做的其他事情在viewDidLoad里面做更合适。
2、如果你没有用nib,这个方法默认就是创建一个空的view对象。如果你想自己控制view对象的创建,例如创建一个特殊尺寸的view,那么可以重载这个方法,自己创建一个UIView对象,然后指定 self.view = myView; 但这种情况也没有必要调用super,因为反正你也不需要在super方法里面创建的view对象。如果调用了super,那么就是浪费了一些资源而已
viewWillLayoutSubView你总是知道的。。
当viewController的bounds又改变,调用这个方法来实现subview的位置。可重写这个方法来实现父视图变化subview跟着变化。
GCD里面有哪几种Queue?你自己建立过串行queue吗?背后的线程模型是什么样的?
两种queue,串行和并行。main queue是串行,global queue是并行。有些开发者为了在工作线程串行的处理任务会自己建立一个serial queue。背后是苹果维护的线程池,各种queue要用线程都是这个池子里取的。GCD大家都用过,但很多关键的概念不少人都理解的模凌两可。串行,并行,同步,异步是GCD的核心概念。
用过coredata或者sqlite吗?读写是分线程的吗?遇到过死锁没?咋解决的?
当然如果你必须要coredata多线程。注意
1、只用一个NSPersistentStoreCoordinator
2、每个线程创建一个NSManagedObjectContext
3、不要传递NSManagedObject,传objectID,通过fetch获得。
4、先存后取,利用NSManagedObjectContext
-mergeChangesFromContextDidSaveNotification:
5、保护思路清晰。
关于sqlite,这个其实要简单的多,它的存储都是优化的,你自己建一个串行的线程,用来存储就好了,FMDB可以看看。
http的post和get啥区别?(区别挺多的,麻烦多说点)
1、get用于获取数据,post用于提交数据
2、get提交参数追加在url后面,post参数可以通过http body提交
3、get的url会有长度上的限制,则post的数据则可以非常大
4、get提交信息明文显示在url上,不够安全,post提交的信息不会在url上显示
5、get提交可以被浏览器缓存,post不会被浏览器缓存
GET产生一个TCP数据包;POST产生两个TCP数据包。
长的说:
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。