iOS面试题( 一 )

准备刷一波面试题,来巩固一下自己的基础知识,暂时题源来自这里,刷完这波以后遇到新的会再更。

第一波,这的 https://github.com/lzyy/iOS-Developer-Interview-Questions


1.什么是响应链,它是怎么工作的?

写了一篇来解释这个,传送门。


2.如何访问并修改一个类的私有属性?
  • 通过KVC来设置
  • 通过runtime动态改变
  • 通过 msg_send() 设置

例子:
有这样一个类 TestClassA:

@interface TestClassA ()

@property (nonatomic, assign) NSInteger priviteNum;
@property (nonatomic, strong) UIView *pView;

@end

@implementation TestClassA

-(void)showProperty
{
    NSLog(@"priviteNum = %@", @(_priviteNum));
    NSLog(@"pView = %@", _pView);
}

@end

我们在外部改完后,调用 showProperty 方法去查看这两个私有属性的值。

1.KVC:

TestClassA *classA = [[TestClassA alloc] init];
[classA setValue:@(4) forKey:@"_priviteNum"];
[classA setValue:self.view forKey:@"_pView"];
[classA showProperty];

控制台打印:

priviteNum = 4
pView = >

2.通过runtime动态改变

TestClassA *classA = [[TestClassA alloc] init];

unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([TestClassA class], &outCount);

for (int i = 0; i < outCount; i ++) {
    Ivar ivar = ivars[i];
    
    const char *ivarName = ivar_getName(ivar);
    
    //这里要注意ARC下, 这个会报错
    if (strcmp(ivarName, "_priviteNum") == 0) {
        object_setIvar(classA, ivar, 22);
    }
    
    if (strcmp(ivarName, "_pView") == 0) {
        object_setIvar(classA, ivar, self.view);
    }
}

[classA showProperty];

控制台打印:

priviteNum = 22
pView = >

这里要注意下:
在修改NSInteger型变量的时候,ARC下,编译器不允许你将NSInteger类型的值赋值给id,在buildsetting中将Objective-C Automatic Reference Counting修改为No即可。但是这样工程就会变成MRC,所以,如果是非对象类型就不建议用object_setIvar这样的方法去修改了。

3.msg_send() 去修改 (适用私有属性,不适用私有变量)
既然是私有属性了,必然有setter方法, 那我们动态调用一下。

TestClassA *classA = [[TestClassA alloc] init];
((void (*)(id, SEL, int))(void *) objc_msgSend)((id)classA, @selector(setPriviteNum:) , 33);
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)classA, @selector(setPView:) , self.view);
[classA showProperty];

控制台打印:

priviteNum = 33
pView = >

3.iOS Extension 是什么?能列举几个常用的 Extension 么?

类扩展,其实Extension我们经常用,但是我们可能并不知道这种用法还有Extension这样一个名字。

那我们具体看一下:
Extension和Category一样有独立的文件,当我们新建Objective-C File 的时候,会发现下面这样:


iOS面试题( 一 )_第1张图片
54AEFA1C-AF43-444B-94D3-10C62AA79E09.png

我们大部分时候都在建立Category文件或Protocol文件,这次我们建个Extension文件,于是我们得到一个这样命名的文件:

Paste_Image.png

并且文件内部是这样的:

iOS面试题( 一 )_第2张图片
Paste_Image.png

是不是很熟悉,我们经常会在很多.m文件中,给很多类做一个这样的东西,然后在里面声明一些变量或属性,作为私有的看待,而这种写法就是类的Extension.


4.如何把一个包含自定义对象的数组序列化到磁盘?

先给自定义的对象实现 NSCoding 协议:

@interface CodingClass()
@property (nonatomic, assign) NSInteger objectID;
@end

@implementation CodingClass

-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    if (self) {
        self.objectID = [[aDecoder decodeObjectForKey:@"objectID"] integerValue];
    }
    return self;
}

-(void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:@(self.objectID) forKey:@"objectID"];
}
@end

然后就可以在外部给这个自定义类归档了:

CodingClass *codingA = [[CodingClass alloc] init];
CodingClass *codingB = [[CodingClass alloc] init];

NSArray *array = [NSArray arrayWithObjects:codingA, codingB, nil];

[NSKeyedArchiver archiveRootObject:array toFile:[NSString stringWithFormat:@"%@/%@",NSHomeDirectory(), @"archiver.dat"]];

5.iOS 的沙盒目录结构是怎样的? App Bundle 里面都有什么?
  • Documents
    文稿目录,存放app的数据,不可以放一些占用空间大的文件,会被同步到iCloud
  • Library
  • Caches
    存放缓存目录,不会被同步到iCloud
  • Preferences
    应用程序的偏好设置文件目录,可以放一些配置文件,NSUserDefaults 的plist文件也在该目录下
  • tmp
    临时文件目录,数据随时可能会被清空,譬如手机空间不足,系统自动清除缓存时,该目录下的文件就会被系统清除掉

6.iOS 的签名机制大概是怎样的?

确实不是太了解,看这个学习了:
关于iOS签名机制的理解


7.iOS 7的多任务添加了哪两个新的 API? 各自的使用场景是什么?

后台获取(Background Fetch):后台获取使用场景是用户打开应用之前就使app有机会执行代码来获取数据,刷新UI。这样在用户打开应用的时候,最新的内容将已然呈现在用户眼前,而省去了所有的加载过程。

推送唤醒(Remote Notifications):使用场景是使设备在接收到远端推送后让系统唤醒设备和我们的后台应用,并先执行一段代码来准备数据和UI,然后再提示用户有推送。这时用户如果解锁设备进入应用后将不会再有任何加载过程,新的内容将直接得到呈现。


8.Objective-C 的 class 是如何实现的?

OC中的Class其实是一个 objc_class 结构体指针:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

我们看一下 objc_class 结构体:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

每个Class其实也是对象,看下几个重要的属性:

  • Class isa : 指向该类元类的指针
  • Class super_class : 指向该类父类的指针,如果该类已经是类似NSObject的这样的根类,则父类为nil
  • char *name : 类名
  • struct objc_ivar_list *ivars : 成员变量列表
  • struct objc_method_list **methodLists : 方法列表
  • struct objc_cache *cache : 方法缓存,每个方法被调用之后会存放到这个列表里,当类的方法再次被调用时,会先在这个列表里查找,如果找不到才会在 methodLists 里查
  • struct objc_protocol_list *protocols : 协议列表

9.Selector 是如何被转化为 C 语言的函数调用的?

首先看下SEL的含义:

typedef struct objc_selector *SEL;

是一个 objc_selector结构体指针,但是objc_selector结构体具体是什么,我们就查不到了,但是我们试着打印一下:

-(void)viewATap:(UITapGestureRecognizer *)tap
{
    NSLog(@"viewA tap~");
}

NSLog(@"sel = %s", @selector(viewATap:));

却发现是可以打印出来的:

sel = viewATap:

所以,SEL 表示的就是那个方法的名字。

然后我们再研究一下 seleter 在方法转发中起的作用
先看一下类中method的结构:

struct objc_method {
   SEL method_name                                          OBJC2_UNAVAILABLE;
   char *method_types                                       OBJC2_UNAVAILABLE;
   IMP method_imp                                           OBJC2_UNAVAILABLE;
} 

SEL method_name :方法名/方法标识
char *method_types :方法返回类型
IMP method_imp :方法具体的实现地址

然后方法调用简单过程,举个例子:

TestClassA *classA = [[TestClassA alloc] init];
[classA showProperty];

编译器会将方法调用转换为发消息的模式,由 objc_msgSend进行消息发送,转换为:

objc_msgSend(classA, @selector(showProperty))

收到消息后,classA 会从 methodLists 中找到 SEL 为 showProperty的 objc_method,然后找到对应的 IMP ,并执行。


10.Objective-C 如何对已有的方法,添加自己的功能代码以实现类似记录日志这样的功能?

用 method swizzling 。
譬如在viewDidLoad加入记录日志功能

+(void)load
{
    [super load];
    Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
    Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewDidLoad));

    method_exchangeImplementations(fromMethod, toMethod);
}

- (void)swizzlingViewDidLoad {
    [self swizzlingViewDidLoad];
    //自己的记录日志代码
    [self saveLog];
}

11.+load 和 +initialize 的区别是什么?
  • load
  • load会在类或者分类添加到runtime的时候调用,所以在顺序上,要比 main 函数更早调用
  • 一个类的 load 方法调用总是在其父类的 load 调用之后的
  • 一个类的 load 方法调用总是在该类的类别(Category) 的 load 调用之前的
  • initialize
  • initialize 是在类收到第一个消息的时候调用,
  • runtime 只会向每个类发送一次 initialize 消息
  • 如果该类是子类,且该子类中没有实现 + (void)initialize 方法,或者子类显示调用父类实现 [super initialize], 那么则会调用其父类的实现。也就是说,父类的 + (void)initialize 可能会被调用多次
  • 如果类的分类中实现了+ (void)initialize,则会覆盖该类的+ (void)initialize
  • 一个类的 initialize 方法先于子类的 initialize 方法调用

12.如何让 @protocol 和 Category 支持属性?

我们知道在类里定义一个属性时,会在该类的ivar_list添加ivar描述,会在method_list添加该属性的seter和geter方法描述,在property_list中添加一个属性描述。

但在@protocol 和 category 中如果定义一个 @property 时,只会在property_list中添加一个属性描述,method_list 和 ivar_list中都不会有相关的描述添加。

所以要想@protocol 和 category 中的 @property 有意义,就必须在类中或category中,实现这个@property的setter和getter。

但是 ivar_list 是没有该属性对应的 ivar, 所以 setter 中你是没法赋值的, 这时候就要用到 objc_setAssociatedObject 了,在 setter 中用 objc_setAssociatedObject 赋值,并在 getter 中用 objc_getAssociatedObject 取值,这样就变相完成了@protocol 和 category 中属性的设置


13.NSOperation 相比于 GCD 有哪些优势?

简单说,NSOperation有着更好的封装性,具有面向对象编程的思想,在线程管理上有这GCD无法比拟的优势。
我们结合场景来说这两者:

我们一般在工程中使用GCD都是一些零散的地方,对线程只具有简单的操作,需要用线程去跑一些代码,GCD简洁,高效,易用,在这些场景有这很好的表现。

而一些具有定向功能的任务模块或组件,需要很好的管理的场景,则更多用NSOperation,譬如我们最常用的AFNetworking组件、SDWebImage里的下载器等,这些场景都需要对多线程任务状态有很好的管理,而NSOperation则能更好的完成这种场景的任务。

但大部分状态下,NSOperation封装的线程任务中,也会出现GCD的身影,混用,结合两者的优势。


14.strong / weak / unsafe_unretained 的区别?
  • strong :强引用,引用计数 +1 ,不能修饰基本数据类型
  • weak : 弱应用,引用计数保持不变,不能修饰基本数据类型,对象释放会置为nil
  • unsafe_unretained:弱引用,引用计数保持不变,能修饰基本数据类型,如果引用的对象,释放不会置为nil,所以 unsafe

15.如何为 Class 定义一个对外只读对内可读写的属性?

用 readonly 修饰, 这样的话走 setter 的赋值就都不可用了,所以外部就都是只读了,内部可以用 成员变量直接赋值, 如:

@property (nonatomic, strong, readonly) NSString *str1;

赋值时:

_str1 = @"str1";

打印可见,成功赋值。


16.Objective-C 中,meta-class 指的是什么?

meta-class :元类,说白了就是 类对象 的类, 就是 类对象 的 isa 指向

我们知道当我们向一个对象发消息的时候,会从这个对象的 Class对象 中去找到对应消息的实现方法并执行,但是我们也经常调用一个类的类方法,我们知道 Class 其实也是一个对象,而存放类方法的就是 该Class对象 的元类对象,所以就是:

  • 你给 对象 发消息,runtime机制会从该对象的 类对象 的消息列表中,查找对应的消息
  • 你给 发消息,runtime机制会从该类的 元类 的消息列表中,查找对应的消息

一张老图,说明各种关系:

虚线走向是 isa 指针关系走向, 实线是 superclass 关系走向:

iOS面试题( 一 )_第3张图片
meta-class.png

可以看到 对象 的 isa 指向他的 类对象 , 类对象 的 isa 指向他的 元类对象, 元类对象 的 isa 指向 root class 的 元类对象 ,但是 root class 的 元类对象
isa 指向的是他自己, 所以,我们现在大部分 NSObject 为 root class 的体系下,可以认为所有 元类对象 的 isa 都指向 NSObject元类对象, 包括他自己。

在看下 元类对象 的父类关系, 各 元类对象父类, 均为其类对象父类元类对象, 最后到了 root class 的 元类对象, 而 root class 的 元类对象父类是 root class 的 类对象, 这就导致 所有 类对象元类对象基类 都是 root class。


17.UIView 和 CALayer 之间的关系?
  • 每个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示,并且 UIView 的尺寸样式都由内部的 Layer 所提供。两者都有树状层级结构,layer 内部有 SubLayers,View 内部有 SubViews.但是 Layer 比 View 多了个AnchorPoint
  • 在 View显示的时候,UIView 做为 Layer 的 CALayerDelegate,View 的显示内容由内部的 CALayer 的 display
  • CALayer 是默认修改属性支持隐式动画的,在给 UIView 的 Layer 做动画的时候,View 作为 Layer 的代理,Layer 通过 actionForLayer:forKey:向 View请求相应的 action(动画行为)
  • layer 内部维护着三分 layer tree,分别是 presentLayer Tree(动画树),modeLayer Tree(模型树), Render Tree (渲染树),在做 iOS动画的时候,我们修改动画的属性,在动画的其实是 Layer 的 presentLayer的属性值,而最终展示在界面上的其实是提供 View的modelLayer
  • 两者最明显的区别是 View可以接受并处理事件,而 Layer 不可以

17.+[UIView animateWithDuration:animations:completion:] 内部大概是如何实现的?

不知道...


18.什么时候会发生「隐式动画」?

首先知道下什么叫 "隐式动画" :

Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画。动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则他会一直存在。

当你改变CALayer的一个可做动画的属性,它并不能立刻在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作。这其实就是所谓的隐式动画

之所以叫隐式是因为我们并没有指定任何动画的类型。我们仅仅改变了一个属性,然后Core Animation来决定如何并且何时去做动画。

举个例子:

@interface ViewController ()
@property (nonatomic, strong) CALayer *showLayer;
@end

- (void)viewDidLoad {

    self.showLayer = [CALayer layer];
    _showLayer.frame = CGRectMake(0.0f, 20.0f, 100.0f, 100.0f);
    _showLayer.backgroundColor = [UIColor redColor].CGColor;;

    [self.view.layer addSublayer:_showLayer];
}

然后加个button,点击事件改变其frame:

-(void)layerChange
{
    _showLayer.frame = CGRectMake(200.0f, 20.0f, 100.0f, 100.0f);
}

大概就是这样:

iOS面试题( 一 )_第4张图片
点击前.png

点击后,位移发生改变,但是我们会发现layer的位移变化并不是直接去了新的位置,而是 动画平移 过去的,这就是隐式动画的功劳,隐式动画的动画时间为0.25秒。

iOS面试题( 一 )_第5张图片
点击后.png

所以对layer操作是,发生了隐式动画。

但是还有一种情况,当我们直接修改 view.layer 的时候,隐式动画却被view给禁掉了,这是为什么呢?

我们知道Core Animation通常对CALayer的所有属性(可动画的属性)做动画,但是UIView把它关联的图层的这个特性关闭了。为了更好说明这一点,我们需要知道隐式动画是如何实现的。

我们把改变属性时CALayer自动应用的动画称作行为,当CALayer的属性被修改时候,它会调用-actionForKey:方法,传递属性的名称。剩下的操作都在CALayer的头文件中有详细的说明,实质上是如下几步:

  • 图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。如果有,直接调用并返回结果。
  • 如果没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。
  • 如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
  • 最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法。

所以一轮完整的搜索结束之后,-actionForKey:要么返回空(这种情况下将不会有动画发生),要么是CAAction协议对应的对象,最后CALayer拿这个结果去对先前和当前的值做动画。

于是这就解释了UIKit是如何禁用隐式动画的:每个UIView对它关联的图层都扮演了一个委托,并且提供了-actionForLayer:forKey的实现方法。当不在一个动画块的实现中,UIView对所有图层行为返回nil,但是在动画block范围之内,它就返回了一个非空值。

关于显示\隐式动画的东西看这个,很详细 :http://blog.csdn.net/catsmen/article/details/46546897


19:frame 和 bounds 的区别是什么?

参考坐标系不一样,frame的参考坐标系是其superview, bounds 的参考坐标系是其本身


20:如何把一张大图缩小为1/4大小的缩略图?

UIImageJPEGRepresentation(image, 0.25)


21:一个 App 会处于哪些状态?
  1. Not running:应用还没有启动,或者应用正在运行但是途中被系统停止。
  1. Inactive:当前应用正在前台运行,但是并不接收事件(当前或许正在执行其它代码)。一般每当应用要从一个状态切换到另一个不同的状态时,中途过渡会短暂停留在此状态。唯一在此状态停留时间比较长的情况是:当用户锁屏时,或者系统提示用户去响应某些(诸如电话来电、有未读短信等)事件的时候。
  1. Active:当前应用正在前台运行,并且接收事件。这是应用正在前台运行时所处的正常状态。
  1. Background:应用处在后台,并且还在执行代码。大多数将要进入Suspended状态的应用,会先短暂进入此状态。然而,对于请求需要额外的执行时间的应用,会在此状态保持更长一段时间。另外,如果一个应用要求启动时直接进入后台运行,这样的应用会直接从Not running状态进入Background状态,中途不会经过Inactive状态。比如没有界面的应用。注此处并不特指没有界面的应用,其实也可以是有界面的应用,只是如果要直接进入background状态的话,该应用界面不会被显示。
  1. Suspended:应用处在后台,并且已停止执行代码。系统自动的将应用移入此状态,且在此举之前不会对应用做任何通知。当处在此状态时,应用依然驻留内存但不执行任何程序代码。当系统发生低内存告警时,系统将会将处于Suspended状态的应用清除出内存以为正在前台运行的应用提供足够的内存。

22:Push Notification 是如何工作的?
  • 本地推送: 通过相关api本地实现,无需服务器参与
  • 远程推送:必须经过苹果的APNs(Apple Push Notification service),大概流程就是:客户端将 bundleID 和 UDID 传给苹果,苹果返回一个对应的deviceToken 给客户端,客户端需要把这个deviceToken传给应用服务器保存,当服务器收到需要推送的消息,就拿着deviceToken通知苹果服务器,给这个deviceToken 的设备推送消息,然后苹果查到deviceToken的设备,把应用服务器同时传给苹果的消息推送给对应设备。

23:什么是 Runloop?

iOS系统的一种运行机制,本身一个循环,实现合理对事件接受和分发,也是线程的基本架构部分。runloop保证了所在线程能在忙碌的时候合理调配任务,在不忙碌的时候休眠,以节省cpu资源。

之后写一篇详细介绍runloop的东西吧,东西其实还不少。


24:Toll-Free Bridging 是什么?什么情况下会使用?

有一些数据类型是可以在 Core Foundation Framework 和 Foundation Framework 之间互相转换的,意思就是这些数据类型只要做一个转换就可以在这两个框架使用,这就是 Toll-Free Bridging ,而转换的方法就是 用 __bridge 关键字。

什么情况下使用
其实就是当需要转换的时候用的,那么什么时候需要转换,举个例子:
我们一般情况下都是使用 Foundation Framework 的方法较多,但是当我们在写代码的过程中突然需要用 Core Foundation Framework 的函数,但是 Core Foundation Framework 函数需要的参数的类型 Foundation Framework 的类型肯定是不能直接用的,所以就需要用 __bridge 转一下,其实这种情况我们经常遇到。

再附一个 Toll-Free Bridging 的类型转换对应表
再附一个 Toll-Free Bridging 原理


25:当系统出现内存警告时会发生什么?

首先身为一个使用者,会发现机器很卡,然后甚至会闪退。

而作为系统,在内存警告时,会尽可能得释放没必要的内存,比如未显示的view。对于非界面的数据,需要具体分析,可以恢复的数据可以释放,不能恢复的数据就不要释放。


26:什么是 Protocol,Delegate 一般是怎么用的?

A protocol declares a programmatic interface that any class may choose to implement. Protocols make it possible for two classes distantly related by inheritance to communicate with each other to accomplish a certain goal. They thus offer an alternative to subclassing. Any class that can provide behavior useful to other classes may declare a programmatic interface for vending that behavior anonymously. Any other class may choose to adopt the protocol and implement one or more of its methods, thereby making use of the behavior. The class that declares a protocol is expected to call the methods in the protocol if they are implemented by the protocol adopter.

协议声明了一个任何类都可以去选择实现的程序接口,协议使两个没有继承关系的类可以互相沟通并实现特定目标。因此,协议其实是提供了另外一个替代子类化的方式。任何可以为其他类提供有用的行为的类,都可以声明一个编程接口去以匿名的方式提供这种有用行为。而其他的类则可以选择一个协议,并选择实现协议里的一个或多个接口来使用这种行为。如果遵守协议者实现了协议内的方法,那么声明协议者,将会调用遵守协议者实现的协议方法。

而Delegate一般会成为 “遵守协议者” 和 “声明协议者” 的 "桥梁", 因为 Delegate 一般会指向 “遵守协议者” ,这样才能在 “声明协议者”内部通过 Delegate 调用到协议方法。


26:autorelease 对象在什么情况下会被释放?

有两种情况:

  • 我们手动写的 autoreleasepool
    在 autoreleasepool 里的对象,会在 autoreleasepool 运行完的时候释放自动释放池
  • runloop 里的 autoreleasepool
    runloop 会在 BeforeWaiting(准备进入休眠) 和 Exit(即将退出Loop) 时调用_objc_autoreleasePoolPop() 来释放自动释放池

27:UIWebView 有哪些性能问题?有没有可替代的方案。

没研究过,看看这个吧: 传送门


28:为什么 NotificationCenter 要 removeObserver? 如何实现自动 remove?

为什么 NotificationCenter 要 removeObserver?

  • 如果不删除 在 NotificationCenter 始终会存在一个没有用的消息,占用内存浪费性能
  • 如果注册消息的对象不小心被野指针了,那么当然消息发过来就会崩溃掉。当然如果正常释放且置为nil的话,就没这个问题。

想要实现自动remove,可以通过 method swizzling 在 dealloc 中统一加上删除observer 的操作。当然有些业务需求并不一定是要在dealloc中删除,根据业务作出灵活变动。


29:当 TableView 的 Cell 改变时,如何让这些改变以动画的形式呈现?
[tableView beginUpdates];
[tableView endUpdates];

关于beginUpdates/endUpdates 我们需要知道几点:

  • 这两个方法必须成对使用。可以嵌套使用。
  • 为了使 tableview 后续的插入、删除和选择操作(例如,cellForRowAtIndexPath:和indexpathsforvisiblows)可以同时进行动画,可以调用此方法
  • 可以使cell行高度的更改在不重新加载cell的情况下进行动画
  • 如果没有在这两个方法中间有插入、删除和选择的行为,那么诸如行数之类的tableView属性可能会变得无效
  • 不应该在这两个方法之间调用reloadData;如果在调用reloadData,则必须自己执行动画。

30:什么是 Method Swizzle,什么情况下会使用?

被称为iOS的黑科技,原理是利用iOS的运行时机制,进行方法的hook,以达到动态修改一些系统方法的目的。
简单举个例子,hook一个 viewController 的 viewDidload 方法:

+(void)load
{
    [super load];
    Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
    Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewDidLoad));

    method_exchangeImplementations(fromMethod, toMethod);
}

- (void)swizzlingViewDidLoad {
     [super swizzlingViewDidLoad];
     NSLog(@"my swizzling viewDidLoad");
}

31:为什么 UIScrollView 的滚动会导致 NSTimer 失效?

主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为"Common"属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。

有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 "commonModeItems" 中。"commonModeItems" 被 RunLoop 自动更新到所有具有"Common"属性的 Mode 里去。

你可能感兴趣的:(iOS面试题( 一 ))