100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > iOS面试题简答题

iOS面试题简答题

时间:2023-02-17 14:56:31

相关推荐

iOS面试题简答题

级别方面:

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。