iOS面试题总结(知乎分享面试题)

  1. 什么是ARC(ARC是为了解决什么问题诞生的)?

    • ARC是Auto Reference Counting的缩写,即自动引用计数,由编译器在代码合适的位置中自动添加retain/Release/Autorelease/dealloc方法从而进行内存管理.
    • ARC几个要点:
      在对象被创建时 retain count +1,在对象被release时 retain count -1.当retain count 为0 时,销毁对象。
      程序中加入autoreleasepool的对象会由系统自动加上autorelease方法,如果该对象引用计数为0,则销毁。
      那么ARC是为了解决什么问题诞生的呢?这个得追溯到MRC手动内存管理时代说起。
      MRC下内存管理的缺点:
      • 当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了。(避免提前释放)
      • 释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次。(MRC下即谁创建,谁释放,避免重复释放)
      • 模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。
      • 多线程操作时,不确定哪个线程最后使用完毕
  2. 请解释一下关键词的区别:

    • assign && weak
      • assign不会改变引用计数,通常用于非指针变量的基础数据类型直接赋值,weak通常用于指向同一块内存地址的对象.
      • weak修饰的指针默认nil。在OC中向nil对象发送消息是安全的
    • __block对应property的copy __weak对象proerty的weak
    • 加了_block的局部变量才可以在block内进行修改
    • __block修改数组或指针时,block只修改指针上的内容
    • __block修改变量,内存管理的关键字为copy,会引起内存泄露。在ARC下,要避免block出现循环引用 weak typedof(self)weakSelf = self;
    • __weak修饰变量是弱引用
  3. __block在ARC和非ARC下含义一样吗?

    • 不一样的。在MRC中的block variable在block中使用是不会retain的
      但是在ARC中的block则retain的,取而代之的是用weak或者unsafe-unretained来更精确的描述weak reference的目的。
      其中前者只能iOS5以后才能用,但是比较好(对象release之后,指针会直接nil),后者则是ARC环境为了兼容4.x下面的
    __block MyClass *temp = ...;//MRC环境下使用
    __weak MyClass *temp = ...;//ARC支持iOS5.0以上的版本
    __unsafe_retained MyClass *temp = ...;//ARC且可兼容到4.x以下的版本
    
  4. 为什么其他语言里叫函数调用,Object-C里则叫给我对象发消息(或者谈下对runtime的理解)

    • 在java中,类和方法在编译期就绑定在一起
    • 在OC中,方法调用是向类发送消息,如(bady cry)在运行时会转换成objc_msgSend(bady,cry),向对象发送消息时根据isa指针找到类,在根据类的调度表查找方法,没找到方法则在父类中查找直至基类,如果始终没有找到返回nil
    • Objective-C 的 Runtime 铸就了它动态语言的特性,这些深层次的知识虽然平时写代码用的少一些,但是却是每个 Objc 程序员需要了解的。Objc Runtime使得C具有了面向对象能力,在程序运行时创建,检查,修改类、对象和它们的方法。可以使用runtime的一系列方法实现。
  5. 什么是method swizzling?

    • 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
      每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
      iOS面试题总结(知乎分享面试题)_第1张图片
      image1

      我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,我们可以利用 class_replaceMethod 来修改类,我们可以利用 method_setImplementation 来直接设置某个方法的IMP,归根结底,都是偷换了selector的IMP,如下图所示:
      iOS面试题总结(知乎分享面试题)_第2张图片
      image2

    详情:http://blog.csdn.net/yiyaaixuexi/article/details/9374411

  6. UIView和CALayer是什么关系?

    • UIView显示在屏幕上归功于CALayer,通过调用drawRect方法来渲染自身的内容,调节CALayer属性可以调整UIView的外观,UIView继承自UIResponder,比起CALayer可以响应用户操作,Xcode6之后可以方便的通过视图调试功能查看图层之间的关系

    • UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它本身完全是由CoreAnimation来实现的(Mac下似乎不是这样),它真正的绘图部分,是由一个叫CALayer(Core ANimation Layer)的类来管理。UIView本身,更像是一个CALayer的管理器,访问它的跟绘图和坐标有关的属性,如frame,bounds等,实际上内部都是访问它所在CALayer的相关属性

    • UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的 类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示,例如通过

       - (class) layerClass {
      
           return ([CAEAGLLayer class]);
      

    }

  使某个UIView的子类使用GL来进行绘制。
  
  * UIView的CALayer类似UIView的子View树形结构,也可以向它的layer上添加子layer,来完成某些特殊的表 示。例如下面的代码
 会在目标View上敷上一层黑色的透明薄膜。
    ```
grayCover = [[CALayer alloc]init];
grayCover.backgroudColor = [[UIColor blackColor]colorWithAlphaComponent:0.2].CGColor;
[self.layer addSubLayer:grayCover]; 
* UIView的layer树形在系统内部,被系统维护着三份copy(这段理解有点吃不准)。
 - 逻辑树,就是代码里可以操纵的,例如更改layer的属性等等就在这一份。
 - 动画树,这是一个中间层,系统正在这一层上更改属性,进行各种渲染操作。
 - 显示树,这棵树的内容是当前正被显示在屏幕上的内容。

这三棵树的逻辑结构都是一样的,区别只有各自的属性。

  1. 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,那么就是浪费了一些资源而已

      参考:http://www.cnblogs.com/dyllove98/archive/2013/06/06/3123005.html

  2. GCD有哪几种Queue?你自己建立过串行的Queue吗?背后的线程模型是什么样的?

    • globalQueue和mainQueue
    • 建立过,欢迎页完成一系列的动画后,弹出登录框
    • 欢迎页动画并行执行,前者和后者串行执行
    • 主队列dispatch_main_queue();串行更新UI
    • 全局队列dispatch_global_queue();并行,四个优先级:background,low,default,high
    • 自定义队列dispatch_queue_t queue;可以自定义是并行DISPATCH_QUEUE_CONCURRENT或DISPATCH_QUEUE_SERIAL
  3. 用过Core Data 或者 SQLite吗?读写是分线程的吗?遇到过死锁没?如何解决的?

    • 用过SQLite,丢给FMDatabaseQueue或者NSLock加锁
  4. HTTP协议中POST方法和GET方法有那些区别?

    • GET用于向服务器请求数据POST用于提交数据
    • GET请求,请求提以参数拼接形式暴露在地址栏,而POST请求则放在请求体里面,因此GET请求不适合用于验证密码等操作
    • 更加服务器不同,POST请求会有不同的长度限制
  5. 什么是二叉搜索树?时间复杂度是什么?

    • 采用二叉树链表作为存储结构,每个左节点均小于父节点,每个右节点均大于父节点
    • O(log2(n))
  6. 沙盒目录结构是怎样的?各自用于那些场景?

    • Application//存放程序源文件,上架前经过数字签名,上架后不可修改
    • Documents//常用目录,iCloud备份目录,存放数据
    • Library
      • Caches//存放体积大又不需要备份的数据
      • Preference//设置目录,iCloud会备份设置信息
    • tmp//存放临时文件
  7. #define和const变量有什么区别?

    • define在预处理阶段进行简单的替换,const在编译阶段使用
    • 宏不做类型检查,仅仅展开替换,const有数据类型,会执行类型检查
    • 宏不分配内存,仅仅展开替换,const会分配内存
    • define不能调试,const可以调试
    • define定义的常量在替换后运行过程中会不断地占用内存,而const定义的常量存储在数据段,只有一份copy,效率更高
    • definde可以定义一些简单的函数,const不可以
  8. TCP和UDP有什么区别?

    • TCP是面向连接的,建立连接需要经历三次握手,保证数据正确性和数据顺序
    • UDP是非连接的协议,传送数据受生成速度,传输带宽等限制,可能造成丢包
    • UDP一台服务端可以同时向多个客户端传输信息
    • TCP报头体积更大,对系统资源要求更多
  9. 如何制作一个静态库/动态库?他们的区别是什么?

    • Xcode6支持制作静态库/动态库 framework
    • 无论是动态库还是静态库都是区分真机和模拟器的,需要lipo命令进行合并
    • 静态库编译静态库文件装入程序空间,动态库是文件动态装入内存
    • 动态库执行到相关函数才会被调用,节省空间
  10. 请简单的介绍下APNS发送系统消息的机制

    • 为什么需要APNS?为了杜绝安卓那种为了接受通知不停后台唤醒保持长连接的行为
    • 由iOS系统和APNS进行长连接替代
      • 应用在通知中心就像注册,由iOS系统向APNS请求返回设备令牌
      • 应用程序接收到设备令牌并发送给服务器
      • 服务器把要推送的内容和设备发送给APNS
      • APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示
  11. 请简述Responder Chain?

    • UIResponder是UIView 和 UIViewController共同的父类,负责分发处理事件
    • 在捕获到UIEvent后从AppDelegate->UIAppcation->UIWindow->UIViewController->UIView->SubView 进行查找
    • 然后从SubView开始尝试响应这个事件,并传递给nextResponder,如果父节点不能响应则不再继续传递
  12. pushViewController和presentViewController有什么区别?

    • 两者都是在多个试图控制器间跳转的函数
    • presentViewController提供的是一个模态视图控制器
    • pushViewController提供一个栈控制器数组,push/pop
  13. 请简述UITableView的复用机制

    • UITableView在创建表视图时,不会创建numberOfRowsInSection那么多的cell,不然在所有APP标配上拉加载更多条件下,iPhone内存必然是吃不消的,通常只会创建当前页面最大可显示Cell个数加一个(实际测试有时会加二),Cell离开当前屏幕后会从队列中移除,也是基于这个原因,确定高度的Cell因为页面要显示的Cell个数唯一确定,所以在性能上能够有所提高
    • 复用队列的元素增加:只有在cell被滑动出界面的时候,此cell才会被加入到复用队列中。每次在创建cell的时候,程序会首先通过调用dequeueReusableCellWithIdentifier:cellType方法,到复用队列中去寻找标示符为”cellType”的cell,如果找不到,返回nil,然后程序去通过调用[[[UITableViewCell alloc] initWithStyle:style reuseIdentifier:cellType] autorelease]来创建标示符为”cellType”的cell。
  14. 如何应对APP版本升级,数据结构随之变化?

    • 自己解除的Sqlite相对多一些,通常的作法是重命名旧版数据库文件->创建新版本表格->导入旧版本数据->删除旧版本表
    • 跨版本升级的问题,数据库更新的相关操作不做合并,依次迭代更新
    • Core data接触不多,大部分改动都在轻量化迁移支持范围内,复杂的需要重写指定映射关系
  15. 描述一个你所遇到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;
    }];
    
    
  16. +(void)load; +(void)initialize;有什么用处?

    • 当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息. load 方法还是非常的神奇的, 因为它会在每一个类甚至分类被引入时仅调用一次, 调用的顺序是父类优先于子类, 子类优先于分类. 而且 load 方法不会被类自动继承, 每一个类中的 load 方法都不需要像 viewDidLoad 方法一样调用父类的方法. 由于 load 方法会在类被 import 时调用一次, 而这时往往是改变类的行为的最佳时机. 我在 DKNightVersion 中使用 method swizlling 来修改原有的方法时, 就是在分类 load 中实现的.

      initialize 方法和 load 方法有一些不同, 它虽然也会在整个 runtime 过程中调用一次, 但是它是在该类的第一个方法执行之前调用, 也就是说 initialize 的调用是惰性的, 它的实现也与我们在平时使用的惰性初始化属性时基本相同. 我在实际的项目中并没有遇到过必须使用这个方法的情况, 在该方法中主要做静态变量的设置并用于确保在实例初始化前某些条件必须满足.

    • 在Objective-C中,runtime会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。
      共同点:两个方法都只会被调用一次。

  17. 如何高性能的给 UIImageView 加个圆角? (不准说 layer.cornerRadius!)

    • 一般情况下给 UIImageView 或者说 UIKit 的控件添加圆角都是改变 clipsToBoundslayer.cornerRadius, 这样大约两行代码就可以解决这个问题. 但是, 这样使用这样的方法会强制
      Core Animation 提前渲染屏幕的离屏绘制, 而离屏绘制就会为性能带来负面影响.

    我们也可以使用另一种比较复杂的方式来为图片添加圆角, 这里就用到了贝塞尔曲线.

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];  
    

imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];

在这里使用了贝塞尔曲线"切割"个这个图片, 给 `UIImageView` 添加了的圆角.

24. 使用drawRect有什么影响?(这个可深可浅,你至少得用过。。)
    * drawRect方法依赖Core Graphics框架来进行自定义的绘制,但这种方法主要的缺点就是它处理touch事件的方式:每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例。
    
     * 这个方法的主要作用是根据传入的 rect 来绘制图像 [参见文档](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/instm/UIView/drawRect:). 这个方法的默认实现没有做任何事情, 我们可以在这个方法中使用 Core Graphics 和 UIKit 来绘制视图的内容.

       这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为 dirty, 但还是会显示原来的内容, 直到下一次的视图渲染周期, 才会为标记为 dirty 的图层重新建立 Core Graphics 上下文, 然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制.
      
       相关参考:[UIView的layoutSubviews和drawRect方法何时调用](http://xyxdasnjss.iteye.com/blog/1827954)
        
25. ASIHttpRequest 或者 SDWebImage 里面给 UIImageView 加载图片的逻辑是什么样的?
   
   * 我曾经阅读过 SDWebImage 的源代码, 就在这里对如何给 UIImageView 加载图片做一个总结吧, SDWebImage 中为 UIView 提供了一个分类叫做 WebCache, 这个分类中有一个最常用的接口, sd_setImageWithURL:placeholderImage:, 这个分类同时提供了很多类似的方法, 这些方法最终会调用一个同时具有 option progressBlock completionBlock 的方法, 而在这个类最终被调用的方法首先会检查是否传入了 placeholderImage 以及对应的参数, 并设置 placeholderImage.

     然后会获取 SDWebImageManager 中的单例调用一个 downloadImageWithURL:... 的方法来获取图片, 而这个 manager 获取图片的过程有大体上分为两部分, 它首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以 url 作为数据的索引先在内存中寻找是否有对应的缓存, 如果缓存未命中就会在磁盘中利用 MD5 处理过的 key 来继续查询对应的数据, 如果找到了, 就会把磁盘中的缓存备份到内存中.

     然而, 假设我们在内存和磁盘缓存中都没有命中, 那么 manager 就会调用它持有的一个 SDWebImageDownloader 对象的方法 downloadImageWithURL:... 来下载图片, 这个方法会在执行的过程中调用另一个方法 addProgressCallback:andCompletedBlock:fotURL:createCallback: 来存储下载过程中和下载完成的回调, 当回调块是第一次添加的时候, 方法会实例化一个 NSMutableURLRequest 和 SDWebImageDownloaderOperation, 并将后者加入 downloader 持有的下载队列开始图片的异步下载.
而在图片下载完成之后, 就会在主线程设置 image, 完成整个图像的异步下载和配置.

26. 使用atomic一定是线程安全的吗?

   * 不是,atomic的本意是指属性的存取方法是线程安全的(thread safe),并不保证整个对象是线程安全的。比如,声明一个NSMutableArray的原子属性stuff,此时self.stuff 和self.stuff = othersulf都是线程安全的。但是,使用[self.stuff objectAtIndex:index]就不是线程安全的,需要用锁来保证线程安全性。

----------

> 参考: 
> [http://www.zhihu.com/question/19604641](http://www.zhihu.com/question/19604641)
>  [http://draveness.me/guan-yu-xie-ios-wen-ti-de-jie-da/](http://draveness.me/guan-yu-xie-ios-wen-ti-de-jie-da/)
> [http://www.jianshu.com/p/d72c4b595c7b](http://www.jianshu.com/p/d72c4b595c7b)
> [http://weibo.com/1832164643/Ct3B4hzl3?type=comment#_rnd1438104612567](http://weibo.com/1832164643/Ct3B4hzl3?type=comment#_rnd1438104612567)

你可能感兴趣的:(iOS面试题总结(知乎分享面试题))