1. 风格纠错题 知识点官方文档
1 | typedef enum { |
考查知识点:
- enum NS_ENUM NS_OPTIONS
- 不可变类型数据(NSString、NSArray、NSDictionary)的修饰符的使用
- 函数命名规范
- 应避免使用基本类型,建议使用Foundation数据类型
###知识点:
enum枚举类型建议使用 NS_ENUM
或者 NS_OPTIONS
宏来定义
NS_ENUM
和NS_OPTIONS
提供了一种简明、简单的方法来定义基于C语言的枚举和选项,这些宏提高了Xcode中的代码的完成性,并明确指定了枚举和选项的类型和大小。
NS_ENUM
和NS_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后来改变了也没有关系
函数命名规范
- instancetype,使用instancetype关键字作为返回类的实例的方法的返回类型,而不是使用id作为返回类型,这可以提高代码的安全性
- 连接第二参数不要使用with
- 函数命名不要使用两个动词
应避免使用基本类型,建议使用Foundation数据类型
Foundation数据在不同系统上长度是不一样的,在32位系统上int占4个字节 NSInterge也是4个字节,但是在64位系统上NSInterge占8个字节。总的来说Foundation数据兼容性会更好一下,使用Foundation数据会比使用基本类型,代码更健壮一点
####修改后答案
1 | typedef NS_ENUM (NSInteger, CYSex) { |
2. 什么情况下使用weak关键字,相比assign有什么不同
- weak关键字的作用:weak关键字是为了解决strong带来的循环引用的问题而生的,它不会使所引用的对象的引用计数+1,所以也就打破了引用环。weak关键字修饰的属性在其指向的对象销毁了,该属性也会被置为nil。
- assign关键字也不会使所引用的对象引用计数+1,和weak的不同点在于,其引用的对象销毁时,assign关键字修饰的属性不会被置为nil,仍然指向了原来的地址,形成野指针。assign赋值方式:复制数据而不是复制引用
结论:weak关键字适用于可能出现循环引用的地方
3.怎么使用copy关键字?
- NSString、NSArray、NSDictionary等经常使用copy关键字,因为他们有对应的可变数据类型
- block也经常使用copy关键字,因为在方法体内block是被分配在栈上的,使用copy之后就会分配在堆上。然后就可以在作用域外访问了
4. 这个写法会出什么问题: @property (copy) NSMutableArray *array;
- copy修饰的话,在赋值时会变成NSArray不可变对象,那么进行增删改操作时会crash
- property默认为 atomic,这会影响性能。
一般情况下并不需要把属性声明为atomic,因为这并不能保证线程安全,只能保证读写的原子性。例如:一个线程连续多次读取属性值时,也有可能读到不同的值。
5.如何让自己的类用copy修饰符?如何重写带copy关键字的setter?
- 若想让自己的类的对象具备copy功能,则需要实现NSCopy协议,如果自定义的对象分为可变和不可变两个版本,那么还应该实现NSMutableCopy协议
- 重写如下:
1 | -(void) setName:(NSString *)name{ |
6.@property的本质是什么?ivar、getter、和setter是如何生成并添加到这个类中的?
@property本质上等于 实例变量 + 存取方法(getter、setter)
ivar、getter、和setter是编译器通过autosynthesis(自动合成)添加到类中的
7. protocol 和category中如何使用@property?
- protocol中使用@property,只会生成getter和setter方法声明,在protocol中使用property的目的是,希望实现该协议的类能实现该属性
- category中使用@property也是只会生成getter和setter方法声明,如果真的需要给category增加属性需要使用一下两个函数:
objc_setAssociatedObject
和objc_getAssociatedObject
8.Runtime如何实现weak属性
Runtime其实是通过一张weak表来实现weak属性的,weak表其实是一张Hash表,key为weak指向的对象的内存地址,value则是所有weak指针的组成的数组。当weak指向的对象销毁时,Runtime就会从weak表中索引到所有指向改地址的weak指针,并全部置为nil
- 子问题:如何让不使用weak修饰的property,拥有weak的效果?
- 重写setter方法,使用
1 | objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN); |
这样相当于使用assign修饰了property
- 第二步需要在
object
对象销毁时,将property对应的实例变量_object
置为nil;这一步的实现需要借助category
以及关联对象,原理是当一个对象被销毁时,它的关联对象也会被销毁,那么只要在关联对象的的dealloc
方法里执行block就可以达到将 实例变量_object
置为nil的目的了
setter的实现
1 | - (void)setObject:(NSObject *)object |
category的实现
1 | -(void) cyl_runAtDealloc:(voidBlock) block{ |
CYLBlockExecutor的实现
1 | - (id)initWithBlock:(voidBlock)aBlock |
9.@property中有哪些属性关键字
- 原子性:atomic、nonatomic
- 读写权限:readwrite、readonly
- 内存管理语义:strong、weak、assign、copy、unsafe_unretained
- 方法名: getter=
, setter=
10.weak属性需要在dealloc中置为nil吗?
不需要,ARC会自动处理的
11.@synthesize和@dynamic分别有什么作用?
- @property有两个对应的词:@synthesize和@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@synthesize var = _var;
- @synthesize的语义是如果你没有实现getter和setter那么编译器会自动帮你实现这两个方法
- @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异常
这就涉及到消息的发送流程了:
- objc在向一个对象发送消息时,Runtime会根据对象的isa指针找到对象所属的类,然后从方法缓存里查找对应的方法
- 若上一步找不到对应的方法,则会从方法列表里查找,如果还是找不到就会到父类的方法列表里查找直到NSObject类
- 这时如果还是找不到,就会触发Method resolution,这时你有机会提供一个函数的实现,添加到类中,如果添加了实现,这时就会重新启动一次消息发送流程,Method resolution没有提供实现的函数的话,就会触发消息转发
- Fast Forwarding:如果目前对象实现了-forwardingForSelector方法,Runtime就会调用该方法,让目标对象提供一个可以响应该消息的对象,只要不返回nil或者self,就会重新触发消息发送
- Normal Forwarding:Fast Forwarding失败了的话,就是启动Normal Forwarding。首先Runtime会发送-methodForSignaturForSelector:消息获取函数的参数和返回值,然后创建一个NSInvocation对象并发送 -forwardInvocation:消息给目标对象
如果以上流程走完,仍没有响应消息的函数,则会报unrecognized selector异常
19. 一个objc对象如何进行内存布局?(考虑有父类的情况)
- 父类的成员变量和自己的成员变量都存放在该对象的存错空间中
对象的内部有一个isa指针,指向他的类对象。类对象中存放着:
* 每个对象的方法列表 * 成员变量的列表 * 属性的列表 * 类对象的内部也有一个isa指针指向元类对象(meta class),元类对象中存放着类方法列表,类对象内部还有一个superclass指针指向父类对象
20.一个objc对象的isa的指针指向什么?有什么作用?
指向类对象,可以查找对象上的方法
21. 下面的代码输出什么
1 | @implementation Son:Father |
考察的知识点:self与super关键字
self是类的隐藏参数,指向当前调用方法的类的实例
super并不是指向父类的指针,它和self是指向同一个接收者,不同点在于:
* [self class] 编译后 objc_msgSend(obj, @selector(class));
* [super class] 编译后 objc_msgSendSuper(obj, @selector(class));
但是最后调用的都是NSObject的这个方法
1 | - (Class)class { |
所以最后输出都是 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 循环
- 通知Observer即将进入RunLoop
- 通知Observer即将处理Timer
- 通知Observer即将处理Source0
- 处理Source0
- 如果有Source1跳到9
- 通知Observer线程即将休眠
- 休眠等待被唤醒
- 通知Observer线程刚被唤醒
- 处理唤醒时受到的消息,跳回2
- 通知Observer即将退出RunLoop
苹果在主线程注册了两个Observer:
- 第一个Observer监听即将进入RunLoop事件:创建自动释放池AutoreleasePool
- 第二个Observer监听两个事件:
33. ARC通过什么方式帮助开发者管理内存?
编译期自动插入retain/release/autorelease 运行期