2022年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


isa.png

runtime结构

super class指向父类的指针,cache_t,class_data_t(class_ro)


runtimeStruct.png

消息转发机制

消息转发.png

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. 不可变对象的不可变copy(array copy)为浅copy:只创建指针
b.其余为 深copy:开辟内存

五、多线程:

dispatch_queue线程

dispatch_semaphore信号量

1.create+1
2.signal-1
3.wait为0时执行

dispatch_group_async

1.分组任务

自旋锁与互斥锁

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方法)

二、冷启动加载:

1322408-9d9e4ee1a453668c.png
A、Premain T1:main()函数执行前

1.加载Math-O(系统可执行文件)到内存
2.加载 dyld(动态连接器)到内存
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

  1. 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的唤起。

  1. Keychain
    iOS 系统的keychain是一个安全的存储容器,它本质上就是一个sqlite数据库,它的位置存储在/private/var/Keychains/keychain-2.db,不过它索八坪村的所有数据都是经过加密的,可以用来为不同的APP保存敏感信息,比如用户名,密码等。iOS系统自己也用keychain来保存VPN凭证和WiFi密码。它是独立于每个APP的沙盒之外的,所以即使APP被删除之后,keychain里面的信息依然存在
  2. UIPasteBoard
    iOS 系统的keychain是一个安全的存储容器,它本质上就是一个sqlite数据库,它的位置存储在/private/var/Keychains/keychain-2.db,不过它索八坪村的所有数据都是经过加密的,可以用来为不同的APP保存敏感信息,比如用户名,密码等。iOS系统自己也用keychain来保存VPN凭证和WiFi密码。它是独立于每个APP的沙盒之外的,所以即使APP被删除之后,keychain里面的信息依然存在
  3. 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

  1. storyboard可配置参数远远多于xib,这也是为什么storyboard是重量级,而xib是轻量级的
    5.storyboard与xib直接继承,直接改继承的类,页面会混乱。需要重加载。

九、网络相关面试题

基础

19151132adf0955dcb08e33.png

七层协议
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层

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协议+端口号可以唯一标识一台计算机中的进程;


socket三次握手.jpg
三次握手:

确保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密码是否相等决定能否登录成功。


HMAC加密1.png

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


HMAC加密2.png

疑问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:电子密码本,就是每个块都是独立加密


    ECB加密.png

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


    CBC加密.png

    只要是对称加密都有 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;
}

数据结构

数组是有序的,链表是无序的

单链表、双链表、循环链表

链表.png
链表的增删查改
单链表的增:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容