级别方面:
iOS中级:基础70%,底层原理20%,架构10%
iOS高级:基础10%,底层原理50%,架构20%,算法20%
iOS架构:底层原理50%,架构20%,算法20%,手写算法10%
iOS专家:底层原理20%,架构20%,算法40%,手写算法20%
总的来说就是:
中级偏向运用,会不会使用,怎么使用,有没有使用过。
高级偏向底层原理,底层是怎么实现的,你在哪里使用过
架构偏向为什么这么设计(这样设计解决了什么问题,又出现了什么新的问题)一般都是第三方框架,比如ASI和AFN,http2.0和http3.0
专家偏向这两个算法有什么区别,为什么这里要用这个算法,而不是用别的算法,比如:NSHashTable,NSMapTable,NSOrderedSet
价格方面:(广州)
iOS中级:15+都要问底层,手写冒泡或快速排序,简单的算法
iOS高级:20+都要问算法,手写链表反转、二叉树反转等
iOS架构:25+,手写比高级难的算法
iOS专家:30+ 先手撸一套sd伪代码
# 一、runtime
### isa指针
实例对象的isa指向类对象,类对象的isa指向元类,元类的isa指向nsobject
![image.png](https://p3-/tos-cn-i-k3u1fbpfcp/7042f3fe3f37431bb34459b02c45eba7~tplv-k3u1fbpfcp-watermark.image?)
### runtime结构
super class指向父类的指针,cache_t,class_data_t(class_ro)
![image.png](https://p9-/tos-cn-i-k3u1fbpfcp/4f84584af8c94b80b80dbf4a1db5184a~tplv-k3u1fbpfcp-watermark.image?)
### [消息转发机制]
1.动态方法解析
首先是征询接收者所属的类,看其是否能动态添加调用的方法,来处理当前这个未知的选择子;
2.快速消息转发
寻找是否在其他对象内有该方法实现,并将该消息转发给这个对象.如果目标对象实现了该方法,Runtime这时就会调用这个方法,给你把这个消息转发给其他对象的机会.只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然返回的对象会变成return的对象,否则就会继续nurmal fowarding
3.标准消息转发
获取方法签名
如果失败就抛异常,如果成功了,并获取参数和返回值,就会创建invocation 对象,并将forworadinvocation消息转发给该对象
### associate
1.增加属性
2.KVO
a.必须有set方法
b.动态生成子类,并创建set方法,将isa指向子类
c.进行消息转发的时候,转发到该子类中
### Method Swizzling(iOS的机制)钩子
实用:防奔溃,防止hook
第三方库:fishhook
原理:方法交换
1.method_exchangeImpmentations
2.class_replaceMethod
3.method_setImpementation
### 利用ptrace防护debugserver
拿到dylib的句柄,强制指定ptrace函数指针
用方法交换,防止被ptrace调试
### Selector, Method 和 IMP 的区别与联系
一个类(Class)持有一个分发表,在运行期分发消息,表中的每一个实体代表一个方法(Method),它的名字叫做选择子(SEL),对应着一种方法实现(IMP)
/// Method
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
Method包含SEL 方法名,IMP函数地址
# 二、NSRunloop的五大类
一个线程至少有一个runloop
main的默认开启,子线程的默认关闭
### 1.RunloopRep
底层C的runloop
### 2.Model 相当于进程的状态
Default默认模式
Tracking用户交互模式
Common伪模式model集合
initialtialzation启动模式
eventRecei接受内部事件模式
### 3.Timer
等价于NSTimer
刷新有三种:GCD,NSTimer,CADisaplaytime
Timer失效:1. Runloop 没有开启,2.runloop被释放了
Timer无法释放:重写调用方法,用虚类来引用父类进行消息转发
GCD依赖于系统内核,不会长生时差
NSTimer、CADisplayLink:到一定时间后,在进行消息转发,会存在一定的时差
GCD依赖于系统内核,自身不会有
NSTimer、CADisplayLink:会循环引用,需要结合NSProxy,进行消息转发
NSProxy:虚类,消息转发的代理,主要用于弱引用父类,实现消息转发的循环引用问题
多继承:将类名传入,再进行消息转发
### 4.Observer 监听线程状态
监听七种状态
1.即将进入runloop
2.即将处理timer
3.即将处理source
4.即将sleep
5.刚被唤醒,即将退出sleep.
6.即将退出exit
7.全部活动all activity
### 5.Source
1自旋锁
0 互坼锁
Source1 :基于mach_Port的,来自系统内核或者其他进程或线程的事件,可以主动唤醒休眠中的RunLoop(iOS里进程间通信开发过程中我们一般不主动使用)。mach_port大家就理解成进程间相互发送消息的一种机制就好, 比如屏幕点击, 网络数据的传输都会触发sourse1。
苹果创建用来接受系统发出事件,当手机发生一个触摸,摇晃或锁屏等系统,这时候系统会发送一个事件到app进程(进程通信),这也就是为什么叫基于port传递source1的原因,port就是进程端口嘛,该事件可以激活进程里线程的runloop,比如你点击一下app的按钮或屏幕,runloop就醒过来处理触摸事件,你可以做个实验,在主线程的runloop中添加一个CFRunLoopObserverRef,用switch输出runloop6个状态,这时候你每点击一次屏幕,他就会输出Runloop六个状态,然后进入休眠。
• Source0 :非基于Port的 处理事件,什么叫非基于Port的呢?就是说你这个消息不是其他进程或者内核直接发送给你的。一般是APP内部的事件, 比如hitTest:withEvent的处理, performSelectors的事件.
执行performSelectors方法,假如你在主线程performSelectors一个任务到子线程,这时候就是在代码中发送事件到子线程的runloop,这时候如果子线程开启了runloop就会执行该任务,注意该performSelector方法只有在你子线程开启runloop才能执行,如果你没有在子线程中开启runloop,那么该操作会无法执行并崩溃。
简单举个例子:一个APP在前台静止着,此时,用户用手指点击了一下APP界面,那么过程就是下面这样的:
我们触摸屏幕,先摸到硬件(屏幕),屏幕表面的事件会被IOKit先包装成Event,通过mach_Port传给正在活跃的APP , Event先告诉source1(mach_port),source1唤醒RunLoop, 然后将事件Event分发给source0,然后由source0来处理。
如果没有事件,也没有timer,则runloop就会睡眠, 如果有,则runloop就会被唤醒,然后跑一圈。
# 三、[block的三种形式]
### 堆block:
在堆上的block,
用__block修饰的外部参数,会将block拷贝(copy修饰)到栈上,从栈复制到堆并被block持有
### 栈block:
在栈上的block
未copy到堆的block,有外部参数
有值域的问题,用__block将block复制到堆解决值域的问题,从而解决值域问题
### 全局block:
在静态区的block
不使用外部变量的block,或值用static修饰的,是全局block
### copy修饰:
堆block:引用计算+1
栈block:会copy到堆block
全局block:啥也不做
栈block会有值截取的域问题,其他都会随着值变化
解决block循环引用:用weak修饰
解决block延迟执行闪退问题:被释放用strong修饰
# 四、内存管理:
### 五大区
1.栈区(向下增长)(高地址)
2.堆区(向上增长)
3.静态区(全局区)
4.常量区
5.代码区(低地址)
### 循环引用
1.父类持有子类,子类强引用父类
2.weak弱引用
### SideTables()
1.自旋锁
a.自旋锁
轮询任务
Source 1
b.互斥锁
系统激活
Source 0
2.引用计数表
3.弱引用表
### copy
1.block
a.堆
引用计数+1
b.栈
copy到堆并持有
c.全局
不做任何操作
2.深copy浅copy
a.array copy 不可变copy为深copy:开辟内存
b.其余为浅copy:只创建指针
3.可变对象
# 五、多线程:
### dispatch_queue线程
1.
2.
### dispatch_semaphore信号量
1.create+1
2.signal-1
3.wait为0时执行
### dispatch_group_async
1.分组任务
2.
3.
### 自旋锁与互斥锁
1.自旋锁
a.Source 1
b.pthread_mutex、@ synchronized、NSLock、NSConditionLock 、NSCondition、NSRecursiveLock
2.互斥锁
a.Source 0
b.pthread_mutex、@ synchronized、NSLock、NSConditionLock 、NSCondition、NSRecursiveLock
### 异步执行任务,并同步
1.信号量
2.分组任务dispatch_group_async
3.队列 NSOprationQueue
### 线程锁:lock,@sythasy,
# 六、离屏渲染:
### 定义
1.当前屏幕缓冲区外新开辟一个缓冲区进行渲染操作
2.onscreen 跟 offscreen 上下文之间的切换,这个过程的消耗会比较昂贵
### 触发:
1.使用了 mask 的 layer (layer.mask)
2.需要进行裁剪的 layer (layer.masksToBounds / view.clipsToBounds)
3.设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/ layer.opacity)
4.添加了投影的 layer (layer.shadow*)
5.采用了光栅化的 layer (layer.shouldRasterize)
6.绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)
# 七、性能优化面
### 一、Tableview优化
1.减少计算,缓存高度
2.减少线程等待,图片异步加载
3.复用机制,减少UI绘制
4.减少离屏渲染的使用,用图片代替圆角,阴影等
5.时间换空间,尽量注册多的cell,减少cell的重布局
### 二、卡顿优化
1.减少主线程的等待,尽量放到子线程
2.减少一些炫酷动画和炫酷图片的使用
3.尽量把图片的的解码,下载,修剪等放到子线程
4.避免离屏渲染
### 三、包瘦身
1.减少动态库的使用,framewoek,.a
2.图片压缩
3.本地化数据中,去掉不需存储的属性属性
4.定期删除缓存文件,图片、视频等
### 四、电量优化
1.避免长时间使用硬件,能关尽量关(拾音器,摄像头,定位,陀螺仪,闪关灯等)
2.避免长时间使用酷炫动画和动图,能关则关
3.避免高并发的操作
4.避免离屏渲染
5.尽可能降低 CPU、GPU 功耗;
6.少用定时器
7.优化 I/O 操作
尽量不要频繁写入小数据,最好一次性批量写入;
读写大量重要数据时,可以用 dispatch_io,它提供了基于 GCD 的异步操作文件的 API,使用该 API 会优化磁盘访问;
数据量大时,用数据库管理数据;
8。网络优化
减少、压缩网络数据(JSON 比 XML 文件性能更高);
若多次网络请求结果相同,尽量使用缓存;
使用断点续传,否则网络不稳定时可能多次传输相同的内容;
网络不可用时,不进行网络请求;
让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间;
批量传输,如下载视频,不要传输很小的数据包,直接下载整个文件或者大块下载,然后慢慢展示
9.定位优化
如果只是需要快速确定用户位置,用 CLLocationManager 的 requestLocation 方法定位,定位完成后,定位硬件会自动断电;
若不是导航应用,尽量不要实时更新位置,并为完毕就关掉定位服务;
尽量降低定位精度,如不要使用精度最高的 KCLLocationAccuracyBest;
需要后台定位时,尽量设置 pausesLocationUpdatesAutomatically 为 YES,若用户不怎么移动的时候,系统会自暂停位置更新;
### 四、app启动
# 冷启动与热启动
### 一、区别:
冷启动:
第一次打开app或app被杀死后重新打开叫冷启动(走didFinishLaunchWithOptions方法)
热启动
app在后台且存活的状态下,再次打开app叫热启动(不走didFinishLaunchWithOptions方法)
### 二、冷启动加载:
![image.png](https://p9-/tos-cn-i-k3u1fbpfcp/9c1ac4835d3d44f384ffacfa7ab77c45~tplv-k3u1fbpfcp-watermark.image?)
##### A、Premain T1:main()函数执行前
1.加载[Math-O](/p/1429a98542c3)(系统可执行文件)到内存
2.加载 [dyld](/p/bda67b2a3465)(动态连接器)到内存
a.加载动态库:dyld 从主执行文件的 header 获取到需要加载的所依赖的动态库列表,然后找到每个 dylib,而 dylib 也可能依赖其他 dylib,所以这是一个递归加载依赖的过程
b.Rebase 和 Bind:
Rebase 在 Image 内部调整指针的指向。由于地址空间布局是随机的,需要在原来地址的基础上根据随机的偏移量做一下修正
Bind 把指针正确的指向 Image 外部的内容。这些指向外部的指针被符号绑定,dyld 需要去符号表里查找,找到 symbol 对应的实现
c.Objc setup:
1. 注册 Objc 类 2. 把 category 的定义插入方法列表 3. 保证每个 selector 唯一
d.Initializers:
1. Objc 的 +load 函数 2. C++ 构造函数属性函数 3. 非基本类型的 C++ 静态全局变量的创建(通常是类或结构体)
##### B、Aftermain T2:main()函数执行后到didFinishLaunchWithOptions
##### C、到用户看到主界面 T3:didFinishLaunchWithOptions到用户看到首页面
### 三、[冷启动优化]
* 动态库加载越多,启动越慢。
* ObjC类越多,启动越慢
* C的constructor函数越多,启动越慢
* C++静态对象越多,启动越慢
* ObjC的+load越多,启动越慢
### 五、图片优化
#### 1.异步下载/读取图片
这样可以防止这项十分耗时的操作阻塞主线程。
#### 2.预处理图片大小。
如果UIImage大小和UIImageview的size不同的话,CPU需要提前预处理,这是一项十分消耗CPU的工作,特别是在一些缩略图的场景下,如果使用了十分大的图片,不仅会带来很大的CPU性能问题,还会导致内存问题。我们可以用instruments Core Animation 的Misaligned Image debug选项来发现此问题。这里可以使用ImageIO中的CGImageSourceCreateThumbnailAtIndex等相关方法进行后台异步downsample,可以在CPU和内存上获得很好的性能。
#### 3.UIImageView frame取整。
视图或图片的点数(point),不能换算成整数的像素值(pixel),导致显示视图的时候需要对没对齐的边缘进行额外混合计算,影响性能。借助ceilf()、floorf()、CGRectIntegral()等将小数点后数据除去即可。我们可以用instruments Core Animation 的Misaligned Image debug选项来发现此问题
#### 4.使用mmap,避免mmcpy。
解码图片 iOS从磁盘加载一张图片,使用UIImageVIew显示在屏幕上,需要经过以下步骤:从磁盘拷贝数据到内核缓冲区、从内核缓冲区复制数据到用户空间。使用mmap内存映射,省去了上述第2步数据从内核空间拷贝到用户空间的操作,具体可以参考FastImageCache的实现
#### 5.子线程解码。
如果我们使用imgView.image = img; 如果图片没有解码,则会在主线程进行解码等操作,会极大影响滑动的流畅性。
#### 6.字节对齐
如果数据没有字节对齐,Core Animation会再拷贝一份数据,进行字节对齐,也是十分消耗CPU。
#### 7.iOS 12引入了Automatic Backing Store这项技术。
通过在保证色彩不失真的基础上,使用更少的数据量,去表达一个像素的颜色。在UIView.draw()、UIGraphicsImageRenderer、UIGraphicsImageRenderer.Range中是默认开启的。其实我们自己可以针对图片的特点,采用更少的byte来标示一个像素占用的空间,FastImageCache就是使用这种优化手段,有兴趣的读者可以去了解一下。
#### 8.我们日常开发中可以使用一些比较经典的图片缓存库
比如SDWebImage、 FastImageCache、YYImage等。这些第三方库替我们完成的大部分优化的工作,而且接口也十分友好。我们可也使用这些第三方库帮助我们获得更好的性能体验。
# 八、其他
## 一、NSHashTable和NSMapTable
### 1.NSHashTable
#### 定义
NSHashTable 类似NSArray和NSSet,但是NSHashTable除了strong外还能用weak
### 使用场景
当要用到多个代理时
```
// SharedObject.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SharedObject : NSObject
@property (nonatomic,strong,readonly)NSArray *delegates;
+ (instancetype)shared;
- (void)addDelegate:(id)delegate;
@end
NS_ASSUME_NONNULL_END
```
```
#import "SharedObject.h"
@implementation SharedObject
{
NSHashTable *_hashTable;
}
+ (instancetype)shared {
static SharedObject *object = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
object = [[self alloc] init];
});
return object;
};
- (instancetype)init {
if (self=[super init]) {
_hashTable = [NSHashTable weakObjectsHashTable];
}
return self;;
}
- (void)addDelegate:(id)delegate {
if (delegate) {
[_hashTable addObject:delegate];
}
}
- (NSArray *)delegates {
return _hashTable.allObjects;
}
@end
```
```
self.sharedObject = [SharedObject shared];
[self.sharedObject addDelegate:self];
```
### 2. NSMapTable
还是拿上面那个例子说明:新增一个需求,能够添加代理者和回调线程。
此时我们不好用NSHashTable来实现了,因为NSHashTable只能够添加一个参数(当然要实现也是可以的,采用中间件思想,用一个新对象来分别持有这两个参数)。 然而也有另外一种思路是采用NSMapTable我们刚好可以把两个参数分别作为key-value存储起来
```
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SharedObject : NSObject
@property (nonatomic,strong,readonly)NSArray *delegates;
+ (instancetype)shared;
- (void)addDelegate:(id)delegate dispathQueue:(dispatch_queue_t)queue_t;
@end
NS_ASSUME_NONNULL_END
```
```
#import "SharedObject.h"
@implementation SharedObject
{
NSMapTable *_mapTable;
}
+ (instancetype)shared {
static SharedObject *object = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
object = [[self alloc] init];
});
return object;
};
- (instancetype)init {
if (self=[super init]) {
_mapTable = [NSMapTable weakToStrongObjectsMapTable];
}
return self;;
}
- (void)addDelegate:(id)delegate dispathQueue:(dispatch_queue_t)queue_t {
if (delegate) {
//这里需要在delegate上包一层作为key,因为key需要能够实现NSCoping协议,同NSDictiony类似。
NSMutableOrderedSet *orderSet = [NSMutableOrderedSet orderedSet];
[orderSet addObject:delegate];
[_mapTable setObject:queue_t?queue_t:dispatch_get_main_queue() forKey:orderSet.copy];
}
}
- (NSArray *)delegates {
return _mapTable.dictionaryRepresentation.allKeys;
}
@end
```
```
self.sharedObject = [SharedObject shared];
[self.sharedObject addDelegate:self dispathQueue:dispatch_get_main_queue()];
```
## 二、NSProxy
虚类,代理的夫类
使用场景
### 1.用来实现NSTime不能释放问题
```
@interface SEEProxy : NSProxy
+ (instancetype)proxyWithObjs:obj,... NS_REQUIRES_NIL_TERMINATION;
@end
```
```
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface WeakProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;
@end
```
```
//
// WeakProxy.m
// SEEProxy
//
// Created by lvfeijun on /5/20.
// Copyright © 景彦铭. All rights reserved.
//
#import "WeakProxy.h"
@interface WeakProxy ()
@property (weak,nonatomic,readonly)id target;
@end
@implementation WeakProxy
- (instancetype)initWithTarget:(id)target{
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target{
return [[self alloc] initWithTarget:target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [self.target methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if ([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.target respondsToSelector:aSelector];
}
@end
```
### 2.用来实现多继承
```
@interface SEEProxy : NSProxy
+ (instancetype)proxyWithObjs:obj,... NS_REQUIRES_NIL_TERMINATION;
@end
```
```
#import "SEEProxy.h"
@implementation SEEProxy {
NSArray * _objs;
}
+ (instancetype)proxyWithObjs:(id)obj, ... NS_REQUIRES_NIL_TERMINATION {
NSMutableArray * objs = [NSMutableArray arrayWithObject:obj];
if (obj) {
va_list args;
va_start(args, obj);
id obj;
while ((obj = va_arg(args, id))) {
[objs addObject:obj];
}
va_end(args);
}
SEEProxy * instance = [SEEProxy alloc];
instance -> _objs = objs.copy;
return instance;
}
//- (id)forwardingTargetForSelector:(SEL)aSelector {
// __block id target;
// [_objs enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// //判断对象是否能够响应方法
// if ([obj respondsToSelector:aSelector]) {
//target = obj;
//*stop = YES;
// }
// }];
// return target;
//}
- (BOOL)respondsToSelector:(SEL)aSelector {
__block BOOL flag = [super respondsToSelector:aSelector];
if (flag) return flag;
[_objs enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
flag = [obj respondsToSelector:aSelector];
*stop = flag;
}];
return flag;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
__block NSMethodSignature * signature;
[_objs enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//判断对象是否能够响应方法
if ([obj respondsToSelector:sel]) {
signature = [obj methodSignatureForSelector:sel];
*stop = YES;
}
}];
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[_objs enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//判断对象是否能够响应方法
if ([obj respondsToSelector:invocation.selector]) {
[invocation invokeWithTarget:obj];
*stop = YES;
}
}];
}
@end
```
```
SEEProxy * fishMan = [SEEProxy proxyWithObjs:people,fish, nil];
if ([fishMan respondsToSelector:@selector(say)]) {
[fishMan performSelector:@selector(say)];
}
if ([fishMan respondsToSelector:@selector(swimming)]) {
[fishMan performSelector:@selector(swimming)];
}
```
### 3.用来实现懒加载
```
//
// LazyProxy.m
// ObjectCProject
//
// Created by lvfeijun on /5/21.
//
#import "LazyProxy.h"
@implementation LazyProxy{
id _object;// 代理对象
Class _objectClass;// 代理类
NSInvocation *_initInvocation;// 自定义 init 调用
}
+ (instancetype)lazy {
return (id)[[LazyProxy alloc] initWithClass:[self class]];
}
- (instancetype)initWithClass:(Class)cls {
_objectClass = cls;
return self;
}
- (void)instantiateObject {
_object = [_objectClass alloc];
if (_initInvocation == nil) {// 允许一些类 [SomeClass lazy] (没有调用 init)
_object = [_object init];
} else {// 调用自定义 init 方法
[_initInvocation invokeWithTarget:_object];
[_initInvocation getReturnValue:&_object];
_initInvocation = nil;
}
}
#pragma mark 消息转发
- (id)forwardingTargetForSelector:(SEL)selector {
if (_object == nil) {// _object 没有初始化
if (![NSStringFromSelector(selector) hasPrefix:@"init"]) {// 调用 init 开头之外的方法前进行 _object 初始化
[self instantiateObject];
}
}
return _object;// 将消息转发给 _object
}
// 调用自定义 init 方法会进入这个方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *signature = [_objectClass instanceMethodSignatureForSelector:selector];
return signature;
}
// 保存自定义 init 方法的调用
- (void)forwardInvocation:(NSInvocation *)invocation {
_initInvocation = invocation;
}
@end
```
## 三、数组去重
1.nsaset
2. NSArray *result = [originalArr valueForKeyPath:@"@distinctUnionOfObjects.self"];
3.新建数组重新加入,或者字典加入,
## 四、八种跨进程间的通信
1.URL scheme
这个是iOS APP通信最常用到的通信方式,APP1通过openURL的方法跳转到APP2,并且在URL中带上想要的参数,有点类似HTTP的get请求那样进行参数传递。这种方式是使用最多的最常见的,使用方法也很简单只需要源APP1在info.plist中配置LSApplicationQueriesSchemes,指定目标App2的scheme;然后再目标App2的info.plist 中配置好URLtypes,表示该App接受何种URL scheme的唤起。
2. Keychain
iOS 系统的keychain是一个安全的存储容器,它本质上就是一个sqlite数据库,它的位置存储在/private/var/Keychains/keychain-2.db,不过它索八坪村的所有数据都是经过加密的,可以用来为不同的APP保存敏感信息,比如用户名,密码等。iOS系统自己也用keychain来保存VPN凭证和WiFi密码。它是独立于每个APP的沙盒之外的,所以即使APP被删除之后,keychain里面的信息依然存在
3. UIPasteBoard
iOS 系统的keychain是一个安全的存储容器,它本质上就是一个sqlite数据库,它的位置存储在/private/var/Keychains/keychain-2.db,不过它索八坪村的所有数据都是经过加密的,可以用来为不同的APP保存敏感信息,比如用户名,密码等。iOS系统自己也用keychain来保存VPN凭证和WiFi密码。它是独立于每个APP的沙盒之外的,所以即使APP被删除之后,keychain里面的信息依然存在
4. UIDocumentInteractionController
UIDocumentInteractionController 主要是用来实现同设备上APP之间的贡献文档,以及文档预览、打印、发邮件和复制等功能。
5.端口port
原理:一个APP1在本地的端口port1234 进行TCP的bind 和 listen,另外一个APP2在同一个端口port1234发起TCP的connect连接,这样就可以简历正常的TCP连接,进行TCP通信了,然后想传什么数据就可以传什么数据了
6、AirDrop
通过 Airdrop实现不同设备的APP之间文档和数据的分享
7、UIActivityViewController
iOS SDK 中封装好的类在APP之间发送数据、分享数据和操作数据
8、APP Groups
APP group用于同一个开发团队开发的APP之间,包括APP和extension之间共享同一份读写空间,进行数据共享。同一个团队开发的多个应用之间如果能直接数据共享,大大提高用户体验
五、storyboard与xib
1.尽量不要用storyboard与xib,启动加载速度 :代码》xib〉storyboard。
2.xib文件大小(里面的配置参数也少)〈storyboard ,所以加载速度会快一点,xib和storyboard一样可以创建多个图来解决业务逻辑一样界面风格差异的问题
3.xib文件相对storyboard是轻量级的,能用代码不要用xib,能用xib不要用storyboard
4. storyboard可配置参数远远多于xib,这也是为什么storyboard是重量级,而xib是轻量级的
5.storyboard与xib直接继承,直接改继承的类,页面会混乱。需要重加载。
# 九、网络相关面试题
### 基础
![image.png](https://p6-/tos-cn-i-k3u1fbpfcp/baa1fed5350a405ab22186b116409dfd~tplv-k3u1fbpfcp-watermark.image?)
七层协议
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层
### http
##### cookies和session
AFN解决了ASI cookies存本地不安全的问题
ASI用的是cookies存本地不安全
AFN用的是session存服务器,比较安全
##### HTTP与HTTPS的区别
HTTPS解决了HTTP的安全性问题
1.https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。
2.http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl/tls加密传输协议。
3.http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4.http的连接很简单,是无状态的;HTTPS协议是由SSL/TLS+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
### socket
##### 基础
Socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写read/write –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。因为TCP协议+端口号可以唯一标识一台计算机中的进程;
![image.png](https://p6-/tos-cn-i-k3u1fbpfcp/551be5965040446bae478a3b9a447008~tplv-k3u1fbpfcp-watermark.image?)
##### 三次握手:
确保A听到B说话,B也听到A说话
A:听到我说话么
B:听到你说话,你听到我说话么
A:听到
##### 四次挥手
确保A说完了,且B知道A说完了,B说完了,且A也知道B说完了
A:我说完了
B:我知道了,等一下,我可能还没说完
B:我也说完了
A:我知道了,结束吧
socket 的data的zip解压
socket的data小端转大端
```
asyncSocket
自定义类型 2位包类型+4位包大小+ jsonStr
- (NSData *)getSendData:(id)jsonStr type:(int)type{
lvfjLog(@"发送 类型:%d 内容:%@",type,jsonStr);
NSData * cmdData = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
short a = cmdData.length + 6;
Byte pkgSize[4], typeVal[2];
pkgSize[3] = (Byte)(a >> 24);
pkgSize[2] = (Byte)(a >> 16);
pkgSize[1] = (Byte)(a >> 8);
pkgSize[0] = (Byte)(a);
typeVal[1] = (Byte)(type >> 8);
typeVal[0] = (Byte)(type);
NSMutableData * sendData = [NSMutableData dataWithBytes:typeVal length:2];
[sendData appendBytes:pkgSize length:4];
[sendData appendBytes:cmdData.bytes length:cmdData.length];
// lvfjLog(@"发送 NSMutableData %@",sendData);
return sendData;
}
```
# 十、设计模式面试题
### 一、六大设计原则
##### 1.单一职责原则
通俗地讲就是一个类只做一件事
如:CALayer:动画和视图的显示、UIView:只负责事件传递、事件响应
##### 2.开闭原则
对修改关闭,对扩展开放。
要考虑到后续的扩展性,而不是在原有的基础上来回修改
##### 3.接口隔离原则
使用多个专门的协议、而不是一个庞大臃肿的协议
如:UITableViewDataSource、UITableviewDelegate
##### 4.依赖倒置原则
抽象不应该依赖于具体实现、具体实现可以依赖于抽象。
调用接口感觉不到内部是如何操作的
##### 5.里氏替换原则
父类可以被子类无缝替换,且原有的功能不受任何影响
例如 KVO
##### 6.迪米特法则
一个对象应当对其他对象尽可能少的了解,实现高聚合、低耦合
# 十一、数据安全及加密
### 一、哈希HASH
##### 1.MD5加密
MD5加密的特点:
1. 不可逆运算
2. 对不同的数据加密的结果是定长的32位字符(不管文件多大都一样)
3. 对相同的数据加密,得到的结果是一样的(也就是复制)。
4. 抗修改性 : 信息“指纹”,对原数据进行任何改动,哪怕只修改一个字节,所得到的 MD5 值都有很大区别.
5. 弱抗碰撞 : 已知原数据和其 MD5 值,想找到一个具有相同 MD5 值的数据(即伪造数据)是非常困难的.
6. 强抗碰撞: 想找到两个不同数据,使他们具有相同的 MD5 值,是非常困难的
MD5 应用:
一致性验证:MD5将整个文件当做一个大文本信息,通过不可逆的字符串变换算法,产生一个唯一的MD5信息摘要,就像每个人都有自己独一无二的指纹,MD5对任何文件产生一个独一无二的数字指纹。
那么问题来了,你觉得这个MD5加密安全吗?其实是不安全的,不信的话可以到这个网站试试:[md5破解网站](/)。可以说嗖地一下就破解了你的MD5加密!!!
##### 2.加“盐”
可以加个“盐”试试,“盐”就是一串比较复杂的字符串。加盐的目的是加强加密的复杂度,这么破解起来就更加麻烦,当然这个“盐”越长越复杂,加密后破解起来就越麻烦,不信加盐后然后MD5加密,再去到[md5破解网站](/)破解试试看,他就没辙了!!!
哈哈,这下应该安全了吧!答案是否定的。如果这个“盐”泄漏出去了,不还是完犊子吗。同学会问,“盐”怎么能泄漏出去呢?其实是会泄漏出去的。比如苹果端、安卓端、前端、后台等等那些个技术人员不都知道吗。。都有可能泄漏出去。又有同学说那就放在服务器吧,放在服务器更加不安全,直接抓包就抓到了!!!
加固定的“盐”还是有太多不安全的因素,可以看出没有百分百的安全,只能达到相对安全(破解成本 > 破解利润),所以一些金融的app、网站等加密比较高。
下面来介绍另外两种加密方案
##### 3.SHA加密
安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准(Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。当让除了SHA1还有SHA256以及SHA512等。
SHA1有如下特性:不可以从消息摘要中复原信息;两个不同的消息不会产生同样的消息摘要。
##### 4.HMAC加密
HMAC:给定一个密钥,对明文加密,做两次“散列”,得到的结果还是32为字符串。在实际开发中,密钥是服务器生成,客户端发送请求会拿到KEY。一个账号对应一个KEY
以注册为例:当用户把账号提交给服务器,服务器会验证账号的合法性,如果合法就会生成个KEY给客户端(这个KEY只有在注册的时候会出现一次,一个账号只对应一个KEY);客户端会用拿到的KEY给密码用HMAC方式加密(32位字符串)发给服务器,最终服务器会保存这个HMAC密码。这样就注册成功了!以后再登录就会服务器就会比对这个HMAC密码是否相等决定能否登录成功。
![image.png](https://p9-/tos-cn-i-k3u1fbpfcp/153f1bf9c61a4854b295cf0921003367~tplv-k3u1fbpfcp-watermark.image?)
这样一来好像安全了很多哎!即使黑客拿到了客户KEY,也只能拿到一个用户的信息,也就是说只丢失了一个客户的信息。然而上面的加“盐”方式加密,如果“盐”泄漏了,那丢失的可是所有用户信息啊。安全性有了很大提升有木有!!!
但是这还是不够安全,还可以更佳安全!
以登录为例:当用户点击登录时,会生成HMAC密码,然后用HMAC密码拼接上一个时间串(服务器当前时间,01171755,只到分钟),然后一起MD5加密,最后客户端会把加上时间的HMAC值发给服务器;这时候服务器也会用已经存起来的HMAC密码拼接上一个时间串(服务器当前时间),然后一起MD5加密,最后用这个加密后的HMAC值和客户端发来的进行HMAC值对比,对此一样则登录成功!!!
![image.png](https://p1-/tos-cn-i-k3u1fbpfcp/5780c54b4d6a4f019c721ffb0a8f97c6~tplv-k3u1fbpfcp-watermark.image?)
疑问1.为什么一定要用服务器的时间呢?
答:因为客户端可能会修改自己的手机时间,以服务器为准比较好。
疑问2.如果网络有延迟怎么办?
答:这里服务器可以对比两次,再往后加一分钟对比一次。试想一下如果网络延迟了两分钟,还没请求到时间,那这个网络也是够了!!!
疑问3.为什么不让服务器直接修改KEY呢?
答:这样也能保证每次登录的HMAC值不一样?注意:这样做服务器会频繁的更新KEY,加大服务器的压力,一般不会去更新,除非更换密码才会更新。当然服务器可以定期去更新KEY,这样安全等级又会提高,更加安全!!
这个时候如果黑客拦截到了你加了时间戳的HMAC值,不能在两分钟内破解密码,那么他就永远登不进去了。这个密码的破解难度是很大的,代价也高,这样是不是就很安全了,是的,这样就更加安全!!!
## 二、对称加密
简介:
对称加密算法又称传统加密算法。
加密和解密使用同一个密钥。
加密解密过程:明文->密钥加密->密文,密文->密钥解密->明文。
示例:
密钥:X
加密算法:每个字符+X
明文:Hello
密钥为 1时加密结果:Ifmmp
密钥为 2时加密结果:Jgnnq
优缺点:
算法公开,计算量小,加密速度快,加密效率高
双方使用相同的钥匙,安全性得不到保证
注意事项:
密钥的保密工作非常重要
密钥要求定期更换
经典加密算法有三种:
1. DES(Data Encryption Standard):数据加密标准(现在用的比较少,因为它的加密强度不够,能够暴力破解)
2. 3DES:原理和DES几乎是一样的,只是使用3个密钥,对相同的数据执行三次加密,增强加密强度。(缺点:要维护3个密钥,大大增加了维护成本)
3. AES(Advanced Encryption Standard):高级加密标准,目前美国国家安全局使用的,苹果的钥匙串访问采用的就AES加密。是现在公认的最安全的加密方式,是对称密钥加密中最流行的算法。
加密模式:
ECB:电子密码本,就是每个块都是独立加密
![image.png](https://p6-/tos-cn-i-k3u1fbpfcp/e85f4d74a5a1427c845cc7a909e598d6~tplv-k3u1fbpfcp-watermark.image?)
CBC:密码块链,使用一个密钥和一个初始化向量(IV)对数据执行加密转换
![image.png](https://p1-/tos-cn-i-k3u1fbpfcp/ec3d25c1f8cb410ba25e5b86b7f7d90e~tplv-k3u1fbpfcp-watermark.image?)
只要是对称加密都有 ECB和 CBC模式,加密模式是加密过程对独立数据块的处理。对于较长的明文进行加密需要进行分块加密,在实际开发中,推荐使用CBC的,ECB的要少用。
## 三、非对称加密RSA
简介:
1. 对称加密算法又称现代加密算法。
2. 非对称加密是计算机通信安全的基石,保证了加密数据不会被破解。
3. 非对称加密算法需要两个密钥:公开密钥(publickey) 和私有密(privatekey)
4. 公开密钥和私有密钥是一对
如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密。
如果用私有密钥对数据进行加密,只有用对应的公开密钥才能解密。
特点:
算法强度复杂,安全性依赖于算法与密钥。
加密解密速度慢。
与对称加密算法的对比:
对称加密只有一种密钥,并且是非公开的,如果要解密就得让对方知道密钥。
非对称加密有两种密钥,其中一个是公开的。
##### RSA应用场景:
由于RSA算法的加密解密速度要比对称算法速度慢很多,在实际应用中,通常采取
数据本身的加密和解密使用对称加密算法(AES)。
用RSA算法加密并传输对称算法所需的密钥。
### SSL
对称加密MD5 +非对称加密RAS
# 十二、数据结构与算法
### 算法
##### 手写:冒泡
最值先出现在末尾,相邻元素两两比较
```
for (int i=0; i<array.count-1; i++) {
for (int j=0; j<array.count-1-i; j++) {
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
```
##### 手写:选择
最值先出起始端,之后的每个值都和当前值作比较
```
for (int i=0; i<array.count-1; i++) {
for (j=i+1; j<array.count; j++) {
if (array[i]>array[j]) {
int temp = array[I];
array[i] = array[j];
array[j] = temp;
}
}
}
```
##### 机试:杨辉三角
```
func getRow(_ rowIndex: Int) -> [Int] {
if rowIndex == 0 { return [1] }
if rowIndex == 1 { return [1, 1]}
var result:[Int] = [1, 1]
while result.count < rowIndex + 1 {
result = calnew(result)
}
return result
}
func calnew(_ last: [Int]) -> [Int] {
var result:[Int] = [1]
for i in 1..<last.count {
result.append(last[i] + last[i - 1])
}
result.append(1)
return result
}
```
##### 机试:菱形矩阵
```
const int row = 6; // 5行,对于行对称的,使用绝对值来做比较好
const int max = row / 2 + 1;
for (int i = -max + 1; i < max; ++i) {
for (int j = 0; j < abs(i); ++j, std::cout << " ");
for (int j = 0; j < (max - abs(i)) * 2 - 1; ++j, std::cout << "*");
std::cout << std::endl;
}
```
### 数据结构
数组是有序的,链表是无序的
#### 单链表、双链表、循环链表
![image.png](https://p3-/tos-cn-i-k3u1fbpfcp/4ecc2858ed2048ab8f13fc001fd1787f~tplv-k3u1fbpfcp-watermark.image?)
##### 链表的增删查改
###### 单链表的增:
1.增加到第一个数据:将数据的next指针指向原来的第一个数据
2.增加到最后一个数据:将最后一个数据的指针指向改数据,将该数据的next指针指向nil
3.中间插入一个数据:
a.将该数据的next指针指向要插入的下一个数据
b.将要插入的前一个数据的next指针指向该数据
###### 双链表的增:
1.增加到第一个数据:
该数据
a.将该数据的pre指针指向nil
b.将该数据next指针指向原来第一个数据
原来的第一个数据
c.将原来一个数据的pre指针指向该数据
2.增加到最后一个数据:
该数据
a.将该数据的pre指针指向原来最后一个数据
b.将该数据next指针指向nil
原来的最后一个数据
c.将最后一个数据的next指针指向该数据
3.中间插入一个数据:
该数据
a.将该数据的pre指针指向插入的插入的前一个数据
b.将该数据next指针指向插入的后一个数据
插入的前一个数据
c.将插入的前一个数据的next指针指向该数据
插入的后一个数据
d.将插入的后一个数据的pre指针指向该数据
###### 循环链表的增:
参考双链表的:中间插入一个数据
###### 单链表的删:
##### 链表的反转
##### 集合结构
说白了就是一个集合,就是一个圆圈中有很多个元素,元素与元素之间没有任何关系 这 个很简单
##### 线性结构
说白了就是一个条线上站着很多个人。 这条线不一定是直的。也可以是弯的。也可以 是值的 相当于一条线被分成了好几段的样子 (发挥你的想象力)。 线性结构是一对一的关系
### 数据结构
说白了 做开发的肯定或多或少的知道 xml 解析 树形结构跟他非常类似。也可以想象
成一个金字塔。树形结构是一对多的关系
##### 树形结构
说白了 做开发的肯定或多或少的知道 xml 解析 树形结构跟他非常类似。也可以想象
成一个金字塔。树形结构是一对多的关系
二叉树
正二叉树的
二叉树的反转
##### 图形结构
这个就比较复杂了。他呢 无穷。无边 无向(没有方向)图形机构 你可以理解为多对
多 类似于我们人的交集关系
# 十三、其他
### AFN和ASI的区别
ASI用cookies 保存在本地,不安全
AFN用session 保存服务器,相对安全
互坼锁,回到主线程
### 数据库
##### sql
简单的,方便,小的,轻量级的,跨平台
独立于服务器
零配置
多进程和线程下安全访问。
在表中使用含有特殊数据类型的一列或多列存储数据。
##### coredata
基于对象,非跨平台
比SQLite使用更多的内存
比SQLite使用更多的存储空间
比SQLite在取数据方面更快
##### [realm]
基于对象,跨平台,
方案更快,更高效,跨平台,专门为 iOS 和 Android
绝对免费
快速,简单的使用
没有使用限制
为了速度和性能,运行在自己的持久化引擎上
快速的处理大量数据
[算法的基本介绍]
[数据结构的基础介绍]
[数据结构面试题]
### 附:思维导图链接
链接: /s/1vqDq_X-0ryR_BOlvva0Gdg
提取码: 8388
代码仓库地址
/lvfeijun/object-c.git