面试复习备忘录(待整理)

物理内存虚拟内存的关系、堆栈、

比如工作环境,薪资待遇,要不要加班,主要工作内容,要用到什么技术,团队有没有什么缺点?

或者问你们打算用 swift 么?你们怎么看待函数式编程?我不会用 SB,你们要用么

1.进程的有哪几种状态

  1. 就绪-执行(进程调度)
  • 执行-就绪(时间片到)
  • 执行-阻塞(有事件请求)
  • 阻塞-就绪(事件完成)

2.线程和进程的区别

进程有自己独立的地址空间,一个进程崩溃后,在保护模式下,他不会对其他进程造成影响。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,因此一个线程崩溃后就等于整个进程死掉了。
因此多进程的程序一般比多线程程序健壮,但是进程切换的时候,进程资源消耗要大些,效率低一些。

简而言之:

  • 他们在执行过程中也是有区别的,每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够单独执行,必须依存在程序当中,由程序提供多线程的控制。
  • 逻辑角度讲,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行,但是操作系统没有把多线程视为多个应用来进行进程的调度和管理及资源分配。这是线程和进程的重要区别

3.堆和栈的区别

  • 堆空间的内存是动态分配的,一般存放对象,并且需要手动释放内存
  • 栈空间的内存由系统自动分配,一般存放局部变量等,不需要手动管理内存

4.进程间通讯

  1. 消息传递
  • 同步
  • 共享内存区
  • 进程调用

5.电路交换与分组交换有什么区别?

电路交换
优点:

  1. 传输时延小
  2. 实时性强
  3. 不会失序

缺点:

  1. 平均连接建立事件长
  2. 信道利用率低

分组交换
优点:

  1. 不存在连接建立时延

6.进程间通讯方式

  • 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  • 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  • 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。不是用于交换大批数据,而用于多线程之间的同步.常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  • 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 信号 ( signal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  • [共享内存( shared memory )] :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
  • 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

7.内存管理

连续内存分配(有内部碎片)

  • 多分区方法:将内存分成许多个大小相同的模块,浪费巨大,不灵活(单位块大小不好决定),已弃用。
  • 可变分区:操作系统有个表,用于记录哪些内存可用和哪些内存已经被占用。可用内存称为孔,进程进来过后,内存管理单元会自动找到合适的孔(注1),如果孔还有剩余,那么这个孔还是可用,只是内存变小了而已。

注1:策略有最先适应,最佳适应,最差适应。

非连续内存分配

分页:将物理内存分为大小固定的块,称为帧( frame),逻辑地址分为同样大小的块,称为页(page)
TLB缓存访问记录,他是个哈希表,key存的逻辑地址,value存的真正的物理内存。

24 OSI七层协议、TCP四层

物理层:一个(不一定可靠)点对点数据直链。
数据链路层:一个可靠的点对点数据直链。
网络层:在网络的各个节点之间进行地址分配、路由和分发报文。(IP协议)
传输层:在网络的各个节点之间可靠地分发数据包。(TCP协议)
会话层:主机间通讯,管理应用程序之间的会话。
表示层:数据表示形式,加密与解密。
应用层:网络进程到应用进程。(HTTP/Https、FTP、SMTP)

网络接口层
网络互连层
传输层
应用层

6.TCP/IP有哪几层

4层:(主流)
1.网络接口层 2.互联网层 3.传输层 4.应用层

5层:1.物理层 2.数据链路层 3.互联网从 4.传输层 5.应用层

Nullability

7.SDWebImage

1、缓存时间有多长:1周
_maxCacheAge = k

2、SDWebImage的内存缓存是用什么实现的?
NSCache

3、SDWebIamge的最大并发数是多少?
6

4、支持动图吗?
支持gif图

5、

block更加面向结果,而代理更加面向过程。

8.Delegate、Notification、KVO各自优缺点

他们的主要用途都是用来协调不同对象之间的消息同喜的。先说delegate吧,delegate只能一对一通信,通知和KVO可以一对多,但是他们只负责发送消息,没有返回参数。

delegate

当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现,即常用的delegate。委托模式iOS大量使用,委托可以很方便地做到接口和实现的分离,他的优势是解耦合

delegate的优点主要是它有非常严格的语法,它所有监听的事件在协议里都有清晰的声明:

  • 协议可以直接多继承,而且一个对象可以定义多个不同的协议对象,每个协议对象可以有不同的代理实现
  • 如果delegate中的require方法没有定义,编译器会发出警告,这也是它很不错的优点。
  • 更重要的是它不会给架构的设计带来太大的困扰,它在一个应用的控制流程中是可追踪识别的
  • 不需要第三方对象来保持或者监听通信过程。

缺点:

  1. 需要定义很多方法(协议本身定义、的delegate属性、delegate本身的实现)
  2. 释放代理对象时,需要小心的将delegate改为nil,一旦设定失败,那么调用释放对象

通知

一个对象通知另外一个对象,可以用来传递参数、通信等作用,与delegate的一对一不同,通知是一对多的。在一个对象中注册了通知,那么其他任意对象都可以来对这个对象发出通知。
NSNotificationCenter 是一个单例,是同步的。
NSNotificationQueue 是异步的,有三种:

typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1, // 空闲时发送
    NSPostASAP = 2, // 尽快发送
    NSPostNow = 3 // 立即发送
};

他的优点是

  • 比较简单,它不需要写多少代码,实现比较简单。一对多的实现也简单。

他也有一些缺点,

  • 编译期不会检查通知是否能被观察者正确处理。而且调试的时候工作和控制过程不好跟踪,还有就是通知发出后不能从观察者获得反馈信息。
  • 释放对象前一定要取消注册,否则这个通知再出现,会出现unrecognize instance错误,程序会崩溃。通知差不多就这些吧,其他的暂时想不起来了。

KVO

函数原型
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
}

简单来讲,KVO就是当其他对象的属性改变时会让自己收到通知(当然只能通过set来改变)。KVO很强大,但是API做得不是很好,Tompson也吐槽过这个,就是AF的作者

KVO 的优点很明显就是不需要发送通知,底层帮我们实现了自动响应,可以做binder,实现两个对象间的同步。
KVO 可以对属性值实现细粒度的观察,这一点是别的方法不容易做到的,也是很有用的一个特性。

缺点:

  • 严重依赖于string,也就是说,编译器没办法在编译阶段将错误的keyPath给找出来,而且对属性重构过后,观察代码也得改。
  • 所有的observe处理都放在一个方法里,如果KVO处理的事情种类多且繁杂,代码就很长很不雅观。
  • 还有一点就是需要自己处理父类的observe事务,虽然很多时候runtime会自动帮忙处理父类的方法,比如对于dealloc,但是KVO不会,所以我们要在if语句的最后调用super的observeValueForKeyPath:函数。
  • 多次removeObserver还会导致crash,一个文件还比较好debug,但是跨文件执行两次相同的removeObserver就不是那么容易发现了。

总的来说,KVO很强大,但也有点坑,使用它要养成好习惯。

9.MVVM与MVC

MVC

  • Model: 表示业务数据对象
  • View:展现数据的 UI
  • Controller:Model 跟 View 之间的粘合剂。一方面对 View 上的行为作出反应,通常会涉及到 Model 的更改;另一方面将 Model 的改动反映到 View 上

MVC最大的优点就是他的概念简单,容易理解,几乎任何的程序员都会有所了解,它是最基础的设计模式,展现了一个架构抽象分离的精髓思想。在iOS客户端开发里面,MVC也是官方推荐的主流架构,是一种非常通用成熟的架构设计,但是MVC里面View Controller 承担的职责太多:

  • 网络请求
  • 数据访存
  • UI调整
  • View的delegate、datasource
  • 业务逻辑
  • 状态维护

与单一责任准则背道而驰。所以MVC也被很多人称为 Massive View Controller。
臃肿的View Controller使APP的维护成本非常高。所以MVVM和VIPER这些架构就出现了。
(还有个VIPER的)MVVM 相比 MVC 到底有哪些好处呢?我想,主要可以归纳为以下三点:

通常会把大量原来放在 ViewController 里的视图逻辑和数据逻辑移到 ViewModel 里,从而有效的减轻了 ViewController 的负担。另外通过分离出来的 ViewModel 获得了更好的测试性,我们可以针对 ViewModel 来测试,解决了界面元素难于测试的问题。MVVM 通常还会和一个强大的绑定机制一同工作,一旦 ViewModel 所对应的 Model 发生变化时,ViewModel 的属性也会发生变化,而相对应的 View 也随即产生变化。

MVVM的缺点:

  • 不够直观,学习成本和开发成本都很高,MVVM提高了代码整体的复杂度,对新入职的员工有一定的学习成本,这种模式学习曲线很大不好掌握,对于新项目来说可以使用,但是对于一个已经很复杂的大中型项目,就不太好动框架这层东西了。
  • 苹果也没有现成的绑定机制,要发挥MVVM的优势要么用KVO,要么引入ReactiveCocoa这样的第三方库,会增加学习成本和开发成本进一步增高。
  • 另一个缺点是数据绑定使得Debug变得更难了,数据绑定使程序异常能快速的传递到其他位置,比如在界面上发现的bug可能是viewModel造成的,也有可能是model造成的,数据链越长,对bug定位越困难。
  • 在传统的MVVM中,ViewModel依然要承载大量的逻辑,包括业务逻辑,界面逻辑,数据访存和网络相关,使得ViewModel仍然有可能变得和MVC一样臃肿。

基于MVC模式的变形,SOA面向服务,比如Glow公司,(这是一个上海的公司,里面有很多硅谷的大牛),他们的变形就是、应用层、服务层和数据层

10.Sel与IMP

方法的名字、地址(SEL)

方法的实现(IMP,指向 C 函数的指针),他们一一对应的。

像日记记录、本地存储、网络请求,服务器端数据的同步这些技术难点都有涉及。这些问题也都要求多线程编程。特别是数据同步,如何增量记录数据的增删改,什么时机跟服务器端进行同步。解决这些技术难题是非常有意思的工作,也是架构设计的创造性和乐趣所在。

11.AOP

AOP是OOP的延续,是(Aspect Oriented Programming)的缩写,意思是面向切面(方面)编程。

主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。

12.架构

  • 100个人开发同一个app,怎么解决代码冲突
  • 任何人改代码都要重新编译
  • 引用关系杂乱,没有合理解耦
  • 仅仅只是改变了目录结构

cocoapods解决了代码冲突

13.mediator和urlroute解决杂乱的引用

mediator

  • Business Module 引用 mediator
  • mediator用AOP的方式动态调用Class

URLRoute

  • 所有需要调用的服务都对应URL
  • 服务包含却不仅限于View Controller

优点:

  • 三端统一
  • 跨app传参
  • 无缝结合Hybrid App
  • 降级

软实力

  • 有优秀的逻辑思维和抽象思维能力
  • 有优秀的沟通技巧和情绪控制能力,可以无障碍地与开发、设计按照需求进行沟通,并保证项目顺利开展
  • 有优秀的时间管理能力,保证项目开发进程可控
  • 有良好的知识储备习惯
  • 保持洞察一切(用户、产品、世界)的欲望,并具备独立思考精神

14.缓存

  • NSCache 配合NSPurgeableData使用效果会很好,可实现自动清除数据的功能,也就是说当NSPurgebleData对象所在内存被系统所丢弃时,该对象自身也会从缓存中移除。
  • 只有那种“重新计算起来很费事的”数据才值得放入缓存,比如需要从网络或者磁盘读取的数据。
  • removeAllObject方法有bug,移除缓存后不能再缓存对象

15.app瘦身

最后简单把缩减iOS安装包大小的各种方法列出来作为CheckList:


缩减安装包的体积要从资源文件上下手,尽可能用Core Graphics自绘代替资源图片。如果是用户可能用不到的功能,那么应该只把代码合成进去而资源文件应该在用户第一次使用的时候从服务器下载并缓存在本地。裁剪第三方开源库的代码等等。

1、脚步去除无用代码:
  • 文件级:取project里所有源码文件列表,遍历所有代码找出import列表,找出没被import过的文件进行清理
  • 函数级:
    1.正则替换出所有源码文件的定义的方法列表,像-(void)function:(NSString*)a
    2.正则替换出所有源码文件里调用的方法列表,包括@selector()/[object func]
    遍历查找1结果里的每一条方法是否存在于2的结果中,不存在则初步判断没被使用
2、ARC->MRC

ARC在某些情况会多出一些retain和release操作,比如调用一个方法,它返回的对象会retain,退出作用域后会被release,而在MRC下则不会。我在网上查了下ARC大概会使代码段增大10%的大小,而代码段大约占可执行文件的80%,因此ARC大概会增大app8%左右的体积,可以评估一下8%的体积下降是不是值得把项目里某些模块改成MRC,这样程序的维护成本上了,不到特殊情况还是不应该这么做。

16.GCD优势在哪里(牛逼的中枢调度器)

  1. GCD自动利用更多的CPU内核
  2. GCD会自动管理线程的生命周期
  3. 使用简单
  • 任务
  • 队列

17NSOperation相比于GCD有哪些优势?

  • 提供了在GCD中不那么容易复制的有用特性
  • 可以很方便的取消一个NSOperation的执行
  • 可以更容易的添加任务的依赖关系
  • 提供了任务的状态:isExecuting,isFinished

17.Objc优缺点

和c++一样,OC也算是一门面向对象的语言,也支持封装、继承、多态,但是除此之外还有动态的三大特性:

1. 动态类型

运行时再决定对象的类型。简单来说就是id。id类型就是通用对象类,任何对象都可以被id指针所指,在日常开发中用introspection来确定该对象实际所属类。

-isMenberOfClass:是NSObject方法,用以确定某个NSObject对象是否是某个类的成员。

-isKindOfClass:确定某个对象是否是某个类或者子类的成员。

2. 动态绑定

基于动态类型,在某个实例对象被确定后,它的类型就被确定了。该对象对应的属性和响应的消息也被完全确定。当然了,这里的属性和方法包括了原来在类中实现了的,也包含了原来没有在类中实现的,而是在运行时才需要的新加入的实现。
OC消息转发机制被触发之前,对应类的+resolveClassMethod:和+resolveInstanceMethod:将会被调用,这时有机会动态的向类或者实例添加新的方法。就是说类的方法也是可以动态绑定的。

3. 动态加载

根据需求加载所需要的资源,这点很容易理解,最经典的例子是在Retina设备上加载@2x的图片,普通屏上加载原图。因为Retina上一个点代表的像素是2*2

因为OC动态的特性,OC中很少提到“函数”的概念,传统的函数一般是编译时就已经把参数信息和函数实现打包到编译的源码中了,但是在OC中,最常使用的是消息机制。调用一个实例的方法,OC做的是向该实例的指针发送消息,实例收到消息后,从自身的实现中寻找响应这条消息的方法。

缺点

我暂时能想到的不足就是不支持命名空间,但是我们可以用前缀命名来解决这个问题,以前手动引入第三方库会引起冲突,但是现在我用cocoapods还没出现过这个问题。

18.属性readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用?

  1. readwrite:同时生成get和set方法
  2. readonly:只生成get方法
  3. assign:直接赋值,用于基本数据类型
  4. retain:release旧值,retain新值,用于OC对象
  5. copy:release旧值, copy新值,用于OC对象
  6. nonatomic:非原子性,set方法实现不加锁

ARC下指针的默认值是strong,绝大多数情况下这都是没问题的,除了循环引用。

20.谈谈你对Category和Extension的理解

1.Category

分类可以在不修改原来类模型的基础上给类添加方法,也可以通过动态绑定添加实例变量。
分类有一个重要的设计原则就是:分类的实现可以依赖主类,但是主类一定不依赖于分类,也就是说移除任何一个分类都不会对主类产生影响。

所以 Category 一定是简单插拔的,就像买个外接键盘来扩展在 MacBook 上的写码能力,但当拔了键盘,MacBook 的运行不会受到任何影响。

2.Extension

Class Extension 是匿名的 Category但是它可以显示的增加类属性添加类方法。

Class Extension 还能巧妙的解决一个接口暴露问题

// Sark.framework/Sark.h
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *creditCardPassword; // secret!
@end

// Sark.framework/PrivateSarkWife.h
@interface PrivateSarkWife : NSObject
- (void)robAllMoneyFromCreditCardOfSark:(Sark *)sark; // needs password!
@end
// Sark.h
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end

// Sark+Internal.h <--- new
@interface Sark ()
@property (nonatomic, copy) NSString *creditCardPassword;
@end

// Sark.m
#import "Sark.h"
#import "Sark+Internal.h" // <--- new

将对公业务和对私业务用 Class Extension 的形式拆到两个 Header 中,这样私有类对私有属性的依赖就被成功隔离开了:

20.谈谈你对Singleton的理解

单例:保证程序运行过程中,永远只有一个对象实例
目的:全局共享一份资源、节省不必要的内存开销

21.frame和bounds的区别?

frame是以父控件的坐标系为参照,父控件的左上角为原点。
bounds是以自身坐标系为参照,自身的左上角是坐标原点。

22.method和selector有什么不同?

通过一个selector可以找到方法地址,method是一个组合体,包含了名字和实现。

23.说一说你对block的理解

block是OC用来实现闭包的,我对闭包的理解就是说闭包就是一个函数(或者指向函数的指针),再加上该函数执行所需的外部或内部变量。所以块的作用是代替函数指针的语法结构。
block也是是一个对象,因为block_layout里有isa指针。

在OC里面block分为三种,NSConcreteMallocBlock、stackBlock、globalBlock、就是堆块、栈块、全局块。

  • 全局块

    全局块就是说,如果一个block没有引用外部变量,它就是全局块,而且全局块在编译器的时候就定下来了,跟宏一样。

  • 栈块

    如果一个block引用了外部变量,那它就是一个栈块。但是它不会持有外部对象。

  • 堆块

    如果一个block被copy了,那么它就是堆块。

    巧哥对block的理解就有一处错误,就是唐巧,他说ARC下是只有全局块和堆块的,其实这是不对的,只是因为我们经常要把block赋值给常量,ARC下block被赋值给strong类型的对象或者block的成员变量时,编译器会自动把block作为_Block_copy函数,效果等于给block直接调用copy(block 没有retain)

关于__block

block默认是不能修改外部的值的,不能修改是因为block里面copy了一份,比如说指针,你拿到的其实是指向相同地址的不同指针,但用了 __block ,其实拿到的是完全相同的指针。 Apple这样设计,应该是考虑到了block的特殊性,block也属于“函数”范畴,变量进入block,实际上就是已经改变了作用域。在几个变量域之间进行切换,如果不加上这样的限制,变量的可维护性会大大降低。比如我想在block内声明一个与外部同名的变量,此时就不知道该不该允许了。

关于__block关键字我也想讲一下,很多人对它也存在着理解误区,block引用外部是以捕获的形式来捕捉的,没用__block,直接将外部变量copy进block,所以写操作不会生效,这我们都很清楚,但是要修改外部变量的值,就得用__block关键字,然后很多人就单纯的觉得__block就是使block能写操作生效的,但是他们很少有人知道为什么写操作就生效了,而且有些情况不用__block关键字也是可以让写操作生效的,__block的作用就是被__block关键字修饰的变量,那么编译器只要观察到该变量被block所持有,就会将“外部变量”在栈中的内存地址放到堆中,所以在block内部改变了它的值就会对外有影响。

很多人也不知道,在MRC环境下,没有用__block,会对外部采用copy操作,而用了_block则不会用copy操作。

block的生命周期

因此在ARC下用__weak(ARC)解决循环引用,MRC下用__block(MRC)解决循环引用。这里也要主要一下,ARC下用__block关键字会导致变量被block retain一次。

block对于参数形式传进来的对象,会不会强引用?

不会强引用

25 ARC、内存管理

ARC的实现

有了ARC过后,我们开发程序方便了很多,
ARC在某些情况会多出一些retain和release操作,比如调用一个方法,它返回的对象会retain,退出作用域后会被release。

ARC约定

使用ARC之后一个费解的地方是,一个方法生成的对象,没有任何附加标示,ARC怎么知道生成的对象是不是autorelease的呢?这里ARC就用了约定,NS先定义了几种编译属性

#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))
#define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained))
#define NS_RETURNS_INNER_POINTER __attribute__((objc_returns_inner_pointer))

编译器约定,对于alloc,init,copy,mutableCopy,new这几个家族的方法,后面默认加NS_RETURNS_RETAINED标识;而其他不指名标识的family的方法默认添加NS_RETURNS_NOT_RETAINED标识

我了解一点点ARC对self的内存管理:

OC主要采用引用计数器来管理内存,新对象的计数器是1,计数器为0了,就会被销毁。
OC内存的管理原则就是谁创建谁销毁,谁引用谁管理。
比如如果我们copy了一个对象,他会释放旧值,copy新值,需要负责该对象的释放。如果我们retain了一个对象,我们也部分拥有这个对象,也需要在不再使用的时候释放该对象。

也可以用autoreleasepool来管理内存,autoreleasepool会在何时的时机释放对象。Leaks instrument 的Cycles view可以看循环引用。

  • ARC下,self既不是strong也不是weak,而是unsafe_unretained的(init系列方法的self除外)。也就是说ARC不会对方法里的self做retain和release。
  • ARC这样做的原因是性能优化,objc中100%的方法(不是函数)调用的第一个参数都是self,而且,99%的情况下,调用方都不会在方法执行时把这个对象释放掉,所以相比于在每个方法中插入对self的引用计数管理,先retain self,再release 优化的性能是十分可观的:
- (void)start {
    objc_retain(self);
    // 其中的代码self一定不会释放
    objc_release(self);
}

另外的1%就十分有趣了,因为ARC不会对self做retain和release,所以self的生命周期全由它的调用方来保证的,如果调用方在函数执行的过程中把self释放掉了,我们再去访问它就会出现BAD_ACCESS。简化一点说明就是,一个方法里面,首先self被一个delegate持有,然后delegate调用了它的代理方法,在方法里面把这个self对象释放了,然后self就变成了野指针了:

- (void)foo {
    // self被delegate持有
    [self.delegate callout]; // 外部释放了这个对象
    // 这里self野指针
}

解决办法就是用一个强引用指向self。

比如以前猿题库他们那个网络库YTKNetwork就出现了这个问题,具体是这样的:

1.它里面有个YTKRequest对象,负责网络的发送请求,以及请求结束的回调。如果我们使用它,首先就有个viewController 实例化并且强引用了TYKRequest对象,然后把自己作为它的delegate。
2.这个viewController调用了YTKRequest的start方法发起网络请求,start里面有个-requestFinished:方法。
3.然后viewController在代理方法-requestFinished:里面执行了self.request = nil,这个时候我们的request对象就释放变成了野指针了。
4.如果后面再调用比如说什么if( request.successCompletionBlock),就会报BAD ACCESS的错。他们的解决办法就是用一个强指针引用self。

27数据库相关

表面上SQLite将数据分为以下几种类型:integer : 整数、real : 实数(浮点数)、text : 文本字符串、blob : 二进制数据,比如文件,图片之类的。实际上SQLite是无类型的。即不管你在创表时指定的字段类型是什么,存储是依然可以存储任意类型的数据。而且在创表时也可以不指定字段类型。SQLite之所以设置类型就是为了良好的编程规范和方便开发人员交流,所以平时在使用时最好设置正确的字段类型!主键必须设置成integer。

在一般开发过程中,使用的都是第三方开源库 FMDB,封装了基本的c语言方法,使得我们在使用时更加容易理解,提高开发效率。FMDB有三个主要的类:FMDatabase,一个FMDatabase对象就代表一个单独的SQLite数据库,用来执行SQL语句。FMResultSet,使用FMDatabase执行查询后的结果集。FMDatabaseQueue,用于在多线程中执行多个查询或更新,它是线程安全的。具体操作请移步gitHub。

28离屏渲染

那哪些情况会Offscreen Render呢?

  1. drawRect
  2. layer.shouldRasterize = true;
  3. 有mask或者是阴影(layer.masksToBounds, layer.shadow*);
    3.1) shouldRasterize(光栅化)
    3.2) masks(遮罩)
    3.3) shadows(阴影)
    3.4) edge antialiasing(抗锯齿)
    3.5) group opacity(不透明)
  4. Text(UILabel, CATextLayer, Core Text, etc)...

29 load与init

  • load和initialize方法都会在实例化对象之前调用,以main函数为分水岭,前者在main函数之前调用,后者在之后调用。这两个方法会被自动调用,不能手动调用它们。
  • load和initialize方法都不用显示的调用父类的方法而是自动调用,即使子类没有initialize方法也会调用父类的方法,而load方法则不会调用父类。
  • load方法通常用来进行Method Swizzle,initialize方法一般用于初始化全局变量或静态变量。
  • load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。

30什么是 Method Swizzling?

method swizzling 实际上就是一种在运行时动态修改原有方法实现的技术, 它实际上是基于 ObjC runtime 的特性, 而 method swizzling 的核心方法就是 method_exchangeImplementations(SEL origin, SEL swizzle). 使用这个方法就可以在运行时动态地改变原有的方法实现,方法的调用时机就是在上面提到的 load 方法中, 不在 initialize 方法中改变方法实现的原因是 initialize 可能会被子类所继承并重新执行最终导致无限递归, 而 load 并不会被继承.

30 响应者链

  1. 响应者链通常是由视图(UIView)构成的
  2. 一个视图的下一个响应者是它的视图控制器(如果有的话),然后再传递给它的父视图
  3. 视图控制器(如果有的话)的下一个响应者为其管理的视图的父视图。
  4. 在视图层次结构的最顶级视图,我们可以把它看成一棵树,如果也不能处理收到的事件或者消息,就会把事件传递给UIWindow对象进行处理
  5. 如果window对象也不处理,则将事件或消息传递给UIApplication对象
  6. 如果UIApplication也不处理则丢弃

事件分发

iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。

31缓存

缓存首先不是简单的分成内存缓存和沙盒缓存,缓存有不同的分类方法,按功能划分主要是优化型缓存和存储型缓存,按形式划分才是内存缓存和沙盒缓存。我们讲的话就按功能划分

第一种优化型缓存:

  • 描述:处于优化考虑,比如服务器压力、用户体验、为用户省流量等。
  • 常见的就是GET网络请求
  • 比如微信首页的会话列表、微信头像、朋友圈、网易新闻新闻列表(可详细展开)

第二种功能型缓存:

  • 描述:APP离线也能查看,处于功能考虑,那它属于存储范畴
  • 常见的就是离线存储
  • 比如微信聊天记录

Get网络请求缓存

首先要知道post请求不能被缓存,只有Get请求能被缓存。因为从数学的角度讲,GET的结果是幂等的,就好像字典里的key与value就是幂等的,而post不幂等。get缓存的思路就是把查询的参数作为key,对应的结果作为value。

我觉得NSURLCache十分强大了,以前AF的作者Thompson说过一句话,大意是这样的:

  • 无数开发者尝试自己做一个简陋又脆弱的系统来实现网络缓存的功能,却不知道NSURLCache只要两行代码就能搞定且好上100倍。

我觉得他是在讽刺某库。

1.请使用GET请求

2.如果用了GET请求,iOS的SDK就自动帮我们做好缓存了,我们只需要设置一下内存缓存的大小和沙盒缓存的大小,以及缓存路径就可以了。

NSURLCache *urlCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil];
[NSURLCache setSharedURLCache:urlCache];

只是要注意一下
NSURLCache ios5开始支持沙盒缓存但是只支持HTTP的,从iOS6过后才开始支持HTTPS缓存

但是有时候并不能满足需求,比如有这种情况,我们用户改变了我们头像,但是GET得URL并没有变,如果我们直接调缓存,那么头像就不会更新。这种情况是挺常见的,我们要解决这个问题就要借助Etag或者Last-modified来判断图片缓存是否有效。

Last-modified就是资源最后修改的时间戳,只要用来和缓存时间进行对比就能判断缓存是否过期。
我们第一次请求某个URL的时候,服务器会返回200,内容是你请求的资源,同时又一个Last-Modified的属性标记,表明了这个文件在服务器端最后修改的时间。我们第二次再去访问,客户端会像浏览器发送If-Modified-Since报头,询问这个时间过后文件是否被修改过,如果没有修改,服务器直接返回304,内容为空,这样就节省了传输数据量,如果客户端收到了304就直接调缓存就可以了。

Last-Modified需要注意的是服务器可能会回滚启用旧版本,就会导致客户端的Last-modified比服务器端还要新。他是基与时间来校验的,是弱校验。

官方给出的文档提出Etag是首选方式,由于Last-modified,因为Etag是基于hash的,是强校验,Etag的使用和Last-modified差不多,只不过查询头不一样

 If-None-Match: W/"50b1c1d4f775c61:df3"

虽然 ETag 优于 Last-Modified ,但并非所有服务端都会支持,而 Last-Modified 则一般都会有该字段。 大多数情况下需要与服务端进行协调支持 ETag ,如果协商无果就只能退而求其次。

一些建议:

如果是 file 文件类型,用 Last-Modified 就够了。即使 ETag 是首选,但此时两者效果一致。九成以上的需求,效果都一致。
如果是一般的数据类型--基于查询的 get 请求,比如返回值是 data 或 string 类型的 json 返回值。那么 Last-Modified 服务端支持起来就会困难一点。因为比如
你做了一个博客浏览 app ,查询最近的10条博客, 基于此时的业务考虑 Last-Modified 指的是10条中任意一个博客的更改。那么服务端需要在你发出请求后,遍历下10条数据,得到“10条中是否至少一个被修改了”。而且要保证每一条博客表数据都有一个类似于记录 Last-Modified 的字段,这显然不太现实。

如果更新频率较高,比如最近微博列表、最近新闻列表,这些请求就不适合,更多的处理方式是添加一个接口,客户端将本地缓存的最后一条数据的的时间戳或 id 传给服务端,然后服务端会将新增的数据条数返回,没有新增则返回 nil 或 304。

文/iOS程序犭袁(作者)
原文链接:http://www.jianshu.com/p/fb5aaeac06ef
著作权归作者所有,转载请联系作者获得授权,并标注“作者”。

26UITableView性能优化

说说 UITableView 的调优。——一方面是通过 instruments 检查影响性能的地方,另一方面是估算高度并在 runloop 空闲时缓存。
1.数据刷新的原则
通过修改模型数据,来修改tableView的展示
- 先修改模型数据
- 再调用数据刷新方法

  • 不要直接修改cell上面子控件的属性

2.如果只是cell的尺寸没有改变,只是content改变了,不要reload,而是调用cell的redisplay,用setNeedDisplay来刷新

性能优化最大的问题就是卡顿

卡顿产生的原因就是一个VSync信号(显示器的垂直信号)内,CPU计算花费的时间,比如视图的创建,布局计算、文本绘制等。然后CPU会把计算好的内容提交到GPU去,由GPU进行变换、合成渲染。然后GPU会把渲染结果放到帧缓冲区,然后视频控制区会按照VSync信号逐行读取帧缓冲区的数据。由于垂直同步机制,如果一个VSync时间内,CPU或者GPU没有完成内容提交,那么这一帧会被丢弃,等待下一次机会再显示,这个时候屏幕会保留之前的内容不变,这就是界面卡顿的原因。

CPU资源消耗原因和解决方案

  1. 对象创建

对象的创建会分配内存、调整属性、甚至还有读取文件等操作,比较消耗 CPU 资源。尽量用轻量的对象代替重量的对象,可以对性能有所优化。比如 CALayer 比 UIView 要轻量许多,那么不需要响应触摸事件的控件,用 CALayer 显示会更加合适。

  1. 对象销毁
    对象的销毁虽然消耗资源不多,但累积起来也是不容忽视的。通常当容器类持有大量对象时,其销毁时的资源消耗就非常明显。对象销毁有个小 Tip:把对象捕获到 block 中,然后扔到后台队列去随便发送个消息以避免编译器警告,就可以让对象在后台线程销毁了。
NSArray *tmp = self.array;
self.array = nil;
dispatch_async(queue, ^{
    [tmp class];
});
  1. 布局计算
    视图布局的计算是 App 中最为常见的消耗 CPU 资源的地方。如果能在后台线程提前计算好视图布局、并且对视图布局进行缓存,那么这个地方基本就不会产生性能问题了。最好是一次性调整好对应属性,不要经常调整这些属性。

  2. Autolayout

Autolayout 是苹果本身提倡的技术,在大部分情况下也能很好的提升开发效率,但是 Autolayout 对于复杂视图来说常常会产生严重的性能问题。随着视图数量的增长,Autolayout 带来的 CPU 消耗会呈指数级上升。

  1. 图片的解码

如果你用 UIImage 或 CGImageSource 的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 里面,并且是在CPU提交内容到GPU前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。如果想要绕开这个机制,可以用Bitmap解码,常见的做法是在后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。目前很多常见的网络图片库都自带这个功能。

GPU资源消耗原因和解决方案

主要是纹理的渲染、视图的混合、图形的生成,比如CALayer的border、圆角、阴影、遮罩,CASharpLayer矢量图形显示,通常会触发离屏渲染(offscreen rendering),而离屏渲染通常发生在GPU中。当一个列表视图中出现大量圆角的CALayer,并且快速滑动,就可以观察到GPU资源已经占满,而CPU资源消耗很少,这时界面仍然可以正常滑动,但是平均帧数很低,为了避免这种情况,可以开启光栅化属性CALayer.shouldRasterize,这会把离屏渲染的操作转嫁到CPU上。最好的办法还是把要显示的图形在后台线程绘制成图片,避免使用圆角、阴影、遮罩等属性。

深复制、浅复制

面试复习备忘录(待整理)_第1张图片
Paste_Image.png

1、 非容器对象对不可变对象复制,copy是指针复制(浅拷贝)和mutableCopy就是对象复制(深拷贝)。对可变对象复制,都是深拷贝,但是copy返回的对象是不可变的。

2、
容器对象
对于容器本身,同非容器对象

但是,对于容器内的对象,则都是指针复制。真正的深拷贝(即容器本身、容器内的对象均为深拷贝),实现方式如下:
NSArray
*trueDeepCopyArray =
[
NSKeyedUnarchiver

unarchiveObjectWithData
:[
NSKeyedArchiver

archivedDataWithRootObject
:originArray]];
originArray-
待拷贝数组

32.自己在编写程序的时候,遇到BUG是如何解决的?用了哪些解决方法?

  1. 有效的Log;Log一些重要的事件
  2. 全局断点:
  3. 条件断点:
  4. 控制台进行手动打印:po
  5. 跟踪对象的生命周期;跟踪数据传递的过程
  6. 分析层级结构
  7. Enable NSZombie Objects(开启僵尸对象):开启僵尸对象,首先打开“Edit Scheme”,然后选择Diagnostics选项卡,勾选Enable NSZombie Objects选项。
  8. 静态分析(Analyze内存泄漏分析)和动态分析()
    http://blog.csdn.net/totogo2010/article/details/8233565

33.Runtime

一条消息也就 objc_msgSend 做了什么事。举 objc_msgSend(obj, foo) 这个例子来说:

首先,通过 obj 的 isa 指针找到它的 class ;
在 class 的 method list 找 foo ;
如果 class 中没到 foo,继续往它的 superclass 中找 ;
一旦找到 foo 这个函数,就去执行它的实现IMP .

但这种实现有个问题,效率低。但一个 class 往往只有 20% 的函数会被经常调用,可能占总调用次数的 80% 。每个消息都需要遍历一次 objc_method_list 并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是 objc_class 中另一个重要成员 objc_cache 做的事情 - 再找到 foo 之后,把 foo 的 method_name 作为 key ,method_imp 作为 value 给存起来。当再次收到 foo 消息的时候,可以直接在 cache 里找到,避免去遍历 objc_method_list.

一个OBJC对象如何进行内存布局?(考虑有父类的情况)

  • 所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中
  • 父类的方法和自己的方法都会缓存在类对象的方法缓存中,类方法是缓存在元类对象中
  • 每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的如下信息:
    • 对象方法列表
    • 成员变量列表
    • 属性列表

你在项目中遇到的难点?

熟悉 CocoaPods 么?能大概讲一下工作原理么?

CocoaPods原理:
1.Pods项目最终会编译成一个名为libPods.a的文件,主项目只需要依赖这个.a文件即可
2.对于资源文件,CocoaPods提供了一个名为Pods-resources.sh的bash脚本,该脚本在每次项目编译的时候都会执行,将第三方的各种资源文件复制到目标目录中
3.CocoaPods通过一个名为Pods.xcconfig的文件在编译时设置所有的依赖和参数

UIScrollView 大概是如何实现的,它是如何捕捉、响应手势的?

  • 我对UIScrollView的理解是frame就是他的contentSize,bounds就是他的可视范围,通过改变bounds从而达到让用户误以为在滚动,以下是一个简单的UIScrollView实现
  • 第二个问题个人理解是解决手势冲突,对自己添加的手势进行捕获和响应

UIView 和 CALayer 之间的关系?

  • UIView显示在屏幕上归功于CALayer,通过调用drawRect方法来渲染自身的内容,调节CALayer属性可以调整UIView的外观,UIView继承自UIResponder,CALayer不可以响应用户事件
  • UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它内部是由Core Animation来实现的,它真正的绘图部分,是由一个叫CALayer(Core Animation Layer)的类来管理。UIView本身,更像是一个CALayer的管理器,访问它的根绘图和坐标有关的属性,如frame,bounds等,实际上内部都是访问它所在CALayer的相关属性
  • UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示

如果我这次失败的,今年还有没有另外的机会加入xx”,结果面试官说“你为什么觉得自己会失败呢,你对自己没有信心吗?”,我说“不是这样的,只是我非常想加入百度而已”

你可能感兴趣的:(面试复习备忘录(待整理))