级别方面:
 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
 
 ### runtime结构
 super class指向父类的指针,cache_t,class_data_t(class_ro)
 
### [消息转发机制]
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方法)
### 二、冷启动加载:
 
##### A、Premain T1:main()函数执行前
 1.加载[Math-O](https://www.jianshu.com/p/1429a98542c3)(系统可执行文件)到内存     
 2.加载 [dyld](https://www.jianshu.com/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 2021/5/20.
 //  Copyright © 2021 景彦铭. 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 2021/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直接继承,直接改继承的类,页面会混乱。需要重加载。
 # 九、网络相关面试题
 ### 基础
 
 七层协议
 应用层
 表示层
 会话层
 传输层
 网络层
 数据链路层
 物理层
 ### 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协议+端口号可以唯一标识一台计算机中的进程;
 
##### 三次握手:
 确保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破解网站](http://www.cmd5.com/)。可以说嗖地一下就破解了你的MD5加密!!!
##### 2.加“盐”
 可以加个“盐”试试,“盐”就是一串比较复杂的字符串。加盐的目的是加强加密的复杂度,这么破解起来就更加麻烦,当然这个“盐”越长越复杂,加密后破解起来就越麻烦,不信加盐后然后MD5加密,再去到[md5破解网站](http://www.cmd5.com/)破解试试看,他就没辙了!!!
 哈哈,这下应该安全了吧!答案是否定的。如果这个“盐”泄漏出去了,不还是完犊子吗。同学会问,“盐”怎么能泄漏出去呢?其实是会泄漏出去的。比如苹果端、安卓端、前端、后台等等那些个技术人员不都知道吗。。都有可能泄漏出去。又有同学说那就放在服务器吧,放在服务器更加不安全,直接抓包就抓到了!!!
 加固定的“盐”还是有太多不安全的因素,可以看出没有百分百的安全,只能达到相对安全(破解成本 > 破解利润),所以一些金融的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密码是否相等决定能否登录成功。

这样一来好像安全了很多哎!即使黑客拿到了客户KEY,也只能拿到一个用户的信息,也就是说只丢失了一个客户的信息。然而上面的加“盐”方式加密,如果“盐”泄漏了,那丢失的可是所有用户信息啊。安全性有了很大提升有木有!!!
 但是这还是不够安全,还可以更佳安全!
 以登录为例:当用户点击登录时,会生成HMAC密码,然后用HMAC密码拼接上一个时间串(服务器当前时间,201801171755,只到分钟),然后一起MD5加密,最后客户端会把加上时间的HMAC值发给服务器;这时候服务器也会用已经存起来的HMAC密码拼接上一个时间串(服务器当前时间),然后一起MD5加密,最后用这个加密后的HMAC值和客户端发来的进行HMAC值对比,对此一样则登录成功!!!
 
疑问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:电子密码本,就是每个块都是独立加密

 CBC:密码块链,使用一个密钥和一个初始化向量(IV)对数据执行加密转换

 只要是对称加密都有 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;
 }
 ```
### 数据结构
 数组是有序的,链表是无序的
#### 单链表、双链表、循环链表
 
 ##### 链表的增删查改
 ###### 单链表的增:
 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
  绝对免费
 快速,简单的使用
 没有使用限制
 为了速度和性能,运行在自己的持久化引擎上
 快速的处理大量数据
[算法的基本介绍]
 [数据结构的基础介绍]
 [数据结构面试题]
### 附:思维导图链接
 链接: https://pan.baidu.com/s/1vqDq_X-0ryR_BOlvva0Gdg 
 提取码: 8388 
代码仓库地址
 https://gitee.com/lvfeijun/object-c.git










