iOS面试题分享(一)

245 阅读6分钟

话不多少,直接上干货。

自我介绍

开场固定保留节目,我就简单说了一下我的工作经历和项目经历,面试官应该是根据我的介绍有一个初步的定位,然后再做相关问题的询问。

接下来就是技术面试。

技术面试

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内的实现会变成一个函数,即

image.png 这个代码和没有strong修饰是一样的,唯一的区别是在__ViewController__viewDidLoad_block_func_0这个函数里,

image.png

所以说,在函数实现里对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 :

  1. pthread_rwlock_t
  2. 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和内存问题的考察还是比较看重的,所以大家可以在准备的时候稍微注意一些。