iOS面试题整理

【※】@property中有哪些属性关键字?

访问控制组

nonatomic
atomic

内存管理组

weak
strong
copy

读写组

readwrite
readonly
重命名组
getter
setter

【※】weak属性需要在dealloc中置nil么?
不需要, 使用weak修饰的属性, 会在RC从1变为0的时候自动销毁,并置为nil

【※※】@synthesize和@dynamic分别有什么作用?
首先两者都是用在.m文件中的
@synthesize
系统自动生成getter和setter方法, 不需要程序员去写, 如果有需求要同时重写使用@property生成的实例变量的setter和getter方法, 这必须添加@synthesize 声明的属性 = 变量

@synthesize name = _name;

@dynamic
属性的setter和getter方法需要程序员自己生成

【※※※】ARC下,不显示指定任何属性关键字时,默认的关键字都有哪些?
对应基本数据类型默认关键字是
atomic, readwrite, assign
对于普通的OC对象
atomic, readwrite, strong

【※※※】用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
首先NSString, NSArray, NSDictionary都有可变的子类, 当使用NSString声明的属性, 被其子类NSMutableString赋值时, 其实只是把该属性的指针指向了NSMutableString对象, 那么改变NSMutableString的值, NSString修饰的属性值也会改变


name使用copy修饰


【※※※】@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?
@synthesize合成实例变量的规则

  1. 如果手动实现了set方法,那么编译器就只生成get方法和成员变量;
  2. 如果手动实现了get方法,那么编译器就只生成set方法和成员变量;
  3. 如果set和get方法都是手动实现的,那么编译器将不会生成成员变量
    假如property名为foo,存在一个名为_foo的实例变量,是不会合成新变量的


【※※※】使用运行时获取成员变量:
1、获取某个类的成员变量或属性;

unsigned int numIvars; //成员变量个数
Ivar *vars = class_copyIvarList(NSClassFromString(@"Person"), &numIvars);
NSString *key=nil;
for(int i = 0; i < numIvars; i++) {
    Ivar thisIvar = vars[i];
    key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];  //获取成员变量的名字
    NSLog(@"variable name :%@", key);
    key = [NSString stringWithUTF8String:ivar_getTypeEncoding(thisIvar)]; //获取成员变量的数据类型
    NSLog(@"variable type :%@", key);
}
free(vars);

2、获取成员函数

unsigned int numIvars; //成员变量个数
Method *meth = class_copyMethodList(NSClassFromString(@"Person"), &numIvars);
for(int i = 0; i < numIvars; i++) {
    Method thisIvar = meth[i];
    SEL sel = method_getName(thisIvar);
    const char *name = sel_getName(sel);
    NSLog(@"zp method :%s", name);
}
free(meth);

【※※※※※】在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
在声明属性@property的情况下如果重写setter,getter,方法,就需要把未识别的变量在@synthesize中定义,把属性的存取方法作用于变量。@synthesize 声明的属性 = 变量

【※※】objc中向一个nil对象发送消息将会发生什么?
在Objective-C中向nil发送消息是完全有效的——只是在运行时不会有任何作用:
如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:

Person * motherInlaw = [[aPerson spouse] mother];

如果spouse对象为nil,那么发送给nil的消息mother也将返回nil。
1)如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者long long的整型标量,发送给nil的消息将返回0。
2)如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值将都是0。
3)如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。

【※※※】objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?
[receiver message]会被编译器转化为:

objc_msgSend(receiver, selector)

如果消息含有参数,则为:

objc_msgSend(receiver, selector, arg1, arg2, ...)

如果消息的接收者能够找到对应的selector,那么就相当于直接执行了接收者这个对象的特定方法;否则,消息要么被转发,或是临时向接收者动态添加这个selector对应的实现内容,要么就干脆崩溃掉。

【※※※】什么时候会报unrecognized selector的异常?
使用方法选择器@selector(方法名)调用方法时方法未被实现; 在编译时不会报错, 在运行时则会崩溃, 这就是OC的运行时机制

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

  1. 所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中.
  2. 每一个对象内部都有一个isa指针, 指向他的类对象, 类对象中存放着本对象的
    1)对象方法列表(对象能够接收的消息列表,保存在它所对应的类对象中
    2)成员变量的列表
    3)属性列表

【※※※※】一个objc对象的isa的指针指向什么?有什么作用?
指向他的类对象, 从而可以找到对象上的方法
类对象中存放着本对象的
1)对象方法列表(对象能够接收的消息列表,保存在它所对应的类对象中
2)成员变量的列表
3)属性列表

【※※※※】下面的代码输出什么?

@implementation Son : Father
- (id)init
{
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

输出的结果都是:Son,
原因:
super 和 self 都是指向的本实例对象的,
不同的是, super调用的跳过本类方法,调用父类的方法
父类方法的class方法本来都是在基类中实现的,所以无论使用self和super调用都是一样的.
上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前 Son *xxx 这个对象。而不同的是,super是告诉编译器,调用 class 这个方法时,要去父类的方法,而不是本类里的。

【※※※※】runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
每一个类对象中都一个方法列表,方法列表中记录着方法的名称, 方法实现, 以及参数类型, 其实selector本质就是方法名称, 通过这个方法名称就可以在方法列表中找到对应的方法实现.

【※※※※※】objc中的类方法和实例方法有什么本质区别和联系?
类方法
类方法是属于类对象的
类方法只能通过类对象调用
类方法中的self是类对象
类方法可以调用其他的类方法
类方法中不能访问成员变量
类方法中不定直接调用对象方法

实例方法
实例方法是属于实例对象的
实例方法只能通过实例对象调用
实例方法中的self是实例对象
实例方法中可以访问成员变量
实例方法中直接调用实例方法
实例方法中也可以调用类方法(通过类名)

【※※※※※】runtime如何实现weak变量的自动置nil?
要实现weak属性,首先要搞清楚weak属性的特点:
weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

那么runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

【※※※※※】能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

  1. 不能向编译后得到的类中增加实例变量;
  2. 能向运行时创建的类中添加实例变量;

解释下:
因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量;
运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。

【※※※】runloop和线程有什么关系?
run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。Run loops是线程的基础架构部分, Cocoa 和 CoreFundation 都提供了 run loop 对象方便配置和管理线程的 run loop (以下都以 Cocoa 为例)。每个线程,包括程序的主线程( main thread )都有与之相应的 run loop 对象。

runloop 和线程的关系:

  1. 主线程的run loop默认是启动的。
    iOS的应用程序里面,程序启动后会有一个如下的main()函数
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。

  1. 对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。
    在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。
NSRunLoop *runloop = [NSRunLoop currentRunLoop];

【※※※】runloop的mode作用是什么?
model 主要是用来指定事件在运行循环中的优先级的,分为:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
UITrackingRunLoopMode:ScrollView滑动时
UIInitializationRunLoopMode:启动时
NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合

苹果公开提供的 Mode 有两个:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
NSRunLoopCommonModes(kCFRunLoopCommonModes)

【※※※※】以+ scheduledTimerWithTimeInterval…的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?
RunLoop只能运行在一种mode下,如果要换mode,当前的loop也需要停下重启成新的。利用这个机制,ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动:只能在NSDefaultRunLoopMode模式下处理的事件会影响ScrollView的滑动。

如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。

同时因为mode还是可定制的,所以:
Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决

【※※※】对于Run Loop的理解

  • RunLoop,是多线程的法宝,即一个线程一次只能执行一个任务,执行完任务后就会退出线程。主线程执行完即时任务时会继续等待接收事件而不退出。非主线程通常来说就是为了执行某一任务的,执行完毕就需要归还资源,因此默认是不运行RunLoop的;
  • 每一个线程都有其对应的RunLoop,只是默认只有主线程的RunLoop是启动的,其它子线程的RunLoop默认是不启动的,若要启动则需要手动启动;
  • 在一个单独的线程中,如果需要在处理完某个任务后不退出,继续等待接收事件,则需要启用RunLoop;
  • NSRunLoop提供了一个添加NSTimer的方法,可以指定Mode,如果要让任何情况下都回调,则需要设置Mode为Common模式;
  • 实质上,对于子线程的runloop默认是不存在的,因为苹果采用了懒加载的方式。如果我们没有手动调用[NSRunLoop currentRunLoop]的话,就不会去查询是否存在当前线程的RunLoop,也就不会去加载,更不会创建。

【※】objc使用什么机制管理对象内存?
通过 retainCount 的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。

【※※※※】BAD_ACCESS在什么情况下出现?

  1. 死循环
  2. 访问一个僵尸对象

【※※※】使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?
不考虑,参考如下
将Block作为参数传给AFN, dispatch_async, Masonry时,系统会将Block拷贝到堆上,如果Block中使用了实例变量,还将retain self,因为AFN, dispatch_async, Masonry并不知道self会在什么时候被释放,为了确保系统调度执行Block中的任务时self没有被意外释放掉,AFN, dispatch_async, Masonry必须自己retain一次self,任务完成后再release self。但这里使用__weak,使AFN, dispatch_async, Masonry没有增加self的引用计数,这使得在系统在调度执行Block之前,self可能已被销毁,但系统并不知道这个情况,导致Block被调度执行时self已经被释放导致crash。

【※※】GCD的队列(dispatch_queue_t)分哪两种类型?

  1. 串行队列Serial Dispatch Queue
  2. 并行队列Concurrent Dispatch Queue

【※※※※】如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 合并图片
});

【※※※※】dispatch_barrier_async的作用是什么?
在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用 barrier 来等待之前任务完成,避免数据竞争等问题。 dispatch_barrier_async 函数会等待追加到Concurrent Dispatch Queue并行队列中的操作全部执行完之后,然后再执行 dispatch_barrier_async 函数追加的处理,等 dispatch_barrier_async 追加的处理执行结束之后,Concurrent Dispatch Queue才恢复之前的动作继续执行。
(注意:使用 dispatch_barrier_async ,该函数只能搭配自定义并行队列 dispatch_queue_t 使用。不能使用: dispatch_get_global_queue ,否则 dispatch_barrier_async 的作用会和 dispatch_async 的作用一模一样。 )

【※※※※※】苹果为什么要废弃dispatch_get_current_queue?
容易误用造成死锁

【※※※※※】以下代码运行结果如何?

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}

只输出:1 发生主线程锁死。

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

/*
1 观察者,负责处理监听事件的对象
2 观察的属性
3 观察的选项
4 上下文
*/
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];

observer中需要实现一下方法:

// 所有的 kvo 监听到事件,都会调用此方法
/*
 1. 观察的属性
 2. 观察的对象
 3. change 属性变化字典(新/旧)
 4. 上下文,与监听的时候传递的一致
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

【※※※】如何手动触发一个value的KVO

  • 手动触发:
    调用willChangeValueForKey: 和 didChangevlueForKey: 方法
  • 自动触发:
    调用setter方法

自动触发是指类似这种场景:在注册 KVO 之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了。

想知道如何手动触发,必须知道自动触发 KVO 的原理:
键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就会记录旧的值。而当改变发生后, didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。

那么“手动触发”的使用场景是什么?一般我们只在希望能控制回调的调用时机时才会这么做。
具体做法如下:
如果这个 value 是 表示时间的 self.now ,那么代码如下:最后两行代码缺一不可。

//@property (nonatomic, strong) NSDate *now;
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self willChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。
    [self didChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。
}

但是平时我们一般不会这么干,我们都是等系统去“自动触发”。“自动触发”的实现原理:
比如调用 setNow: 时,系统还会以某种方式在中间插入 wilChangeValueForKey: 、 didChangeValueForKey: 和 observeValueForKeyPath:ofObject:change:context: 的调用。

大家可能以为这是因为 setNow: 是合成方法,有时候我们也能看到人们这么写代码:

- (void)setNow:(NSDate *)aDate {
    [self willChangeValueForKey:@"now"]; // 没有必要
    _now = aDate;
    [self didChangeValueForKey:@"now"];// 没有必要
}

这是完全没有必要的代码,不要这么做,这样的话,KVO代码会被调用两次。KVO在调用存取方法之前总是调用 willChangeValueForKey: ,之后总是调用 didChangeValueForkey: 。

【※※※】若一个类有实例变量NSString *_foo,调用setValue:forKey:时,可以以foo还是_foo作为key?
都可以, 先查找_foo, 如果没有再查找foo, 如果都没有则调用
setValue:forUndefinedKey:

【※※※※】KVC的keyPath中的集合运算符如何使用?

  1. 必须用在集合对象上或普通对象的集合属性上
  2. 简单集合运算符有@avg, @count , @max , @min ,@sum
  3. 格式 @”@sum.age“或 @”集合属性.@max.age”

【※※※※】KVC和KVO的keyPath一定是属性么?
KVO支持实例变量, 不通过调用setter, getter方法, 间接赋值取值

【※※※※※】如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?
简单概述下 KVO 的实现:
当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。

原来,这个中间类,继承自原本的那个类。不仅如此,Apple 还重写了 -class 方法,企图欺骗我们这个类没有变,就是原本那个类。更具体的信息,去跑一下 Mike Ash 的那篇文章里的代码就能明白,这里就不再重复。
如何自己动手实现 KVO

【※※※※※】apple用什么方式实现对一个对象的KVO?
KVO 在实现中通过isa 混写(isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向一个个新创建的子类,对象就神奇的变成了新创建的子类的实例。这在Apple 的文档可以得到印证:
Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …
假设“被监听的对象”的类对象是 MYClass ,有时候我们能看到对 NSKVONotifying_MYClass 的引用而不是对 MYClass 的引用。借此我们得以知道 Apple 使用了isa 混写(isa-swizzling)。

【※※】IBOutlet连出来的视图属性为什么可以被设置成weak?
因为既然有外链那么视图在xib或者storyboard中肯定存在,视图已经对它有一个强引用了。
不过这个回答漏了个重要知识,使用storyboard(xib不行)创建的vc,会有一个叫_topLevelObjectsToKeepAliveFromStoryboard的私有数组强引用所有top level的对象,所以这时即便outlet声明成weak也没关系

【※※※※※】IB中User Defined Runtime Attributes如何使用?
User Defined Runtime Attributes 是一个不被看重但功能非常强大的的特性,
它能够通过KVC的方式配置一些你在interface builder 中不能配置的属性。当你希望在IB中作尽可能多得事情,这个特性能够帮助你编写更加轻量级的viewcontroller

【※※※】如何调试BAD_ACCESS错误

  1. 设置全局断点:
  2. 开启僵尸模式 和 BAD_ACCESS检测模式(Xcode7.0以后)

【※※※】lldb(gdb)常用的调试命令?

  1. breakpoint 设置断点定位到某一个函数
  2. n 断点指针下一步
  3. po打印对象

【※※※】@protocol 和 category 中如何使用 @property
在protocol中使用property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象的实现该属性
category 使用 @property 也是只会生成setter和getter方法的声明,如果我们真的需要给category增加属性的实现,需要借助于运行时的两个函数

objc_setAssociatedObject
objc_getAssociatedObject

二叉树遍历算法

前序:先根结点后左孩子最后右孩子
中序:先左孩子后根结点最后右孩子
后序:先左孩子后右孩子最后根结点

推荐阅读更多精彩内容

  • OC的理解与特性OC作为一门面向对象的语言,自然具有面向对象的语言特性:封装、继承、多态。它既具有静态语言的特性(...
    LIANMING_LI阅读 119评论 0 0
  • 基础 1.为什么说Objective-C是一门动态的语言? 所谓的动态类型语言,意思就是类型的检查是在运行时做的。...
    爱运动爱学习阅读 572评论 1 15
  • OC语法篇 面向对象 1. 一个NSObject对象占用多少内存? 系统分配了16个字节给NSobject对象(通...
    内心戏十足的伪胖子阅读 186评论 1 8
  • 今日才知自己与别人的差距那么那么大,没有天赋还不勤加练习,真觉得自己是个废人,干什么都不行。。。。努力吧,别让身边...
    沧海一粟之小傻子阅读 16评论 0 0
  • 对于健康的人来说,这个世界五彩斑斓。可是对于色盲患者来说,却是暗淡乏味的。 小时候总有人问我,最喜欢的颜色是什么,...
    朽木不可雕阅读 42评论 0 1