笔记

2020-11-01  本文已影响0人  joeal

一、UI视图相关

1、UITableView数据源同步

(1)并发访问,数据拷贝
例如:在列表删除一个cell数据,同时还有一个loadmore加载,那么就需要先记录删除的数据,在加载完成后再判断一次,删除已经删除的数据。
(2)串行访问


数据源同步-串行访问

视图刷新

1、layoutSubviews

这个方法,默认没有做任何事情,需要子类进行重写 。 系统在很多时候会去调用这个方法:

1.初始化不会触发layoutSubviews,但是如果设置了不为CGRectZero的frame的时候就会触发。
2.addSubview会触发layoutSubviews
3.设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
4.滚动一个UIScrollView会触发layoutSubviews
5.旋转Screen会触发父UIView上的layoutSubviews事件
6.改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件

setNeedsLayout

标记为需要重新布局,不立即刷新,配合layoutIfNeeded会立即更新。

layoutIfNeeded

如果有需要刷新的标记,立即调用layoutSubviews进行布局。

drawRect

不能直接调用drawRect。
drawRect方法使用注意点:
1、若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate的ref并且不能用于画图。drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或者 setNeedsDisplayInRect,让系统自动调该方法。
2、若使用calayer绘图,只能在drawInContext: 中(类似于drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法
3、若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来掉用setNeedsDisplay实时刷新屏幕。

2、事件传递、事件响应

事件传递:UIApplication->UIWindow->viewcontroller->view->subviews
事件响应:subviews->view->viewcontroller->UIWindow->UIApplication->没有响应者就抛弃


事件传递

3、图像显示原理

图像显示原理1

CPU工作:
Layout:UI布局计算、文本计算
Display:绘制drawRect方法
Prepare:图片编解码,UIImage是不能直接显示的需要先解码
Commit:提交位图给GPU处理


CPU显示原理
GPU显示原理

4、UI卡顿掉帧

(1)卡顿掉帧原因:
在规定的16.7毫秒内,在下一帧到来前CPU、GPU没有共同完成下一帧图像,就会出现卡顿和掉帧


image

(2)解决
CPU层级以下在子线程中完成:

GPU层级

5、异步绘制

(1)UIView的绘制原理:
调用setNeedsDislay时候,(实际上是这个view的layer调用setNeedsDisplay方法,之后相当于在这个layer上打上一个脏标记),然后并没有立即发生当前视图的绘制工作,而是在当前runloop快要结束的时候调用CALayer的display方法,进入到当前视图真正的绘制工作的流程当中。
原因是由于要减少绘制次数,提升性能,所以要在当前runloop快要结束的时候调用CALayer的display方法。

绘制原理

(2)系统绘制流程
[UIView drawRect:]是系统开给我们的异步绘制口子,让我们可以做一些操作。


系统绘制流程

(3)异步绘制原理
通过子线程的切换,借助Global queue,在子线程中进行位图的绘制,此时主线程可以做其它的工作。等子线程绘制位图完毕,再回到主队列中提交位图,设置给CALayer的contents属性,完成一个UI控件的异步绘图过程。


异步绘制原理

1、某个时机调用setNeedsDisplay;
2、runloop将要结束时调用[CALayer display];
3、若代理实现了displayLayer将会调用此方法,在子线程中做异步绘制的工作;
4、在子线程中创建上下文、绘制控件并生成图片;
5、在主线程中设置layer.contents,将生成的视图展示在layer上。

例如我们创建了一个叫AsyncDrawLabel的UIView,实现其异步绘制关键代码在dispalyLayer:layer

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface AsyncDrawLabel : UIView

@property (nonatomic, copy) NSString *text;
@property (nonatomic, strong) UIFont *font;

@end

NS_ASSUME_NONNULL_END
#import "AsyncDrawLabel.h"
#import <CoreText/CoreText.h>

@implementation AsyncDrawLabel

- (void)setText:(NSString *)text {
    _text = text;
}

- (void)setFont:(UIFont *)font {
    _font = font;
}


// 除了在drawRect方法中, 其他地方获取context需要自己创建[https://www.jianshu.com/p/86f025f06d62] coreText用法简介:[https://www.cnblogs.com/purple-sweet-pottoes/p/5109413.html]
 
- (void)displayLayer:(CALayer *)layer {
    CGSize size = self.bounds.size;
    CGFloat scale = [UIScreen mainScreen].scale;
    // 异步绘制,切换至子线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        UIGraphicsBeginImageContextWithOptions(size, NO, scale);
        // 获取当前上下文
        CGContextRef context = UIGraphicsGetCurrentContext();
        [self draw:context size:size];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        // 子线程完成工作,切换至主线程显示
        dispatch_async(dispatch_get_main_queue(), ^{
            self.layer.contents = (__bridge id)image.CGImage;
        });
    });
}

- (void)draw:(CGContextRef)context size:(CGSize)size {
    // 将坐标系上下翻转,因为底层坐标系和 UIKit 坐标系原点位置不同。
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    // 文本沿着Y轴移动
    CGContextTranslateCTM(context, 0, size.height); // 原点为左下角
    // 文本反转成context坐标系
    CGContextScaleCTM(context, 1, -1);
    // 创建绘制区域
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
    // 创建需要绘制的文字
    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc]initWithString:self.text];
    [attrStr addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, self.text.length)];
    // 根据attStr生成CTFramesetterRef
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrStr.length), path, NULL);
    // 将frame的内容绘制到content中
    CTFrameDraw(frame, context);
}

@end

6、离屏渲染

离屏渲染意思是在当前屏幕缓冲区外,创建了一个新的缓冲区,使得GPU触发了openGL的多通道渲染管线,产生了额外开销。可能造成CPU+GPU在一帧的时间内无法完成对应操作,造成卡顿和掉帧。

为什么会产生离屏渲染

有些效果不能直接呈现到屏幕,而需要在缓冲区以外做额外的处理预合成。如图层属性的混合体没有预合成之前不能直接在屏幕中绘制,所以就需要屏幕外渲染。

(1)常见触发场景

(2)离屏渲染导致卡顿掉帧原理
离屏渲染是发生在GPU层面,使得GPU触发了OpenGL的多通道渲染管线,产生了额外的开销。

(3)UITableView等列表滑动优化
CPU层面

二、Object-C相关

1、分类(category)

(1)分类做了哪些事?

(2)分类的特点

(3)分类中添加哪些内容

2、关联对象

关键方法

(1)关联对象本质,关联对象存储在哪里

关联对象本质

3、扩展

4、代理、通知

*代理是代理模式实现、通知是观察者模式实现的跨层级消息传递机制

(1)通知实现机制
全局创建一个map表,key为notificationName,value为observes_list观察者列表


通知实现机制

(2)没移除通知/kvo导致的奔溃
在iOS9 之前,通知中心对通知观察者做了unsafe_unretain引用,而iOS
9 之后做了weak 引用,区别就是,unsafe_unretain引用就是对象释放之后,指针不会置为nil,
会造成野指针,而weak 引用,对象释放之后,指针也会置为nil,不会造成野指针的问题。

(3)通知和kvo区别

5、KVO

willChangeValueForKey:
didChangeValueForKey:
kvo实现机制
6、KVC

KVC会破坏面向对象编程的封装特性。
KVC这里的key是没有任何限制的,如果已知某个类或者实例的内部某个私有成员变量名称的话,我们在外界是可以通过已知的key来访问、设置。即破坏了面向对象的编程思想。

-(id)valueForKey:(NSString *)key
-(void)setValue:(id)value forKey:(NSString*)key
valueForKey查找相似方法
valueForKey查找相似变量 setValueForKey
7、属性关键字

读写相关:readOnly、readWrite
原子性:atomic、nonatomic
引用计数相关:retain、strong、assign、weak、copy

(1)weak/assign区别

(2)__weak和__strong
在多线程环境下在block中还需要使用__strong强引用一下对象。__strong会在block执行完成后对象的引用计数-1。这样就可以避免循环引用。

__weak的实现:
简单来说,系统有一个全局的 CFMutableDictionary 实例,来保存每个对象的 weak 指针列表,因为每个对象可能有多个 weak 指针,所以这个实例的值是 CFMutableSet(Array) 类型。
剩下我们要做的,就是在引用计数变成 0 的时候,去这个全局的字典里面,找到所有的 weak 指针,将其值设置成 nil。如何做到这一点呢?Friday QA 上介绍了一种类似 KVO 实现的方式。当对象存在 weak 指针时,我们可以将这个实例指向一个新创建的子类,然后修改这个子类的 release 方法,在 release 方法中,去从全局的。

CFMutableDictionary 字典中找到所有的 weak 对象,并且设置成 nil。

(2)copy/mutableCopy

@property(copy) NSMutableArray *array
这样声明会有何问题?
答案:copy后得到的是NSArray不可变对象,但是声明的是一个NSMutableArray,会使用append,remove等操作数组就会发生奔溃。

(3)weak修饰String相关问题

    __weak NSString *str1 = [[NSString alloc] initWithFormat:@"First Name"];
    __weak NSString *str2 = [[NSString alloc] initWithFormat:@"joealzhou"];
    __weak NSString *str3 = @"joeal zhou";
    __weak NSString *str4 = [NSString stringWithString:@"joeal zhou"];
    __weak NSString *str5 = [[NSString alloc] initWithString:@"joeal zhou"];
    NSLog(@"str1:%@ str2:%@ str3:%@ str4:%@ str5:%@", str1, str2, str3, str4, str5);
打印结果:
   str1:(null) str2:joealzhou str3:joeal zhou str4:joeal zhou str5:joeal zhou

使用str3、str4、str5方式直接生成的字符是存储在常量区。对引用计数不影响。
而使用initWithFormat:方式生成的字符串这里有两种区别:

(4)swift中的copy on write 写时复制
指的是Swift 中的值类型,并不会在一开始赋值的时候就开辟新的内存空间,只有在需要改变这个值的时候才去开辟新的内存空间,以达到优化内存的目的.

var arr1 = [1,2,3]
var arr2 = arr1//[1,2,3]
print1(address: arr1)
print1(address: arr2)
arr2.append(4)// 改变数组 arr2
print1(address: arr1)
print1(address: arr2)
 
arr1的地址:0x137e2f930
arr2的地址:0x137e2f930
改变arr2 之后:
arr1的地址:0x137e2f930
arr2的地址:0x139901670

如上 初始化数组1,在把数组1赋值给数组2.这时候两个数组的指向的内存地址是一样的,共享了他们的存储部分.当我们改变arr2的时候,共享会被检测到,这时候arr2会重新开辟内存空间,在这个新的内存空间把值复制过去.然后在对arr2 进行操作.. 即 元素复制操作只是在必要的时候发生.

扩展:
OC 中数组字典是引用类型. Swift 里数组字典是值类型.
Swift 中采用了如上所述的写时复制技术. 即当一个结构体发生了写入行为时才会有复制行为.

三、runtime相关

1、runtime数据结构

objc_object
objc_object
objc_class

cache_t里面缓存了之前查找到的方法,是用一张hashmap来实现的,为的就是能快速查找。key对应的是SEL,value对应函数地址IMP。

objc_class
isa

关于对象isa指向的是类对象,关于类对象,指向元类对象。
在oc的方法查找中,如果是实例方法则根据对象的isa指针找到其类对象,再从类对象中的方法列表中查找。如果是类方法则根据类对象的isa指针找到其元类对象,再从元类对象的方法列表中查找。


isa
Type Encodings
type encodings 整体数据结构

2、类对象元类对象、消息传递

经典题分析:


面试题

[self class]转化成objc_msgsend
[super class]转化成objc_msgsendsuper。super是一个结构体,里面的receiver就是当前对象self。所以这道题应该都是打印Phone。


super结构体
类对象元类对象关系图

特别注意最后,根元类对象的superclass指向的是根类对象。

面试题:如果有一个类方法在元类方法及元类的父类中都没有方法实现。但是有实例方法实现,会报错吗?
解析:不会,并且会调用该同名实例方法。因为如果在根元类方法列表中没有找到,就会从根类方法类别中查找同名实例方法。因为根元类的父类是根类。
类对象-元类对象关系图
消息传递
消息传递

消息转发流程:
1、调用动态解析方法resolveClassMethod:(SEL)sel,如果动态添加方法(调用class_addMethod函数)并返回YES,则结束流程
2、如果上一步没有实现动态添加方法,无论返回Yes还是No,都会调用消息接受者重定向forwardingTargetForSelector方法,如果返回重定向接受者,则当前流程结束
3、如果返回上一步nil,则会调用methodSignatureForSelector获取函数的参数和返回值类型,同时调用forwardInvocation消息通知当前对象。
4、如果上一步返回nil,消息无法处理,App crash。
即消息转发三步骤:消息动态解析、消息接受者重定向、消息重定向 如果预防崩溃,在第二步进行消息转发


消息转发

)

1、[obj foo]在编译后会转成objc_msgSend(obj, @selector(foo)),消息转发机制。
2、第二题考察的是方法的查找:先查找类的方法缓存列表,再从父类的方法缓存及方法列表中查找,都没有找到就走消息转发流程。
3、不能向编译后的类添加实例变量,因为内存布局已经结束了。但可以向动态运行时添加的类,添加实例变量。


runtime

+load

1.当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
2.当子类未实现load方法时,不会调用父类load方法
3.类中的load方法执行顺序要优先于类别(Category)
4.当有多个类别(Category)都实现了load方法,这几个load方法都会执行,
但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
5.当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致

+initalize

当使用到这个类时就会调用:类方法、实例方法、runtime发送消息
1.父类的initialize方法会比子类先执行
2.当子类未实现initialize方法时,会调用父类initialize方法,
子类实现initialize方法时,会覆盖父类initialize方法.
3.当有多个Category都实现了initialize方法,会覆盖类中的方法,
只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)

四、内存管理相关

1、内存管理方案

内存管理方案:

散列表方式:SideTables()结构,由多个SideTable组成。
散列表实现内存管理涉及到的数据结构:

自旋锁概念:

分离锁的概念:
系统为了解决只有一张SideTable造成的效率低下的问题,引入了分离锁的概念:
把内存对象的引用计数表分拆成多个部分,比如分拆成8个,就需要对这8个表分别加锁,比如对象A在ptr(1)~ptr(8)的表里,对象B在ptr(57)~ptr(64)的表里,当A和B同时进行引用计数操作时就可以并发操作,提高访问效率。


分离锁

2、MRC与ARC区别

mrc arc

引用计数retain实现
通过对象指针地址hash查找到SideTable,再hash找到存储地址。


retain

对象可以直接释放的五个判断条件(需要全部满足)
1.当前对象不是非指针类型的isa指针。
2.无弱引用
3.无关联对象
4.无c++、没采用ARC
5.没有使用side table


释放条件

3、如何添加弱引用对象

一个被声明为 __weak 的对象指针经过编译器的编译之后,会调用相应的 objc_initWeak(),然后经过一系列的函数调用栈,最终在 weak_register_no_lock() 进行弱引用的添加,具体添加的位置是通过一个哈希算法来进行位置查找的
如果我们查找的对应位置已经有了这个当前对象所对应的弱引用数组,就把新的弱引用变量添加到数组当中
如果没有当前对象所对应的弱引用数组,就创建一个,然后把第0个位置添加上最新的weak指针,后面的都初始化为nil


添加weak

4、当一个对象被释放之后,weak变量是如何处理的?

当一个对象被dealloc之后,在dealloc() 的内部实现当中会去调用弱引用清除的相关函数,然后在weak_clear_no_lock()当中,会根据当前对象指针查找弱引用表,把当前对象相对应的弱引用都取出来得到一个entry数组,然后遍历这个数组当中的弱引用指针,分别置为nil

5、autoreleasepool

实现:是以栈为节点,通过双向链表的形式组合而成的。并且和线程是一一对应的。

autoreleasepool为何可以多层嵌套?
多层嵌套实际上是多次插入哨兵对象。AutoReleasePool的嵌套实际上就是AutoReleasePoolPage::Push调用 ,这个调用实质上就是插入一个哨兵 ,而是否增加链表节点 取决于当下的Page是否是满的。

6、图片加载

使用imageNamed:加载图片:

使用 imageWithContentOfFile:加载图片:

结论:
如果图片较小,并且频繁使用的图片,使用imageName:来加载图片(按钮图片/主页图片/占位图)
如果图片较大,并且使用次数较少,使用 imageWithContentOfFile:来加载(相册/版本新特性)

五、Block

什么是block?
block是封装了函数及其上下文的一个对象。

1、Block截获变量规则

2、什么时候使用__block修饰符?

在block中对被截获变量进行赋值时需要添加__block。

{
  NSMutableArray *array = [NSMutableArray array];
void (^block)(void) = ^{
// 下面这种情况下就可以不用添加__block
  [array addObject:@123];
// 赋值就需要添加__block
  array = [NSMutableArray array];
  };
block();
}

3、block有哪几类

4、block中的__forwarding指针

当block在栈上时__forwarding指针指向的是自身,当发生copy操作时指针指向的是堆上的block变量。__forwarding存在的意义是:不管block在任何内存位置,都可以顺利访问同一个__block变量


block

5、block的循环引用问题

解决这种循环引用需要在使用完blockSelf变量后主动置为nil,但是这样也会导致一个问题,只有调用了block才会断开,没调用该block时,循环引用一直存在。


image

六、多线程

1、GCD

以同步方式提交任务,无论在哪个队列都将在当前线程中执行。


同步并行队列

因为viewDidLoad也是在主队列中,将和gcd中的任务引发队列死锁。如果换成自定义串行队列就没问题。


队列引发的死锁
队列死锁

GCD底层创建的一个线程没有开启runloop,所以performSelector不会执行。


image

那么如何在GCD线程中开启runloop呢?

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
  [self performSelector:@selector(test) withObject:nil afterDelay:2];
  // 这句代码要在performSelector后面执行
  [[NSRunLoop currentRunLoop] run];
});

因为run方法只是尝试想要开启当前线程中的runloop,但是如果该线程中并没有任何事件(source、timer、observer)的话,并不会成功的开启。

2、GCD实现多读单写dispatch_barrier_async

image

3、dispatch_group_async()

应用到具体实例中就是,下载多张小图拼成一张大图。


image

4、NSOperation

任务状态:

状态控制:

系统是如何移除状态isFinished=YES的NSOperation?

是通过kvo来实现的。

5、NSThread

如何启动的?

NSThread的启动流程:
首先调用start,创建pthread调用main方法,再调用performSelector,最后调用exit。
涉及到NSThread的考察都是结合常驻线程的,一般在对应的入口函数selector中添加一个runloop,来达到实现一个常驻线程的目的。

用过哪些锁?

1、synchronize
一般创建单例对象时使用的。
2、automic
修饰属性关键字,只作用于赋值,不包括操作对象。
3、NSLock
4、NSRecursiveLock
递归锁
5、OSSpinlock
自旋锁,常用于轻量级数据访问。
6、dispatch_semaphore_t
信号量访问。
dispatch_semaphore_wait()内部实现:
if value-1<0,将当前进程状态设置为等待。将该进程的PCB插入响应等待队列。
dispatch_semaphore_singal()内部实现:
if value+1<=0,则会唤醒一个等待队列中的一个进程,改变其状态为就绪状态,并将其插入就绪队列。

六、Runloop

runloop是在内部维护事件循环来对事件/消息处理的一个对象。内核态和用户态的相互切换。被唤醒就是内核态切换到用户态。休眠时就是用户态到内核态。

事件循环Event Loop:
1)没有消息需要处理时,进程或者线程会进入休眠状态,而休眠状态的过渡相当于把当前线程的控制权转移给了内核态。
2)有消息需要处理时,就会有一个从用户态到内核态的状态切换。
3)维护的事件循环可以用来不断的处理消息或事件,对他们进行管理,如果没有消息进行处理,会从用户态切换到内核态,进行资源的休眠避免资源占用;当有消息进行处理时,会发生从内核态到用户态的切换,当前用户线程会被唤醒;
状态的切换是回答该问题的关键点。


image

main函数为什么能保持一直运行的状态而不退出?
在main函数中所调用的UIApplicationMain函数内部会启动主线程的runloop,而runloop又是对事件循环的一种维护机制,可以做到有事做的时候做事,没有事情做的时候会通过用户态到内核态的切换,避免资源占用,使当前线程处于休眠状态。
注意:等待不等于死循环

CFRunLoopSource:

source0:需要手动唤醒线程,在我们添加一个source0到对应runloop中,并不会主动唤醒当前线程,需要手动唤醒,把当前线程从内核态切换到用户态。
source1:具备唤醒线程的能力

CommonMode的特殊性:

NSRunLoopCommonModes字符串常量来表达CommonMode。
1)CommonMode并不是实际存在的mode。
2)是同步source、timer、observer到多个mode的一个技术方案。

runloop和线程一一对应,runloop有多个mode,mode有多个source/observer/timer。


image

RunLoop事件循环机制

从屏幕上点击开始系统发生了什么?
调用了main函数之后,会调用UIApplicationMain,在内部会启动主线程的runloop,进过一系列的处理runloop处于休眠状态。如果此时点击屏幕产生了mach-port,最终转成source1事件,把主线程唤醒,运行处理。当我们把程序杀死时,会触发kCFRunloopExit通知,即将退出runloop,线程被销毁。

唤醒操作有:Source1、timer事件、外部手动唤醒。


image

RunLoop总结

1、怎样保证子线程数据回来更新UI的时候不打断用户的滑动操作?
用户滑动操作时runloop是运行在kCFRunLoopUITrackingMode下,网络请求一般放在子线程中,子线程返回给主线程的数据要抛回给主线程进行UI更新,把这部分的逻辑包装起来提交到主线程defaultMode下,这样进行mode隔离就避免了问题。

2、如何实现一个常驻线程:
1)创建一个runoop。
2)给runloop添加source/timer/observer事件以及port。
3)调用run方法。
注意:
运行的模式和资源添加的模式必须是同一个,否则可能由于外部使用while循环会导致死循环。


image

3、runLoop与线程是怎样的关系?
1)两者一一对应的关系
2)一个线程默认是没有runloop,需要手动加上runloop。

七、HTTP网络相关

HTTP协议

image
image

1、请求方法
GET POST HEAD DELETE PUT OPTIONS

2、GET和POST方式的区别 标准答案:从语义的角度来回答

3、连接建立流程:
1)通过TCP的三次握手建立连接。
2)在这条连接上进行http的请求和响应。
3)经历TCP的四次挥手进行连接的释放。


image

4、HTTP的特点:

5、HTTP的持久连接和非持久连接
非持久连接的定义:
每次进行http请求都是重新创建一个连接,经历三次握手和四次挥手。
持久连接的定义:
打开一条tcp通道,多个http请求在同一条tcp通道上进行,在一段time后关闭。


image

持久连接头部字段:

持久连接中,怎样判断一个请求是否结束?
1)通过响应中的content-length字段的值来判断。
2)chunked,比如通过post请求server端给客户端可能会多次响应返回数据,当有多个块通过http的tcp连接传给客户端时,每一个报文都会带有chunked字段,而最后一个块是一个空的chunked。所以可以通过判断哪个chunked是空的来判断前一个网络请求是否结束。

6、charles抓包原理是怎样的?
利用了http的中间人攻击这个漏洞。
中间人攻击的定义:
当client发送一个http请求时,是由中间人进行hold住,然后中间人假冒client的身份向server端进行同样的请求,然后server端返回结果给中间人,再由中间人返给client。
如果使用http进行请求或者响应时,中间人可以篡改我们发起的请求参数,server端发回的数据也可以被篡改之后再发给client。


image

7、HTTPS
HTTPS其实是HTTP加上了ssl/tls加密,是一种安全的通信方式。

8、HTTPS建立连接流程
HTTPS建立流程描述1:
Client端:发送TLS版本号,random number C,所有支持的加密算法给服务器进行协商;
Server端:商定好加密算法,random number S,server证书发给客户端。
Client端:
1)验证server证书;
2)利用预主密钥(对称加密中用到的密钥),random numberC,andom numberS三个值通过一定的算法合成会话密钥后,通过server的公钥进行加密传输。

HTTPS建立流程描述2:
Server端:
1)通过私钥解密收到的会话密钥,得到预主密钥。
2)利用预主密钥(对称加密中用到的密钥),random numberC,andom numberS三个值通过一定的算法合成会话密钥。

HTTPS建立流程描述3:
Client端 发送一条经过会话密钥加密的握手消息给server端;
Server端 发送一条经过会话密钥加密的握手消息给client端;
来验证安全通道是否已经建立完成。


image

9、HTTPS都使用了哪些加密手段?为什么
1)连接建立过程中使用非对称加密,非对称加密很耗时。
2)后续通信过程使用对称加密
非对称加密:使用的是一对私钥公钥进行加密算法。
对称加密:使用的是同一个密钥进行加密算法。

10、为什么需要进行三次握手?【为了应对网络中存在的延迟或者重复数据的问题。】
比如:客户端发送syn同步报文时,超时了,会触发超时重传策略,又发送一条syn报文,服务端收到了重传的,回复了syn同步报文和ack确认报文,此时超时的syn同步报文被服务器收到,服务端也会回复syn同步报文和ack确认报文,客户端实际只需要建立一次连接,通过第三次握手确认,即回复服务端ack确认报文,服务端会忽略没有确认报文的连接

11、为什么断开连接需要四次挥手?
客户端和服务端建立的TCP通道是双向通道:
即:一条通道双方都可以接收和发送。
正是因为这样的双通道机制所以需要双方面的连接释放,所以需要四次挥手。先断开的是客户端发送到服务端,第二次是断开服务端到客户端。


image

12、UDP,用户数据报协议:
1)无连接 不用在数据传输之前进行连接和释放连接。
2)尽最大努力交付
3)面向报文 既不合并,也不拆分。

13、TCP可靠传输表现在哪些方面:
1)无差错
2)不丢失
3)不重复
4)按序到达

14、TCP可靠传输是通过停止等待协议实现的:
四方面理解:
1)无差错情况
2)超时重传
3)确认丢失
4)确认迟到

15、TCP流量控制
基于滑动窗口协议。接收方可以根据可接收的大小动态修改发送方的窗口大小,这里就体现了流量控制。
流量控制:
基于滑动窗口协议:

16、TCP拥塞控制

17、cookie和session
cookie性质:
1)客户端发送的cookie在http请求报文的cookie首部字段中。
2)服务器端设置http响应报文的set-cookie首部字段。

session工作流程:
1)客户端发送http请求报文,服务器端会进行2步,比如记录用户状态,密码和用户名,同时会生成sesssionid,再把sessionid用setCookie设置给http响应报文的头部发给客户端;
2)客户端在后续的请求过程中,在http请求头部字段的cookie中,设置所接收到的sessionid,这样server就可以通过sessionid来识别用户。

1、清除cookie
1)新cookie覆盖旧cookie;
2)覆盖规则:name,path,domain等需要与原cookie一致。
3)设置cookie的expires=过去的一个时间点,或者maxAge = 0,相当于说明这个cookie是无效的;

DNS相关

1、DNS解析
将需要访问的域名发送给DNS服务器,DNS服务器会返回给客户端对应的IP地址,然后客户端用这个IP地址采用HTTP连接该域名对应的服务器进行交互。

2、DNS解析查询方式

3、DNS解析存在的问题

4、解决DNS劫持问题

5、DNS和HTTP的关系?
没关系。DNS解析是发生在HTTP连接之前。DNS解析请求使用UDP数据报,端口号53(httpDNS是使用http协议向DNS服务器的80端口进行请求。)

八、设计模式相关

一、六大设计原则

1:单一职责原则 一个类只负责一件事,例如:UIView与CALayer
2:依赖倒置原则 抽象不应该依赖于具体实现,具体实现可以依赖于抽象 (即上层业务调用时,不关心具体实现,只关心接口)
3:开闭原则 对修改关闭,对扩展开放, 例如:类的定义
4:里氏替换原则 父类可以被子类无缝替换,且原有功能不受任何影响 例如:KVO,当开始监听变量的时候,系统在动态运行时已自动生成并指向子类,对外暴露的还是在操作父类,实际上系统内部是对子类的操作
5:接口隔离原则 使用多个专门的协议,而不是一个庞大臃肿的协议, 例如:UITableView代理
6:迪米特法则 一个对象对其他对象应尽可能少的了解,高内聚,低耦合

二、六大设计模式

1、责任链模式
比如:事件响应链
责任链模式就是为一个请求或者一个动作创建一个接收者对象的链,这条链上的每一个对象都可以去响应和处理这个请求和动作,把发送者和接收者进行解耦,在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

2、桥接模式
ClassA、ClassB为抽象类,A1、A2、A3和B1、B2、B3分别为对应的子类


image

3、适配器模式

4、单例模式
单例重写方法必不可少
原因:主要为了规避外部调用单例时没有调用share方法而用alloc的方法或者copy的方法,外部可能会对单例对象进行copy操作来创建一个新的对象

image

5、命令模式
命令模式是做行为参数化的,作用是降低代码重合度

九、算法

有序数组合并

创建两个指针分别指向初始两个有序数组,对比两个值,小的放入新数组同时移动小的指针。一直循环下去直到指针移动到末尾,那么把剩余值全部放入新数组。


截屏2021-03-05 16.03.04.png

字符串反转

定义两个指针分别指向开头和结尾,替换其值,再begin+1 end-1直到中间说明全部交换完了。

截屏2021-03-05 16.06.42.png

两个视图寻找共同父视图

image

无序数组的中位数

使用快速排序,当左右长度一样说明当前值就是数组的中位数。


image

十、swift各版本

Swift2

Swift3

Swift4

Swift5

十一、xcode打包编译过程

1、写入辅助文件:将项目的文件结构对应表、将要执行的脚本、项目依赖库的文件结构对应表写成文件,方便后面使用;并且创建一个 .app 包,后面编译后的文件都会被放入包中;
2、运行预设脚本:Cocoapods 会预设一些脚本,当然你也可以自己预设一些脚本来运行。这些脚本都在 Build Phases 中可以看到;
3、编译文件:针对每一个文件进行编译,生成可执行文件 Mach-O,这过程 LLVM 的完整流程,前端、优化器、后端;
4、链接文件:将项目中的多个可执行文件合并成一个文件;
5、拷贝资源文件:将项目中的资源文件拷贝到目标包;
6、编译 storyboard 文件:storyboard 文件也是会被编译的;
7、链接 storyboard 文件:将编译后的 storyboard 文件链接成一个文件;
8、编译 Asset 文件:我们的图片如果使用 Assets.xcassets 来管理图片,那么这些图片将会被编译成机器码,除了 icon 和 launchImage;
9、运行 Cocoapods 脚本:将在编译项目之前已经编译好的依赖库和相关资源拷贝到包中。
10、生成 .app 包
11、将 Swift 静态库拷贝到包中
12 、对包进行签名
13、完成打包

在上述流程中:2 - 9 步骤的数量和顺序并不固定,这个过程可以在 Build Phases 中指定。Phases:阶段、步骤。这个 Tab 的意思就是编译步骤。其实不仅我们的整个编译步骤和顺序可以被设定,包括编译过程中的编译规则(Build Rules)和具体步骤的参数(Build Settings),在对应的 Tab 都可以看到。

本地存储的方式

1、NSKeyedArchiver归档(NSCoding)序列化
2、NSUserDefaults:用来保存应用程序设置和属性、用户保存的数据。
3、NSFileManager write 的方式直接写入磁盘
4、SQLite:采用SQLite数据库来存储数据
5、coredata
6、keychain:跨APP

NSUserDefaults与plist的区别

优化相关

1、包优化

2、性能优化

3、错误处理

swift闭包

1、自动闭包

//函数声明 参数是一个自动闭包 
func removeNameAuto(nameIndex: @autoclosure ()->String){
    print("auto ameArray first name is \(nameIndex())")
}
//函数调用并传入一个普通的表达式,因为是自动的闭包,会将该普通的表达式自动转化为闭包传入。

removeNameAuto(nameIndex: nameArray.remove(at: 0))

swift消息派发方式

一、直接派发
二、函数表派发

类的方法默认使用函数派发的方式

三、消息派发

方法前面加上dynamic来支持消息派发(注意@objc只是用于把方法暴露给ObjectiveC,使用的还是函数表派发方式)

四、协议Protocol

协议所指向的对象,只有在运行时才能确定类型,Swift对于协议默认都使用函数表派发

五、NSObject

String和NSString区别

Swift中Struct和Class的区别

swift中的闭包和oc的block

上一篇下一篇

猜你喜欢

热点阅读