话不多少,直接上干货。
自我介绍
开场固定保留节目,我就简单说了一下我的工作经历和项目经历,面试官应该是根据我的介绍有一个初步的定位,然后再做相关问题的询问。
接下来就是技术面试。
技术面试
Q :说一下属性修饰符。
A : 属性修饰符分有几类,
- 内存管理相关
- strong 强引用,引用计数会加一
- weak 弱引用,引用计数不会加一,释放后会置为nil
- assgin 基本数据类型
- copy 引用计数会加一
- 原子性相关
- atomic 原子性的
- nonatomic 非原子性的
- 读写相关
- readwrite
- readonly
- Setter,getter相关
- 重写set get方法,简单略过
第一个问题只是一个抛砖引玉,接着下面的,
Q :weak的应用场景有哪些
A :weak的特点是两个:不会形成强引用,释放会置为nil,所以,一般用来解决循环引用的问题,比如,
- 修饰Delegate
- 修饰SB的控件
- block循环引用问题
- NSTimer 用第三方的Object来解决循环引用问题时,使用weak
Q :Weak原理
A :weak相关的函数主要有三个,
- initWeak(),主要就是reture了StoreWeak(),
- StoreWeak() 中,简单来说就是如果weak指针有指向一个弱引用,则就移除掉,如果没有就添加到新的弱引用表中,其实就是在操作sidetable中的weak_table。其中关键的数据结构关系sidetable---weak_table_t ----weak_entrys ---- weak_entry_t
- clearDeallocating,通过key即对象地址,找到对应的value即weak指针数组,然后遍历,置为nil。
Q :Code
简单代码示例
//有一个person类,该类有一个strong修饰的属性name
@interface Person()
@proprety(nonatomic,strong)NSString *name;
@end
在ViewController中
-(void)viewDidLoad{
[super viewDidLoad];
Person *person1 = [Person new];
Person *person2 = [Person new];
person1.name = @"abc";
perosn2.name = person1.name;
person1.name = @"edf";
NSlog(@"person2.name = %@",person2.name);
这个题其实很简单,可能是问strong修饰的name会不会变,但是我觉得可能出的题和面试官想问的有偏差,我也没太搞明问什么,肯定的是person1和person2两份独立的内存,person1的name改变不会影响到person2,所以 person.name还是”abc“
Q :Code
-(void)viewDidLoad{
[super viewDidLoad];
self.block =^(){
[self func];
}
}
-(void)func{
//....dosomething
}
对于以上代码,我相信会iOS的都知道有循环引用,并且weak解决。代码如下,
__weak typeof(self) weakSelf = self;
self.block =^(){
[weakSelf func];
}
接着,如果该block不是立即执行的,可能在某些特定时候执行,需要怎么修改,我相信这个大家也应该都遇到过了,代码如下,
__weak typeof(self) weakSelf = self;
self.block =^(){
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf func];
}
然后的问题是,strong修饰后会不会出现循环引用,为什么?
A :其实这个问题的本质还是对于blcok变量捕获的考察,首先,答案是不会造成循环引用,原因很简单,block捕获的变量还是weak的,通过clang编译源码到cpp文件可知,编译后的block内的实现会变成一个函数,即
这个代码和没有strong修饰是一样的,唯一的区别是在__ViewController__viewDidLoad_block_func_0这个函数里,
所以说,在函数实现里对self进行strong修饰不会造成循环引用,不会造成block对于self的强引用。
Q :atomic修饰的字符串能保证线程安全吗
A :答案是肯定的,atomic不能保证线程安全。
首先,atomic的作用是属性进行setter、getter操作时,会进行加锁操作,这个锁是从全局的一个锁数组中取到的,加的是互斥锁(os_unfair_lock)。通过源码可知,get的时候是给objc_retain的操作加锁,set的时候赋值操作加锁,也就是说,保证原子性部分是有限的,正常在多线程访问数据的时候,其实都是复合操作,所以并不能保证线程安全。
比如两个线程,A在写数据,当B来写的时候就需要等待,A写完了以后B就立马写,A再读取的时候其实已经读到了B写的数据,但是A需要的是自己写的数据,就会出问题了。
Q :读写锁 A :
- pthread_rwlock_t
- dispatch_barrier_async(写),dispatch_async(读)
Q :名词解释: 同步 异步 串行 并发
A :
-
同步(sync)、异步(async)
同步表示在当前线程执行任务,异步表示开启新线程执行任务。
-
并发、串行
并发表示任务的执行方式为多个同时执行,当然了同时并不是并行的同时,而是CPU快速切换。 串行表示任务依次执行,当前任务执行完毕再执行后续任务。
在GCD中以上组合的情况,
标题 | 串行队列 | 并发队列 | 主队列 |
---|---|---|---|
同步(sync) | 不开启新线程、串行 | 不开启新线程、串行 | 不开启新线程(主线程)、串行 |
异步 (async) | 开启新线程、串行 | 开启新线程、并行 | 不开启新线程(主线程)、串行 |
也就是说,同步(sync)情况下,串行队列和并发队列都是串行执行任务,如果当前线程中添加了同步任务到串行队列会造成死锁。
Q :内存泄漏的场景有哪些
A :内存泄露是指的开辟的堆空间无法释放或者没有释放,内存开销变大,程序卡顿,甚至crash。一般情况下,引起内存泄露的原因是循环引用,比如,
- block
- NStimer
- delegate
- CoreFoundation中create copy创建的对象
- 循环创建大量临时对象。
详细分析NSTimer的内存泄露,
case1
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector: @selector**(tet:) userInfo:@"" repeats:YES];
循环 : timer ---- target ---- self ---- timer
执行[_timer invalidate],可以打破这个循环
case2
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector: @selector**(tet:) userInfo:@"" repeats:YES];
这样写也会循环引用,因为执行这句代码时会返回一个timer,只是没有用变量接收而已。
case3
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(tet) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
这个地方没有循环,但是有这样一个持有关系,runloop ---- timer ---- self,也就是说self被timer持有,timer在runloop里没有释放,所以self也不会释放。
Q :UIButton上添加一个UIView,点击的时候UIButton的target会不会执行
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustome];
[btn addTarget:self action:@selector(test) forControlEvents:UIControlEventTouchUpInside];
UIView *view = [UIView new];
[button addSubView:view];
A :点击view后button不会有反应,原因是事件响应的时候view不会响应此事件,传递到btn,btn如果实现了touch(-(void)touchesBegan:withEvent:)回调会执行,但是target-action不会执行,最终此事件没有view处理就会丢弃掉。
总结,总的来说面试体验还是挺好的,面试的内容也比较基础,如果平时开发中有留心细节的话回答起来会比较轻松一些,再就是 大厂对于GCD和内存问题的考察还是比较看重的,所以大家可以在准备的时候稍微注意一些。