前言
笔者一直很懒,恰逢最近又一次离职,躺平了一段时间后,终于出来面试了。所以整理了一些面试遇见的问题,希望自己也希望大家能用得到。
一、iOS的三大特性
封装:是指将对象的属性和方法隐藏在对象内部,并通过对象提供的接口进行访问。这种特性有助于降低代码之间的耦合度,提高代码的可维护性和安全性。在iOS开发中,封装意味着通过访问修饰符(如public、private、protected)来控制类成员(包括属性和方法)的可见性和可访问性。封装的好处包括提高数据的安全性、降低代码的耦合率,以及允许代码的重复使用。
继承:是面向对象编程中的一个概念,它允许一个类(称为子类)继承另一个类(称为父类)的属性和方法。这样,子类可以重用父类的代码,同时还可以添加自己特有的属性和方法。继承有助于减少代码冗余,提高代码的可重用性,并支持代码的模块化设计。
多态:是指允许用父类类型的引用来操作子类的对象,这是通过方法的动态绑定实现的。在运行时,根据对象的实际类型来确定调用哪个实现的方法。多态提供了更大的灵活性,使得程序可以根据需要使用不同类型的对象,而无需修改原有的代码。
这些特性共同构成了面向对象编程的基础,使得iOS开发能够更加灵活、高效地处理复杂的问题和需求。
二、六大设计原则
单一职责原则:每个类或对象应该只有一个引起变化的原因,即每个类应该只有一个职责。例如,在iOS开发中,
CALayer
负责动画和视图的显示,而UIView
则负责事件传递和响应,两者各有其明确的职责。开闭原则:系统应对扩展开放,对修改关闭。这意味着当需求变化时,应通过添加新的代码来扩展功能,而不是修改已有的代码。
依赖倒置原则:高层模块不应该依赖于低层模块的实现细节,而应该依赖于抽象。这有助于减少代码之间的耦合,提高系统的可维护性。
里氏替换原则:子类必须能够替换它们的基类。这意味着在软件设计中,如果基类能够正常工作的代码,那么用它的子类替换后也应该能够正常工作。
接口隔离原则:客户端不应该依赖于它不需要的接口。通过将庞大的接口拆分成更小的、更具体的接口,可以提高系统的可维护性和可扩展性。
迪米特法则:一个对象应当对其他对象尽可能少的了解。这有助于减少对象之间的耦合,提高系统的内聚性和可维护性。
这些原则是iOS开发中的基本指导原则,旨在提高软件的可维护性、可扩展性和稳定性。遵循这些原则可以帮助开发者构建高质量的应用程序。
三、AppDelegate的生命周期
AppDelegate的生命周期可以概括为以下几个主要方法:
application:didFinishLaunchingWithOptions:
应用程序启动后,系统调用这个方法。可以在这里进行一些初始化设置,例如添加窗口、设置根视图控制器等。applicationDidBecomeActive:
应用程序进入前台时,系统调用这个方法。这是开始执行应用程序核心逻辑的好地方。applicationWillResignActive:
应用程序即将进入非活跃状态(如进入后台)时,系统调用这个方法。通常在这里保存用户数据、暂停游戏或多媒体播放。applicationDidEnterBackground:
应用程序已经进入后台时,系统调用这个方法。这是进行资源释放、保存状态等操作的好地方。applicationWillEnterForeground:
应用程序即将进入前台(从后台返回到前台)时,系统调用这个方法。applicationDidReceiveMemoryWarning:
应用程序接收到内存警告时,系统调用这个方法。在这里,你可以释放缓存、关闭不需要的视图控制器等,以减少内存使用。applicationWillTerminate:
应用程序即将终止时,系统调用这个方法。这是清理工作的最后机会,比如保存用户设置、结束网络连接等。
四、单例的优缺点
单例模式的优点:包括提供全局唯一实例的访问点,确保一个类只有一个实例存在,节省系统资源,避免多个实例之间的冲突和不一致性,管理共享的状态或数据,以及延迟实例化(只在需要时创建实例,避免不必要的资源消耗)。此外,单例模式还提供了一个全局访问点,使得其他对象可以方便地获取单例类的实例,确保了系统的性能提升,因为只创建一次实例,所以会常驻内存之中,在频繁使用该类的时候可以提升系统的性能。单例模式适用于需要共享一份资源的情况,如系统的UIApplication、NSNotificationCenter、NSBundle、NSFileManager、NSUserDefaults等,以及用户信息管理、网络请求、数据查询、日志管理等场景。
单例模式的缺点:首先,它的可扩展性较差,不易于进行重写或扩展,但可以通过分类来实现一定的扩展。其次,单例类的实例是全局共享的,对单例类的修改可能会影响到系统的其他部分,使得扩展和维护变得困难。此外,单例模式违背了单一原则,因为只存在一个实例,职责过重。最后,单例实例一旦创建,对象指针被保存在静态区,在堆区申请分配的内存空间只有当程序运行结束后才会被释放,这可能导致内存管理上的问题。
综上所述,单例模式在iOS开发中具有其独特的优势和适用场景,但同时也需要注意其潜在的问题和限制,以确保代码的可维护性和扩展性。
五、自动释放池
六、isa指针的结构
七、引用计数的原理
八、load 和 initialize 的区别
九、Swift和OC的区别
十、综合题
- 问:下面代码输出的结果是什么?(这是这些天面试,遇见中最喜欢的一道题。)
- (void)textAction {
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
dispatch_async(queue,^{
NSLog(@"3");
});
[self performSelector:@selector(action:) withObject:@4 afterDelay:0];
[self performSelector:@selector(action:) withObject:@5];
dispatch_sync(queue, ^{
NSLog(@"6");
});
NSLog(@"7");
});
NSLog(@"8");
}
- (void)action:(NSNumber*)num {
NSLog(@"%ld",num.integerValue);
}
解析:因为没有固定答案,所以一点点说。
首先输出1,这没有什么好说的;
然后说3,3是异步线程,所以3的位置也不确定。如果忽略1和8,那么3可能出现在3~7中的任一位置。
4很有意思,4不会出现。因为
afterDelay:
等同于NSTimer,子线程中创建的NSTimer需要加入到对应线程的RunLoop中,而子线程RunLoop的运行需要手动开启[[NSRunLoop currentRunLoop] run];
。其次是5,在3~7的子线程中,串行执行,所以正常输出,没什么好说的。
6在同步线程中,所以在3~7的子线程中,依然串行执行,所以6在5之后正常输出,没什么好说的。
最后是7,和5一样,在3~7的子线程中,串行执行,所以7在6之后正常输出。
最后的最后,我们说说8。并发队列,且1和8中间代码为异步线程,所以并发、异步导致8并不确定。8有两种触发方式:
1.是8先触发,3至7的异步线程后执行,那么此时8在1之后,排第二位:1、8、3至7。
2.是8后触发,3至7的异步线程先执行,恶心的地方来了,因为3本身还是异步线程,所以3不确定,那么就有可能出现1、3、8、5、6、7;(且因为3至7是并发、异步子线程,考虑到并发的机制是来回线程切换执行,所以我认为8可能出现在1之后的任一位置(未验证))。