iOS经典面试题总结2

2020-10-18  本文已影响0人  bytebytebyte
以后每个月都要至少出去面试一次,市场可以给自己一个最清晰的定位
1.动画了解吗?

2.请求序列化?

3.APP启动流程的优化?
一、APP启动过程
什么是镜像?
二进制文件.o、动态链接库dylib、资源文件bundle(指正在运行时使用dlopen()加载)
1.main()前阶段:加载应用的可执行文件.o、动态链接器dyld、动态链接库dylib。
2.main()阶段:
dyld调用main()、
UIApplicationMain()、
applicationWillFinishLaunching、
didFinishLaunchingWithOptions

二、main()的过程和可优化项
1.load dylibs
1.1、尽量不使用内嵌的dylib(我们自己写的framework)和无用的dylib,加载内嵌的dylib性能开销大;
1.2、合并已有的dylib和使用静态库,减少dylib的使用个数
1.3、懒加载dylib,但是要注意dlopen()可能造成一些问题,且实际上懒加载做的工作会更多
检查framework应当设为optional和required,如果该framework在当前APP支持的所有iOS
系统版本都存在,那么就设为required,否则就设为optional

2.rebase-Bind
Rebase在前,Bind在后,rebase做的是将镜像读入内存,修正镜像内部的指针,性能消耗主要在IO上。
Bind做的是查询符号表,设置指向镜像外部的指针,性能消耗主要在CPU计算,所以指针数量越少越好。
2.1、减少OC类(Class)、方法(Selector)、分类(category)的数量
2.2、减少C++虚函数的数量,创建虚函数表有开销
2.3、使用swift structs,内部做了优化,符号数量更少
2.4、压缩资源图片、删除无用的图片(IO操作)

3.ObjC setup
大部分ObjC初始化工作已经在Rebase-Bind阶段做完了,这一步dyld会注册所有声明过的ObjC类,将分类插入到
类的方法列表里,再检查每个Selector的唯一性,这一步没有什么优化可做,Rebase-Bind阶段优化好了,这一
步的耗时也会减少。

4.Initializers
dyld开始运行程序的初始化函数,调用每个Objc类和分类的+load方法,调用C/C++的构造器函数(用attribbute
(constructor)修饰的函数),创建非基本类型的C++静态全局变量(通常是类或结构体),Initializers后dyld
开始调用main().在这一步我们可以做的优化有:
4.1、少在类的load方法里做事情,尽量把这些事情推迟到+ initialize
4.2、减少构造器函数个数,在构造器函数里少做些事情
4.3、减少C++静态全局变量的个数

三、main()阶段的可优化项
这一阶段的优化主要是减少didFinishLaunchingWithOptions方法里的工作,在didFinishLaunchingWithOptions
方法里,我们会创建应用的window,指定其rootViewController,调用window的makeKeyAndVisible方法
让其可见。由于业务需要,我们会初始化各个二、三方库,设置系统UI风格,检查是否需要显示引导页、是否需要登录、
是否有新版本等,由于历史原因,这里的代码容易变得比较庞大,启动耗时难以控制。所以满足业务需要的前提下,
didFinishLaunchingWithOptions在主线程里做的事情越少越好。
1、梳理各个二、三方库,找到可以延迟加载的库做延迟加载处理,比如放到首页控制器或tabBar控制器的viewDidAppear方法里
2、梳理业务逻辑,把可以延迟执行的逻辑做延迟执行处理,比如检查新版本、注册推送通知等逻辑
3、避免复杂、多余的计算
4、采用性能更好的API
5、避免在首页控制器的viewDidLoad和viewWillAppear做太多的事情,这2个方法执行完,首页控制器才能显示,
部分客户延迟创建的视图做延迟创建、懒加载处理。
6、首页控制器、注册登录页面用纯代码来构建
7、延迟持久化数据的读取到内存

四、启动优化总结
main()前阶段的优化
1.1、尽量不使用内嵌的dylib(我们自己写的framework)和无用的dylib,加载内嵌的dylib性能开销大;
1.2、合并已有的dylib和使用静态库,减少dylib的使用个数
1.3、懒加载dylib,但是要注意dlopen()可能造成一些问题,且实际上懒加载做的工作会更多
检查framework应当设为optional和required,如果该framework在当前APP支持的所有iOS
系统版本都存在,那么就设为required,否则就设为optional
2.1、减少OC类(Class)、方法(Selector)、分类(category)的数量
2.2、减少C++虚函数的数量,创建虚函数表有开销
2.3、使用swift structs,内部做了优化,符号数量更少
2.4、压缩资源图片、删除无用的图片(IO操作)
4.1、少在类的load方法里做事情,尽量把这些事情推迟到+ initialize
4.2、减少构造器函数个数,在构造器函数里少做些事情
4.3、减少C++静态全局变量的个数

main()阶段的优化
1、梳理各个二、三方库,找到可以延迟加载的库做延迟加载处理,比如放到首页控制器或tabBar控制器的viewDidAppear方法里
2、梳理业务逻辑,把可以延迟执行的逻辑做延迟执行处理,比如检查新版本、注册推送通知等逻辑
3、避免复杂、多余的计算
4、采用性能更好的API
5、避免在首页控制器的viewDidLoad和viewWillAppear做太多的事情,这2个方法执行完,首页控制器才能显示,
部分客户延迟创建的视图做延迟创建、懒加载处理。
6、首页控制器、注册登录页面用纯代码来构建
7、延迟持久化数据的读取到内存

4.列表的优化?
(1)复用cell
(2)cell高度。定高cell用rowHeight而不是heightForRow,不定高cell缓存cell的高度。
(3)cell渲染。
cell的opaque值设为YES,背景色不要使用clearColor,不要使用阴影渐变;
减少subviews的个数和层级,多用drawRect绘制元素,在rect范围之外的区域我们不需要进行绘制,否则会消耗相当大的资源;
避免calayer的特效阴影颜色、偏移、透明度、圆角;
不要给cell动态添加subview,在初始化cell的时候就将所有需要展示的添加完毕,然后根据需要来设置hide属性显示和隐藏。
(4)离屏渲染。
使用贝塞尔曲线UIBezierPath和Core Graphics画出一个圆角;
使用CAShapeLayer和UIBezierPath设置圆角;
美工、服务端处理圆角。

5.NSArray和NSSet的区别?
内存中的存储方式
NSArray是有序集合在内存中的存储方式是连续的,
NSSet是无序集合在内存中的存储方式是不连续的
元素查找效率
NSArray 遍历查找
NSSet   哈希查找效率高 hash是如何实现的?

6.swift和OC有什么区别?
    swift             OC
语言特性:静态语言,更加安全。    动态语言,不安全
语法:精简            冗长
命名空间:有        无
方法调用:直接调用,函数表调用,消息转发。消息转发。
泛型/元组/高阶函数:有。    无        
语言效率:性能更高,速度更快。略低。
文件特性:.swift单文件。    .h/.m包含头文件
编程特性:函数式、响应式编程。 面向对象编程。

7.如何扩大按钮的响应范围?
不改变大小的情况下,扩大按钮点击范围的两种方式
(1)新建一个类继承自UIButton然后重写UIButton的pointInside:WithEvent方法,在里边改变它的内边距
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect btnBounds = self.bounds;
    btnBounds = CGRectInset(btnBounds, -10, -10)//扩大按钮的点击范围改为负值
    return CGRectContainsPoint(btnBounds,point) //若点击的点在新的bounds里,就返回YES
}
(2)给UIButton类添加类别category,然后重写UIButton的pointInside:WithEvent方法
-(void)setEnLargeEdge:(CGFloat)size
{
    objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:size], OBJC_ASSOCIATION_COPY_NONATOMIC);

}
// 设置可点击范围到按钮上、右、下、左的距离
-(void)setEnlargeEdgeWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left
{
    objc_setAssociatedObject(self, &topNameKey, [NSNumber numberWithFloat:top], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &rightNameKey, [NSNumber numberWithFloat:right], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &bottomNameKey, [NSNumber numberWithFloat:bottom], OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &leftNameKey, [NSNumber numberWithFloat:left], OBJC_ASSOCIATION_COPY_NONATOMIC);

}
-(CGRect)enlargedRect
{
    NSNumber *topEdge=objc_getAssociatedObject(self, &topNameKey);
    NSNumber *rightEdge=objc_getAssociatedObject(self, &rightNameKey);
    NSNumber *bottomEdge=objc_getAssociatedObject(self, &bottomNameKey);
    NSNumber *leftEdge=objc_getAssociatedObject(self, &leftNameKey);

    if(topEdge && rightEdge && bottomEdge && leftEdge){
        return CGRectMake(self.bounds.origin.x-leftEdge.floatValue,
                          self.bounds.origin.y-topEdge.floatValue,
                          self.bounds.size.width+leftEdge.floatValue+rightEdge.floatValue,
                          self.bounds.size.height+topEdge.floatValue+bottomEdge.floatValue);

    }else{
        return self.bounds;
    }

}
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGRect rect=[self enlargedRect];
    if(CGRectEqualToRect(rect, self.bounds))
    {
        return [super pointInside:point withEvent:event];
    }
    return CGRectContainsPoint(rect, point)?YES:NO;

}
[self.button2 setEnlargeEdgeWithTop:50 right:50 bottom:200 left:50];

8.控制器的生命周期?
loadView、===viewDidLoad===、viewWillAppear、viewDidAppear、viewWillDisappear、dealloc
可以参考:https://www.jianshu.com/p/d60b388b19f5

9.点击屏幕中一个按钮都发生了什么?
屏幕会产生一个MachPort,基于MachPort最终会转成source1可以把主线程唤醒运行。

10.求二叉树的前序遍历?用递归和迭代两种方式实现?
二叉树的前序遍历访问顺序:根节点,前序遍历左子树,前序遍历右子树。
                    7
            4                9
        2        5        8        11
    1    3                        10    12
访问顺序是:7、 左4、2、1、5、 右9、8、11、10、12

11.16瓶水其中1瓶水有毒如何找出这瓶水?水可以无限分解。分组

12.alloc的底层实现
alloc -> _objc_rootAlloc -> callAlloc -> class_createInstance -> _class_createInstanceFromZone
instanceSize:计算需要的内存空间大小,并会进行内存对齐
calloc:开辟指定大小内存空间
initInstanceIsa:将calloc开辟的内存空间与当前的Class进行绑定

13.init底层实现
init是个工厂模式,什么都没做,交给子类可以自定义去重写

14.new的底层实现
+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

15.Swift常用第三方库
Alamofire:类似AFN
Kingfisher:SD
SwiftyJSON
KakaJSON
ReachabilitySwift:Reachability
MonkeyKing:社会化分享
ObjectMapper:字典模型互转
SnapKit:Masonry

16.swift和iOS是如何互相调用?
swift调用OC:将暴露的OC头文件写入TestSwift2-Bridging-Header.h中
OC调用swift:用@objcMembers、@objc、NSObject修饰类,并引入桥接文件 项目名-Swift.h

17.GCD实现原理?
a.GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?) 而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。

b.如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。

c.如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。

d.就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开5~8条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:3~5条最为合理。

18.FMDB如何保证线程安全?
创建数据库的队列

19.如何保证数组的线程安全?
@synchronized

20.weak为何会被置为nil?

21.iOS的内存时如何管理的?

22.如何加快视频的下载和写入速度?
服务端、移动端优化。

23.耗电优化?
耗电的主要来源:CPU GPU处理、网络、定位、图像
(1)尽可能的让CPU和GPU少做事情
1>少用定时器
2>优化I/O操作:
a.尽量不要频繁写入小数据,最好批量一次性写入。
b.读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的API,用dispatch_io系统会优化磁盘访问。
c.数据量比较大时,建议使用数据库。
(2)网络优化
1>减少、压缩网络数据
2>如果多次请求的结果是相同的,尽量使用缓存
3>使用断点续传,否则网络不稳定时可能多次传输相同的内容
4>网络不可用时,不要尝试执行网络请求
5>让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间。
6>批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封不要一封一封地下载。

(3)定位优化
1>如果只是需要快速确定用户位置,最好用CLLocationManager的requestLocation方法,定位完成后会自动让定位硬件断电;
2>如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务;
3>尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest
4>需要后台定位时,尽量设置pauseLocationUpdatesAutomatically = YES,如果用户不太可能移动的时候系统会自动暂停位置更新;
5>尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion
(4)硬件检测优化
用户移动、摇晃、倾斜设备时,会产生动作motion事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测,在不需要检测的场合,应该及时关闭这些硬件。

24.安装包瘦身?
安装包主要由可执行文件、资源组成。
(1)资源:图片、音频、视频等。采取无损压缩,去除没有用到的资源。
(2)可执行文件瘦身:
1>编译器优化:Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES,去掉异常支持Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO、Other C Flags添加-fno-exceptions
2>利用AppCode检测未使用的代码:菜单栏->Code->Inspect Code
3>编写LLVM插件检测出重复代码、未被调用的代码
4>生成LinkMap文件,可以查看可执行文件的具体组成,可借助第三方工具([https://github.com/huanxsd/LinkMap](https://github.com/huanxsd/LinkMap))解析LinkMap文件

25.方法查找时的返回条件是什么?
26.MapTable和HashTabble的区别?
27.如何实现一个按钮的多次点击?如果是很多地方都用到这个按钮呢?
28.数据结构是如何分类的?
数据结构分类:
(1)数据的逻辑结构:数据与数据之间的联系,根据关系的紧密程度,逻辑结构分为4种。
集合:数据结构中的元素之间除了“同属一个集合”的相互关系外,别无其他关系。
线程结构:数据结构中的元素存在一对一的相互关系,数学老师给我补课不给其他人补课,那么我和数学老师就是一对一的关系。
树性结构:数据结构中的元素存在一对多的相互关系,比如一个数学老师给两个或者多个学生补课,那么老师和学生之间就是一对多的关系。
图形结构:数据结构中的元素存在多对多的相互关系。郑州到北京有m条高速公路,北京到郑州有n条高速公路,郑州到北京是一对多的关系,北京到郑州市多对一的关系,那么郑州和北京就是多对多的关系。
10157465-f17a536b716916f7.jpg
(2)数据的物理结构:数据的逻辑结构在计算机存储空间的存放形式。
顺序存储结构:逻辑上相邻的结点存储在物理位置上相邻的存储单元里,如数组
链接存储结构:链表
数据索引存储结构:字典存储单词从过程。
数据散列(哈希)存储结构。

29.手写冒泡排序算法?并分析冒泡排序的最大时间复杂度和最小时间复杂度?
- (void)bubbleSort:(NSMutableArray *)mutArr {
    if (mutArr == nil || mutArr.count == 0) {
        return;
    }
    for (int i = 0; i < mutArr.count - 1; i ++) {
        for (int j = 0; j < mutArr.count - 1 - i ; j ++) {
            if ([mutArr[j] integerValue] > [mutArr[j + 1] integerValue]) {
                [mutArr exchangeObjectAtIndex:j withObjectAtIndex:(j + 1)];
            }
        }
        NSLog(@"bubbleSort -- mutArr == %@",mutArr);
    }
}
最好情况下,要排序的数据已经是有序的了,我们只需要进行一次冒泡操作,就可以结束了,所以 最好情况时间复杂度是O(n)。而最坏的情况是,要排序的数据刚好是倒序排列的,我们需要进行n 次冒泡操作,所以最坏情况时间复杂度为O(n2)。
30.进程和线程的区别?
31.swift和OC互相调用时需要注意哪些点?
32.Autoreleasepool是什么时候被释放的?
分两种情况:
(1)  手动添加的@autoreleasepool大括号结束后释放
(2)主线程的@autoreleasepool BeforeWaiting(准备进入休眠) 和xit(即将退出Loop) 时
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
33.iOS默认的关键字有哪些?
在声明 property 时,如果不指定关键字,编译器会为 property 生成默认的关键字
基本数据类型,默认关键字为:atomic,assign,readwrite
对象类型,默认关键字为:atomic,strong,readwrite

上一篇下一篇

猜你喜欢

热点阅读