2022年iOS面试题简答题

news/2024/7/20 5:40:27 标签: ios, objective-c, swift

级别方面:
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-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7042f3fe3f37431bb34459b02c45eba7~tplv-k3u1fbpfcp-watermark.image?)


### runtime结构
super class指向父类的指针,cache_t,class_data_t(class_ro)
![image.png](https://p9-juejin.byteimg.com/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-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9c1ac4835d3d44f384ffacfa7ab77c45~tplv-k3u1fbpfcp-watermark.image?)

##### 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直接继承,直接改继承的类,页面会混乱。需要重加载。


# 九、网络相关面试题
### 基础
![image.png](https://p6-juejin.byteimg.com/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-juejin.byteimg.com/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破解网站](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密码是否相等决定能否登录成功。

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/153f1bf9c61a4854b295cf0921003367~tplv-k3u1fbpfcp-watermark.image?)

这样一来好像安全了很多哎!即使黑客拿到了客户KEY,也只能拿到一个用户的信息,也就是说只丢失了一个客户的信息。然而上面的加“盐”方式加密,如果“盐”泄漏了,那丢失的可是所有用户信息啊。安全性有了很大提升有木有!!!
但是这还是不够安全,还可以更佳安全!
以登录为例:当用户点击登录时,会生成HMAC密码,然后用HMAC密码拼接上一个时间串(服务器当前时间,201801171755,只到分钟),然后一起MD5加密,最后客户端会把加上时间的HMAC值发给服务器;这时候服务器也会用已经存起来的HMAC密码拼接上一个时间串(服务器当前时间),然后一起MD5加密,最后用这个加密后的HMAC值和客户端发来的进行HMAC值对比,对此一样则登录成功!!!
![image.png](https://p1-juejin.byteimg.com/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-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e85f4d74a5a1427c845cc7a909e598d6~tplv-k3u1fbpfcp-watermark.image?)
CBC:密码块链,使用一个密钥和一个初始化向量(IV)对数据执行加密转换

![image.png](https://p1-juejin.byteimg.com/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-juejin.byteimg.com/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
 绝对免费
快速,简单的使用
没有使用限制
为了速度和性能,运行在自己的持久化引擎上
快速的处理大量数据

[算法的基本介绍]
[数据结构的基础介绍]
[数据结构面试题]

### 附:思维导图链接
链接: https://pan.baidu.com/s/1vqDq_X-0ryR_BOlvva0Gdg 
提取码: 8388 

代码仓库地址
https://gitee.com/lvfeijun/object-c.git


http://www.niftyadmin.cn/n/994034.html

相关文章

Sql Server2005 Transact-SQL 新兵器学习总结之-EXCEPT和INTERSECT运算符

1.简介EXCEPT和INTERSECT运算符使您可以比较两个或多个SELECT语句的结果并返回非重复值。 2.区别EXCEPT运算符返回由EXCEPT运算符左侧的查询返回、而又不包含在右侧查询所返回的值中的所有非重复值。INTERSECT返回由INTERSECT运算符左侧和右侧的查询都返回的所有非重复值。 3.注…

有点紧张啊

那么想去 就是不知道微创的考官在电话里会如何刁难我…… 唉 果断接着复习去……转载于:https://www.cnblogs.com/fabulouswolfzyq/archive/2010/11/14/1876794.html

jodconverter4.1.0版本改进解析

序 jodconverter 4.1.0版本的话&#xff0c;改进了api的结构&#xff0c;同时新增了local以及online的模块&#xff0c;本文就来分析一下。 maven <dependency><groupId>org.jodconverter</groupId><artifactId>jodconverter-spring-boot-starter</…

ASP.NET2.0状态管理系列(7) 应用程序状态 Application State

Application state is a global storage mechanism that is accessible from all pages in the web application. 转载于:https://www.cnblogs.com/xuxiaoguang/archive/2008/08/25/1275624.html

flutter组件化调研

# 一、组件化接入方式 跟原生组件化类似&#xff0c;共有两种方式接入&#xff1a;pod和framework 怎么配置flutter环境&#xff0c;flutter官网上讲得很详细了&#xff0c;不在累赘了 ## 1.以pod的方式接入 1.创建一个flutter_module flutter create -t module flutter_m…

个体工商户核名查询_公司刚注销完成可以注册个体户吗

大家都知道&#xff0c;营业执照是每一个公司老总的“身份证”&#xff0c;但要想获得这一凭证并没那么容易&#xff0c;不单单要准备许多申报材料&#xff0c;还需要为公司选取一个最合适的名称。昨天有个朋友向鑫鑫财务咨询&#xff1a;“我打算在衡水注册个体户&#xff0c;…

提供一个Red Hat Enterprise Linux 6 下载地址

红帽在本周三&#xff08;11月10日&#xff09;发布了其企业级Linux&#xff0c;RHEL 6的正式版红帽官方已经不用RHEL这个简称了&#xff0c;其全称叫做Red Hat Enterprise Linux&#xff09;。“红帽RHEL 6是10年研发和合作的结晶”&#xff0c;下面我给大家提供一个RHEL6 32/…

《Greenplum5.0 最佳实践》 系统参数 (二)

《Greenplum 数据库最佳实践 》 系统参数配置 系统配置 本章主要描述在Greenplum部署之前&#xff0c;系统参数的配置 文件系统 (File System) 推荐使用XFS作为Greenplum默认文件系统, 目前redhat,Centos 7.0 都开始使用XFS作为默认文件系统如果系统不支持 需要使用下面的挂载命…