iOS基础面试题
1、#import 跟#include 又什么区别,@class呢? #import<> 跟 #import"" 有什么区别?
- #import是OC引入头类文件的关键字,#include是C/C++引入头文件的关键字;#import引入的头文件,无论你引入几次,都会只做一次引入操作,防止重复倒入,相当于#include+#pragma once;
- @class 是声明类的,多用于头文件相互引用;
- #import<>用于导入系统头文件和三方库,#import"" 用于导入用户头文件。
2、属性atomic/nonatomic,assign/week/retain/strong/copy,readwrite/readonly ,@synthesize/@dynamic各是什么作用,在那种情况下用?
- atomic:原子性访问属性,会对setter和getter方法加锁,保证属性读写是安全的;但是在多线程中对集合类元素的添加删除操作没有加锁,无法保证线程安全;由于对读写方法加锁操作,导致性能降低。
- noatomic:非原子性访问属性,无法保证线程安全,但是性能比atomic有提升。由于atomic并不能完全保证线程安全,所以一般使用noatomic。
- assign:assign修饰基础数据类型或对象类型,不会使引用计数+1;修饰对象类型时,和week类似,但是在指向对象被释放时,不会把指针指向nil,容易造成野指针crash,所以一般用于修饰基础数据类型。
- week:week只能修饰对象类型属性,不会使引用计数+1,当对象释放时,指针会指向nil;runtime会维护一个week表,是一个哈希表,key为week指针指向的对象的地址,value为所有指向这个对象的week指针的地址数组;当该对象引用计数为0时,runtime会根据对象地址查找对应的弱引用数组,然后遍历数组,把所有的week指针指向nil,避免野指针的出现。
- retain/strong:修饰对象属性,使引用计数+1,ARC下一般使用strong;
- copy:一般用于NSString和block,属性赋值时,会copy一份新变量给属性变量;
- readwrite:可读写属性,默认属性修饰关键字;
- readonly:只读属性,不允许外部修改属性变量;可通过KVC修改属性变量值,但不建议。
- @synthesize:告诉编译器,需要为该属性生成setter和getter方法,默认修饰;一般用于类实现protocol属性时;
- @dynamic:告诉编译器,不生成setter和getter方法,将有程序员自己实现。
3、浅copy和深copy
- 浅copy是指针copy,copy一份指针,指向对象内存地址,使对象引用计数+1;
- 深copy是对象内存copy,会copy一份对象到新的内存地址,不会使对象引用计数+1;
- 不可变集合类的copy是浅copy,mutableCopy是深copy;
- 可变集合类的copy和mutableCopy都是深copy;
4、引用计数和AutoreleasePool
- 当我们创建一个对象时,它的引用计数会+1,系统会为对象分配内存;每当对象有一个新的引用,引用计数+1,每当对象取消一个引用,引用计数-1;当对象引用计数为0,系统会释放该对象内存。
- MRC下,使用retain方法使对象引用计数+1,避免被释放;使用release使引用计数-1,释放对对象的引用;
- ARC下,编译器会自动插入retain和release操作,不需要我们手动管理内存;
- AutoreleasePool是由AutoreleasePoolPage构成的双向链表,当runloop启动时,会创建AutoreleasePool,并将所有标记延迟释放的对象加入自动释放池,当runloop退出时,自动释放池里的对象会被释放。(手动创建的自动释放池里的对象会在AutoreleasePool{}大括号结尾处释放);当调用push方式时,会把一个POOL_BOUNDARY对象加入page并返回其地址,当调用pop方法时传入POOL_BOUNDARY地址,并从最后入栈的对象开始一直到POOL_BOUNDARY的位置之间的对象,全部调用release,id *next地址指向下一个能存放auto release对象的地方;
- 由new、alloc、copy、mutableCopy关键字生成的对象会被生成者持有,ARC下系统会在合适的时机release,不会进入AutoreleasePool,被一般类方法比如+(instancetype)createModelWith:创建的对象,会被标记为autorelease,会被自动加入最近创建的释放池中;
- 子线程没有启动runloop时,没有创建自动释放池,当有对象被标记为auto releasing时,才会创建,并把对象加入自动释放池,当子线程退出时,自动释放池里的对象会被release。
常见的autoreleasepool使用
for(inti=0; i<1000000; i++) {
NSString * str = [NSString stringWithFormat:@"abcdefghijklmn1234566!@#$"];
}
for(inti=0; i<1000000; i++) {
@autoreleasepool {
NSString * str = [NSString stringWithFormat:@"abcdefghijklmn1234566!@#$"];
}
}
- 野指针和僵尸对象:指针指向的对象已被释放,内存被回收了,那么这个对象就叫僵尸对象,指向这个对象的指针,就是野指针。向指向nil的指针发送消息,不会crash,向指向僵尸对象的野指针发送消息会crash;
- Tagged Pointer:从64bit开始,iOS引入tagged pointer技术,用于解决NSNumber、NSDate、NSString数据比较小时的存储问题;tagged pointer之前,这些对象需要动态分配内存,对象的指针存储的是堆上对象的地址,tagged pointer之后,NSNumber对象指针存储的是tag+data,也就是指针直接存储了数据,当指针不够存储数据时,才会走动态内存分配,把对象存在堆上。
5、KVC、KVO
- KVC:健值编码模式,是通过对象的成员变量名,对对象的成员变量进行读写的方法。通过-setValue:forKey:写值,通过-valueForKey:读值;KVC在写值和读值时会优先通过setter和getter方法,然后再去通过key值找对应的变量;如果没有找到,就会走-setValue:forUnderfindKey:和-valueForUnderfindKey:方法,如果这两个方法没有实现,则程序将crash。
- -setValue:forKey: 依次通过查找方法-setKey: 、-_setKey: 、-setIsKey: 如果找到就调用,如果没找到,就走+(BOOL)accessInstanceVariablesDirectly方法,返回YES(默认YES),接着找_key、_isKey、key、isKey成员变量,有的话直接赋值没有的话走,-setValue:forUnderfindKey:;返回NO走-setValue:forUnderfindKey:
- -valueForKey: 依次通过-getKey、-key、-isKey、-_key方法,如果没找到,就走+(BOOL)accessInstanceVariablesDirectly方法,返回YES(默认YES),接着找_key、_isKey、key、isKey成员变量,有的话直接取值返回;
- -setValue:forKeyPath:和-valueForKeyPath用于对多级的对象属性赋值,或者对集合类元素的属性赋值
- 对于可变字典:NSMutableDictionary来说,使用-setValue:forKey:,内部会走-setObject:forKey:,其中使用-setValue:forKey:时,value可以为nil,为nil时,会走-removeObjectForKey:删除健值对,key只能为字符串类型;使用-setObject:forKey:时,value不能为nil,key可以为任意对象(遵守NSCopying协议的对象)
- KVO:健值监听,是观察者模式的一种实现。KVO是基于runtime实现的,A对象对B对象的属性的改变做出监听的一种开发模式;runtime会创建B类的一个名字为NSKVONotifying_B的子类,并把B对象的is a指针指向这个子类,子类重写对应的属性setter方法,增加了-willChangeValueForKey:和-didChangeValueForKey:方法的调用,当对应属性值改变时通知观察者A对象,该属性已经改变。
6、代理、通知、KVO
三种模式都是对象之间事件传递的一种方式;
- 通知:一对多,多对多。优点是一对多,多对多;可跨越多个层级之间传递消息,代码解耦;缺点是字符串硬编码,编译期不容易排查错误;代码比较分散不容易维护。
- 代理:一对一;优点是方案简便,代码比较紧凑,方便阅读调试和维护;缺点是代码需要写的比较多;典型的是TableView的delegate和dataSource;
- KVO:一对一;优点实时监听对象变化,实现同步;高聚合,监听变化和处理变化在一起容易维护;缺点是。。。感觉没啥,主要看实际开发中适合选用那种方案。
7、扩展(Extension)、分类(Category)、继承、协议(Protocol);面向对象以及OC动态语言特性
- 扩展-Extension:扩展是一种特殊的分类,可以在类声明之外,为类声明额外的属性、实力变量、方法、协议等,但是不需要实现具体的@implementation;优点:一般用于封装组件时隐藏不希望暴露的属性和方法;缺点是所声明属性方法等只能在类内部使用,不能被继承;
- 分类-Category:在不改变现有类的情况下,为类添加新的方法;优点:不改变原始类;可以运行时动态调用新方法;可以拆分工作量大的类,使代码结构更清晰,更容易维护;可以为系统类NSArray等常用类添加便捷方法,提高代码复用。缺点:不能调用原有类的私有属性和方法;滥用分类覆盖系统类方法,会造成不可控的风险;多处对同一个类写分类有可能会有命名冲突,导致方法覆盖,出现不可控风险。
- 继承:子类继承父类的属性和方法,提高代码复用;继承是面向对象的一个基础;
- 协议-Protocol:协议是定义一套方法或属性,但不提供实现,由具体的类实现协议的能力;优点是一套协议由多个类实现相同接口不同的能力;
- 封装(只暴露使用接口,隐藏内部实现细节)、继承(子类继承父类的属性方法)、多态(运行时才确定接口的具体实现),是OC为面向对象编程的基础;
- 动态是指:类型检查和消息发送是在运行时才确定的;动态绑定、动态类型、动态加载是OC的动态特性。
8、const、宏定义、static、extern
- const:常量修饰词;修饰基础数据类型时,表示一个常量,值不可变,修饰指针类型变量时,const关键字在左边,指针的地址不可变,指针指向的值可变,const关键字在右边,指针的地址可变,指针指向的值不可变;
- 宏定义:可以把常量,函数等声明成宏,在编译时替换;
- static:修饰局部变量,只分配一次内存,局部变量生命周期会延长到程序生命周期;修饰全局变量,改变全局变量作用域,静态全局变量只能在本文件内访问。
- extern:声明外部变量;B类中定义的全局变量,可以在A类中用extern声明之后使用,不需要再导入B头文件。
9、OC、C、C++、Swift混编
.m文件只识别OC语法和C语法;
.mm文件识别OC、C、C++语法;
.cpp识别C++、C语法
- OC调用Swift,需要OC头文件导入TargetName-Swift.h文件
- Swift调用OC,需要把OC头文件加入TargetName-Bridging-Header.h头文件里。
10、isa指针
实例对象的isa指针指向对象的类,类里包含了实例对象可使用的属性成员变量方法等所有信息。类的isa指针指向元类,元类保存了类对象的方法和属性等信息,元类的isa指针指向根元类,根元类的isa指针指向自己。
- 系统为NSObject对象分配16个字节的内存,但是一般只占用8个字节。
// 打印对象的地址
NSLog(@"%p", obj);
// 打印指针的地址
NSLog(@"%p", &obj);
11、响应者链和事件传递
- 事件传递:当用户触摸屏幕时,系统会把触摸事件通过端口传递给前台APP的UIApplication,由UIApplication传递给Window,window传递给最上层的视图。事件的传递是由下到上,由-hitTest:withEvent:判断触摸点在不在当前view,如果在的话就遍历子view,依次找到最上层的view。
- 响应者:继承自UIResponder的类都可以成为响应者。
- 响应者链:由响应者组成的事件响应链条;当一个事件传递给响应者后,会判断响应者能不能处理事件,也就是有没有实现-touchesBegan:withEvent:等系列方法或者是有手势存在,如果没有就确定这个view不处理这次触摸事件,事件会传递给父view(如果是Controller的view,会出递给Controller),依次向下传递,直到UIApplication,如果都不能处理事件,则事件会被废弃。
12、block、__weak、__block,以及对变量的捕获原理
- block:本质是一个对象,有isa指针,内部封装了一个函数以及函数的调用环境,可以在需要的时候执行。
- __weak :一般用于block解循环引用。
- __block:一般用于block内部修改外部变量值时,对外部变量的修饰词。
- NSGlobalBlock:block内部不捕获外部局部变量,是全局block;
- NSStackBlock:block捕获了外部局部变量,是栈block;
- NSMallocBlock:栈block进行copy操作,会被copy到堆上(block的copy、block作为函数返回值、block被赋值给强指针、block作为OC里面带有usingBlock的函数参数、block作为GCD函数参数时,都会被copy到堆上)
为什么block要捕获变量?
- 函数内部定义的block和当前函数的作用域不同,当前函数内定义的局部变量会在函数执行结束时被释放,但是block仍然有可能访问变量,所以需要捕获变量到block内部。
为什么block能捕获变量?
- 因为block对象内部生成了对应的变量属性,所谓捕获,就是block内部持有这个变量。
为什么block内部不能直接修改局部变量值?
- 基本类型变量,是值copy,外部局部变量和block内部的变量已经不是同一个变量,所以不能修改;
- 对象类型变量,被捕获的是变量的指针,是变量指针的copy;block内部copy一份变量指针,指向对象地址,这个指针和外部指针不是同一个了,自然不能修改。
- 全局变量:block内部引用全局变量,是直接访问,不会发生捕获。
- 局部变量-基本类型:捕获基本类型是值copy,block保存了一份变量值到block内部,
- 局部变量-对象类型:捕获对象类型是指针copy,block内部会生成一个对象类型的指针指向该对象,对象的引用计数会根据外部对象引用计数修饰词(__strong、__weak)等决定是否+1;
- 静态变量:静态变量的生命周期会持续到App退出,所以对静态变量的捕获是指针的传递,block内部直接访问;
- __block修饰的变量:被__block修饰的局部变量,会被包装成一个__block结构体,它内部持有指向局部变量的指针TestModel *model3,__Block_byref_model3_1 *__forwarding指向自己:
void *__isa;
__Block_byref_model3_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
TestModel *model3;
};
block在被copy到堆上时,__block结构体同时被copy到堆上,它的__forwarding指针指向被copy到堆上的结构体内存地址,block内部通过__cself->model3.__forwarding->model3访问原始变量的地址,从而修改值。
13、数据持久化
数据存储的主要方式:
NSUserdefault:存储轻量数据
NSKeyedArchiver:序列化存储,可以存储实现NSCoding协议的model;
Sqlite:建立数据库存储数据;
CoreData:Apple特有的一种数据库支持方案,apple自动建立了数据表和模型的关联,使用方式更加面向对象,更加方便。
KeyChin:钥匙串存储,一般存储一些重要的、保密要求高的轻量数据。
14、id、NSObject、instancetype、nil、Nil、NULL、NSNULL的区别?
id:可以指向任意类型的对象,不会做编译时检查,不会做类型转换。
NSObject:指向任意NSObject子类对象,会做编译时检查,会做类型转换。
instancetype:只能作为方法的返回值,会做类型校验。
nil:空实例对象。
Nil:空类对象.
NULL:空基本数据类型。
NSNULL:在不能使用nil的时候,表示一个空对象。
15、BOOL和bool
在64位操作系统或者ArmV7k后,BOOL其实就是bool
在32位系统中,BOOL是signer char,取值-128~127,当BOOL a = 8960时,超过一个字节,会把8960转化为二进制0010 0011 0000 0000,取后八位0000 0000,也就是a = 0,会判断出a为NO。
另外下面的比较也会出问题,慎用 == YES:
if (c == YES) {
NSLog(@"c YES");
} else {
NSLog(@"c NO");
}
16、NSSet 、NSArray、NSPointerArray、NSHashTable、NSDictionary、NSMapTable、NSCache
NSSet:无序集合,只能存放对象,会自动去重,查询元素速度很快;
NSArray:有序集合,只能存放对象,在内存中是连续的,增加删除比较快;
NSPointerArray:有序集合,允许NULL元素加入集合,可以使用weak修饰集合元素,而NSSet和NSArray对集合内对象的必须是强引用的;
NSHashTable:无序集合,可以使用weak修饰集合元素
NSDictionary:健值对,key必须遵循NSCopyding,value为非空对象;
NSMapTable:类似NSDictionary,可以对key或value设置是否强引用;
NSCache:健值存储,不会对key强引用,当发生内存警告,NSCache会自动清除缓存,NSCache线程安全,非常合适做缓存。
17、runtime
动态运行时,是OC语言的特性之一。runtime使OC的方法调用,在编译时不会去确定具体调用的方法,而是在运行时动态查找执行方法。
OC里面对象调用方法,其实是向这个对象发送消息,通过对象的isa指针找到对象的类,从类的方法列表里面查找方法,从而执行。
runtime常见用法:
1、交换方法实现,使已有类的方法有新的能力。
2、动态添加方法
3、添加关联属性
4、实现模型字典转换
5、实现自动归解档
7、消息转发,可以规避方法未找到的crash
18、进程
在iOS中,一个APP启动,就是开启一个进程,一个进程有单独的内存空间、系统资源、端口等,进程可以开启多个线程执行任务,各线程共享进程的资源。进程间通信可以通过Port(端口)、URL Scheme、Keychain、UIPasteboard(剪切板)、UIDocumentInterationController(共享文档)、AirDrop、UIActivityController(分享面板)、App Groups(系统分享)
19、多线程
多线程,是同时开辟多个线程,并发执行任务,提升性能的技术。同一时间一个CPU核心只能处理一个线程,所谓的并发执行,就是CPU快速在多个线程之间调度,所呈现的同时执行的假象,它的优势是充分利用多核处理器的优势,使多个任务可以在多核CPU中同时执行,他的缺点是新线程会占用空间,线程间切换会有时间开销。
常见的多线程方案:
p_thread、NSThread、GCD、NSOperation。
常见的多线程通信方案:
1、访问同一个变量
2、通过- performSelect:
3、线程间加锁
4、通过NSPort传递信息
p_thread:是一套C的接口,需要手动管理线程生命周期,使用难度大;
NSThread:OC接口,使用简单,多用于简单的开启线程执行任务,不适合复杂的线程操作,需要手动管理线程生命周期,常见使用方式:
-performSelector: onThread: withObject: waitUntilDone:
-performSelectorOnMainThread: withObject: waitUntilDone:
GCD:一套C接口,能充分利用多核技术,不需要管理线程生命周期,使用便捷
dispatch_sync:同步执行任务
dispatch_async:异步执行任务
dispatch_group_t:调度组,配合dispatch_group_async、dispatch_group_notify实现执行完一组操作之后,再执行接下来的操作。
dispatch_barrier_(a)sync:栅栏函数,会等待barrier任务之前的任务执行完之后,再执行barrier提交的任务
dispatch_semaphore:信号量,一种锁,用于等待异步任务执行结束后再执行后续任务
dispatch_once_token:多用于创建单例
GCD取消任务:只能取消未开始执行的任务
通过dispatch_block_cancle取消加入GCD且未开始执行的block
通过bool变量+return操作,使未执行的block不再执行。
NSOperation:apple对GCD封装的一套面向对象的多线程接口,不需要管理线程生命周期,可以实现复杂的线程操作(添加线程间依赖等)
NSOptration是抽象类,只提供了接口,没有类的实现。使用NSOptration主要使用NSBlockOperation、NSInvocationOperation两个子类,当然也可以自定义NSOptration实现多线程操作,通过两个子类创建任务,添加到NSOperationQueue里面执行。
-addDependency:添加线程间依赖
-cancle:取消未执行的任务
-cancelAllOperations:取消所有未执行的任务
线程安全:在多线程执行任务时,有可能多个线程同时访问同一个资源,并对资源做出修改,容易引起资源数据混乱,造成程序运行不可预测,所以需要保证线程安全。
加锁:保证线程在访问资源时,不会有其他线程同时操作这个资源
设置线程同步执行:同步执行任务,保证了对资源有序访问。
自旋锁:自旋锁会忙等,也就是当一个线程访问一块加锁资源,线程不会休眠,会执行while循环,直到资源解锁
互斥锁:线程会休眠,也就是当一个线程访问一块加锁资源,会休眠,CPU可以调度其他线程执行其他操作,当资源解锁时,通知该线程唤醒并开始执行
死锁:多个操作互相等待对方执行完成,才执行自己,导致任务卡住,造成死锁。
常见锁:
OSSpinLock:自旋锁,iOS10.0后已被废弃
os_unfair_lock:iOS10.0之后,用于取代OSSpinLock,并不会使线程忙等
pthread_mutex_t:互斥锁
NSLock:apple对mutex的封装
NSConditionLock:递归锁(多次加锁不会造成死锁),apple对mutex的封装
@ synchronized:apple对mutex的封装
dispatch_semaphore:最常使用的锁,性能也比较好
pthread_rwlock_t:读写锁,当多线程访问同一块资源时,对读操作不加锁,对写操作加锁;也就是多个读操作的线程会同时执行,当有线程进行写操作执行时才进行加锁,其他线程读操作和写操作等待写操作执行完成,多用于IO操作文件读写时,提高了效率。
20、runloop
runloop:是一个事件处理循环机制,负责事件接收和分发,是多线程的框架的基础构成。一个runloop对应一个线程,对应关系在一个全局字典里,key为线程,value为runloop,开启线程时,并不会开启runloop,当第一次获取runloop时,runloop才被创建。
runloop的运行模式:
NSDefaultRunLoopMode:默认运行模式,主线程在这种模式下运行
UITrackingRunLoopMode:页面滑动时的模式
NSRunLoopCommonModes:NSDefaultRunLoopMode+ UITrackingRunLoopMode
runloop运行逻辑:
source0:非基于端口的事件源,通常是用户触摸事件、select事件等,是app内部生成的事件源,不基于端口传递。
source1:基于端口事件源,一般是系统间的通信、网络请求等,基于端口传递消息。
1、通知observer即将进入runloop
2、同时observe即将处理timer
3、通知observer即将处理source0
4、处理source0
5、如有source1,且已经准备好,则进入第9步
6、通知observe,即将休眠
7、休眠中,等待唤醒(timer源时间到了,手动唤醒,source0事件源)
8、通知observe,即将被唤醒
9、处理唤醒时收到的消息(timer,source1),然后跳转到第2步
10、通知observe,即将退出runloop
常驻线程:
@interface WYYThread : NSThread
@end
@implementation WYYThread
- (void)dealloc {
NSLog(@"WYYThread - %s", __func__);
}
@end
@interface TestThread ()
@property (nonatomic, strong) WYYThread *thread;
@property (nonatomic, assign) BOOL thread_stop;
@end
@implementation TestThread
- (instancetype)init {
if (self = [super init]) {
self.thread_stop = NO;
__weak typeof(self) weakSelf = self;
self.thread = [[WYYThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
while (weakSelf != nil && !weakSelf.thread_stop) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"runloop 结束");
}];
self.thread.name = @"TestThread";
[self.thread start];
}
return self;
}
- (void)addTask:(dispatch_block_t)block {
[self performSelector:@selector(takeTask:) onThread:self.thread withObject:block waitUntilDone:YES];
}
- (void)takeTask:(dispatch_block_t)block {
block();
}
- (void)takeStopTask {
self.thread_stop = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)stop {
[self performSelector:@selector(takeStopTask) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)dealloc {
NSLog(@"TestThread - %s", __func__);
[self stop];
}
@end
21、网络相关
HTTP协议:
超文本传输协议,使用80端口,是基于TCP/IP的应用层协议,规定了客户端和服务器之前传输数据的规则,HTTP不只能传输文本,可以传输任意类型数据。
常用方法
GET:用于向服务器请求数据,参数跟在URL后面,有参数长度限制,明文传递数据
POST:用于向服务端提交数据,数据放在包体里,理论上没有参数大小限制,相比于GET会更安全。
HEAD:类似GET,单数请求返回数据里没有包体,只有请求头
DELETE:向服务器请求删除数据
PUT:向服务器请求更新数据
特点:
灵活:可以传输任意类型数据
无连接:一次链接只发送一次请求
无状态:不记录请求状态,断联需要重传
联接过程:
客户端向服务端发起连接
服务端客户端建立连接,客户端向服务端请求数据
服务端响应客户端请求
客户端收到请求,关闭连接
请求报文:
请求行:包含请求方式(Method)、请求URL、HTTP版本号
请求头:请求附加数据,accept:客户端接收的资源类型(如text/html),content-length:数据长度等
请求体:请求数据
响应报文:
响应行:服务器返回的状态内容,HTTP版本号、状态码等
响应头:和请求头类似
响应体:具体的响应数据
HTTPS:超文本传输安全协议,使用443端口,是由HTTP+SSL组成,旨在解决HTTP传输不安全的问题。
HTTPS连接过程:
客户端向服务器请求连接
服务器申请CA证书,发送证书包含公钥给客户端
客户端通过根证书确认服务器证书有效性,进行身份确认
客户端生成对称加密密钥,通过公钥加密,发送给服务器
服务器收到加密密钥,使用私钥解密对称加密的密钥
客户端和服务器使用对称加密密钥进行数据传输
对称加密:加密解密使用同一密钥,加解密速度很快,使用很广泛,常见有AES算法,DES算法,3DES算法等
非对称加密:生成公钥私钥一对密钥,加解密使用不同的密钥,私钥需要严格保存不能泄漏。常见RAS算法。
TCP:传输控制协议
面向连接可靠的基于字节流的传输层控制协议,是为了在不可靠的网络中传输局数据定义的一套协议,有三次握手建立连接,有确认、窗口、重传、拥塞控制机制,传输完成后,会断开连接(四次挥手)节省资源
第一次握手:客户端->服务器 发送SYN标志位 序列号为X(随机生成的,标志数据传输序列的数字)的数据包,请求建立连接
第二次握手:服务端收到客户端SYN数据包,想客户端发送SYN+ACK数据包,SYN包中有服务器生成的随机序列号Y,ACK包序列号为X+1,表示服务端收到客户端请求,同意建立连接。
第三次握手:客户端收到服务器SYN+ACK包,向服务器发送ACK包,序列号为Y+1,联接建立,开始数据传输
数据传输:TCP数据会被分成TCP段,包含固定长度的头部,和数据部分,头部数据包含源端口号,目标端口号,序列号,确认应答号,标志位等信息。
序列号:发送方在发送数据时,对每个字节数据进行编码,序列号就是该段数据第一个字节的编码,接收方通过该序列号对接收到的TCP段进行重组,确保数据完整性和顺序性。
确认应答号:接收方在收到TCP段后,会向发送方发送ACK包,确认应答号为接收方期望接收的下一个序列号的TCP段,发送方根据收到的确认应答号确认接收方有没有正确接收数据,如果没有收到,或确认应答号不对,则触发重传。
滑动窗口机制:在接收方和发送方之间维护缓冲区,暂存发送方已发送,接收方未应答的TCP段,发送方可以连续发送多个TCP段,随着接收方不断接收确认数据,窗口向前滑动,发送发可以连续发送新的数据。
第一次挥手:客户端完成数据传输之后,向服务端发送FIN数据包,请求关闭连接
第二次挥手:服务器收到客户端FIN数据包,向客户端发送ACK数据包,表示已知客户端数据发送完毕,但此时服务器数据有可能没有发送完毕,则继续发送。
第三次挥手:服务端数据发送完毕,向客户端发送FIN数据包,请求关闭连接
第四次挥手:客户端向服务器发送ACK包,连接关闭。
UDP:数据报协议
对应TCP,UDP是非连接的协议,不与对方建立连接,直接把数据发送过去,适合对及时性要求高,数据量小,且对质量要求不高的场景。
Socket:基于对TCP/IP的封装,提供一套接口,用于两个程序通过双向连接传输数据。也就是常说的长连接。
心跳:长连接中,用于保证长连接有效的机制,客户端向服务器发送心跳包,收到服务器响应确认长连接有效。
DNS:域名解析服务,用于域名和IP地址的映射,是互联网提供的一种服务。
DNS劫持:域名劫持,拦截域名解析请求,返回假的域名地址或使域名无法访问。
网络七层模型:
应用层:网络服务与用户的接口,有HTTP协议
表示层:数据的表示,安全,压缩等
会话层:本地主机与远程主机连接、管理、终止
传输层:定义数据传输的协议、端口等,以及流控和差错校验。TCP、UDP等协议
网络层:进行逻辑地址寻址,实现不同网络之间的路径选择
数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验 [2] 等功能
物理层:建立、维护、断开物理连接
断点续传:
使用HTTP:利用HTTP头部有Range字段,请求的数据的具体部分,然后拼接在已请求到的数据之后。
22、性能优化
画面卡顿:iOS屏幕刷新频率是一秒钟60帧,也就是16.7ms刷新一次屏幕,而屏幕上一帧画面是由CPU和GPU协作完成的,CPU负责对象的创建、布局计算等数据处理,GPU负责把CPU准备好的数据进行渲染,渲染完成之后,会把画面放到帧缓冲区。那么当CPU处理数据时间占用过多,或者GPU渲染耗时过多,没有在16.7ms之内把渲染好的数据放到帧缓冲区,就会出现掉帧,就会造成画面卡顿。
离屏渲染:iOS是双缓存机制,有前帧缓存和后帧缓存,GPU会把需要显示的屏幕数据渲染好放进前帧缓存,提供给视频读取器读取,会把这一帧之后需要显示的数据渲染在后帧缓存,当接收到Vsync信号后,会把视频读取器指向前帧缓存的指针指向后帧缓存,前帧缓存的数据被丢弃,变为后帧缓存。GPU在渲染时遵循“画家算法”,由远及近渲染画面。当我们设置光栅化(shouldRasterize)、添加遮罩(mask)、圆角(coreRadius)、阴影(shadow)等时,由于前帧缓存使用过之后数据已经没了,我们又要对这些数据进行设置,所以只好再开辟一块缓存区,用于操作已经渲染的数据,当这些操作做完后,再把渲染好的数据放入离屏帧缓冲区用于显示。这种开辟新的非屏幕当前缓冲区来渲染数据的方式,就是离屏渲染。
离屏渲染触发卡顿:1、创建新的帧缓冲区有资源消耗;2、从当前屏幕缓冲区切换到离屏缓冲区,显示完之后需要再切换回当前屏幕帧缓冲区,多次上下文切换浪费时间。
性能优化:
CPU-优化:
1、使用Table View的cell重用机制
2、Table View高度做缓存,因为当滑动时,highForRow会频繁调用,缓存高度避免重复计算
3、减少视图层级,减少容易引起离屏渲染的操作
4、Table View刷新不要盲目的调用reloadData,有时候只刷新一个cell就行
5、当view不需要响应事件时,尽量用Layer显示图像
6、在布局变动时,尽量提前计算好所有布局,统一提交改动,不要一个属性一个属性的改动
7、AutoLayout最终会转化为frame,所以会比frame更慢
8、尽量把耗时操作放在子线程
GPU-优化:
1、避免短时间内显示大量图片,合成一张显示
2、大图片要分片展示
3、减少视图数量和层级
4、减少透明图层
5、减少离屏渲染
离屏渲染-优化:
1、使用UIBezierPath画圆角,用shadowPath指定阴影效果路径,避免裁剪等方式
2、设置layer的opaque为YES,避免复杂图层的合成
3、尽量不使用包含透明通道(alpha)的图片
4、最好让美工做出相应效果(阴影、圆角)的图片
APP 启动过程:
dyld:加载Mach-O文件,连接动态库
runtime:当dyld完成所有可执行文件加载,动态库连接之后,会通知runtime进行下一步
调用map_images进行可执行文件的解析和处理
在load_images中调用call_load_methods,执行所有类和分类的+load方法
进行各种Objc结构的初始化(注册Objc类,初始化类对象)
调用C++静态初始化器和attribute((constructor))修饰的函数
至此,可执行文件周所有的符号(class、protocol、IMP、selector等)都已经加载完毕
main:初始化工作完成,开始调用main函数,UIApplicationMain函数,APPDelegate的application:didFinishLaunchingWithOptions:函数,至此APP启动完成
APP启动优化:
减少动态库
减少类,分类,方法等
swift尽量用struct
+load 方法入无必要,放在+initialize中
application:didFinishLaunchingWithOptions:只做必要的工作,其他的工作可以等启动完成后再做
23、常见三方库
SDWebImage
三个核心类:SDWebImageManager、SDImageCache、SDImageDownloader
当加载图片时,会由SDWebImageManager执行loadImageWithUrl:
首先由SDImageCache在内存缓存中找,找到则回调显示图片;
未找到则去磁盘缓存中找,找到的话,就把图片缓存到内存中,再回调显示图片;
未找到则由SDImageDownloader开始下载图片,下载完成后,则缓存到内存和磁盘中,回调显示图片。
缓存是以图片URL的MD5值为key,图片为Value
SDImageDownloader的最大并发数为6
超时时间为15s
最大缓存时长为7天
AFNetworking
2.0是对NSURLConnection的封装,没用过
3.0是对NSURLSession的封装
核心架构:
URLSession:进行网络通信
Serialization:进行序列化操作
Reachability:网络状态监听
Security:证书等安全认证
UIKit:对UI控件的扩展