美文网首页IOS面试专题
iOS 完整的面试准备(大厂面试)

iOS 完整的面试准备(大厂面试)

作者: Sonoface | 来源:发表于2020-09-09 16:03 被阅读0次

首先找到或是向HR要到岗位的要求。一般格式如下:
(免责声明,以下内容纯属本人YY啊,如有雷同,绝不可能啊)

职位描述
1、根据业务需求,负责iOS客户端的设计、开发和维护。
2、结合项目完成技术方案研究、实施和优化;
3、主动推进任务执行,有效处理团队沟通。
4、...
职位要求
1、 XX及以上学历,XXX或相关专业,至少X年iOS开发经验;
2、 精通OC,具备扎实的Objective-C语言基础,了解常用设计模式,组件化开发技术;
3、 熟悉swift,熟练掌握iOS平台各类API,熟悉iOS内存管理机制,对iOS SDK有深入理解;
4、 熟练使用苹果平台相关开发及调试工具,具有一定的网络编程基础。
5、 熟悉HTTP/HTTPS、TCP/UDP、RTP/RTCP、H.323与SIP等网络通信协议者优先。
6、 音视频、视频会议相关开发经验者优先;
7、...

一般此类格式,其他部分基本可以无视。
岗位本身主要分两部分,职位描述&职位要求。

职位描述

其中职位描述主要是对方对岗位工作内容的简单描述或者是之前人员的主要工作内容。在本例里基本也可以看出,岗位本身就是一个基本的Coder工作。特别一点是里面提到主动推进任务执行,看来是需要招尽可能有活力积极的同学。以这个诉求为基点。可能年轻一点的同学、积极一点的同学更容易获得机会。所以在面试时,尽量保持自己积极的心态,面试前做好心里暗示可能会更好。
在职位描述中,对自己“面试”本身的有效信息,会相对较少。对岗位本身的有效信息内容会更多一些,注意辨别排坑就好。

职位要求

职位要求是我们做面试准备的重中之重,这里我们来逐条分析。

1.“XX及以上学历,XXX或相关专业,至少X年iOS开发经验”

属于对方公司硬性要求,会找你,说明已经达标了。这里无视。

2.“精通OC,具备扎实的Objective-C语言基础,了解常用设计模式,组件化开发技术”

这里业务要求就开始了。首先是精通OC,具备扎实的OC语言基础。这里需要准备的内容范围一般是大厂一面的问题范围。
我们这里做一些准备,偏向于OC本身的知识。
(为方便大家阅读,问题会加粗。答案部分会用代码块,补充部分会以注视形式显示)

修饰符相关

@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?

属性可以拥有的特质分为四类:
原子性---nonatomic特质
//在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。
//如果属性具备nonatomic特质,则不使用同步锁。
//请注意,尽管没有名为“atomic”的特质(如果某属性不具备nonatomic特质,那它就是“原子的” ( atomic) ),
//但是仍然可以在属性特质中写明这一点,编译器不会报错。若是自己定义存取方法,那么就应该遵从与属性特质相符的原子性。
读/写权限---readwrite(读写)、readooly (只读)
内存管理语义---assign、strong、 weak、unsafe_unretained、copy
方法名---getter=、setter=
getter=的样式:
//  @property (nonatomic, getter=isOn) BOOL on;复制代码
//( setter=这种不常用,也不推荐使用。故不在这里给出写法。)
不常用的:nonnull,null_resettable,nullable

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

两个问题:
1、添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为copy就是复制一个不可变NSArray的对象;
2、使用了atomic属性会严重影响性能。

//第2条原因,如下:
//该属性使用了同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,
//这会带来性能问题,通过声明nonatomic可以节省这些虽然很小但是不必要额外开销。

//在iOS开发中,你会发现,几乎所有属性都声明为nonatomic。
//一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全” ( thread safety),
//若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。

//因此,开发iOS程序时一般都会使用nonatomic属性。但是在开发Mac OS X程序时,使用 atomic属性通常都不会有性能瓶颈。
(我多嘴一句,但是该用的时候还是要用!!)

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

1)@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,
//那么默认的就是@syntheszie var = _var;
2)@synthesize的语义是如果你没有手动实现setter方法和getter方法,
//那么编译器会自动为你加上这两个方法。
3)@dynamic告诉编译器:属性的setter与getter方法由用户自己实现,不自动生成。
//(当然对于readonly的属性只需提供getter即可)。
//假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,
//编译的时候没问题,但是当程序运行到instance.var = someVar,
//由于缺setter方法会导致程序崩溃;或者当运行到 someVar = var时,
//由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

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

1)因为父类指针可以指向子类对象,使用copy的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
2)如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.
//copy此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。
// 当属性类型为NSString时,经常用此特质来保护其封装性,
//因为传递给设置方法的新值有可能指向一个NSMutableString类的实例。
//这个类是NSString的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,
//那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。
//所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。
//只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

为了理解这种做法,首先要知道,对非集合类对象的copy操作:
在非集合类对象中:对immutable对象进行copy操作,是指针复制,mutableCopy操作时内容复制;对mutable对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:

[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //深复制
[mutableObject copy] //深复制
[mutableObject mutableCopy] //深复制

比如以下代码:

NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy];

查看内存,会发现 string、stringCopy 内存地址都不一样,说明此时都是做内容拷贝、深拷贝。即使你进行如下操作:

[string appendString:@"origion!"]

stringCopy的值也不会因此改变,但是如果不使用copy,stringCopy的值就会被改变。 集合类对象以此类推。 所以,
用@property声明 NSString、NSArray、NSDictionary 经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

感谢:《招聘一个靠谱的iOS》 http://www.cocoachina.com/articles/12872
这篇文章挺好的,也挺火的,有时间可以都看下。

存储篇

OC中数据持久存储的方式?

1.plist          //存文件,故要注意安全性问题
2.NSUserDefault  //系统自带,本质也是存文件,同上注意安全性问题
3.对象归档        //可放入Document目录,通过系统自带的NSKeyedArchiver,压缩成二进制文件
4.SQLite        //移动端存储解决方案,微型数据库。进一步封装有CoreData、FMDB、LKDB

沙盒相关?

Documents    //持久化目录,itunes会同步保存
Library/Caches    //持久化目录
tmp    //临时目录,应用不运行,会清空。

内存管理

OC中内存管理的几种方式、区别?

1.MRC  //手动内存管理
2.ARC  //自动内存管理
3.autoreleasepool  //自动释放池

引用计数器:
在MRC时代,系统判定一个对象是否销毁是根据这个对象的引用计数器来判断的。
//1.每个对象被创建时引用计数都为1
//2.每当对象被其他指针引用时,需要手动使用[obj retain];让该对象引用计数+1。
//3.当指针变量不在使用这个对象的时候,需要手动释放release这个对象。 让其的引用计数-1.
//4.当一个对象的引用计数为0的时候,系统就会销毁这个对象。
ARC自动内存管理
//WWDC2011和iOS5所引入自动管理机制——自动引用计数(ARC),
//它是编译器的一种特性。ARC管理机制与MRC手动机制差不多,
//只是不再需要手动调用retain、release、autorelease;
//当你使用ARC时,编译器会在在适当位置插入release和autorelease;
//ARC时代引入了strong强引用来带代替retain,引入了weak弱引用。
autoreleasepool自动释放池 
//当自动释放池被销毁时,会给池里所有对象发release。
//注意:
//1.自动释放池只是在释放的时候给池中的对方发送一个release消息
//不一定保证对象的销毁。
//2.自动释放池会在同一时间释放
//3.自动释放池不会改变对象的引用计数。
//相关可以在下方链接中查看

感谢:@树下敲代码的超人 https://www.jianshu.com/p/5eac83471b23

多线程相关

OC中多线程的实现使用

1.NSThread  
最简易的多线程功能。由苹果封装的,生命周期需要自己管理,一般跟随当前所在线程/对象
2.GCD
C的Api,使用了大量block,灵活方便。
3.NSOperation
使用起来更加复杂,功能更加强大,因为面向对象,所以可以去继承完成更复杂的业务场景。

NSOperation本身是一个抽象类,使用时需要用其子类。
使用其子类的方法有:
1. NSInvocationOperation
2. NSBlockOperation
3.其他自定义的NSOperation子类

关于多线程,如果你很熟悉这里可以跳过。不熟悉的小伙伴,至少也要有以下相关概念。
任务&队列
任务,包含同步、异步两种。任务本身会开辟一条线程资源供程序调用。
同步(sync)会阻塞当前线程。
异步(async)不会阻塞当前线程。

队列,包含串行队列、并行队列。
串行队列,特点是先进先出。(这个跟数据结构有关)
并行队列,特点是一起执行。
只有在并行+异步,才会是异步执行。

其他
相较于GCD,NSOpertion可以很简单的控制并发数,可以很简单的在不同的Queue中建立依赖。
关于依赖:
A->B->C,任务可以相互形成执行顺序。
要注意,防止出现循环♻️依赖。可以使用跨队列依赖。

线程同步&锁
问这个的概率不高,不过多线程相关有可能涉及。
首先描述下线程同步,线程不同步是一种问题,要保证线程同步。线程同步即防止多个线程抢夺同一个资源,保证同一资源在正常时序中被访问。

主要的处理办法
1.互斥锁。
(不过我感觉这种方法也不好啊,除非做一个队列,保证后来被阻塞的线程继续或重新执行,还不如用atomic内部的自旋锁)
2.同步执行,把可能存在的多个线程,放到同一个串行队列中执行。
(其实就是针对特殊资源,开一个队列,所有操作都在该队列中执行)
3.文件的多读单写
下文中文件的多读单写方案有pthread_rwlock_t和dispatch_barrier_async两种方案。
感谢 https://www.jianshu.com/p/3741f6f6e820
这两种方法要注意在读的过程中,不要去执行写的方法。很容易出现这种情况,往往读的事件执行的时候需要做其他处理。这种方式就会有问题。虽然没试过,但是感觉应该保证读和写分开线程,或者是保证写操作的时候会回归到其他线程会好一点。

计时器的实现
OC中定时器有三种,分别是NSTimer、CADisplayLink、dispatch_source。
(发现自己居然完全不知道😭CADisplayLink,唉看来上次面试这个问题也没答好)

NSTimer
特性:
1.存在延迟
不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,
如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,
如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行。

2.必须加入Runloop
使用上面的创建方式,会自动把timer加入MainRunloop的NSDefaultRunLoopMode中。
如果使用以下方式创建定时器,就必须手动加入Runloop:
CADisplayLink

当把CADisplayLink对象add到runloop中后,selector就能被周期性调用,类似于重复的NSTimer被启动了;
执行invalidate操作时,CADisplayLink对象就会从runloop中移除,selector调用也随即停止,
类似于NSTimer的invalidate方法。

特性
屏幕刷新时调用
CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。
CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,
runloop就会向CADisplayLink指定的target发送一次指定的selector消息,
CADisplayLink类对应的selector就会被调用一次。所以通常情况下,按照iOS设备屏幕的刷新率60次/秒
(也就是说这个计时器是受程序性能影响的,感觉也不太好啊,尤其是iphone低电量模式时,感觉还是很容易出问题的)

延迟
iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。
但如果调用的方法比较耗时,超过了屏幕刷新周期,就会导致跳过若干次回调调用机会。
如果CPU过于繁忙,无法保证屏幕60次/秒的刷新率,就会导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。

使用场景
从原理上可以看出,CADisplayLink适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。
(哇原来如此,但是感觉在这个里面做重绘,性能开销也很大啊)

重要属性
1.frameInterval
NSInteger类型的值,用来设置间隔多少帧调用一次selector方法,默认值是1,即每帧都调用一次。
2.duration
readOnly的CFTimeInterval值,表示两次屏幕刷新之间的时间间隔。需要注意的是,
该属性在target的selector被首次调用以后才会被赋值。
selector的调用间隔时间计算方式是:调用间隔时间 = duration × frameInterval。
dispatch_source

特性
默认是重复执行的,可以在事件响应回调中通过dispatch_source_cancel方法来设置为只执行一次,如下代码:
dispatch_source_set_event_handler(_timer, ^{
    //执行事件
    dispatch_source_cancel(_timer);
});

* start
计时器起始时间,可以通过`dispatch_time`创建,如果使用`DISPATCH_TIME_NOW`,则创建后立即执行
* interval
计时器间隔时间,可以通过`timeInterval * NSEC_PER_SEC`来设置,其中,`timeInterval`为对应的秒数
* leeway
这个参数告诉系统我们需要计时器触发的精准程度。
//所有的计时器都不会保证100%精准,这个参数用来告诉系统你希望系统保证精准的努力程度。如果你希望一个计时器每五秒触发一次,
//并且越准越好,那么你传递0为参数。
//另外,如果是一个周期性任务,比如检查email,那么你会希望每十分钟检查一次,但是不用那么精准。所以你可以传入60,
//告诉系统60秒的误差是可接受的。这样有什么意义呢?简单来说,就是降低资源消耗。如果系统可以让cpu休息足够长的时间,
//并在每次醒来的时候执行一个任务集合,而不是不断的醒来睡去以执行任务,那么系统会更高效。
//如果传入一个比较大的leeway给你的计时器,意味着你允许系统拖延你的计时器来将计时器任务与其他任务联合起来一起执行。
优点:
 * 时间准确
 * 可以使用子线程,解决定时间跑在主线程上卡UI问题
注意事项:
 * 需要将dispatch_source_t timer设置为成员变量,不然会立即释放

感谢:@HK_Hank https://www.jianshu.com/p/53c07eb57223

Runtime

这里又是一个巨大的话题,从当年的黑魔法到如今的面试基础题。
https://www.jianshu.com/p/2d54cd4854b4
https://zhuanlan.zhihu.com/p/111054784 这篇强烈推荐

首先至少知道这两张图的相关知识


6082051-89c86615a0f9f2ef.png 6082051-434d82bcea61a394.png

1.动态方法解析
Objective-C运行时会调用 +resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程。
如果resolve方法返回 NO ,运行时就会移到下一步:forwardingTargetForSelector。
2.备用接收者
如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。
3.完整消息转发
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

感谢@Jaren_lei https://www.jianshu.com/p/45db86af7b60

说说OC的消息机制?

OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息。(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发

可以看看这篇 https://www.jianshu.com/p/45db86af7b60

runtime具体应用?

 * 利用关联对象(AssociatedObject)给分类添加属性
 * 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
 * 交换方法实现
 * 利用消息转发机制解决方法找不到的异常问题

简单算法&排序相关

其他问题

谈谈id和instancetype的异同?

1、相同点
都可以作为方法的返回类型
2、不同点
(1)instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;
(2)instancetype只能作为返回值,不能像id那样作为参数。

imageName与imageWithContentsOfFile的区别?
(当年少不更事,面试被这个小问题,问的尴尬的一批,导致心态大崩,遂记录在案)

imageName的内部会做大约三件事:
1.在Cache中寻找图片。找到返回。找不到进下一步。
2.在bundle文档中寻找,找到返回,上报。
3.找到后在Cache中缓存。
imageWithContentsOfFile则是直接数据加载。不会缓存,销毁后直接释放。
//所以imageName,适合使用在容量小,且重复使用的情况。代码也要少。
//imageWithContentsOfFile适合使用容量大,重复情况少的时候。
///项目中曾经出现过用户传了婚纱照九宫格图片,一张5mb,然后安卓没崩,iOS崩了。
//就很尴尬,像这种情况就是没有做个极限测试,没有对特殊情况做处理。

关于分类(category)和类扩展(匿名分类)(extension)
因为国内有时会叫类扩展、有时也会叫匿名分类,所以在面试的时候直接用extension的英文交流会更好,省的和面试官习惯不同。类似的在关于category加入属性的问题,需要用到runtime中objc_setAssociatedObject,在和面试官交流的时候,如果方法名读不准确,其实直接说“关联对象”这样的专有名称反而会比较好。所以在面试时,合理的使用中英文交流,也是程序面可以把握的一个良好风貌。
言归正传,回到这个问题。

category
1.可以修改(重写)和添加方法。
2.在语法上不能添加成员变量。
extension
1.可以声明成员变量、也可以声明方法。
2.类扩展声明可以在.h中,也可以在.m声明。
(不过这个声明技巧基本没什么应用,欢迎大家补充)

聊聊循环引用

可能会出现循环引用的场景有很多,一般项目中主要有以下几种场景。
1.父类子类
//父类通常会strong子类,此时子类如果再strong父类,则相互强引用,触发循环引用
2.NSTimer
//首先,NSTimer 的 target 对传入的参数都是强引用(即使是 weak 对象)。通常需要自己封装
3.block
//block在copy时都会对block内部用到的对象进行强引用的。
self.testObject.testCircleBlock = ^{
   [self doSomething];
};
self将block作为自己的属性变量,而在block的方法体里面又引用了 self 本身,此时就很简单的形成了一个循环引用。

应该将 self 改为弱引用

__weak typeof(self) weakSelf = self;
 self.testObject.testCircleBlock = ^{
      __strong typeof (weakSelf) strongSelf = weakSelf;
      [strongSelf doSomething];
};

在 ARC 中,在被拷贝的 block 中无论是直接引用 self 还是通过引用 self 的成员变量间接引用 self,该 block 都会 retain self。

快速定义宏
// weak obj
#define WEAK_OBJ(type)  __weak typeof(type) weak##type = type;
// strong obj
#define STRONG_OBJ(type)  __strong typeof(type) str##type = weak##type;
4.delegate
@property (nonatomic, weak) id <TestDelegate> delegate;
如果将 weak 改为 strong,则会造成循环引用
// 假如是 strong 的情况
// bVc.delegate ===> AViewController (也就是 A 的引用计数 + 1)
// AViewController 本身又是引用了 <BViewControllerDelegate> ===> delegate 引用计数 + 1
// 导致: AViewController <======> Delegate ,也就循环引用啦
深入问题:既然用weak,为什么不用assgin,且过去MRC都是用assgin?
对于assign:也有weak的功效。assign是指针赋值,不对引用计数操作,使用之后如果没有置为nil,可能就会产生野指针;
而weak一旦不进行使用后,永远不会使用了,就不会产生野指针。
//感谢来自 https://www.jianshu.com/p/9bcdf84eae3b

感谢 @jrw7878 https://www.jianshu.com/p/e3302eac8536

其实还有一些特殊或奇葩情况,我曾经在项目中遇到过。贴这儿大家可以了解了解。
1.懒加载的滥用或是不合理的懒加载
//懒加载的使用确保了属性对象的创建,有时候会有这样一个场景,
//某类释放时调用X.xxview = nil 为保证释放,然后xxview本身又是懒加载的。
//正常情况下,确实会如所愿的释放,但是如果xxview已经nil,则会被懒加载创建后再释放。
2.需要反复创建与释放的单例,或是中途释放的单例。
// 单例的使用往往是跟随整个app生命周期,不进行释放。
//当然也有只是需要利用其单一实例的功能。这种情况下的单例需要提前释放。
//而单例的提前释放是不安全的,释放是不一定成功的。

聊聊delegate、block、notification、kvo

其实异同很多,这里简单写两句。欢迎大家补充。
block
1.代码更加集中,方便移植,一定程度上降低了耦合性。
2.使用更加轻便、简单,不需要提前的实现与声明。
3.block不够安全,更容易出现循环引用问题。
4.使用的内存开销大,效率略低。
delegate
1.声明与实现分开,连贯性不如block。使用起来相对麻烦,delegate更加适合反复调用的情况。
2.效率更高,因为底层是以指针的方式实现。
notification
1.notification与上两个相比,notification是一对多的关系,并且是全局的。notification和kvo一样一定要注意注销。否则会crash。

关于如何使用
//如果你从其他语言转到 Objective-C 或者 Swift ,相信 Delegation 肯定让你觉得更加亲切,
//那么在初级阶段请使用好这个语法糖,多用,多去理解;
//如果你用着 AFNetworking 看着其他老前辈的说法用 Block 觉得效率很高很开心,
//那就开心的用,直到你被循环引用烦到了为止;
//然后,在你代码写多了之后,你可以开始尝试接触其他回调方式,
//去感受这些回调方式的不同。关键在于对于回调流程的理解。
//你要知道你的回调是一个什么性质的回调,如果这个回调是一个不定期触发,
//或者会多次触发的,那么 Delegation 应该更适合;如果这个回调是一个一次性的,
//并且和调用方法是单线性关系的,那么 Block 应该更适合。
//在不同的执行线(不是线程),不同的执行次数、执行数量上的区别,
//是鉴别使用哪一种回调的最好判断方法。

//对于 Block 来说,他的执行线应该是和调用方法、回调方法连续在一起的;
//对于 Delegation 和 他的执行线可以是连续的,
//也可以是调用方法和回调方法之间有很长的间隔,或者说回调方法在执行线上会多次出现。

感谢:https://blog.csdn.net/huangshanfeng/article/details/82106580
https://www.jianshu.com/p/69f30fa80025 这里后半部分提到的如何使用

kvo的底层实现

KVO的实现依赖于 Objective-C 强大的 Runtime,当观察某对象 A 时,
KVO 机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性 keyPath 的 setter 方法。
setter 方法随后负责通知观察对象属性的改变状况。

消息转发(热更新)解决Bug(JSPatch)
关于消息转发,消息转发分为三级,我们可以在每级实现替换功能,实现消息转发,从而不会造成崩溃。JSPatch不仅能够实现消息转发,还可以实现方法添加、替换能一系列功能

多代理模式的理解与大致实现
基本逻辑数组+代理模式。需要分发事件的时候,通过数组遍历分发。
这里涉及到另一个问题,就是数组内是强引用。而deleagte为了防止循环引用是使用weak的,所以这里光用到数组+代理模式是不够的,而是NSHashTable+代理模式才行。即使用weak数组。
NSHashTable相关可以看这个 https://www.jianshu.com/p/de71385930ba

Objective-C 如何实现多重继承?
Object-c的类没有多继承,只支持单继承,如果要实现多继承的话,可使用如下几种方式间接实现

1.通过组合实现//A和B组合,作为C类的组件
2.通过协议实现//C类实现A和B类的协议方法
3.消息转发实现//forwardInvocation:方法

专项问题

聊一聊核心动画
感觉面试问到这一块的很少,但这一块其实很重要,涉及到layer和view的关系,离屏渲染相关的。和性能也有很大关系,CA动画本身其实倒没那么重要了。实际情况中,问得挺少的。
聊一个你比较熟悉的第三方框架
问AFNetWorking的概率挺高的,建议搞搞清楚。
聊聊组件化
这个问题和公司规模有关系,相对来说在大厂用的会多。因为很多同质化业务模块,可以交叉使用。开发一个SDK,多个项目受用。尤其是类似登录模块这种,每个项目都要用的、很重复的模块。不过小公司或者单一自研产品的公司又基本完全用不到组件化。所以还是要根据情况去准备,这边正好岗位要求中提及,总体来说里面用到的SEL&IMP相关的用法、runtime消息转发还是要掌握的(我感觉我一直不太懂),至于组件化实现其实每个公司都有每个公司的方式,并没有什么标准答案。下文会补充。

这边篇幅弄得太长了

继续对方岗位要求的第二点,了解常用设计模式,组件化开发技术

常用设计模式

1.工厂模式
工厂模式---Factory Method


1346720-be0cfaca492b5375.png

把工厂变成新建类的唯一入口,其他地方不需要知道某个class具体怎么做的,就可以建立需要的类,工厂于是就可以把内部和外部实现隔绝起来的目的
个人理解是把同质化的类、相似的类。抽象出一个抽象虚类,然后用一个工厂来统一构造。
2.MVVM

基于MVC
Model负责存储、定义、操作数据;
View用来展示书给用户,和用户进行操作交互;
Controller是Model和View的协调者,Controller把Model中的数据拿过来给View用。Controller可以直接与Model和View进行通信,而View不能和Controller直接通信。View与Controller通信需要利用代理协议的方式,当有数据更新时,Model也要与Controller进行通信,这个时候就要用Notification和KVO,这个方式就像一个广播一样,Model发信号,Controller设置监听接受信号,当有数据更新时就发信号给Controller,Model和View不能直接进行通信,这样会违背MVC设计模式。
MVVM
ViewModel层,就是View和Model层的粘合剂,他是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。说白了,就是把原来ViewController层的业务逻辑和页面逻辑等剥离出来放到ViewModel层。
View层,就是ViewController层,他的任务就是从ViewModel层获取数据,然后显示。
MVC:它是应用的一种基本架构,主要目的是将不同的代码归并为不同的模块,做到低耦合、代码分配合理、易于扩展维护。

装饰模式(Decorator)
它可以在不修改原代码的基础上进行扩展。注意它与继承最大的区别是:继承时,子类可以修改父类的行为,而装饰模式不希望如此。

适配器模式(Adapter)
它将一个类的接口转换为另一个类的接口,使得原本互不兼容的类可以通过接口一起工作。

外观模式(Facade)
它用一个公共接口来连接多个类或其他数据类型。公共接口让多个互相之间保持独立,解耦性能良好。同时,使用接口时,外部无须理解其背后复杂的逻辑。另外,就算接口背后的逻辑改变也不影响接口的使用。

单例模式(Singleton):此模式保证对于一个特有的类,只有一个公共的实例存在。它一般与懒加载一起出现,只有被需要时才会创建。单例模式的例子有UserDefaults.standard,UIApplication.shared和UIScreen.main。

观察者模式(Observer):它定义对象之间的一种一对多的依赖关系,每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。在iOS中的典型实现是NotificationCenter和KVO.

备忘录模式(Memento):它在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将改对象恢复到保存之前的状态。

感谢@张璠 https://www.jianshu.com/p/28f8ffb2a805

组件化
感觉许多地方文章讨论的组件化不是一个事情。这边先留个坑,以后再填。

后面几条XXXX优先,主要看个人情况,临时抱佛脚也是没用的,我会在本篇后面贴入相关的内容。以及面前需要如何准备。这里继续回到我们的职位要求。

熟悉swift,熟练掌握iOS平台各类API,熟悉iOS内存管理机制,对iOS SDK有深入理解;
熟练使用苹果平台相关开发及调试工具,具有一定的网络编程基础。

熟练使用苹果平台相关开发及调试工具
1.熟悉swift
这里是“熟悉”swift,其实可深可浅。
2.熟练掌握iOS平台各类API
这个其实没法准备,因为面试官也不可能直接问你某某API how to spell,就算你spell一大堆,也不能说明什么问题,主要是在其他问题上沟通时能正常的交流,不要出现“XXX类的那个方法”、“那个回调”就好,这点主要也是功在平时。
3.熟悉iOS内存管理机制
前面内存篇涉及到了。这里单独强调了一下,说明会重点问。要好好准备。
4.对iOS SDK有深入理解
这个话题也是巨大无比,感觉也是没法准备啊,只能说功在平时。如果工作中,能积累到的话最好。
可以看看:
iOS SDK开发 https://www.jianshu.com/p/c88904d392e8
iOS学习之深入理解程序编译过程 http://www.cocoachina.com/articles/21888
逆向相关的也可以看看
5.熟练使用苹果平台相关开发及调试工具
Xcode本身是一个超级强大的IDE,里面有很多功能你可能都没用到过。

Debug Memory Graph

截屏2020-09-09 下午3.07.17.png
这个功能很实在,可以先跑自己的项目,然后都操作一下,然后点击Debug Memory Graph。
其中项目内被标记浅紫色感叹号,是首先可以查看的。
其次Xcode主体会有图像化的对象引用Graph,如果有循环引用也可以分析。
最后,左侧的工程括号中有对象数,也可以在App某操作前后,分析这个对象数的变化。以此来寻找可能存在的内存问题。

instrument
Xcode内置的强大工具集,感觉对面试作用也不大,主要是Leaks会用到吧。

Leaks(泄漏)
//一般的查看内存使用情况,检查泄漏的内存,并提供了所有活动的分配和泄漏模块的类对象分配统计信息以及内存地址历史记录;
Time Profiler(时间探查)
//执行对系统的 CPU上运行的进程低负载时间为基础采样。
Allocations(内存分配)
//跟踪过程的匿名虚拟内存和堆的对象提供类名和可选保留/释放历史;
Activity Monitor(活动监视器)
//显示器处理的 CPU、内存和网络使用情况统计;
Blank(空模板)
//创建一个空的模板,可以从 Library 库中添加其他模板;
Automation(自动化)
//这个模板执行它模拟用户界面交互为 iOS 手机应用从 instrument 启动的脚本;
Core Data
//监测读取、缓存未命中、保存等操作,能直观显示是否保存次数远超实际需要。
Cocoa Layout
//观察约束变化,找出布局代码的问题所在。
Network
//跟踪 TCP/IP 和 UDP/IP 连接。
Automations
//创建和编辑测试脚本来自动化 iOS 应用的用户界面测试。
摘自 https://www.jianshu.com/p/7cb8b2180e13

下面这篇的内容也还可以,不过对我们面试可能效果不大。可以看一下对工作有帮助。
(大多数我都没有用过😭)
https://baijiahao.baidu.com/s?id=1625406673142163373&wfr=spider&for=pc


后话

面完啦,问的问题基本都复习到了,
关于copy问了个意料之外的问题,如果NSArray内对象copy需要对其中的对象也拷贝或强引用怎么做?
面试时想到重写copyCode的两个方法。
现在想想应该也是用HashMap相关的东西来做。
然后关于atomic的自旋锁,问了更加深入的问题,问我如果三个异步线程同时去访问,这里怎么处理。
压根不知道会发生什么🤷‍♂️
现在想想,后占用的线程会定期去等待轮询再次访问资源。但是怎么处理?还是有点不明白需要答什么。
然后第一个问题,问IM的网络实现,太菜了,根本答不出😭

晚点来补

为什么分类不加属性?
runtime:
如何监测,系统内当前正在滚动的view的offset。?
这里涉及到.delegate,有无?方法有无声明?

相关文章

网友评论

    本文标题:iOS 完整的面试准备(大厂面试)

    本文链接:https://www.haomeiwen.com/subject/nvzzsktx.html