找一个靠谱的iOS面试题

1. 风格纠错题 知识点官方文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef enum {
UserSex_Man,
UserSex_Woman
} UserSex;


@interface UserModel : NSObject

@property(nonatomic, strong) NSString *name;
@property(assign, nonatomic) int age;
@property (nonatomic, assign) UserSex sex;

-(id) initUserModelWithUserName:(NSString*)name withAge:(int) age

-(void) doLogin;

考查知识点:

  • enum NS_ENUM NS_OPTIONS
  • 不可变类型数据(NSString、NSArray、NSDictionary)的修饰符的使用
  • 函数命名规范
  • 应避免使用基本类型,建议使用Foundation数据类型

###知识点:

enum枚举类型建议使用 NS_ENUM 或者 NS_OPTIONS 宏来定义

NS_ENUMNS_OPTIONS提供了一种简明、简单的方法来定义基于C语言的枚举和选项,这些宏提高了Xcode中的代码的完成性,并明确指定了枚举和选项的类型和大小。

  • NS_ENUMNS_OPTIONS 的区别

如果需要以按位或操作来组合的枚举应该使用NS_OPTIONS,;如枚举不需要相互组合,则可以使用NS_ENUM来定义。也就是NS_OPTIONS适合多挣枚举类型同时存在的情况,而NS_ENUM适合只同时只存在一种枚举的情况

不可变类型数据(NSString、NSArray、NSDictionary)的修饰符的使用,属性的修饰符

不可变类型数据(NSString、NSArray、NSDictionary),都有其对应的可变类型,而且是不可变类型的子类,如果使用 strong来修饰NSString,那么NSMutableString也可以赋值给该属性,如果NSMutableString赋值给NSString后,又被修改了,那么就会出现跟预想不一致的情况了,而copy修饰NSString,在setter方法中只会将NSMutableString复制一份,而不会保留,就算NSMutableString后来改变了也没有关系

函数命名规范

  1. instancetype,使用instancetype关键字作为返回类的实例的方法的返回类型,而不是使用id作为返回类型,这可以提高代码的安全性
  2. 连接第二参数不要使用with
  3. 函数命名不要使用两个动词

应避免使用基本类型,建议使用Foundation数据类型

Foundation数据在不同系统上长度是不一样的,在32位系统上int占4个字节 NSInterge也是4个字节,但是在64位系统上NSInterge占8个字节。总的来说Foundation数据兼容性会更好一下,使用Foundation数据会比使用基本类型,代码更健壮一点

####修改后答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef NS_ENUM (NSInteger, CYSex) {
CYSexMan,
CYSexWoman
};


@interface UserModel : NSObject

@property(nonatomic, copy, readonly) NSString *name;
@property(nonatomic, assign, readonly) NSUInterge age;
@property(nonatomic, assign, readonly) CYSex sex;

-(instancetype) userModelWithUserName:(NSString*)name age:(int) age

-(void) login;

2. 什么情况下使用weak关键字,相比assign有什么不同

  • weak关键字的作用:weak关键字是为了解决strong带来的循环引用的问题而生的,它不会使所引用的对象的引用计数+1,所以也就打破了引用环。weak关键字修饰的属性在其指向的对象销毁了,该属性也会被置为nil。
  • assign关键字也不会使所引用的对象引用计数+1,和weak的不同点在于,其引用的对象销毁时,assign关键字修饰的属性不会被置为nil,仍然指向了原来的地址,形成野指针。assign赋值方式:复制数据而不是复制引用

结论:weak关键字适用于可能出现循环引用的地方

3.怎么使用copy关键字?

  1. NSString、NSArray、NSDictionary等经常使用copy关键字,因为他们有对应的可变数据类型
  2. block也经常使用copy关键字,因为在方法体内block是被分配在栈上的,使用copy之后就会分配在堆上。然后就可以在作用域外访问了

4. 这个写法会出什么问题: @property (copy) NSMutableArray *array;

  1. copy修饰的话,在赋值时会变成NSArray不可变对象,那么进行增删改操作时会crash
  2. property默认为 atomic,这会影响性能。

一般情况下并不需要把属性声明为atomic,因为这并不能保证线程安全,只能保证读写的原子性。例如:一个线程连续多次读取属性值时,也有可能读到不同的值。

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

  1. 若想让自己的类的对象具备copy功能,则需要实现NSCopy协议,如果自定义的对象分为可变和不可变两个版本,那么还应该实现NSMutableCopy协议
  2. 重写如下:
1
2
3
-(void) setName:(NSString *)name{
_name = [name copy];
}

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

@property本质上等于 实例变量 + 存取方法(getter、setter)

ivar、getter、和setter是编译器通过autosynthesis(自动合成)添加到类中的

7. protocol 和category中如何使用@property?

  1. protocol中使用@property,只会生成getter和setter方法声明,在protocol中使用property的目的是,希望实现该协议的类能实现该属性
  2. category中使用@property也是只会生成getter和setter方法声明,如果真的需要给category增加属性需要使用一下两个函数:objc_setAssociatedObjectobjc_getAssociatedObject

8.Runtime如何实现weak属性

Runtime其实是通过一张weak表来实现weak属性的,weak表其实是一张Hash表,key为weak指向的对象的内存地址,value则是所有weak指针的组成的数组。当weak指向的对象销毁时,Runtime就会从weak表中索引到所有指向改地址的weak指针,并全部置为nil

  • 子问题:如何让不使用weak修饰的property,拥有weak的效果?
  1. 重写setter方法,使用
1
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);

这样相当于使用assign修饰了property

  1. 第二步需要在object对象销毁时,将property对应的实例变量_object置为nil;这一步的实现需要借助category以及关联对象,原理是当一个对象被销毁时,它的关联对象也会被销毁,那么只要在关联对象的的dealloc方法里执行block就可以达到将 实例变量_object置为nil的目的了

setter的实现

1
2
3
4
5
6
7
- (void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}

category的实现

1
2
3
4
-(void) cyl_runAtDealloc:(voidBlock) block{
CYLBlockExecutor *executor = [CYLBlockExecutor initWithBlock:block];
objc_setAssociatedObject(self, "runAtDeallocBlockKey", executor, OBJC_ASSOCIATION_RETAIN);
}

CYLBlockExecutor的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
- (id)initWithBlock:(voidBlock)aBlock
{
self = [super init];
if (self) {
_block = [aBlock copy];
}
return self;
}

- (void)dealloc
{
_block ? _block() : nil;
}

9.@property中有哪些属性关键字

  1. 原子性:atomic、nonatomic
  2. 读写权限:readwrite、readonly
  3. 内存管理语义:strong、weak、assign、copy、unsafe_unretained
  4. 方法名: getter=, setter=

10.weak属性需要在dealloc中置为nil吗?

不需要,ARC会自动处理的

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

  1. @property有两个对应的词:@synthesize和@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@synthesize var = _var;
  2. @synthesize的语义是如果你没有实现getter和setter那么编译器会自动帮你实现这两个方法
  3. @dynamic是告诉编译器:属性的getter和setter由开发者自己实现。@dynamic一般用来实现动态绑定,需要重写+ (BOOL) resolveInstanceMethod:(SEL)sel方法

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

13. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

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

15. 在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?

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

objc在想一个对象发送消息时,runtime会根据对象的isa指针找到该对象所属的类,然后再该类的方法列表和其父类的方列表里查找响应的方法,然后再发送消息的时候,objc_msgSend不会有返回值,所谓的返回值都是在发生方法调用的时候执行的。如果向nil对象发送消息,首先就会在寻找对象的isa指针时就返回了,也就不会发生任何错误了

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

[objc foo]会在编译时,被转意为:objc_msgSend(obj, @selector(foo))

18. 什么情况下会报unrecognized selector异常

这就涉及到消息的发送流程了:

  1. objc在向一个对象发送消息时,Runtime会根据对象的isa指针找到对象所属的类,然后从方法缓存里查找对应的方法
  2. 若上一步找不到对应的方法,则会从方法列表里查找,如果还是找不到就会到父类的方法列表里查找直到NSObject类
  3. 这时如果还是找不到,就会触发Method resolution,这时你有机会提供一个函数的实现,添加到类中,如果添加了实现,这时就会重新启动一次消息发送流程,Method resolution没有提供实现的函数的话,就会触发消息转发
  4. Fast Forwarding:如果目前对象实现了-forwardingForSelector方法,Runtime就会调用该方法,让目标对象提供一个可以响应该消息的对象,只要不返回nil或者self,就会重新触发消息发送
  5. Normal Forwarding:Fast Forwarding失败了的话,就是启动Normal Forwarding。首先Runtime会发送-methodForSignaturForSelector:消息获取函数的参数和返回值,然后创建一个NSInvocation对象并发送 -forwardInvocation:消息给目标对象

如果以上流程走完,仍没有响应消息的函数,则会报unrecognized selector异常

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

  • 父类的成员变量和自己的成员变量都存放在该对象的存错空间中
  • 对象的内部有一个isa指针,指向他的类对象。类对象中存放着:

    * 每个对象的方法列表
    * 成员变量的列表
    * 属性的列表
    * 类对象的内部也有一个isa指针指向元类对象(meta class),元类对象中存放着类方法列表,类对象内部还有一个superclass指针指向父类对象
    

20.一个objc对象的isa的指针指向什么?有什么作用?

指向类对象,可以查找对象上的方法

21. 下面的代码输出什么

1
2
3
4
5
6
7
8
@implementation Son:Father
-(id) init{
self = [super init];
if(self){
NSLog(@"%@", NSStringFromClass[self class]);
NSLog(@"%@", NSStringFromClass[super class]);
}
}

考察的知识点:self与super关键字

self是类的隐藏参数,指向当前调用方法的类的实例
super并不是指向父类的指针,它和self是指向同一个接收者,不同点在于:

* [self class] 编译后 objc_msgSend(obj, @selector(class));
* [super class] 编译后 objc_msgSendSuper(obj, @selector(class));

但是最后调用的都是NSObject的这个方法

1
2
3
- (Class)class {
return object_getClass(self);
}

所以最后输出都是 Son

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

23. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

不需要
它们会在NSObject -dealloc调用的object_dispose()方法中释放掉

对象的内存释放时间表:

1. 调用release:引用计数变为0
2. 子类调用 -dealloc
3. NSObject 调用-dealloc

4.调用object_dispose()
  * 为C++实例变量们(ivars)调用析构函数
  * 为了ARC状态下的 实例变量们(ivars)调用releas
  * 释放所有使用runtime associate 方法关联的对象
  * 释放所有__weak引用
  * 调用free

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

25. _objc_msgForward函数是做什么的?直接调用他会发生什么?

_objc_msgForward是用来实现消息转发的:向一个对象发送消息,但它并没有对应的实现的时候,_objc_msgForward会尝试消息转发

直接调用_objc_msgForward是非常危险的事,直接调用_objc_msgForward,会跳过查找IMP的过程,直接出发消息转发,如果用不好会直接导致程序Crash。但是如果用得好,能做很多非常酷的事,比如热修复。

26. runtime如何实现weak变量的自动置nil?

同第8题 weak的实现

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

  • 不能向编译后的类增加实例变量
  • 可以向运行时创建的类添加实例变量

解释:
编译后的类,类结构体中的objc_ivar_list 实例变量链表和实例变量的内存大小 instance_size都已经确定了,所以不能向编译后的类增加实例变量

运行时创建的类可以增加实力变量。不过得在调用objc_allocateClassPair之后,objc_registerClassPair之前。

增加方法则没有限制

28 RunLoop与线程的关系

RunLoop与线程的紧密相连的,每个线程都有一个对应的RunLoop。
不过只有主线程的RunLoop默认是开启的

其他线程的RunLoop默认不开启 必要时得手动获取:

1
NSRunLoop*runloop = [NSRunLoop currentRunLoop];

29.RunLoop的mode的作用是什么?

mode是用来处理不同分组的Source/Timer/Obsever事件的,系统提供的mode有5种:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认。空闲状态

  • UITrackingRunLoopMode:ScrollView的滑动时

  • GSEventReceiveRunLoopMode

  • UIInitializationRunLoopMode:启动时
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合

系统公开提供的Mode有两个:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认。空闲状态
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合

RunLoop只能运行在一种Mode下,如果要切换Mode,需要退出RunLoop再重新启动,当ScrollView滑动时,主线程RunLoop会由NSDefaultRunLoopMode切换为UITrackingRunLoopMode,而以+ scheduledTimerWithTimeInterval…的方式触发的timer,实质上是注册到NSDefaultRunLoopMode的,所以这时候timer会被暂停回调。要想不被暂停,可以将timer注册到NSRunLoopCommonModes下,因为NSDefaultRunLoopMode和UITrackingRunLoopMode都在NSRunLoopCommonModes mode集合中

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

同上一题

31. 猜想runloop内部是如何实现的?

do-while 循环

  1. 通知Observer即将进入RunLoop
  2. 通知Observer即将处理Timer
  3. 通知Observer即将处理Source0
  4. 处理Source0
  5. 如果有Source1跳到9
  6. 通知Observer线程即将休眠
  7. 休眠等待被唤醒
  8. 通知Observer线程刚被唤醒
  9. 处理唤醒时受到的消息,跳回2
  10. 通知Observer即将退出RunLoop

苹果在主线程注册了两个Observer:

  • 第一个Observer监听即将进入RunLoop事件:创建自动释放池AutoreleasePool
  • 第二个Observer监听两个事件:
    • 即将进入休眠 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新的池;
    • 退出RunLoop调用 _objc_autoreleasePoolPush() 释放自动释放池

      32.objc使用什么机制管理对象内存?

      引用计数机制来管理对象内存

33. ARC通过什么方式帮助开发者管理内存?

编译期自动插入retain/release/autorelease 运行期