iOS逆向

iOS 面试题整理

2022-05-12  本文已影响0人  AmumuHandsome

常见面试题链接: https://www.jianshu.com/p/25324d04797d
高级iOS面试题:https://www.jianshu.com/p/c10a4701ac28

进阶版:

1.介绍下App启动的完成过程?

 1. App启动过程
▪    解析Info.plist
▪    加载相关信息,例如如闪屏
▪    沙箱建立、权限检查
   Mach-O加载
▪    如果是胖二进制文件,寻找合适当前CPU类别的部分
▪    加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
▪    定位内部、外部指针引用,例如字符串、函数等
▪    执行声明为__attribute__((constructor))的C函数
▪    加载类扩展(Category)中的方法
▪    C++静态对象加载、调用ObjC的 +load 函数

 程序执行
·    1.main函数
·    2.执行UIApplicationMain函数
·      1.创建UIApplication对象
·      2.创建UIApplicationDelegate对象并复制
·      3.读取配置文件info.plist,设置程序启动的一些属性,(关于info.plist的内容可网上搜索下)
·      4.创建应用程序的Main Runloop循环
·    3.UIApplicationDelegate对象开始处理监听到的事件
·      1.程序启动成功之后,首先调用application:didFinishLaunchingWithOptions:方法,
·      如果info.plist文件中配置了启动storyboard文件名,则加载storyboard文件。
·      如果没有配置,则根据代码来创建UIWindow--->UIWindow的rootViewController-->显示

2.比如App启动过慢,你可能想到的因素有哪些?

利用DYLD_PRINT_STATISTICS分析main()函数之前的耗时
▪    重新梳理架构,减少动态库、ObjC类的数目,减少Category的数目
▪    定期扫描不再使用的动态库、类、函数,例如每两个迭代一次
▪    用dispatchonce()代替所有的__attribute__((constructor))函数、C++静态对象初始化、ObjC的+load
▪    在设计师可接受的范围内压缩图片的大小,会有意外收获
•    利用锚点分析applicationWillFinishLaunching的耗时
▪    将不需要马上在applicationWillFinishLaunching执行的代码延后执行
▪    rootViewController的加载,适当将某一级的childViewController或subviews延后加载
▪    如果你的App可能会被后台拉起并冷启动,可考虑不加载rootViewController
•    不应放过的一些小细节
▪    异步操作并不影响指标,但有可能影响交互体验,例如大量网络请求导致数据拥堵
▪    有时候一些交互上的优化比技术手段效果更明显,视觉上的快决不是冰冷的数据可以解释的,好好和你们的设计师谈谈动画

3.多线程有哪几种?你更倾向于哪一种?

image.png
 NSOperation相对于GCD:
1,NSOperation拥有更多的函数可用,具体查看api。NSOperationQueue 是在GCD基础上实现的,只不过是GCD更高一层的抽象。
2,在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
3,NSOperationQueue支持KVO。可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
4,GCD 只支持FIFO 的队列,而NSOperationQueue可以调整队列的执行顺序(通过调整权重)。NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。

使用NSOperation的情况:各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等。

使用GCD的情况:一般的需求很简单的多线程操作,用GCD都可以了,简单高效。

从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。

4.TCP UDP区别?

1.基于连接与无连接;

2.对系统资源的要求(TCP较多,UDP少);

3.UDP程序结构较简单;

4.流模式与数据报模式 ;

5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证

TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)。

UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。

5.哈希表是如何实现的?如何解决地址冲突?

哈希表是也是通过数组来实现的,首先对key值进行哈希化得到一个整数,然后对整数进行计算,得到一个数组中的下标,
然后进行存取,解决地址冲突常用方法有开放定址法和链表法。
runtime源码的存放weak指针哈希表使用的就是开放定址法,Java里的HashMap使用的是链表法。

6 Runloop

▪为什么只有主线程的runloop是开启的
▪为什么只在主线程刷新UI
▪PerformSelector和runloop的关系
▪如何使线程保活

1.为什么只有主线程的runloop是开启的

mian()函数中调用UIApplicationMain,这里会创建一个主线程,用于UI处理,为了让程序可以
一直运行并接收事件,所以在主线程中开启一个runloop,让主线程常驻.

2.为什么只在主线程刷新UI

  我们所有用到的UI都是来自于UIKit这个基础库.因为objc不是一门线程安全的语言所以存在多
线程读写不同步的问题,如果使用加锁的方式操作系统开销很大,会耗费大量的系统资源(内存、
时间片轮转、cpu处理速度…),加上上面讲到的系统事件的接收处理都在主线程,如果UI异步线
程的话 还会存在 同步处理事件的问题,所以多点触摸手势等一些事件要保持和UI在同一个线程
相对是最优解.

  另一方面是 屏幕的渲染是 60帧(60Hz/秒), 也就是1秒钟回调60次的频率,(iPad Pro 是120Hz/
秒),我们的runloop 理想状态下也会按照时钟周期 回调60次(iPad Pro 120次), 这么高频率的调用
是为了 屏幕图像显示能够垂直同步 不卡顿.在异步线程的话是很难保证这个处理过程的同步更
新. 即便能保证的话 相对主线程而言 系统资源开销 线程调度等等将会占据大部分资源和在同一
个线程只专门干一件事有点得不偿失.

3.PerformSelector和runloop的关系

当调用NSObect的 performSelector:相关的时候,内部会创建一个timer定时器添加到当前线程的
runloop中,如果当前线程没有启动runloop,则该方法不会被调用.

开发中遇到最多的问题就是这个performSelector: 导致对象的延迟释放,这里开发过程中注意一
下,可以用单次的NSTimer替代.

4.如何使线程保活?

想要线程保活的话就开启该线程的runloop即可,注意:在NSThread执行的方法中添加while(true)
{},这样是模拟runloop的运行原理,结合GCD的信号量,在{}代码块中处理任务.

但是注意 开启runloop的方法要正确

如下代码

//测试开启线程
- (void)memoryTest {
    for (int i = 0; i < 100000; ++i) {
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
        [thread start];
        [self performSelector:@selector(stopThread) onThread:thread withObject:nil waitUntilDone:YES];
    }
}
//线程停止
- (void)stopThread {
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSThread *thread = [NSThread currentThread];
    [thread cancel];
}
//运行线程的runloop 注意 意添加的那个空port,否则会出现内存泄露
- (void)run {
    @autoreleasepool {
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        if (!self.emptyPort) {
            self.emptyPort = [NSMachPort port];
        }
        [runLoop addPort:self.emptyPort forMode:NSDefaultRunLoopMode];
        [runLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]];
    }
}
//下列代码用于模拟线程内部做的一些耗时任务
- (void)printSomething {
    NSLog(@"current thread = %@", [NSThread currentThread]);
    [self performSelector:@selector(printSomething) withObject:nil afterDelay:1];
}
//模拟手动点击按钮 让 runloop停掉
- (void)stopButtonDidClicked:(id)sender {
    [self performSelector:@selector(stopRunloop) onThread:self.thread withObject:nil waitUntilDone:YES];
}

- (void)stopRunloop {
    CFRunLoopStop(CFRunLoopGetCurrent());
}


上一篇下一篇

猜你喜欢

热点阅读