1.对于block,理解,mrc和arc下有什么区别,使用注意事项
- mrc 下栈上的 block,在 arc 中并且符合一定条件的情况下会自动被拷贝到堆上
- block 内部如果使用了 __block 修饰的局部变量,在 mrc 下 block 不会对这个变量产生强引用,在 arc 下 block 会对这个变量产生强引用。block 从栈上拷贝到堆上的时候,如果是在 mrc 下,那么 block 结构体中的 bref 结构部不会从栈上拷贝到堆上,所以 bref 不会持有变量;如果是在 arc 下,那么 bref 也会跟着一起拷贝到堆上,所以 bref 会强引用变量。
- arc 下,解决循环引用可以使用 __weak,__unsafe__unretained,或者使用__block 并且在块内将变量置 nil,当代码块内的代码执行完的时候,block 就不会再持有局部变量了。如果是在 mrc 下,可以使用 __unsafe__unretained、__block 解决循环引用的问题。
2.使用CADisplayLink、NSTimer有什么注意点?
- 当定时器添加到 runloop 中的时候,runloop 会持有定时器,定时器会持有 target,就会导致 target 不释放。
- block,在内部使用 weakSelf
- NSProxy,使用消息转发
- NSTimer 与 CADisplayLink 依赖与 RunLoop,如果 RunLoop 的任务过于繁重,可能导致 NSTimer 不准时
- 使用 GCD 的定时器
3. 介绍一下内存的几大区域
从低地址到高地址依次是:
- 代码段
- 数据段(常量段):字符串、全局变量和静态变量(未初始化的、已初始化的)
- 堆区:自己 alloc、calloc、malloc 出来的对象
- 栈区:函数的开销,比如局部变量。(栈区的地址是从高往低)
- 内核区
4. 对mrc和arc的理解。ARC都帮我们做了什么。讲一下你对 iOS 内存管理的理解。ARC 的本质(阿里二面)。ARC 都帮我们做了什么?
- iOS 是使用引用计数来管理 OC 对象内存的,当引用计数为 0 的时候对象的内存就会被释放。
- 在 mrc 下我们需要手动地管理引用计数。使用 retain 让对象的引用计数加一,使用 release 让对象的引用计数减一。
- 使用 arc 时,LLVM 编译器会自动在合适的时候往我们的代码里添加 release 或者 autorelease 和 retain 等,我们不需要再写 retain 或者 release了。运行时会在 dealloc 的时候把 weak 修饰的指针自动置 nil。
5. 引用计数是怎么存储的?
- 在 64bit 中,引用计数可以直接存储在 isa 里面,也可能存储在 SideTable 类中
struct SideTable {
sipinlock_t slock;
RefcountMap refcnts; //存放着对象引用计数的散列表,以对象的地址为key
weak_table_t weak_table; //散列表;key 是对象的地址,value 是所有弱引用了这个对象的指针的数组
}
复制代码
6. @property 的本质是什么?
- 自动帮我们生成成员变量、setter 与 getter 方法的声明和实现
7. property 的常用修饰词有哪些?
- 原子性:atomic/nonatomic
- 读写权限:readwrite/readonly
- 内存管理:strong、copy、assign、unsafe_unretained、weak
- 方法名:getter=<name> 、setter=<name>
8. 如果属性完全不加修饰词入weak,atomic,系统会怎么处理
- property 默认使用的是 atomic、readwrite、assign
9. weak 和 assign 的区别?
- assign 可以修饰基本类型和对象,weak 只能修饰对象
- 被 weak 修饰的指针会在对象被释放的时候自动置 nil
10. 对于strong,weak,atomic等等理解
- atomic 是属性的原子性,给系统自动生成的 setter、getter 方法加锁
- 被 strong 修饰的属性在指向对象的时候,对象的引用计数加一
- 被 weak 修饰的属性在指向对象的时候,不会持有对象,对象销毁的时候属性自动置nil
11. 使用 atomic 一定是线程安全的吗?
- 使用 atomic 就是给系统自动生成的 setter、getter 加锁,它可以保证数据的完整性,但是并不能保证数据是准确的。比如 A 线程的写操作结束后,B 线程又开始写,这时候 A 线程读操作就会等到 B 线程的写操作结束后再做,那么最后 A 线程的读操作读到的是 B 线程写进去的数据。
- atomic 也不能保证整个对象是线程安全的。比如 A 线程在进行读操作之前,对象被 C 线程 release 了,那么就会崩溃。再比如,一个可变的数组在多个线程下使用
addObjectAtIndex:
,这就是不安全的,这也和 setter 与 getter 没什么关系。
12. __block
vs __weak
?
- 使用
__block
的目的是在代码块中修改局部变量。使用__block
后,变量会被包装成 bref 结构体被捕获进 Block 结构体,当 Blcok 拷贝到堆上的时候,在 arc 中 bref 也会被拷贝到堆上,在 mrc 中 bref 不会被拷贝到堆上,所以在 arc 中使用__block
还要注意解决循环引用的问题,而在 mac 中使用__block
不会引起循环引用的问题。 - 使用
__weak
的目的是在 arc 中解决 Block 的循环引用的问题。Block 捕获局部变量时,如果变量是被__weak
修饰的,捕获进 Block 结构体的时候,也会被 weak 修饰,当 Block 拷贝到堆上的时候,捕获进来的这个变量引用计数不会增加。
13. weak 属性需要在 dealloc 中置 nil 吗?
- 不需要,weak 修饰的变量会在对象被释放后自动置 nil
14. IBOutlet 连出来的属兔属性为什么可以被设置成 weak?
- xib 有一个数组持有着这个对象
15. 怎么使用 copy 关键字?
- copy 的目的
- 产生一个副本对象,跟源对象互不影响
- 修改了源对象,不会影响副本对象
- 修改了副本对象,不会影响源对象
- 常用 copy 的对象: NSString、NSArray、NSDictionary
- iOS 提供了两个拷贝方法:copy、mutableCopy
- copy:产生不可变副本
- mutableCopy:产生可变副本
NSString *str1 = @"123"; //这是一个在常量区的不可变字符串
NSString *str2 = [str1 copy];//还是这个字符串。不采用taggedPointer技术
NSString *str3 = [str1 mutableCopy];//把它拷贝成可变的字符串,并且是拷贝到堆上去了。
复制代码
NSMutableString *str1 = [NSMutableString stringWithFormat:@"123"];//在堆上的一个可变字符串
NSString *str2 = [str1 copy];//拷贝成不可变字符串。会采用taggedPointer技术,比较短的话就是在常量区的taggedPointer,长一点的话会变成在堆上的字符串。
NSString * str3 - [str1 mutableCopy];//拷贝成一个新的、可变的字符串。在堆上的,所以它会是不一样的。
复制代码
16. 深复制和浅复制指的是什么?对于深拷贝和浅拷贝的理解
- 深拷贝
- 内容拷贝,产生了新的对象
- 比如拷贝堆上的对象
- 浅拷贝
- 指针拷贝,没用产生新的对象
- 比如 copy 常量区的字符串。
NSString *str = [@"123" copy];
- mutableCopy 一定是深拷贝。 copy 不一定是浅拷贝。
17. 如何重写带 copy 关键字的 setter?
- (void)setKey:(id)key
{
if (key != _key) {
//[_key release]; //MRC
_key = [key copy];
}
}
复制代码
18. 如何让自己的的类用copy修饰符?
- 自定义的类要想使用 copy 方法,必须得先遵守 NSCopying 协议
- 实现
- (id)copyWithZone:(NSZone *)zone
方法
- (id)copyWithZone:(NSZone *)zone
{
Person *person = [[Person allocWithZone:zone] init];
person.age = self.age;
person.weight = self.weight;
return person;
}
复制代码
19. 这种写法会出现什么问题:@property(copy)NSMutableArray *array;?
- 使用 self.array 对 _array 进行赋值之后,会 copy 一个不可变数组赋值给它,当调用 NSMutableArray 中的方法时,会报错。
20. @property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
- 因为它们有对应子类,根据里氏替换原则,父类指针可以指向子类对象,所以可以给他们赋值一个可变的对象,通过改变这个对象就改了属性的值。使用 copy 是为了保证属性指向的对象的值是不能被修改的。
21. Objective-C 中的 copy 方法(阿里)
- 拷贝的目的是产生一个副本,它们修改的时候互不影响。
- NSString、NSArray、NSDictionary 等这些类有对应的可变类型,所以它们的拷贝有 copy 和 mutableCopy
- copy 一定产生一个不可变对象
- mutableCopy 一定产生一个不可变对象
- 不可变对象使用 copy ,copy 出来的时候也是一个不可变的,拷贝的目的是拷贝之后它们修改的时候互不影响,既然它们都不能修改,那就没有产生新对象的必要了。所以,只会拷贝一个新的指针指向这个不可变对象。这是浅拷贝。
- 拷贝的时候产生了新的对象,这就是深拷贝
- 自定义类要想使用 copy 方法,需要先遵守 NSCopying 协议,实现
copyWithZone:(NSZone *)zone
方法。
22. weak 的实现原理是什么?
- 对象的弱引用关系会存入 SideTable 里面的弱引用散列表中,这个列表中的 key 是对象的地址,value 是所有弱引用了这个对象的的指针的数组。当对象调用 release 的时候,会判断引用计数是否为零,如果为零的话就会调用 dealloc 方法,然后就会通过对象的地址在 SideTable 的弱引用表中找到这个数组,遍历这个数组对数组里边的指针清空置 nil。
23. 谈谈对自动释放池的理解。autoreleasepool 的使用场景和原理。Autoreleasepool 什么时候释放,在什么场景下使用?(阿里)
- AutoreleasePool 本质上是一个结构体,里面有 push 和 pop 函数,这两个函数时对 AutoreleasePoolPage 的封装,所以 AutoreleasePool 没有单独的结构,是由若干个 AutoreleasePoolPage 以双链表的形式组成的,自动释放机制的核心在于 AutoreleasePoolPage 这个类。
- 每一个 AutoreleasePoolPage 对象占 4096 个字节。出了自己结构体所占的内存,都用来存放的是 autorelease 对象的地址。
- 当创建 @autoreleasepool{} 时,会调用 push,会往里面存入一个哨兵对象。
- 在 autoreleasepool{} 中创建对象的时候就会向 next 指向的地址中写入新创建的这个对象的地址。
- 当 @autoreleasepool{} 结束的时候会调用 pop 的,这时从最新写入的地址开始,向前找,依次向它们指向的对象发送 release 消息,直到找到前一个哨兵对象。(可能跨了好几页)
- 使用场景:当有大量临时变量的时候,避免内存峰值过高
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
}
}
复制代码
24. 自动释放池在mrc和arc区别
25. 多层自动释放池嵌套的对象在哪一层释放
- 最里面的一层先 release。因为它先出大括号,先调用 AutoreleasePool 的 pop 方法。
25. 方法里有局部对象,出了方法后会立即释放吗?
- 在 arc 下,如果编译器帮我们添加的代码是在出方法前
[obj release]
,那么就是出了方法会立即释放;如果编译器帮我们添加的代码是在创建的时候添加 autorelease,那么就不是立即 release 的。它会在 runloop 休眠前进行 release。
26. 你在项目中是怎么优化内存的?
- 使用 instruments 的 LEAKS 或者 第三方工具比如 LeakFinder 来检测内存泄漏,然后进行处理