iOS面试题总结(一)

把网上的一些结合自己面试时遇到的面试题总结了一下,以后有新的还会再加进来。

1. OC 的理解与特性

OC 作为一门面向对象的语言,自然具有面向对象的语言特性,如封装、继承、多态。它具有静态语言的特性又有动态语言的效率。它的动态特性表现在三个地方:动态类型、动态绑定和动态加载。之所以叫做动态,是因为必须到运行时才会做一些事情。

动态类型:即运行时再决定对象的类型,比如 id 类型;

动态绑定:基于动态类型,在某个实例对象被确定后,其类型便被确定了,该对象对应的属性和响应的消息也被完全确定。

动态加载:根据需求加载所需要的资源,比如根据不同的机型做适配。

2. 简述对内存管理的理解

OC 通过引用计数来对内存进行管理,核心思想是遵循 “谁创建,谁释放,谁引用,谁管理” 的机制,分为两种方式:MRC 和 ARC 。MRC 是 当创建或引用一个对象的时候,需要向它发送 alloc、copy、retain 消息,当释放该对象时需要发送 release 消息,当该对象的引用计数为 0 时,系统将释放该对象。ARC 是由系统在适当的位置插入 release 或 autorelease,它引用了 strong 和 weak 关键字,用 strong 修饰的指针变量指向对象时,当指针指向新值或指针不再存在时相关联的对象就会自动释放,而用 weak 修饰的指针指向对象时,当对象的拥有者指向新值或不存在时,指针会置为 nil。

3. 如何理解 MVC 模式

MVC 是一种架构模式。M 表示数据模型 model,V 表示视图 VIew,C 表示控制器 Controller。

Model 负责存储、定义、操作数据;

View 用来展示数据给用户,和用户进行操作交互;

Controller 是 Model 和 View 的协调者,Controller 把 Model 里的数据拿过来给 View 用。Controller 可以直接与 Model 和 View 进行通信,而 View 不能和 Controller 直接通信需要代理协议的方式,当有数据更新时,Model 也要与 Controller 进行通信,比如使用 notification 和 KVO 。Model 和 View 不能直接通信,因为这样就违背了 MVC 的设计思想。

4. 定时器和线程的区别

定时器:可以执行多次,默认在主线程中;

线程:只能执行一次

5. NSNotification、Block、Delegate 和 KVO 的区别

代理是一种回调机制,且是一对一的关系,而通知可以一对多,代理效率比通知高;

KVO 是被观察者向观察者直接发送通知,而通知相反;

代理需要定义协议方法,代理对象实现协议方法,并且需要建立代理关系才能实现通信,而 Block 更加简洁,不需要定义繁琐的协议方法,但若通信事件比较多的话,建议用代理,而通知主要用于一对多情况下通信,通信对象之间不需要建立关系。

6. TCP 和 UDP 的区别

TCP 为传输控制层协议。这种协议提供面向连接的、可靠的、点到点的通信;

UDP 为用户数据报协议。这种协议提供非连接的、不可靠的、点到多的通信但是比 TCP 快。

7. TCP 的三次握手

第一次握手:客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SEND 状态 ,等待服务器确认;

第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 syn 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN+RECV 状态;

第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ack=k+1),此时发送完毕,客户端和服务端进入 ESTABLISHED 状态,完成三次握手

8. 对沙盒的理解,App Bundle 里面都有什么

每个 iOS 应用都被限制在 “沙盒” 中,“沙盒” 相当于一个加了仅主人可见权限的文件夹,就是应用程序在安装过程中,系统为每个单独的应用程序生成它的主目录和一些关键的子目录。

苹果对沙盒有以下几条限制:

1. 应用程序可以在自己的沙盒里运作,但不能访问任何其他应用程序的沙盒;

2.应用程序间不能共享数据,沙盒里的文件不能被复制到其他应用程序文件夹中,也不能把其他应用程序文件夹中的文件复制到沙盒里;

3.苹果禁止任何读、写沙盒以外的文件,禁止应用程序将内容写到沙盒以外的文件夹中;

沙盒根目录里有三个文件夹和一个 .app 包:

AppName.app 目录:这是应用程序的程序包目录,包含应用程序的本身。由于应用程序必须经过签名,所以您在运行时不能对这个目录中的内容进行修改,否则可能会使应用程序无法启动。

Documents:iTunes会备份该目录。一般用来存储需要持久化的数据,这里不能存缓存文件,否则上架不被通过;

Library:下有两个文件夹,Library/Caches:缓存,iTunes不会备份该目录。内存不足时会被清除,应用没有运行时,可能会被清除,SDWebImage缓存路径就是这个。一般存储体积大、不需要备份的非重要数据;Library/Preference:iTunes同会备份该目录,可以用来存储一些偏好设置

Temp:iTunes不会备份这个目录,用来保存临时数据,应用退出时会清除该目录下的数据。

App Bundle 里面包括:

Info.plist:此文件包含了应用程序的配置信息.系统依赖此文件以获取应用程序的相关信息

可执行文件:此文件包含应用程序的入口和通过静态连接到应用程序target的代码

资源文件:图片,声音文件一类的

其他:可以嵌入定制的数据资源

9. 为什么 OC 中的分类、扩展和协议是重要的

因为它们允许以不创建子类的方式复用代码,这意味着与其他只能通过子类化来共享代码的语言相比,在 OC 中。类层次结构可能更加扁平。

10. 为什么封装是重要的

封装使得维护工作变得更加容易,方便复用,降低耦合

11. 为什么访问器是重要的

通过访问器可以在不直接访问实例变量的情况下设置和获取实例变量的值

12. 静态类型和 id 类型的区别

使用 id 之后变量的类型是在运行时确定的,对象所属的类将确定哪些方法可用,但这会稍微增加一些运行时开销。静态类型则不会增加运行时开销,它允许编译器而非运行时代码进行错误检测。

13. 垃圾收集和引用计数的区别

垃圾收集是在运行时的一个过程,引用计数是在编写代码和编译代码的时候实现的,但其作用需要在运行时才能看出来

14. 什么是委托,它是如何跟协议 相关联的

委托一般由一个类来实现并会将其赋给这个类的 delegate 属性。实现委托的类必须要能够响应发送给委托的消息,那些消息通常是在委托的协议中指定的。一个类通常拥有不超过1个委托。但采用协议的个数没有限制。

15. 分类和协议的比较

相同点:可以在接口文件中声明方法,但不能声明变量和属性,在分类的实现中可以声明私有的实例变量

不同点:分类是绑定在一个特定类上的,而协议可以被任意类所采用。分类是在运行时被添加到某个特定类中。

16. 三个默认的全局并发队列分别是什么,在使用队列时,什么可以取代锁

默认优先级、低优先级、高优先级

使用线形队列

17. OC 的类可以多重继承么?可以实现多个接口么? Category 是什么?重写一个类的方式用继承好还是分类好?为什么?

OC 的类不可以多重继承;可以实现多个接口,通过实现多个接口可以完成 C++ 的多重继承; Category 是类别,一般情况用分类好,用 Category 去重写类的方法,仅对本 Category 有效,不会影响到其他类与原有类的关系。

18. #import 跟 #include 又什么区别,@class 呢, #import<> 跟 #import “”又什么区别

#import 是 OC 导入头文件的关键字,#include 是 C/C++ 导入头文件的关键字。使用 #import 头文件会自动只导入一次,不会交叉编译,相当于 #include 和 #pragma once ; @class 告诉编译器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含; #import <> 用来包含系统的头文件,#import"" 用来包含用户头文件

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

(1) readwrite 是可读可写特性;需要生成getter方法和setter方法时

(2) readonly 是只读特性 只会生成getter方法 不会生成setter方法 ;不希望属性在类外改变

(3) assign 是赋值特性,setter方法将传入参数赋值给实例变量;仅设置变量时;

(4) retain 表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1;

(5) copy 表示赋值特性,setter方法将传入对象复制一份;需要完全一份新的变量时。

(6) nonatomic 非原子操作,决定编译器生成的setter getter是否是原子操作,atomic表示多线程安全,一般使用nonatomic

20. 写一个setter方法用于完成@property (nonatomic,retain)NSString *name,写一个setter方法用于完成@property(nonatomic,copy)NSString *name

-(void)setName:(NSString*) str

{

[str retain];

[name release];

name=str;

}

-(void)setName:(NSString*)str

{

Id t=[str copy];

[name release];

Name = t;

}

21. 对于语句NSString*obj = [[NSData alloc] init]; obj在编译时和运行时分别时什么类型的对象

编译时是 NSString 类型,运行时是 NSData 类型的对象

22. atomic 和 non-atomic 属性的区别

atomic 本意是指属性的存取方法是线程安全的,并不保证整个对象是线程安全的。它仅限对setter、getter方法是线程安全的。如果对一个@property(atomic,strong)NSMutableArray *arr;如果一个线程循环读数据,一个线程循环写数据,肯定会产生内存问题;

non-atomic 在自己管理内存环境中,解析的访问器保留并自动释放返回的值,若指定了 nonatomic ,那么访问器只是简单的返回这个值

23. 如何对 iOS 设备进行性能测试

Profile-> Instruments ->Time Profiler

24. 浅拷贝和深拷贝的区别

浅拷贝只复制指向对象的指针,而不复制引用对象本身

深拷贝复制引用对象本身

25. 类别的作用,继承和类别在实现中有何区别

类别可以在不获悉、不改变原来代码的情况下往里面添加新的方法,只能添加,不能修改、删除,如果类别和原来类中的方法名产生冲突,则类别将覆盖原来的方法,因为类别具有更高的优先级。继承可以增加、修改、删除方法,并且可以增加属性。

类别的主要作用:

1. 将类的实现分散到多个不同文件或多个不同框架中;

2. 创建对私有方法的向前引用;

3.向对象添加非正式协议

26. 类别和类扩展的区别

category 和 extensions 的不同在于后者可以添加属性,且后者添加的方法必须要实现,extensions 可以认为是一个私有的 category。

27. 代理的作用

代理的目的是改变或传递控制链,允许一个类在某些特定时刻通知到其他类,而不需要获取到那些类的指针。可以减少框架的复杂度

28. frame 和 bounds 的区别

frame 指的是该 view 在父 view 坐标系统中的位置和大小,参照点是父亲的坐标系统

bounds 指的是该 view 在本身坐标系统中的位置和大小,参照点是本身坐标系统

29. 方法和选择器的区别

selector 是一个方法的名字,method 包含了方法的名字和实现

30. 在 iPhone 应用中如何保存数据

1. 通过 web 服务,保存在服务器上;

2. 通过 NSCoder 固化机制,将对象保存在文件中;

3. 通过 SQLite 或 CoreDate 保存在数据库中

31. 什么是 NSManagedObject 模型,什么是 NSManagedObjectContext?

NSManagedObject 是 NSObject 的子类,也是 CoreData 的重要组成部分,它是一个通用类,实现了 CoreData 模型层所需的基本功能,用户可通过子类化 NSManagedObject,建立自己的数据模型。

NSManagedObjectContext 对象负责应用和数据库之间的交互

32. 手机明明还有内存,但为何还会提示内存不足?

手机厂商通常会将运行内存(RAM)和存储内存(ROM)统称为手机内存,手机提示内存不足,并不指明是运行内存还是存储内存,事实上,往往是运行内存不足。所以,不少人发现手机仍有很多内存,其实这部分是存储内存。

33. 在项目中什么时候选择使用 GCD, 什么时候选择 NSOperation

NSOperation 的优点是对线程的高度抽象,在项目中使用它,会使得项目的程序结构更好,子类化 NSOperation 的设计思路,是具有面向对象的优点,使得实现是多线程支持,而接口简单,建议在复杂的项目中使用;

GCD 的优点是 GCD 本身非常简单、易用、更加轻量级,对于不复杂的多线程操作,会节省代码量,而 Block 参数的使用,会使得代码更为易懂,建议在简单的项目中使用。

34. GCD 和 NSOperation 的区别

1. GCD 是底层的 C 语言构成的 API , 而 NSOperationQueue 及相关对象是 OC 的对象。在 GCD 中,在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构,而 NSOperation 作为一个对象,为我们提供了更多的选择;

2. 在 NSOperationQueue 中,我们可以随时取消已经设定要准备执行的任务,当然,已经开始的任务就无法阻止了,而 GCD 没法停止已经加入 queue 的 block (其实是有的,但需要许多复杂的代码);

3. NSOperation 能够方便的设置依赖关系,我们可以让一个 Operation 依赖于另一个 Operation,这样的话尽管两个 Operation 处于同一个并行队列中,但前者会直到后者执行完毕后再执行;

4. 我们能将 KVO 应用在 NSOperation 中,可以监听一个 Operation 是否完成或取消,这样能比 GCD 更加有效的掌控我们执行的后台任务;

5. 在 NSOperation 中,我们能够设置 NSOperation 的 priority 优先级,能够使同一个并行队列中的任务区分先后的执行,而在 GCD 中,我们只能区分不同任务队列的优先级,如果要区分 block 任务的优先级,也需要大量复杂的代码;

6. 我们能够对 NSOperation 进行继承,在这之上添加成员变量与方法,提高整个代码的复用度,这比简单的将 block 任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。

35. ViewController的didReceiveMemoryWarning怎么被调用

[supper didReceiveMemoryWarning];

36. 用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL

37. const 意味着 “只读” ,下面声明都是什么意思

const int a;

int const a;

const int *a;

int * const a;

int const * a const;

前两个的作用是一样,a是一个常整型数。

第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。

第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。

最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。

38. 关键字volatile有什么含意?并给出三个不同的例子

一个定义为 volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

下面是volatile变量的几个例子:

并行设备的硬件寄存器(如:状态寄存器)

一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

多线程应用中被几个任务共享的变量

39. 一个参数既可以是const还可以是volatile吗? 一个指针可以是volatile 吗?解释为什么。

(1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

(2)是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时

40. static 关键字的作用

(1)函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,

因此其值在下次调用时仍维持上次的值;

(2)在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

(3)在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明

它的模块内;

(4)在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

(5)在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。

41. 进程之间通信的途径

共享存储系统消息传递系统管道:以文件系统为基础

42. iOS 的系统架构

iOS 的系统架构分为:核心操作系统层、核心服务层、媒体层和 Cocoa 界面服务层

43. Cocoa Touch 提供了哪几种 Core Animation 过度类型

交叉淡化、推挤、显示和覆盖

44. Quartz  2D 的绘图功能的三个核心概念是什么并简述其作用

1. 上下文:主要用于描述图形写入哪里;

2. 路径:是在图层上绘制的内容;

3. 状态:用于保存配置变换的值、填充和轮廓,alpha 值等

45. 通知与协议的区别

协议有控制链的关系,通知没有;

通知可以1对多,代理1对1;

代理的接受者可以返回值给发送者,通知不能;

代理比通知效率高

46. 什么是简便构造方法

简便构造方法一般由 Cocoa Touch 框架提供,如NSNumber的 + numberWithBool: + numberWithChar: + numberWithDouble: + numberWithFloat: + numberWithInt:

Foundation下大部分类均有简便构造方法,我们可以通过简便构造方法,获得系统给我们创建好的对象,并且不需要手动释放。

47. 简述视图控制器的生命周期

按照以下顺序执行:

1. initWithCoder: 通过 nib 文件初始化时触发;

2. awakeFromNib: nib 文件被加载的时候,会发生一个 awakeFromNib 的消息到 nib 文件中的每个对象;

3. loadView:开始加载视图控制器自带的 view。当用到控制器 view 时,会调用 view 的 get 方法,在 get 方法内部,先判断 view 是否已创建,若已存在,则直接返回存在的 view,若不存在,则调用控制器的 loadView 方法,在控制器没有被销毁的情况下,loadView 可能会被执行多次;

4. viewDidLoad:视图控制器的 view 被加载完成。当 loadView 执行完毕,view 被创建成功后就会执行 viewDidLoad 方法,与 loadView 一样,可能会被执行多次。比如 A push 到 B,此时窗口显示的是 B 的 View,此时若收到内存警告,我们一般会将 A 中没用到的变量及 View 销毁掉,之后当 B pop 回 A 时,就会再次执行 loadView 与 viewDidLoad 方法;

5. viewWillAppear:视图控制器的 view 将要显示在 window 上;

6. updateViewConstraints:视图控制器的 view 开始更新 AutoLayout 约束;

7. viewWillLayoutSubviews:视图控制器的 view 将要更新内容视图的位置;

8. viewDidLayoutSubviews:视图控制器的 view 已经更新视图的位置;

9. viewDidAppear:视图控制器的 view 已经展示到 window 上;

10. viewWillDisappear:视图控制器的 view 将要从 window 上消失;

11. viewDidDisappear:视图控制器的 view 已经从 window 上消失。

48. 动画的基本类型有哪些

隐式动画和显式动画,在做 iOS 动画时,当修改非 RootLayer 属性(比如位置、背景色)会默认产生隐式动画。

49. UIView 和 CALayer 的关系

UIView 是 iOS 界面元素的基础,看得见摸得着的基本都是 UIView,比如一个按钮、一个图标等。其实 UIView 之所以能显示在屏幕上,完全是因为它内部有一个图层。当创建一个 UIView 对象时,UIView 内部会自动创建一个图层(即 CALayer 对象),通过 UIView 的 layer 属性可以访问这个层。当 UIView 需要显示到屏幕上时,会调用 drawRect 方法进行绘图,并将所有内容绘制在自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,于是完成了 UIView 的显示。换句话说,UIView 本身不具备显示的功能,是它内部的层才有显示功能。

CALayer 内部控制两个属性 presentationLayer 和 modelLayer,modelLayer 为当前 layer 真实的状态,presentationLayer 为当前 layer 在屏幕上展示的状态。presentationLayer 会在每次屏幕刷新时更新状态,如果有动画则根据动画获取当前状态进行绘制,动画移除后则取 modelLayer 的状态。

CALayer 是定义在 QuartzCore 框架中的,CGImageRef、CGColorRef 两种数据类型是定义在 CoreGraphics 框架中的,而 UIColor 、UIImage 是定义在 UIKit 框架中的,QuartzCore 和 CoreGraphics 是可以跨平台使用的,在 iOS 和 Mac OS X 上都能使用,但 UIKit 只能在 iOS 中使用。所以为了保证可移植性,QuartzCore 不能使用 UIImage、UIColor,只能使用 CGImageRef、CGColorRef,这就是为什么 layer 对边框颜色设置时需要加上 CGColor 方法。

区别:

UIView 可以响应事件而 CALayer 不能;

CALayer 性能会高一些,更加轻量级;

UIView 主要是对显示内容的管理,而CALayer 主要侧重显示内容的绘制;

在做 iOS 动画时,当修改非 RootLayer 属性(比如位置、背景色)会默认产生隐式动画,而修改 UIView 则不会,这是因为任何可动画的 layer 属性改变时,layer 都会寻找并运行合适 action 来实行这个改变。

UIView 和 CALayer 是处于一个平级的关系。其实原因很简单, 是为了职责分离, 也算是模块化划分吧, 因为在 Mac OS 当中, 并没有 UIView 这个类, 所以并不能处理 Touch 事件, 但却有 CALayer, 所以苹果为了能够让 Mac OS 和 iOS 两个系统相互共享代码, 直接把 CALayer 给独立出来了。

50. UIImage 的初始化方法有哪些及区别

1. ImageNamed 是 UIImage 的类方法,系统先检查缓存中是否存在该名字的图像,若存在则直接返回,若不存在,则会先加载到缓存中,再返回该对象。适合频繁用到某一图片时使用。若我们需要短时间内频繁的加载一些一次性的图像,最好不要使用这种方法。

优点是方便快捷,只用第一次使用的时候稍慢,接下来再使用就会稍微快点。缺点是如果在当前工程中只使用一次,就会浪费内存。

[UIImage imageNamed:]加载图片有个好处在于可以立刻解压图片而不用等到绘制的时候。但是[UIImage imageNamed:]方法有另一个非常显著的好处:它在内存中自动缓存了解压后的图片,即使你自己没有保留对它的任何引用。

对于iOS应用那些主要的图片(例如图标,按钮和背景图片),使用[UIImage imageNamed:]加载图片是最简单最有效的方式。在nib文件中引用的图片同样也是这个机制,所以你很多时候都在隐式的使用它。

但是[UIImage imageNamed:]并不适用任何情况。它为用户界面做了优化,但是并不是对应用程序需要显示的所有类型的图片都适用。有些时候你还是要实现自己的缓存机制,原因如下:

[UIImage imageNamed:]方法仅仅适用于在应用程序资源束目录下的图片,但是大多数应用的许多图片都要从网络或者是用户的相机中获取,所以[UIImage imageNamed:]就没法用了。

[UIImage imageNamed:]缓存用来存储应用界面的图片(按钮,背景等等)。如果对照片这种大图也用这种缓存,那么iOS系统就很可能会移除这些图片来节省内存。那么在切换页面时性能就会下降,因为这些图片都需要重新加载。对传送器的图片使用一个单独的缓存机制就可以把它和应用图片的生命周期解耦。

[UIImage imageNamed:]缓存机制并不是公开的,所以你不能很好地控制它。例如,你没法做到检测图片是否在加载之前就做了缓存,不能够设置缓存大小,当图片没用的时候也不能把它从缓存中移除。

2. imageWithContentsOfFile 和 initWithContentsOfFile 一个是类方法,一个是对象方法,这两种是通过图片来加载图片的,没有缓存。当收到内存警告的时候,系统可能会将 UIImage 内部的存储图像的内存释放,下一次需要的时候重新加载。

3. imageWithCGImage:scale:orientation:,该方法使用一个 CGImageRef 创建 UIImage,在创建时还可以指定方法倍数以及旋转方向。

51. 协议和分类中怎么使用 property

在协议中使用 @property 只会生成 setter 和 getter 方法声明,我们使用属性的目的就是希望遵守我们协议对象能够实现该属性。内部是没有实例变量的,并且你不能去重定义它,只能在 setter 和 getter 里处理;

在分类中使用 @property 也只是会生成 setter 和 getter 方法声明,如果真需要在分类增加属性的实现需要借助两个运行时的函数 objc_setAssociatedObject 和 objc_getAssociatedObject

建立分类后,h文件:@property (nonatomic , strong) NSString * str;m文件导入运行时#import设置中转站:static char AddressKey;

写set和get方法:

-(NSString *)str{

return objc_getAssociatedObject(self, &AddressKey);

}

-(void)setStr:(NSString *)str{

objc_setAssociatedObject(self, &AddressKey, str,OBJC_ASSOCIATION_COPY_NONATOMIC);

}

52. 简述 Runloop 原理

一个 runloop 是一个事件处理环,系统利用这个环来安排事务,协调输入的各种事件。Runloop 的目的是让你的线程在有工作的时候忙碌,没有工作的时候休眠。线程和 Runloop 之间是以键值对的形式一一对应的,其中 key 是 thread,value 是 runloop。Runloop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。它提供了 NSRunLoop 和 CFRunLoopRef 两个对象。

CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。

NSRunloop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

53. iOS 内存分区和 block 的使用及内存位置

iOS 内存分区有5个:栈区、堆区、全局区、常量区、代码区。这5个区在物理上是分开的。

栈区(stack):这一块区域系统会自己来管理,我们不用干预,主要存一些局部变量以及函数跳转时的现场保护,因此大量的局部变量、深递归、函数循环调用都可能耗尽内存而造成运行崩溃。优点是快速高效,缺点是时有限制,数据不灵活(先进后出)。

堆区(heap):与栈区相对,这一块由开发人员管理,存储一些自己创建的对象。若程序员不释放,程序结束时,可能会由操作系统回收。优点是灵活方便、数据适应面广泛,但效率有一定降低(顺序随意)。

全局区(又叫静态区 static):全局变量和静态变量都存储在这里,已经初始化和没初始化的变量会分开存储在相邻区域,程序结束后由系统释放。

常量区:存储常量字符串和 const 常量,程序结束后由系统释放。

代码区:存我们写的代码,二进制。

block 会根据情况有两种存储位置,一种在代码区,一种在堆区:

1. 如果 block 块没有访问处于栈区的变量,比如局部变量,也没有访问堆区的变量,比如我们创建的对象,那它就存在代码区,即使访问了全局变量,也依然存在代码区;

2. 如果访问了栈区或堆区的变量,那就会存在堆区(实际上是存在栈区,在 ARC 下,当被赋值给 strong 对象或 block 类型的变量时,会触发 [block copy],将其拷贝到堆区)。

关于存在堆区的情况,需要注意的是,堆区是不断变化的,不断有变量被创建和释放。如果 block 没有强引用,那随时也会被销毁,这就导致一旦在销毁后访问 block,程序会崩溃。所以定义 block 时,最好用 strong 或 copy 修饰,且在使用时最好先判断一下 block 是否为空。

既然用了 strong 修饰,那另一个问题就来了,那就是循环引用问题

当使用 strong 修饰后,self 会强引用 block,而如果在 block 中又需要访问 self 的一些属性或方法,从而调用了 self,这时 self 和 block 就会进入循环引用,导致内存溢出。所以需要在用到 self 时,事先将 self 用 __weak 修饰,从而打破循环引用。

但这还不够,在多线程下,单单使用 weakself ,可能前一刻 weakself 还在,后面需要使用的时候却被释放了,毕竟弱引用是不稳定的,这时候需要使用 __strong 来在 block 中修饰:

__weak __typeof(self) *weakSelf = self;

self.block = ^{

__strong __typeof(self) strongSelf = weakSelf;

if (strongSelf) {

strongSelf.str = @"123";

}

// 如果不用了,应置为空

strongSelf.block = nil;

};

54. 什么是响应者链(UIResponder)

UIResponder 类是专门用来响应用户的操作,处理各种事件,包括触摸事件、运动事件、远程控制事件。而 UIApplication、UIView、UIViewController 这几个类是直接继承自 UIResponder 的,所以这些类都可以响应事件。当用户触发某一事件时,UIKit 会创建一个 UIEvent 事件对象,事件对象会加入到 FIFO 先进先出的队列中,UIApplication 对象处理事件时,会从队列头部取出一个事件对象进行分发。

(1) 触摸事件,touchesBegan 等和 UIGestureReconizer 手势,UIApplication  会首先把事件交给 KeyWindow,window 会将事件交给 UIGestureReconizer 处理,如果它识别出了传递过来的事件,则会交给对应的 target 去处理,事件不会再传递。若没有识别出来,事件会传递到视图树形结构,会分别寻找接受者和事件响应这两个步骤:

1. 在 iOS 视图树形结构中找到最终的接受者,也就是触摸事件发生的那个最上层的 View 上,这一过程称为 hit-testing(测试命中),通过一层层的遍历找到最终的命中视图称为 hit-test view;

2. 找到了 hit-test view ,下一步就是响应事件。说明一下,对于触摸事件来说,无论 View 是否处理事件,即使是 Application 通过 [application beginIgnoringInteractionEvents] 忽略了触摸事件,上面 hit-testing 过程依然存在,它只影响第二个步骤事件响应的过程。

如果整个响应者链结束,都没有对事件做处理,那么该事件会被丢弃。

总结一下响应者链的传递过程:由第一响应者(对于触摸事件来说是 hit-test view)开始向上传递。如果该视图是控制器的根视图,先传递给控制器,再传递给父视图;如果不是控制器的根视图,直接传递给父视图。只要在响应者的处理方法里面调用父类的方法,就可以让多个视图和控制器响应同一个事件,响应者链条的根本目的是:共享事件,让多个视图和控制器可以对同一事件做不同的处理。

(2)运动事件,motionBegan 等,这里的运动事件特指 shake 事件,用于手机摇动触发手机内部的加速器。响应者链条对 shake 事件同样适用,只不过,没有 hit-testing 过程,如果当前显示的视图界面没有一个 view 声明为第一响应者(调用 becomeFirstResponder 申明并且 View 需要重写canBecomeFirstResponder 方法返回 YES,默认返回为 NO ),默认当前视图控制器为第一响应者,并将事件沿着响应者链条传递,直到被处理。如果有视图声明为第一响应者,就从该视图开始传递事件直到被处理,如果该事件最终没有被处理并且 UIApplication 的applicationSupportsShakeToEdit 属性为 YES(默认就是 YES ),当键盘显示的时候,系统会有一个是否撤销正在输入的警告。就是微信和 QQ 上在输入的时候摇动手机提示撤销输入的那种效果。

55. 什么是类对象结构

Class 是一个指向 objc_class 结构体的指针。这个结构体主要包含:

1. isa:它指向 metaClass (元类);

2. super_class:指向该类的父类,若该类已经是最顶层的根类,则为NULL;

3. cache:用于缓存最近使用的方法。在我们每次调用过一个方法后,这个方法就被缓存到 cache 列表中,下次调用时 runtime 就会优先去 cache 中查找。若 cache 没有才去 methodLists 中查找方法;

4. version:我们可以使用这个字段来提供类的版本信息。这对于对象序列化很有用,它可以让我们识别出不同类定义版本中实例变量布局的改变。

Object 是一个指向 objc_object 结构体的指针。这个结构体只用一个字段 isa,它指向它的类结构。当我们向一个对象发送消息时,运行时库会根据对象的 isa 指针找到这个对象所属的类。运行时库会在类的方法列表及父类的方法列表中寻找与消息对应的 selector 指向的方法,找到后运行这个方法。id 是一个指向 objc_object 结构体的指针。

所以类自身也是一个对象,我们可以向其发送消息(调用类方法)。既然是对象,它也有个指向其类的 isa 指针,而这个类就是 metaClass(元类)。元类存储着一个类的所有类方法,每个类都会有一个单独的元类。而元类也是一个类,可以向它发送消息,那它的 isa 又指向什么呢?为了不让这种结构无限延伸下去,苹果规定任何 NSObject 继承体系下的元类都使用 NSObject 的元类作为自己的所属类,而基类的元类的 isa 指向它自己。

56. int 和 NSInteger 的区别

NSInteger 会根据系统的位数(32 或 64)自动选择 int 的最大数值(int 或 long)。

57. 死锁

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成一种互相等待的现象。

造成死锁的原因:

1. 系统资源不足;

2. 进程运行推进的顺序不合适;

3. 资源分配不当

产生死锁的必要条件:

1. 互斥条件:一个资源每次只能被一个进程使用;

2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;

3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺;

4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

58. HTTPS 的传输过程

HTTPS 由 HTTP + SSL/TLS 组成,也就是在 HTTP 上又加了一层处理加密信息的模块

SSL:(Secure Socket Layer,安全套接字层),位于可靠的面向连接的网络层协议和应用层协议之间的一种协议层。SSL 通过互相认证、使用数字签名确保完整性、使用加密确保私密性,以实现客户端和服务器之间的安全通讯。该协议由两层组成:SSL 记录协议和 SSL 握手协议。

TLS:(Transport Layer Security,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。该协议由两层组成:TLS 记录协议和 TLS 握手协议。

传输过程:

1. 客户端发起 HTTPS 请求;

2. 服务端配置,采用 HTTPS 协议的服务器必须要有一套数字证书,就是一套公钥和私钥。公钥和私钥就像一把锁和钥匙,全世界只有你有这把钥匙,你可以把锁给别人用它锁上重要的东西然后发给你,因为只有你有钥匙,所以只用你能看到被锁上的东西;

3. 传送证书,服务器向客户端发送公钥,其中包含了证书的颁发机构、过期时间等;

4. 客户端解析证书,这部分工作由客户端的 TLS 来完成,首先验证公钥是否有效,若发现异常,则会弹出警告框,提示证书存在问题。若证书没有问题,则会生成一个随机值,然后用证书对该随机值进行加密。就像用锁头把随机值锁上,除非有钥匙,否则看不到被锁住的内容;

5. 客户端传送加密信息,客户端将用证书加密后的随机值发给服务端,以后就可以通过这个随机值来进行加密解密了;

6. 服务端解密信息,服务端用私钥解密后,得到客户端传过来的随机值,然后把内容通过该值进行对称加密。所谓对称加密就是将信息和这个随机值通过某种算法混合在一起,这样除非知道这个随机值,不然无法获取内容,而正好客户端和服务端都知道这个随机值,所以只要加密算法够强大,随机值够复杂,数据就够安全;

7. 传输加密后的信息,服务端将加密信息发给客户端;

8. 客户端解密信息,客户端用之前生成的随机值解密服务端传过来的信息,展现。

59. assign 与 weak 的区别

weak 表明该属性定义了一个非拥有关系。用这种属性设置新值时,设置方法既不保留新值,也不释放旧值。用 weak 声明的变量不用时会自动清空,赋值为 nil,weak 可避免循环引用,一般用于 OC 对象和 delegate,自定义的 IBOutlet 控件属性一般也使用 weak。

assign 主要用来基础类型,是指针赋值,不对引用计数操作,使用完若不置为 nil,就会造成野指针。

60. 如何使用 copy

1. NSString、NSArray、NSDictionary 经常使用 copy。copy 所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其拷贝。当属性类型是 NSString 时经常用此特性来保护其封装性,因为传递给 set 方法的新值有可能指向一个 NSMutableString 类的实例,这个类是 NSString 的子类,若是不拷贝字符串,那么设置完属性后,字符串的值就可能会在对象不知情的情况下遭人更改,所以使用 copy 可确保字符串不会无意间变动。

用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

2. block 使用 copy 是在 MRC 遗留下来的,在 MRC 中,方法内部的 block 是存在栈区的,使用 copy 可以把它放在堆区。为了能够在 block 的声明域外使用,所以要把 block 拷贝( copy )到堆,所以说为了 block 属性声明和实际的操作一致,最好声明为 copy。

61. 下面的写法会出什么问题:

@property (atomic,copy) NSMutableArray *array;

1. 添加、删除、修改数组内的元素时,程序会找不到对应的方法而崩溃。因为 copy 就是复制一个不可变的 NSArray 对象。

2. 使用了 atomic 属性,会严重影响性能。

62. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter

若想令自己写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就需要同时实现 NSCopying 和 NSMutableCopying 协议:(1)首先遵循协议@interface CYLUser : NSObject@property (nonatomic, readonly, copy) NSString *name;

@property (nonatomic, readonly, assign) NSUInteger age;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

@end

(2)然后实现协议中规定的方法:

- (id)copyWithZone:(NSZone *)zone {

CYLUser *copy = [[[self class] allocWithZone:zone] initWithName:_name age:_age sex:_sex];

return copy;

}

重写 copy 的 setter 方法时候,一定要调用一下传入的对象的 copy 方法,然后在赋值给该 setter 的方法对应的成员变量

63. 浅拷贝与深拷贝

(1)对非集合类对象的copy操作:

在非集合类对象中:对 immutable 对象进行 copy 操作,是指针复制,mutableCopy 操作时内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制

NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy

NSString *stringCopy = [string copy];

查看内存,会发现 string、stringCopy 内存地址都不一样,说明此时都是做内容拷贝、深拷贝

[string appendString:@"origion!"]

stringCopy 的值也不会因此改变,但是如果不使用 copy,stringCopy 的值就会被改变。

[immutableObject copy] // 浅复制

[immutableObject mutableCopy] //深复制

[mutableObject copy] //深复制

[mutableObject mutableCopy] //深复制

(2)在集合类对象中,对 immutable 对象进行 copy,是指针复制, mutableCopy 是内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。

[immutableObject copy] // 浅复制

[immutableObject mutableCopy] //单层深复制

[mutableObject copy] //单层深复制

[mutableObject mutableCopy] //单层深复制

64. @property 的本质是什么,ivar、getter、setter 是如何生成并添加到这个类中的

在普通 OC 对象中,@property 就是编译器自动帮我们生成一个成员变量(ivar)和它的 setter 和 getter 方法,它大概生成了五个东西:

1. objc_ivar_$类名$属性名称 该属性的偏移量

2. setter 与 getter 方法对应的实现函数

3. ivar_list 成员变量列表

4. method_list 方法列表

5. prop_list 属性列表

也就是说我们每次增加一个属性,系统就会在成员变量列表中添加一个成员变量的描述,在方法列表中添加 setter 与 getter 方法的描述,在属性列表中增加一个属性描述,然后计算该属性在对象中的偏移量,然后生成 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量开始复制,在 getter 中从偏移量开始取值,为了能够读取正确的自己数,系统对对象偏移量的指针类型进行了类型强转。

65. Runtime 如何实现 weak 属性

Runtime 对注册的类会进行布局,对于 weak 对象会放入一个 hash 表中。用 weak 指向的对象地址作为 key,当此对象的引用计数为0时会 dealloc,进而在这个 weak 表中找到此对象地址为键的所有 weak 对象,从而设置为 nil。

66. weak 属性需要在 dealloc 中置 nil 么

不需要,在 ARC 环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil,ARC 会自动帮我们处理

67. @synthesize 和 @dynamic 分别有什么作用

@property 有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize 和 @dynamic 都没写,那么默认的就是 @syntheszie var = _var;

@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。

@dynamic 告诉编译器,属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter 方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var =someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

68. ARC 下,不显示指定任何属性关键字时,默认的关键字都有哪些?

对应基本数据类型默认关键字时 atomic,readwrite,assign;

对应普通的 OC 对象是 atomic,readwrite,strong

69. objc 中向一个 nil 对象发送消息将会发生什么

在 OC 中向 nil 发送对象是完全有效的,只是在运行时不会有任何作用,OC 中 nil 是被当做0定义的。也就是说 Runtime 要去获取这个 nil 的信息,会去读取内存中0的位置,这肯定是不允许的,会返回 nil,0,0.0等数据。

70. objc 中向一个对象发送消息 [obj foo] 和 obj_msgSend() 函数之间有什么关系?

该方法编译之后就是 obj_msgSend() 函数调用,[obj foo] 就是在 objc 动态编译时,会被转义为: objc_msgSend(obj, @selector(foo));

71. 什么时候会报 unrecognized selector 的异常

当调用该对象上某个方法,而该对象上没有实现这个方法的时候

72. 下面代码会输出什么

@implementation Son : Father

- (id)init

{

self = [super init];

if (self) {

NSLog(@"%@", NSStringFromClass([self class]));

NSLog(@"%@", NSStringFromClass([super class]));

}

return self;

}

@end

输出都是 son 。

因为在 iOS 中,对方法的调用是通过发送消息来完成的。也就是说使用 [self class] 时,会使用 obj_msgSend(id theReceiver, SEL selector, ...)函数向 receiver 来发送消息。而使用 [super class] 时,会使用 obj_msgsendSuper(...) 函数向 receiver 来发送消息。然而 obj_msgSend(...)和 obj_msgSendSuper(...) 中的 receiver 都是 self,这里的 self 也就是 son,所以输出都是 son

73. iOS 中的 SEL 和 IMP 是什么

SEL 是类成员方法的指针,但不同于 C 语言中和函数指针,函数指针直接保存了方法的地址,但 SEL 只是方法编号;

IMP 是一个函数指针,保存了方法的地址。

SEL 和 IMP 关系:每一个继承于 NSObject 的类都能自动获得 runtime 的支持。在这样一个类中,有一个 isa 指针指向该类定义的数据结构体,这个结构体是由编译器编译时为类(需要继承于 NSObject)创建的,在这个结构体中有包括了指向其父类类定义的指针以及 Dispatch table,它是一张 SEL 和 IMP 的对应表。

也就是说方法编号 SEL 最后还是要通过 Dispatch table 表寻找到对应的 IMP,IMP 就是一个函数指针,然后执行这个方法。

74. Runtime 如何通过 selector 找到对应的 IMP 地址(分别考虑类方法和实例方法)

每个类对象中都有一个方法列表,方法列表中记录着方法的名称、方法实现以及参数类型。其实 selector 本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现。在寻找 IMP 地址时,Runtime 提供了两种方法:

IMP class_getMethodImplementation(Class cls, SEL name);

IMP method_getImplementation(Method m)

而根据官方描述,第一种方法可能会更快一点。对于第一种方法,类方法和实例方法实际上都是调用 class_getMethodImplementation() 来寻找 IMP 地址的,不同之处在于传入的第一个参数不同:

类方法是 :class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName));

实例方法是:class_getMethodImplementation([A class],@selector(methodName));

selector 本质就是方法的名称,通过该方法名称,即可在结构体中找到相应的实现。

struct objc_method {

SEL method_name

char *method_types

IMP method_imp

}

而对于第二种方法而言,传入的参数只有 method,区分类方法和实例方法在于封装 method 的函数,最后调用 IMP method_getImplementation(Method m) 获取 IMP 地址。

75. 使用 Runtime Associate 方法关联的对象,需要在主对象 dealloc 的时候释放吗?

在 ARC 下不需要;

在 MRC 下对于使用 retain 或 copy 策略的需要

76. objc 中的类方法和实例方法有什么本质区别和联系

类方法是属于类对象的,实例方法是属于实例对象的;

类方法只能通过类对象调用,实例方法只能通过实例对象调用;

类方法中的 self 是类对象,实例方法中的 self 是实例变量;

类方法中不能访问成员变量 ,实例方法中可以访问成员变量;

类方法中不能直接调用对象方法,实例方法中可以直接调用实例方法;

类方法可以调用其他的类方法;

实例方法中也可以调用类方法(通过类名)。

77. _objc_msgForward函数是做什么的,直接调用它将会发生什么?

当对象没有实现某个方法,会调用这个函数进行方法转发。(某方法对应的 IMP 没找到,会返回这个函数的 IMP 去执行)。

如果直接调用这个方法,就算实现了想调用的方法,也不会被调用,会直接走消息转发步骤。

78. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?

因为编译后的类已经注册在 Runtime 中,类结构中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小已经确定,同时 Runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用,所以不能向存在的类中添加实例变量。

运行时创建的类是可以添加实例变量的,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。

79. runloop 和线程有什么关系

每一个线程中都有一个 runloop,只用主线程的 runloop ,默认是开启的,其他线程的 runloop 默认是关闭的,可以通过 CFRunLoopRun() 函数开启一个事件循环。

80. runloop 的 mode 作用是什么

mode 主要是用来指定事件在运行循环中的优先级

苹果公开提供的 mode 有两个:kCFRunLoopDefaultMode 和 kCFRunLoopCommonModes。

如果我们把一个 NSTimer 对象以 kCFRunLoopDefaultMode 添加到主运行循环中的时候,当一直有用户事件处理的时候,NSTimer 将不再被调度。如果我们把一个 NSTimer 对象以 kCFRunLoopCommonModes 添加到主运行循环中的时候,当一直有用户事件处理的时候,NSTimer 还能正常的调度,互不影响。

81. 以 + scheduledTimerWithTimeInterval... 的方式触发的 timer,在滑动页面上的列表时,timer 会暂定回调,为什么?如何解决?

在主线程中以 +scheduledTimerWithTimeInterval... 的方式触发的 timer 默认是运行在 NSDefaultRunLoopMode 模式下的,当滑动页面上的列表时,进入了 UITrackingRunLoopMode 模式,这时候 timer 就会停止可以修改 timer 的运行模式为 NSRunLoopCommonModes,这样定时器就可以一直运行了

82. 不手动指定 autoreleasepool 的前提下,一个 autorealese 对象在什么时刻释放?(比如在一个 vc 的 viewDidLoad 中创建)

在每次事件循环开始创建自动释放池,在每次事件结束销毁自动释放池

以viewDidLoad方法为例,可以理解为在viewDidLoad方法开始执行之前创建自动释放池,

在viewDidLoad方法执行之后销毁自动释放池

83. BAD_ACCESS 在什么情况下出现

1. 死循环;

2. 访问了一个僵尸对象

84. 使用 block 时什么情况会发生循环引用

只要是一个对象对该 block 进行了强引用,在 block 内部有直接使用到该对象

85. 在 block 内如何修改 block 外部变量

通过 __block 修改的外部变量,可以在 blcok 内部修改。

block 不允许修改外部变量的值。这里所说的外部变量的值,指的是栈中指针的内存地址。__block 的作用就是只要观察到该变量被 block 所持有,就将外部变量在栈中的内存地址放到了堆中,进而使得在 block 内部也可以修改外部变量的值。

block 不允许修改外部变量的值,苹果这样设计,应该是考虑到了 block 也属于函数的范畴,变量进入 block,实际上就已经改变了作用域。在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。

86. GCD 的队列分哪两种类型

串行队列和并行队列

87. 如何用 GCD 同步若干个异步调用(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

创建异步队列,创建 dispatch_group,通过组来执行异步下载任务:dispatch_group_async(queueGroup,aQueue,^{

NSLog(@“下载图片”));

});

等到所有任务完成:dispatch_group_wait(queueGroup,DISPATCH_TIME_FOREVER);

合成图片

88. dispatch_barrier_async 的作用是什么

dispatch_barrier_async 的作用是在并行队列中,等待前面操作并行任务完成再执行dispatch_barrier_async 中的任务,若后面还有并行任务,会开始执行后续的并行任务。

89. 苹果为什么要废弃 dispatch_get_current_queue

容易误用造成死锁

90. 下面代码运行结果

- (void)viewDidLoad

{

[super viewDidLoad];

NSLog(@"1");

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"2");

});

NSLog(@"3");

}

只输出1,然后线程主线程死锁

因为 dispatch_get_main_queue 得到的是一个串行队列,串行队列的特点是一次只调用一个任务,一个任务执行完毕后再执行下一个任务。同步(sync)操作,它会阻塞当前线程并等待 block 中的任务执行完毕,然后当前线程才会继续往下进行。dispatch_sync 提交一个打印任务 NSLog(@”2”) 到主线程关联的串行队列中,主线程关联的串行任务到现在有一个 viewDidLoad 任务,打印任务 NSLog(@”2”) 排在 viewDidLoad 后面,队列 FIFO(先进先出)的原则,打印任务 NSLog(@”2”);想要得到执行必须等到 viewDidLoad 执行完毕后才能得到执行,但 viewDidLoad 想要执行完必须要等打印任务 NSLog(@”2”) 执行完毕,所以就卡死在这了。

91. addObserver:forKeyPath:options:context: 各个参数的作用分别是什么,observer 中需要实现哪个方法才能获得 KVO 回调?

1. 观察者,负责处理监听事件的对象;

2. 观察的属性;

3. 观察的选项;

4. 上下文

- (void) observeValueForKeyPath:(NSString )keyPath ofObject:(id)object change:(NSDictionary )change context:(void *)context;

92. IBOutlet 连出来的视图属性为什么可以被设置成 weak

使用 storyboard 创建的 VC,那么会有一个叫 _topLevelObjectsToKeepAliveFromStoryboard 的私有数组强引用所有 toplevel 的对象,同时 toplevel 对象强引用所有子对象,那么 VC 没必要再强引用 toplevel 对象的子对象。

93. IB中User Defined Runtime Attributes如何使用

它能够通过 KVC 的方式配置一些你在 interface builder 中不能配置的属性。当你希望在 IB 中作尽可能多得事情,这个特性能够帮助你编写更加轻量级的 viewcontroller。

94. isMemberOfClass 和 isKindOfClass 联系与区别

联系:两者都能检测到一个对象是否是某个类的成员;

区别:isKindOfClass 不仅用来确定一个对象是否是一个类的成员,也可以用来确定一个对象是否是派生自该类的成员,而 isMemberOfClass 只能做到第一点。

比如 Class A 派生自 NSObject 类,ClassA *a = [ClassA alloc] init];  [a isKindOfClass:[NSObject class]] 可以检查出 a 是否是 NSObject 派生类的成员,但 isMemberOfClass 做不到。

95. get 与 post 的区别

get 是向服务器索取数据的一种请求,post 是向服务器提交数据的一种请求;

get 没有请求体,post 有请求体;

get 使用 url 或 cookie 传参,而 post 将数据放在 body 中;

get 请求的数据会暴露在地址栏中,而 post 不会,所以 post 比 get 更安全;

get 请求对 url 长度有限制,而 post 请求对 url 理论上没有限制,但实际上,各个服务器会规定对 post 提交数据大小进行限制。

96. 设计模式是什么,你知道哪些设计模式?

设计模式是一种编码经验,就是用比较成熟的逻辑去处理某种类型的事情。

1. MVC 模式:模型包含了数据和逻辑的处理,控制器确保 model 与 view 的同步。

2. MVVM 模式:使用 Model View ViewModel 把模型、视图、业务逻辑层进行解耦和编写。

3. 单例模式:通过 static 关键字,声明全局变量,在整个进程运行期间只会被赋值一次。

4. 观察者模式:KVO 是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。

5. 委托模式:代理 + 协议的组合,实现1对1的反向传值操作。

6. 工厂模式:通过一个类方法,批量的根据已有模板生产对象。

97.  类变量的 @public,@protected,@private,@package 声明各有什么含义

@public 任何地方都能访问;

@protected 该类和子类中访问,是默认的;

@private 只能在本类中访问;

@package 本包内使用,跨包不可以。

98. 什么是谓词

谓词就是通过 NSPredicate 给定的逻辑条件作为约束条件,完成对数据的筛选:

//定义谓词对象,谓词对象中包含了过滤条件(过滤条件比较多)

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<%d",30];

//使用谓词条件过滤数组中的元素,过滤之后返回查询的结果

NSArray *array = [persons filteredArrayUsingPredicate:predicate];

99. APNS 原理

1. 应用在通知中心注册,由 iOS 系统向 APNS 请求返回设备令牌(Device Token)

2. 应用程序接受到 Device Token 并发送给自己的后台服务器

3. 服务器要把推送内容和设备发送给 APNS

4. APNS 根据 Device Token 找到设备,再由 iOS 根据 APPID 把推送内容展示

100. 描述下 SDWebImage 里面给 UIImageView 加载图片的逻辑

SDWebImage 中为 UIImageView 提供了一个分类 UIImageView+WebCache.h。这个分类中有一个最常用的方法:sd_setImageWithURL:placeholderImage: 会在真实图片出来前先显示占位图片,当真实图片被加载出来后再替换占位图片。

加载过程大致如下:

首先会在 SDWebImageCache 中寻找图片是否有对应的缓存,它会以 url 作为数据索引先在内存中寻找是否有对应的缓存,如果缓存未找到就会利用通过 MD5 处理过的 key 来继续在磁盘中查询对应的数据,如果找到了,就会把磁盘中的数据加载到内存中,并将图片显示出来,如果在内存中和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片,下载后的图片会加载到缓存中,并写入磁盘中。整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来。

原理:

从内存中找到图片,找到直接使用;

从沙盒中找,找到使用,缓存到内存中;

从网络上获取,使用,缓存到内存,缓存到沙盒。

iOS 面试题总结(二)