综合面试题

1. 谈谈你对KVC的理解

  1. KVC可以通过 key直接访问对象的属性
  2. 给对象属性赋值
  3. 运行时动态的访问或修改对象的属性

底层执行机制如下
以[self setValue:@“小明” forKey:@“name”];这句代码作为例子进行说明。

1.程序优先调用setKey:属性方法,代码通过setter方法完成设置。注意这里的Key是指成员变量名,首字母大小写要符合KVC命名规范

2.如果没有找到setName:方法KVC机制会检查+(BOOL)accessInstanceVariablesDirectly方法有没有返回YES如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUndefinedKey:方法不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为<key的成员变量。无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以key>命名的变量,KVC都可以对该成员变量赋值。

3.如果该类即没有set:方法,也没有_成员变量,KVC机制会搜索_is<Key的成员变量。

4.和上面一样,如果该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值。

  1. 如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。

6.特别需要注意的是:如果开发者想让这个类禁用KVC里,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set:属性名时,会直接用setValue:forUndefinedKey:方法。

6.特别需要注意的是:如果开发者想让这个类禁用KVC里,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set:属性名时,会直接用setValue:forUndefinedKey:方法。

跟深层次的讲解

2. 项目中引用多个第三方库引发的解决方法

第三方冲突会保存:duplicate symbols

目前见效最快的就是把**.framework** 选中。taggert Membership
的对钩取消,编译就没有问题了,但是后续其他问题还会出现。

我想说的是像这种开源的使用率很高的源代码本不应该包含在lib库,就算包含也该改个名字。没办法既然包含了,那就想办法分离

1.查看文件结构 lipo -info xxx.a(如果是fat file,代表支持多个平台,如 armv7,arm64等)。

  1. 创建文件夹,用于存放解压出来的.o文件 mkdir armv7

3.取出平台的包 lipo xxx.a -thin armv7 -output armv7/xxx-armv7.a

4.解压出.o文件(首先输入指令 cd armv7 到我们第二步创建的文件夹) ar xv xxx-armv7.a

5.删除冲突的包 rm ***.o

6.重新打包.o文件(首先输入指令 cd … 回到上一级目录) ar rcs xxx-armv7.a armv7/*.o

多平台SDK的话需要多次操作,操作完成后,合并多个平台的文件为一个.a文件然后我们将这些.a文件重新打包成fat file类型的.a文件就可以了。

将修改好的文件,拖拽到原文件夹下,替换原文件即可。

3. GCD实现多读单写

#import "WHObject.h"

@interface WHObject()

@property (nonatomic, strong) dispatch_queue_t dictQueue;//并发队列
@property (nonatomic, strong) NSMutableDictionary *dict;//可变字典

@end

@implementation WHObject

- (instancetype)init {
    if(self = [super init]) {
        _dictQueue = dispatch_queue_create("com.huangwenhong.queue", DISPATCH_QUEUE_CONCURRENT);
        _dict = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)huangwenhongmethod {
    self.target = [NSObject new];
}

- (id)valueForKey:(NSString *)key {
    id __block obj;
    dispatch_sync(_dictQueue, ^{//因为有数据 return,所以这里是同步调用
        obj = [self.dict valueForKey:key];
    });
    return obj;
}

- (void)setObject:(id)obj forKey:(id<NSCopying>)key {
    //重点:dispatch_barrier_async(),异步栅栏调用;
    //只有提交到并行队列才有意义
   key = [key copy]
    dispatch_barrier_async(_dictQueue, ^{
        [self.dict setObject:obj forKey:key];
    });
}




@end


  1. 首先我们要维系一个GCD队列,最好不用全局队列,毕竟大家都知道全局队列遇到栅栏函数是坑点。

2.因为考虑性能,死锁,堵塞的因素不考虑串行队列,用到的是自定义的并发队列。

       _dictQueue = dispatch_queue_create("com.huangwenhong.queue", DISPATCH_QUEUE_CONCURRENT);

3.首先我们来看看读操作:valueForKey:,考虑到多线程影响是不能用异步函数的,

线程2获取name,线程3获取age,如果因为异步并发,导致混乱本来读的是name结果读到了age,如果允许多个任务同时进去!但是读操作需要同步返回,所以我们选择:同步函数

  1. 再来看看写操作,在写操作时对key进行了copy

函数调用者可以自由传递一个NSMutableDictionary的key,并且能够在函数返回后修改它,因为我们必须对传入的字符串使用copy,以确保准确的,如果传入的字符串是不可变的,使用copy基本上是空操作。

  1. 为什么使用dispatch_barrier_async栅栏函数,而不是异步或者同步。

栅栏函数任务:之前所有的任务执行完毕,并且在它后面的任务开始之前,期间不会有其他任务执行,这样写操作一个接一个的写(写写互斥)

假如用同步函数,就有可能存在:我写需要等待读操作回来才能执行,显然不合理。

4. Autoreleasepool的原理?所使用的的数据结构是什么

  • Autoreleasepool 是由多个AutoreleasepoolPage 以双向链表的形式连接起来的。
  • Autoreleasepool 的基本原里:在每个自动释放池创建的时候,会在当前的AutoreleasepoolPage 中设置一个标记位,在此期间,当有对象调用Autorelease时,会把对象添加AutoreleasepoolPage中。
  • 若当前页添加满了,会初始化一个新页,然后用双向链表连接起来,并把新初始化的这一页设置为hotPage,当自动释放池pop时,从最下面依次往上pop,调用每个对象的release方法,直到遇到标志位。

AutoreleasepoolPage结构如下
在这里插入图片描述

5. 冷启动热启动优化

冷启动

pre-main阶段

排查无用的dylib,移除不再使用的libicucore.tbd

删除无用文件&库,合并重复文件(多个重复的分类)。移除不再使用的库UMSocial、PSTCollectionView、MCSwipeTableViewCell,移除功能重复的库Mantle

梳理各个类的+load方法,将多个类中+load方法做的事延迟到+initiailize里去做。

main()阶段的优化

去掉其中100ms的dispatch_after…检查代码发现之前会故意让启动图多显示100ms,不知道是什么逻辑…

将多个二方/三方库延迟加载。包括TBCrashReporter、TBAccsSDK、UT、TRemoteDebugger、ATSDK等。

将若干系统UI配置、业务逻辑延迟执行。包括注册推送、检查新版本、更新Orange配置等。

避免多余的计算。之前会前后两次获取是否要显示广告图,每次获取都需要反序列化Orange中的配置信息,再比较配置中的开始/结束时间,大约耗时20ms。目前的解决方案是第一次计算后,用一个BOOL属性缓存起来,下次直接取用。

延迟加载&懒加载部分视图。快捷密码验证页是启动图消失后用户看到的第一个页面,这个页面由于涉及到图片的解码、多个视图的创建&布局,viewDidLoad阶段会耗时100ms左右。目前的解决方案是把其中密码输入框视图延迟到viewDidAppear里加载,对密码错误提示视图做成懒加载,耗时降低到30m左右。

热启动优化

具体讲解详看

6. App 网络层有哪些优化策略

  • 优化DNS解析和缓存
  • 对传输的数据进行压缩,减少传输的数据
  • 使用缓存手段减少请求发起次数
  • 使用策略来减少请求发起次数,比如在上一个请求未着地之前,不进行新的请求
  • 避免网络抖动,提供重发机制

7. 为什么创建少的category 类 宏能优化性能 这些文件都被怎么加载了

8.你是如何组件化解耦的?

9. 怎么防止别人反编译你的app?

1.本地数据加密

iOS应用防反编译加密技术之一:对NSUserDefaults,sqlite存储文件数据加密,保护帐号和关键信息

2.URL编码加密

iOS应用防反编译加密技术之二:对程序中出现的URL进行编码加密,防止URL被静态分析

3.网络传输数据加密

iOS应用防反编译加密技术之三:对客户端传输数据提供加密方案,有效防止通过网络接口的拦截获取数据

4.方法体,方法名高级混淆

iOS应用防反编译加密技术之四:对应用程序的方法名和方法体进行混淆,保证源码被逆向后无法解析代码

程序结构混排加密

iOS应用防反编译加密技术之五:对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低

10.优化你是从哪几方面着手?

一、首页启动速度

启动过程中做的事情越少越好(尽可能将多个接口合并)

不在UI线程上作耗时的操作(数据的处理在子线程进行,处理完通知主线程刷新节目)

在合适的时机开始后台任务(例如在用户指引节目就可以开始准备加载的数据)

尽量减小包的大小

优化方法:

量化启动时间

启动速度模块化

辅助工具(友盟,听云,Flurry)

二、页面浏览速度

json的处理(iOS 自带的NSJSONSerialization,Jsonkit,SBJson)

数据的分页(后端数据多的话,就要分页返回,例如网易新闻,或者 微博记录)

数据压缩(大数据也可以压缩返回,减少流量,加快反应速度)

内容缓存(例如网易新闻的最新新闻列表都是要缓存到本地,从本地加载,可以缓存到内存,或者数据库,根据情况而定)

延时加载tab(比如app有5个tab,可以先加载第一个要显示的tab,其他的在显示时候加载,按需加载)

算法的优化(核心算法的优化,例如有些app 有个 联系人姓名用汉语拼音的首字母排序)

三、操作流畅度优化:

Tableview 优化(tableview cell的加载优化)

ViewController加载优化(不同view之间的跳转,可以提前准备好数据)

四、数据库的优化:

数据库设计上面的重构

查询语句的优化

分库分表(数据太多的时候,可以分不同的表或者库)

五、服务器端和客户端的交互优化:

客户端尽量减少请求

服务端尽量做多的逻辑处理

服务器端和客户端采取推拉结合的方式(可以利用一些同步机制)

通信协议的优化。(减少报文的大小)

电量使用优化(尽量不要使用后台运行)

六、非技术性能优化

产品设计的逻辑性(产品的设计一定要符合逻辑,或者逻辑尽量简单,否则会让程序员抓狂,有时候用了好大力气,才可以完成一个小小的逻辑设计问题)

界面交互的规范(每个模块的界面的交互尽量统一,符合操作习惯)

代码规范(这个可以隐形带来app 性能的提高,比如 用if else 还是switch ,或者是用!还是 ==)

code review(坚持code Review 持续重构代码。减少代码的逻辑复杂度)

11. 静态库符合冲突

利用动态库解决第三方静态库与项目符号冲突问题

静态库A 与项目冲突

  1. 新建一个动态库B,
  2. 并将静态库A拖入动态库B中,
  3. 并将静态库A的头文件复制一份放入库B中
  4. 编译生成动态库B,拖进工程中

AB两个静态库符号冲突
A和B转成动态库

静态库冲突其实就是符号冲突

  • 删除相同的.o文件,但是符号冲突不一定在相同的文件
  • 收到添加类前缀,拿到源码的情况下
  • llvm-objcopy 拿到符号文件,然后重命名

11. 沙盒 面试题

请添加图片描述

Documents

保存持久化数据,会备份。一般用来存储需要持久化的数据。
此文件夹是默认备份的,备份到iCloud

存储用户生成的一些信息,向用户公开的一些信息如(
崩溃日志,
配置资源,)文件格式txt,以及plist,资源文件banner,小图,

Library

请添加图片描述

Cache

缓存数据在设备低存储空间时可能会被删除,iTunes或iCloud不会对其进行备份
比如5G视频,换肤的文件

Preferences

NSUserDefaults就是默认存放在此文件夹下面,iTunes或iCloud会备份该目录。

tmp

临时文件夹(系统会不定期删除里面的文件)

11. 越狱检测方案

还有一篇博客

具体请看

  1. 检测动态库

可能存在stat也被hook了,可以看stat是不是出自系统库,有没有被攻击者换掉
1.2 判断是否注入了动态库 _dyld_get_image_name来获取动态库的(越狱机都会安装MobileSubstrate
// /Applications/Cydia.app/ /var/lib/cydia/绝大多数越狱机都会安装)

  1. 判断是否有越狱相关文件或权限

2.1 判断是否可以访问一些越狱的文件
查看是否有权限写入私有目录

  1. 利用系统命令来判断
    通过lstat命令来判断系统的一些目录是否存在还是变成了链接

因为越狱后会变动一些文件,这些文件目录会迁移到其他区域,但是原来的文件位置必须有效,所以会创建符号链接,链接到原来的路径,我们可以检测这些符号链接是否存在,存在说明就越狱了

3.2 是否能够fork一个子进程

未越狱的设备是无法fork子进程的,可以通过这个检测,还有其他类似的命令:方法posix_spawn,kill,popen等

  1. 查看是否有异常类和异常的动态库

查看是否有注入异常的类,比如HBPreferences 是越狱常用的类,这里无法绕过,只要多找一些特征类就

4.2 检测是否有异常的动态库

这个和1.2章检测注入动态库的区别是,一般反越狱插件会hook_dyld_get_image_name这个方法,把越狱使用的一些动态库给影藏掉(比如返回其他动态库名称,或者返回正常的),导致匹配不到,可以利用image加载时的回调来从MachO Header中去动态库信息,

  1. 检测是否在调试

//如果攻击者给MobileSubstrate改名,但是原理都是通过DYLD_INSERT_LIBRARIES注入动态库,检测当前程序运行的环境变量

5.2 判断当前进程是否为调试模式

使用sysctl方法来获取当前进程的相关信息,从而确实是否在进行pTraced调试,具体参考sysctl

二、防越狱检测的方案和对策

MobileSubstrate会将 SpringBoard 的 [FBApplicationInfo environmentVariables] 函式做 hook ,将环境变量DYLD_INSERT_LIBRARIES设定新增需要载入的动态库,但是应用的二进制包无需做任何变化,dyld会在载入应用的时候因为DYLD_INSERT_LIBRARIES去插入具体的库

用DYLD_INSERT_LIBRARIES来加载subsrate的注入代码:
https://github.com/jevinskie/substrate/blob/97fa4bae349b867ae789bb756f6c45c311d16e7d/Environment.hpp#L25-L26 。并利用__attribute__((constructor)),关键字hook的相关代码在mian函数执行前执行。

生产发布前,使用objc_copyImageNames方法记录使用的所有动态库,做成白名单,在运行过程中,再运行objc_copyImageNames去查看当前的动态库是否一致

12. 应用签名的原理

具体文章

假如 App 是只能从 App Store 上下载

由**苹果官方生成一对公私钥,**在 iOS 系统中内置一个公钥私钥由苹果后台保存

我们把 App 上传到 App Store 时,苹果后台用私钥对 App 数据进行签名,iOS 系统下载这个 App 后,用公钥验证这个签名,如果签名正确则这个 App 肯定是由苹果后台认证的,并且没有被修改或损坏。

请添加图片描述

请添加图片描述

  1. Mac 用公钥去申请数字证书
  2. Mac 的私钥去签名app
  3. 把app的签名和证书放到一起 == 可执行包

数字证书只能设备里面的公钥解开,
验证APP的签名只能mac的公钥(而mac的公钥在数字证书里面)。

这样那个安装任何app , 所以用描述文件限制

请添加图片描述

13. DNS

具体文章

2、DNS缓存

DNS缓存(DNS Caching):为了改善时延性能并减少在因特网上到处传输的DNS报文数量,DNS广泛使用了缓存技术。
在一个请求链中,当某DNS服务器接收一个DNS回答时,它能将该回答中的信息缓存在本地存储器中。那么另一个对相同主机名的查询到达该DNS服务器时,该DNS服务器就可以提供所要求的IP地址,即使它不是该主机名的权威服务器。

DNS解析过程

1、发起基于域名的请求后,首先检查本地缓存(浏览器缓存–>操作系统的hosts文件)
2、如果本地缓存中有,直接返回目标IP地址,否则将域名解析请求发送给本地DNS服务器
3、如果本地DNS服务器中有,直接返回目标IP地址,到这一步基本能解析80%的域名。如果没有,本地DNS服务器将解析请求发送给根DNS服务器
4、根DNS服务器会返回给本地DNS服务器一个所查询的TLD服务器地址列表
5、本地DNS服务器再向上一步返回的TLD服务器发送请求,TLD服务器查询并返回域名对应的权威域名服务器的地址
6、本地DNS服务器再向上一步返回的权威域名服务器发送请求,权威域名服务器会查询存储的域名和IP的映射关系表,将IP连同一个TTL(过期时间)值返回给本地DNS服务器
7、本地DNS服务器会将IP和主机名的映射保存起来,保存时间由TTL来控制
8、本地DNS服务器把解析的结果返回给用户,用户根据TTL值缓存在本地系统缓存中,域名解析过程结束

14. socket

socket 是一个文件,具体来说是一种双向管道

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值