IOS-Swift面试相关基础
文章目录
- Xcode部分
- Storyboard基础控件:
- 控件:
- 基础交互手势:
- 计算机组成部分
- 内存管理
- 一.存储器
- 二.App启动运行过程
- 三.RAM内存分布
- 栈区(stack):
- 堆区(heap):
- 全局区/静态区(static):
- 常量区:
- 代码区:
- 四.Swift内存管理
- 1.ARC机制:
- 强引用循环
- 弱引用
- 无主引用
- 闭包的强引用循环解决
- 2.App共存情况
- 什么是Jetsam
- Jetsam知识点
- Jetsam处理方式
- 占用内存分析
- 3.内存问题
- 4.内存警告
- 5.可选型
- 6.性能优化
- 1.造成tableView卡顿的原因有哪些?
- 2.如何提升 tableview 的流畅度?
- 3.APP启动时间应从哪些方面优化?
- 4.如何降低APP包的大小
- 5.如何检测离屏渲染与优化
- 6.怎么检测图层混合
- 7.单个APP最大占用内存
- 五.网络传输
- 1.TCP&UDP
- (1)TCP和UDP优缺点
- (2)TCP和UDP的使用场景
- (3)TCP和UDP的区别
- (4)UDP应用场景
- (5)编程应用步骤
- (A)TCP:
- (B)UDP:
- (6)补充说明
- (A)TCP补充:
- (B)UDP补充:
- 2.TCP三次握手和四次挥手
- (1)TCP报文中的字段含义
- (2)TCP三次握手过程
- (3)TCP三次握手的必要性
- (4)三次握手错误分析
- (5)SYN攻击
- (6)TCP四次挥手
- (7)四次挥手必要性
- (8)计网面试常见题
- 3.HTTP & HTTPS
- (1)基本概念
- (2)HTTP & HTTPS的区别
- (3)HTTPS工作流程
- (4)HTTPS优缺点
- a.优点
- b.缺点
- (5)SSL证书
- 1、SSL的作用
- 2、SSL证书申请的3个主要步骤
- 基础算法
- 快速排序
- 设计思路
- Runtime
- 1.什么是Runtime?
- 2.Runtime的用法以及所使用的地方
- (1)用来干什么 基本作用
- (2)用在哪些地方 Runtime的典型事例
- 多线程
- 1.进程与线程
- (1)进程:
- (2)线程
- (3)进程和线程的关系
- 2.多线程的含义
- 3.多线程的优缺点
- 4.多线程的 并行 和 并发
- 5.IOS多线程方案
- 6.死锁
- 7.GCD执行原理
- 同步和异步
- (1)Swift中Alamofire的异步请求思路
- 补充
- 堆
- 栈
- 堆栈区别
- 1.堆栈空间分配
- 2.堆栈缓存方式
- 3.堆栈数据结构区别
- 4.例子
- 数组链表
- 内存释放时间
- TCP的CS
- c++编译过程
- 网络错误开头
Xcode部分
Storyboard基础控件:
视图控制器在SB种称为场景
控件:
1.Label:可包含任意数量的文本,UILabel可收缩,换行以及截断文本。
2.Button:按钮。
3.Segmentel Control:可表示单个或多个选择或命令列表,可显示文本或图像,但无法同时显示两者。
4.Text Field:用户可编辑的文本框。
5.Slider:可滑动的水平条。
6.Switch:显示给定的Bool状态,点击控件可以切换状态。
7.Activity Indicator View:转菊花。
8.Progress View:进度条。
基础交互手势:
import UIKitclass ViewController: UIViewController {@IBOutlet weak var tapLabel: UILabel!@IBOutlet weak var pinchLabel: UILabel!@IBOutlet weak var rotationLabel: UILabel!@IBOutlet weak var swipeLabel: UILabel!@IBOutlet weak var panLabel: UILabel!@IBOutlet weak var screenEdgePanLabel: UILabel!@IBOutlet weak var longPressLabel: UILabel!override func viewDidLoad() {super.viewDidLoad()//tapLabel.isUserInteractionEnabled = true //动态打开允许用户交互属性//轻触手势let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(tap:)))//tap.numberOfTapsRequired = 1 //指定-需要轻点几下才会触发监听函数(handleTap)//tap.numberOfTouchesRequired = 1 //指定-需要用几个手指轻点才会触发监听函数(handleTap)tapLabel.addGestureRecognizer(tap)//可以给这个手势添加更多的事件监听函数,也就是说用户轻点之后会发生好几件事情。所有手势通用。//tap.addTarget(self, action: #selector(handleTap2(sender:)))//捏合手势-缩小和放大用-缩放let pinch = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(pinch:)))pinchLabel.addGestureRecognizer(pinch)//旋转手势let rotation = UIRotationGestureRecognizer(target: self, action: #selector(handleRotation(rotation:)))rotationLabel.addGestureRecognizer(rotation)//轻扫手势let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(swipe:)))//swipe.direction = .right //指定轻扫方向//swipe.numberOfTouchesRequired = 1 //指定需要几个手指轻扫swipeLabel.addGestureRecognizer(swipe)//平移拖拽手势--开发中用的比较多let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(pan:)))//pan.maximumNumberOfTouches 指定最多几个手指-默认是无限多-一般不设定//pan.minimumNumberOfTouches = 1 //指定至少需要几个手指才能拖拽panLabel.addGestureRecognizer(pan)//屏幕边缘平移手势--属于Pan手势的一种let screenEdgePan = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleScreenEdgePan(screenEgdePan:)))//指定从屏幕的哪边可以滑。不受屏幕转向影响,比如规定左边,横屏时还是从左边滑screenEdgePan.edges = .left//screenEdgePanLabel没有靠到边缘,所以不能给他加,这里给rootview加view.addGestureRecognizer(screenEdgePan)//长按手势let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(longPress:)))//longPress.numberOfTapsRequired = 0 //指定长按前需要轻点的次数//longPress.numberOfTouchesRequired = 1//longPress.minimumPressDuration = 0.5 //最小长按时间//longPress.allowableMovement = 10.0 //长按时允许移动的最大距离-容错范围,单位:点longPressLabel.addGestureRecognizer(longPress)}//离散型(一次性)手势@IBAction func handleIBTap(IBTap: UITapGestureRecognizer){guard let IBTapLabel = IBTap.view as? UILabel else{return}//就算是离散手势,也要在做操作之前check一下stateif IBTap.state == .ended{IBTapLabel.text = "被翻牌了"}}//离散型(一次性)手势@objc func handleTap(tap: UITapGestureRecognizer){if tap.state == .ended{tapLabel.text = "被翻牌了"}}//持续型手势-持续调用这个函数@objc func handlePinch(pinch: UIPinchGestureRecognizer){if pinch.state == .began || pinch.state == .changed{pinchLabel.transform = pinchLabel.transform.scaledBy(x: pinch.scale, y: pinch.scale)//pinch.scale表示总缩放比,transform里已包含上一帧的缩放比了,不能重复相乘,所以把他归为初始值1pinch.scale = 1.0//print(pinch.velocity) //缩放速度(放大为正,缩小为负) 单位:缩放比/秒}}//持续型手势-持续调用这个函数@objc func handleRotation(rotation: UIRotationGestureRecognizer){if rotation.state == .began || rotation.state == .changed{rotationLabel.transform = rotationLabel.transform.rotated(by: rotation.rotation)//同scalerotation.rotation = 0.0//rotation.velocity //旋转速度(顺时针为正,逆时针为负) 单位:弧度/秒}}//离散型(一次性)手势@objc func handleSwipe(swipe: UISwipeGestureRecognizer){if swipe.state == .ended{swipeLabel.text = "被翻牌了"}}//持续型手势-持续调用这个函数var startCenter = CGPoint.zero@objc func handlePan(pan: UIPanGestureRecognizer){//手指在panLabel的父视图中平移的x和y(含上下左右各多少四个信息)let translation = pan.translation(in: panLabel.superview)//pan.setTranslation(<#T##translation: CGPoint##CGPoint#>, in: <#T##UIView?#>)//设置手指在某个view中的偏移量//pan.velocity(in: <#T##UIView?#>)//平移速度 单位:point/秒(CGPoint,有x和y)if pan.state == .began{startCenter = panLabel.center//开始时先把当前panlabel的center保存在startcenter中}//.began, .changed, .ended 状态时更新panlabel的位置(改center)if pan.state != .cancelled{panLabel.center = CGPoint(x: startCenter.x + translation.x, y: startCenter.y + translation.y)}else{//.cancelled 时恢复到原位置-被系统事件中断-比如来电话panLabel.center = startCenter}}//持续型手势-持续调用这个函数@objc func handleScreenEdgePan(screenEgdePan: UIScreenEdgePanGestureRecognizer){let x = screenEgdePan.translation(in: view).x //根据使用习惯只获取x就可以//这里练习用一下transformif screenEgdePan.state == .began || screenEgdePan.state == .changed{screenEdgePanLabel.transform = CGAffineTransform(translationX: x, y: 0)}else{//ended和cancelled的时候UIView.animate(withDuration: 0.3) {self.screenEdgePanLabel.transform = .identity}}}//持续型手势-持续调用这个函数@objc func handleLongPress(longPress: UILongPressGestureRecognizer){if longPress.state == .began{view.backgroundColor = #colorLiteral(red: 0.9686274529, green: 0.78039217, blue: 0.3450980484, alpha: 1)}}}
计算机组成部分
内存管理
一.存储器
- RAM:随机存取存储器(random access memory),是与CPU直接交换数据的内部存储器,在断电时候将丢失所存储的内容。
- ROM:只读存储器(Read-Only Memory),只能读出事先存储好的数据的存储器,一旦存储数据就无法改变或者删除,CPU无法直接访问
- Cache:高速缓存,集成于CPU,作为系统两端处理速度不匹配时的一种折衷策略。
二.App启动运行过程
App安装到手机后,存储在 ROM中,程序启动后,系统会把App程序从ROM里面拷贝到RAM,然后从RAM里面执行代码。
三.RAM内存分布
RAM内存中分5个区
栈区(stack):
存放的局部变量、函数的参数值、函数跳转地址
先进后出,一旦出了作用域就会被销毁
栈区地址从高到低分配
自动管理内存
代码实现
struct Stack<Element> { //使用泛型让栈能存储各种元素var items = [Element]()mutating func push(_ item: Element) { //入栈items.append(item)}mutating func pop() -> Element?{ //出栈if items.count < 1 {return nil}return items.removeLast()}
}
堆区(heap):
堆区的内存分配使用的是alloc;
ARC的内存的管理,是编译器再便宜的时候自动添加 retain、release、autorelease;
堆区的地址是从低到高分配)
需要程序猿管理内存;
堆结构很像二叉树,堆也是一个近似树形结构,堆的每个节点也最多有左、右两个孩子,但是堆实质是存储在数组中的结构,所以他和二叉树只是近似的有某些共同的特性。
堆结构是存储在数组中的。
1.堆通过 priority 优先事项进行排序,可以分为,max-priority(最高优先级)、和 max-priority(最低优先级)两种。
2.二叉搜索树中,有left<parent<right 的特性,但是在堆中,只是只能保证孩子一定是小于或大于父节点的。
3.二叉树在存储的过程中需要更多的内存,因为节点需要存储指向父节点,子节点等的指针,但是堆不需要,他存储在数组中。
4.二叉树中的一些特别树,如红黑树,AVL树等,删除,插入,及搜索的多度都很快,但是,堆结构的搜索相对来说会慢很多,但是堆结构对于查找,最大或者最小等需求时,查找速度是很快的。
5.堆有一个很重要的特性,就是只有在上一层完全满之后,才能到下一层来进行存储,不允许中间某个节点的子节点为空,因为是通过数组存储的。
节点 node 的孩子在数组中的位置:
左孩子:index = 2 * i + 1
右孩子:index = 2 * i + 2
其中i为当前节点n在数组中的下标
代码实现
struct Heap<Element> {///存放元素的数组var elements: [Element]//比较两个元素大小的方法let priorityFunction: (Element, Element) -> Boolinit(elements: [Element] = [], priorityFunction: @escaping (Element, Element) -> Bool) { // 1 // 2self.elements = elementsself.priorityFunction = priorityFunction // 3buildHeap() // 4}mutating func buildHeap() {for index in (0 ..< count / 2).reversed() { // 5siftDown(index) // 6}}var isEmpty : Bool {return elements.isEmpty}var count : Int {return elements.count}func peek() -> Element? {return elements.first}func isRoot(_ index: Int) -> Bool {return (index == 0)}///当前节点的左孩子func leftChildIndex(of index: Int) -> Int {return (2 * index) + 1}///当前节点的右孩子func rightChildIndex(of index: Int) -> Int {return (2 * index) + 2}///当前节点的父节点func parentIndex(of index: Int) -> Int {return (index - 1) / 2}//插入 mutating func equeue(_ element: Element) {elements.append(element)siftUP(elementAtIndex: elements.count - 1)}//删除mutating func dequeue() -> Element? {//判空guard !isEmpty else { return nil }//将第一个节点和最后一个节点交换位置swapElement(at: 0, with: count - 1)//删除原本的第一个,现在的最后一个elements.removeLast()guard !isEmpty else { return nil }//向下判断,新的节点是不是优先级最高的节点return nil}
}private extension Heap {func isHigherPriority(firstIndex: Int, secondIndex: Int) -> Bool{return priorityFunction(elements[firstIndex], elements[secondIndex])}func highestPriorityIndex(of parentIndex: Int, and childIndex: Int) -> Int {guard childIndex < count && isHigherPriority(firstIndex: childIndex, secondIndex: parentIndex)else { return parentIndex }return childIndex}func highestPriorityIndex(for parent: Int) -> Int {return highestPriorityIndex(of: highestPriorityIndex(of: parent, and: leftChildIndex(of: parent)), and: rightChildIndex(of: parent))}mutating func swapElement(at firstIndex: Int, with secondIndex: Int) {guard firstIndex != secondIndexelse { return }elements.swapAt(firstIndex, secondIndex)}mutating func siftDown(_ elementIndex: Int) {//从当前节点及子节点中找出优先级最高的节点let highestIndex = highestPriorityIndex(for: elementIndex)//如果当前节点就是优先级最高的节点,直接放回if highestIndex == elementIndex { return }//如果不是优先级最高的节点,和优先级最高的节点对换位置swapElement(at: elementIndex, with: highestIndex)//从新的节点开始递归的向下判断优先级siftDown(highestIndex)}mutating func siftUP(elementAtIndex: Int) {//获得父节点的索引let parentIndex = self.parentIndex(of: elementAtIndex)//如果当前节点不是根节点,比较当前节点和父节点的优先级guard !isRoot(elementAtIndex), isHigherPriority(firstIndex: elementAtIndex, secondIndex: parentIndex) else {return}//如果当前节点的优先级高于父节点,兑换位置swapElement(at: elementAtIndex, with: parentIndex)//递归的从新的父节点开始向上比较siftUP(elementAtIndex: parentIndex)}
}
全局区/静态区(static):
存放全局变量和静态变量(未初始化过 、初始化过)
初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域
程序结束后由系统释放
常量区:
常量字符串就是放在这里;
存放常量字符串
程序结束后由系统释放
代码区:
存放App代码
程序结束后由系统释放
四.Swift内存管理
Swift 和 OC 用的都是ARC的内存管理机制,它们通过 ARC 可以很好的管理对象的回收,大部分的时候,程序猿无需关心 Swift 对象的回收。
只有引用类型变量所引用的对象才需要使用引用计数器进行管理,对于枚举、结构体等,他们都是值类型的。因此不需要使用引用计数进行管理。
1.ARC机制:
- ARC自动统计该对象被多少引用变量引用,该值就是引用计数器。
- 每当引用计数器计数变为0的时候,ARC就会回收这个对象。
强引用循环
大部分时候,ARC能够很好的处理程序中对象的内存回收,但如果这两个对象之间存在着相互的引用,也就是当两个对象都使用存储属性相互的引用对方的时候,此时两个对象的引用计数都等于 1 ,但实际上它们都没有被真正的引用变量所引用,这时候的 ARC是无法回收它们的。
这时候两个对象之间不再有真正的引用变量引用他们,但两个对象之间的相互引用,形成了"强引用循环",此时它们的引用计数为 1 ,ARC也不会去回收它们,任何一个对象释放,都要等对方先释放,因此两个对象只爱你谁都没办法被回收,这两个对象在这时候就变成了垃圾。为了结局上面的强引用循环,我们就必须让一方先放手,允许对方先释放。
Swift这时候提供了两种机制: 弱引用和无主引用
弱引用
弱引用不会增加对方的引用计数,因此不会阻止ARC回收被引用的实例,这样就避免了形成强引用循环, 在定义属性的 var 关键字之前加 weak 就定义了弱引用。
- 弱引用变量要求该变量必须要能被设置成 nil ,也就是弱引用的属性最好是使用可选类型来定义。
- 弱引用的属性只能声明为变量类型,因为该属性在运行期内只有可能会发生变化,因此不能设置成常量。
- 也没必要把两个相互引用的属性都设置成弱引用,有一个就可以了。
无主引用
与弱引用相似的是,无主引用也不会增加对方的引用计数,无主引用于弱引用的区别
无主引用不允许接受nil,意思就是这个属性要一直有值!因此无主引用只能定义为非可选类型。
在定义属性 var 或者 let 之前,添加 unowned 关键字即可。
闭包的强引用循环解决
一个持有外部环境变量的函数就是闭包。
创建闭包:只要在一个函数中再定义一个函数,这个内部函数就是一个闭包。
当闭包和捕获的对象总是相互引用,并且总是同时销毁,应该将闭包内捕获的实例定义为无主引用。
当闭包捕获的引用变量有可能是 nil 时,将闭包捕获的引用变量定义为弱引用。
如果程序将该对象本身传入了闭包,那么闭包本身就会捕获该对象,于是该对象就持有了闭包属性,反过来,闭包也持有对象,这样子就形成了强引用。
2.App共存情况
1.app 内存消耗较低,同时其他 app 也非常“自律”,不会大手大脚地消耗内存,那么即使切换到其他应用,我们自己的 app 依然是“活着”的,保留了用户的使用状态,体验较好
2.app 内存消耗较低,但是其他 app 非常消耗内存(可能是使用不当,也可能是本身就非常消耗内存,比如大型游戏),那除了当前在前台的进程,其他 app 都会被系统回收,用来给活跃进程提供内存资源。这种情况我们无法控制
3.app 内存消耗比较大,那切换到其他 app 以后,即使其他 app 向系统申请不是特别大的内存,系统也会因为资源紧张,优先把消耗内存较多的 app 回收掉。用户会发现只要 app 一旦退到后台,过会再打开时就会重新加载
4.app 内存消耗特别大,在前台运行时就有可能被系统 kill 掉,引起闪退
在 iOS 上管理杀进程释放资源策略模块叫做 Jetsam
什么是Jetsam
关于投弃物,可能有些人还不是很理解我们可以从。手机设置- >隐私- >分析这条路径看看系统-的日志,会发现手机上有许多JetsamEvent。开头的日志打开这些日志,一般会显示一些内存大小,CPU时间什么的数据。
之所以会发生这么JetsamEvent,主要还是由于iOS设备不存在交换区导致的内存中断,所以iOS内核不得不把一些优先级不高或者占用内存过大的杀掉。这些JetsamEvent就是系统在杀掉App后记录的一些数据信息。
从某种程度来说,JetsamEvent是一种替代类的Crash事件,但是在常规的Crash捕获工具中,由于iOS上能捕获的信号量的限制,所以因为内存导致应用被杀掉是无法被捕获的。因此,许多圣地亚哥的前辈通过设计flag的方式自己的记录所谓的abort事件来采集数据。但是这种采集的异常,一般情况下都只能简单的记录次数,而没有详细的细分。
Jetsam知识点
- MacOS /iOS是一个从BSD衍生而来的系统。其内核是Mach,但是对于上层暴露的接口一般都是基于BSD层对于Mach包装后的。虽然说Mach是个微内核的架构,真正的虚拟内存管理是在其中进行,但是BSD对于内存管理提供了相对较高上层的接口,同时,各种常见的JetSam事件也是由BSD产生。
- 内核里面对于所有的进程都有一个优先级的分布,通过一个数组维护,数组每一项是一个进程的列表。这个数组的大小是JETSAM_PRIORITY_MAX + 1。
- 线程在Mach下采用了不同的优先级,其中MAXPRI_KERNEL代表的是分配给内核可用范围内最高优先级的线程。用户应用的程序的线程不可能高于操作系统内核状语从句。而且,在用户态的应用程序间的线程优先级分配也有区别,前台活动的应用程序优先级高于后台的应用程序。iOS上大名鼎鼎的跳板是应用程序中优先级最高的程序。
- 针对每一个线程的重组和整体系统负载动态调整优先级。如果耗费CPU太多就降低优先级,如果一个线程过度饿饿CPU提升其优先级。 。但是无论怎么变,程序都不能超过其所在的线程优先级区间范围。
Jetsam处理方式
BSD层起了一个内核优先级最高的线程VM_memorystatus,这个线程会在维护两个列表,一个是我们之前提到的基于进展优先级的进程列表,还有一个是所谓的内存快照列表,即保存了每个进程消耗的内存页memorystatus_jetsam_snapshot。
这个常驻线程接受从内核关于内存的守护程序pageout通过内核调用给每个应用程序发送的内存压力通知,来处理事件,这个事件转发成上层的UI事件就是平常我们会收到的分配内存警告或者每一个ViewController里面的didReceiveMemoryWarning。
当然,我们自己开发的App是不会主动注册监听这个内存警告事件的,帮助我们在逐步完成这一切的都是libdispatch。
需要注意的是,JETSAM不一定只杀一个进程,他可能会大杀特杀,杀掉N多进程。
占用内存分析
图片渲染:解压的图片是由像素组成的,每个像素点一般包括红,绿,蓝和Alpha值。每个值为8位,故而一个像素点通常占用4字节(少数专业App可能会用更大的空间表现色深,消耗的内存会相应增加。)一些通常的图片开销
普通图片大小,如 500 * 600 * 32bpp = 1MB
跟 iPhone X 屏幕一样大的:1125 * 2436 * 32bpp = 10MB
即刻中允许最大的图片,总像素不超过1500w:15000000 * 32bpp = 57MB
缩放
内存开销多少与图片文件的大小(解压前的大小)没有直接关系,而是跟图片分辨率有关。举个例子:同样是 100 * 100,jpeg 和 png 两张图,文件大小可能差几倍,但是渲染后的内存开销是完全一样的——解压后的图片 buffer 大小与图片尺寸成正比,有多少像素就有多少数据。
通常我们下载的图片和最终展示在界面上的尺寸是不同的,有时可能需要将一张巨型图片展示在一个很小的 view 里。如果不做缩放,那么原图就会被整个解压,消耗大量内存,而很多像素点会在展示前被压缩掉,完全浪费了。所以把图片缩放到实际显示大小非常重要,而且解码量变少的话,速度也会提高很多。
如果在网上搜索图片缩放方案的话,一般都会找到类似“新建一个 context ,把图画在里面,再从 context 里读取图片”的答案。此时如果原图很大,那么即使缩放后的图片很小,解压原图的过程仍然需要占用大量内存资源,一不小心就会 OOM。但是如果换用 ImageIO 情况就会好很多,整个过程最多只会占用缩放后的图片所需的内存(通常只有原图的几分之一),大大减少了内存压力。
文字渲染的CPU和内存开销
黑白的像素只占用 1 个字节。
3.内存问题
内存泄漏:该释放的时候没有释放
内存溢出:简单说就是内存不够用。
- 静态分析:检测代码中是否有潜在的内存泄露。编译器觉得不太合适的代码。
- 方法:Xcode -> Product -> Analyze
- 动态分析:检测程序在运行过程中的内存变化。
- 方法:Xcode -> product -> Profile ->Allocations(或者Leaks)
- IOS App内存主要由两部分组成
- All Heap Allocations(堆内存,程序启动占用的内存)如:
- View 函数的调用
- 注册通知
- 抛出通知
- view 的布局
- 函数代码的执行
- sqlite 数据库的创建
- 向字典中增加对象
- Anonymous VM(虚拟内存),虚拟内存由操作系统分配,不需要过多的人为掌控。
4.内存警告
ios下每个app可用的内存是被限制的,如果一个app使用的内存超过了这个阀值,则系统会向该app发送Memory Warning消息。收到消息后,app必须尽可能多的释放一些不必要的内存,否则OS会关闭app。
几种内存警告级别(便于理解内存警告之后的行为)
- OSMemoryNotificationLevelAny = -1,
- OSMemoryNotificationLevelNormal = 0,
- OSMemoryNotificationLevelWarning = 1,需要关闭一些不不要的后台程序。
- OSMemoryNotificationLevelUrgent = 2,需要退出所有后台程序。
- OSMemoryNotificationLevelCritical = 3,需要重启手机
5.可选型
(1)因为nil这个东西,swift中没有就是没有。 Int? 叫 整型可选型,如果不提前声明,直接赋值变量 nil会报错 。
可以将Int赋值给Int? ,但是不能将Int?赋值给Int 。
(2)不能将 普通 String 和可选型直接一起混用,必须 Unwrap 。也就是这样 在某个可选型变量后面加个!
表示担保它一定不为nil 。这个 叫 强制解包 , 是有风险的。
(3)swift提供了一种语法用来解包 ,let 也可以用var
if let errorCode = errorCode {
仅在这里面可以使用解包后的errorCode ,此时不需要加!
但是出了花括号 ,errorCode又是可选型了。这是因为swift是严格的类型语言。
}
if let 这里可以用逗号隔开,对多个可选型进行解包 ,这里还可以用where关键字判断是否为某个值{
}
(4)隐式解析可选:
非Optional的变量必须在声明时或者构造器中进行初始化,但是如果想在viewDidLoad中初始化,所以只能声明Optional:var myLabel: UILabel? ,虽然我们确定在viewDidLoad 中初始化,并且在ViewController的生命周期内不会置为nil, 但是对myLabel操作时,每次依然要加上!来强制拆包
myLabel!.text = “text”
myLabel!.frame = CGRectMake(0,0,10,10)
对于这种类型的值,我们可以直接这么声明 var myLabel: UILabel!,这种是特殊的Optional,称为Implicitly Unwrapped Optionals, 直译就是隐式拆包的Optional,就等于说你每次对这种类型的值操作时,都会自动在操作前补上一个!进行拆包
(5)Swift 的nil和 Objective-C 中的nil并不一样。在 Objective-C 中,nil是一个指向不存在对象的指针。在 Swift 中,nil不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选都可以被设置为nil,不只是对象类型。
6.性能优化
1.造成tableView卡顿的原因有哪些?
- 最常用的就是cell的重用, 注册重用标识符
如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell
如果有很多数据的时候,就会堆积很多cell。
如果重用cell,为cell创建一个ID,每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell
- 避免cell的重新布局
cell的布局填充等操作 比较耗时,一般创建时就布局好
如可以将cell单独放到一个自定义类,初始化时就布局好
- 提前计算并缓存cell的属性及内容
当我们创建cell的数据源方法时,编译器并不是先创建cell 再定cell的高度
而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入凭虚都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell
- 减少cell中控件的数量
尽量使cell得布局大致相同,不同风格的cell可以使用不用的重用标识符,初始化时添加控件,
不适用的可以先隐藏
- 不要使用ClearColor,无背景色,透明度也不要设置为0
渲染耗时比较长
- 使用局部更新
如果只是更新某组的话,使用reloadSection进行局部更
加载网络数据,下载图片,使用异步加载,并缓存
少使用addView给cell动态添加view
按需加载cell,cell滚动很快时,只加载范围内的cell
不要实现无用的代理方法,tableView只遵守两个协议
缓存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。所以我的建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可
不要做多余的绘制工作。在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制。例如上例中,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否需要绘制image和text,然后再调用绘制方法。
预渲染图像。当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕;
使用正确的数据结构来存储数据。
2.如何提升 tableview 的流畅度?
本质上是降低 CPU、GPU 的工作,从这两个大的方面去提升性能。
CPU:对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制
GPU:纹理的渲染
- 卡顿优化在 CPU 层面
尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView ( UIView真正的绘图部分,是由一个叫CALayer(Core Animation Layer)的类来管理的。UIView本身更像是一个CALayer的管理器,访问它跟绘图和跟坐标有关的属性,实际上内部都是在访问它所包含的CALayer的相关属性。)
不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的修改
尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
图片的 size 最好刚好跟 UIImageView 的 size 保持一致
控制一下线程的最大并发数量
尽量把耗时的操作放到子线程
文本处理(尺寸计算、绘制)
图片处理(解码、绘制)
- 卡顿优化在 GPU层面
尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
GPU能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸
尽量减少视图数量和层次
减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES
尽量避免出现离屏渲染
iOS 保持界面流畅的技巧
- 预排版,提前计算
在接收到服务端返回的数据后,尽量将 CoreText 排版的结果、单个控件的高度、cell 整体的高度提前计算好,将其存储在模型的属性中。需要使用时,直接从模型中往外取,避免了计算的过程。
尽量少用 UILabel,可以使用 CALayer 。避免使用 AutoLayout 的自动布局技术,采取纯代码的方式
- 预渲染,提前绘制
例如圆形的图标可以提前在,在接收到网络返回数据时,在后台线程进行处理,直接存储在模型数据里,回到主线程后直接调用就可以了
避免使用 CALayer 的 Border、corner、shadow、mask 等技术,这些都会触发离屏渲染。
异步绘制
全局并发线程
高效的图片异步加载
3.APP启动时间应从哪些方面优化?
App启动时间可以通过xcode提供的工具来度量,在Xcode的Product->Scheme–>Edit Scheme->Run->Auguments中,将环境变量DYLD_PRINT_STATISTICS设为YES,优化需以下方面入手
dylib loading time
核心思想是减少dylibs的引用
合并现有的dylibs(最好是6个以内)
使用静态库
rebase/binding time
核心思想是减少DATA块内的指针
减少Object C元数据量,减少Objc类数量,减少实例变量和函数(与面向对象设计思想冲突)
减少c++虚函数
多使用Swift结构体(推荐使用swift)
ObjC setup time
核心思想同上,这部分内容基本上在上一阶段优化过后就不会太过耗时
initializer time
使用initialize替代load方法
减少使用c/c++的attribute((constructor));推荐使用dispatch_once() pthread_once() std:once()等方法
推荐使用swift
不要在初始化中调用dlopen()方法,因为加载过程是单线程,无锁,如果调用dlopen则会变成多线程,会开启锁的消耗,同时有可能死锁
不要在初始化中创建线程
4.如何降低APP包的大小
降低包大小需要从两方面着手
可执行文件
编译器优化:Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default 设置为 YES,去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions 设置为 NO, Other C Flags 添加 -fno-exceptions 利用 AppCode 检测未使用的代码:菜单栏 -> Code -> Inspect Code
编写LLVM插件检测出重复代码、未被调用的代码
资源(图片、音频、视频 等)
优化的方式可以对资源进行无损的压缩
去除没有用到的资源
5.如何检测离屏渲染与优化
检测,通过勾选Xcode的Debug->View Debugging–>Rendering->Run->Color Offscreen-Rendered Yellow项。
优化,如阴影,在绘制时添加阴影的路径
6.怎么检测图层混合
模拟器debug中color blended layers红色区域表示图层发生了混合
Instrument-选中Core Animation-勾选Color Blended Layers
避免图层混合:
确保控件的opaque属性设置为true,确保backgroundColor和父视图颜色一致且不透明
如无特殊需要,不要设置低于1的alpha值
确保UIImage没有alpha通道
- UILabel图层混合解决方法:
iOS8以后设置背景色为非透明色并且设置label.layer.masksToBounds=YES让label只会渲染她的实际size区域,就能解决UILabel的图层混合问题
iOS8 之前只要设置背景色为非透明的就行
- 为什么设置了背景色但是在iOS8上仍然出现了图层混合呢?
UILabel在iOS8前后的变化,在iOS8以前,UILabel使用的是CALayer作为底图层,而在iOS8开始,UILabel的底图层变成了_UILabelLayer,绘制文本也有所改变。在背景色的四周多了一圈透明的边,而这一圈透明的边明显超出了图层的矩形区域,设置图层的masksToBounds为YES时,图层将会沿着Bounds进行裁剪 图层混合问题解决了
7.单个APP最大占用内存
- iPad1:127MB / 256MB / 49%
- iPad2:275MB / 512MB / 53%
- iPad3:645MB / 1024MB / 62%
- iPad4:585MB / 1024MB / 57%(iOS 8.1)
- 第一代iPad Mini:297MB / 512MB / 58%
- iPad Mini视网膜:696MB / 1024MB / 68%(iOS 7.1)
- iPad Air:697MB / 1024MB / 68%
- iPad Air 2:1383MB / 2048MB / 68%(iOS 10.2.1)
- iPad Pro 9.7英寸:1395MB / 1971MB / 71%(iOS 10.0.2(14A456))
- iPad Pro 10.5吋:3057/4000/76%(iOS 11 beta4)
- iPad Pro 12.9英寸(2015):3058/3999/76%(iOS 11.2.1)
- iPad Pro 12.9英寸(2017年):3057/3974/77%(iOS 11 beta4)
- iPad Pro 11.0吋(2018年):2858/3769/76%(iOS 12.1)
- iPad Pro 12.9英寸(2018,1TB):4598/5650/81%(iOS 12.1)
- iPad 10.2:1844/2998/62%(iOS 13.2.3)
- 第四代iPod touch:130MB / 256MB / 51%(iOS 6.1.1)
- 第五代iPod touch:286MB / 512MB / 56%(iOS 7.0)
- iPhone4:325MB / 512MB / 63%
- iPhone4s:286MB / 512MB / 56%
- iPhone5:645MB / 1024MB / 62%
- iPhone5s:646MB / 1024MB / 63%
- iPhone6:645MB / 1024MB / 62%(iOS 8.x)
- iPhone6 +:645MB / 1024MB / 62%(iOS 8.x)
- iPhone6s:1396MB / 2048MB / 68%(iOS 9.2)
- iPhone6s +:1392MB / 2048MB / 68%(iOS 10.2.1)
- iPhoneSE:1395MB / 2048MB / 69%(iOS 9.3)
- iPhone7:1395 / 2048MB / 68%(iOS 10.2)
- iPhone7 +:2040MB / 3072MB / 66%(iOS 10.2.1)
- iPhone8:1364 / 1990MB / 70%(iOS 12.1)
- iPhone X:1392/2785/50%(iOS 11.2.1)
- iPhone XS:2040/3754/54%(iOS 12.1)
- iPhone XS Max:2039/3735/55%(iOS 12.1)
- iPhone XR:1792/2813/63%(iOS 12.1)
- iPhone 11:2068/3844/54%(iOS 13.1.3)
- iPhone 11 Pro Max:2067/3740/55%(iOS 13.2.3)
五.网络传输
1.TCP&UDP
(1)TCP和UDP优缺点
TCP的优点: 可靠,稳定。TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。
TCP的缺点: 慢,效率低,占用系统资源高,易被攻击。TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。
UDP的优点: 快,比TCP稍安全。UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击。
UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。
(2)TCP和UDP的使用场景
使用TCP:当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。
在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP。
PuTTY,用的Telnet、SSH传输,QQ文件传输。使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。
比如,日常生活中,常见使用UDP协议的应用如下: QQ语音,QQ视频,TFTP。
有些应用场景对可靠性要求不高会用到UPD,比如长视频,要求速率。
(3)TCP和UDP的区别
区别 | TCP | UDP |
---|---|---|
信道 | 全双工 | 不可信信道 |
通信连接 | 点对点传输 | 支持多播或者广播 |
程序结构 | 较复杂 | 较简单 |
首部开销 | 20字节 | 8字节 |
应用场合 | 传输少量数据 | 传输大量数据 |
数据单位协议 | TCP报文段,面向字节流 | UDP用户数据报 |
对系统资源的要求 | 较多 | 较少 |
传输之前是否连接 | 需要连接 | 无连接 |
是否保证数据顺序 | 保证 | 不保证 |
是否保证数据正确性 | 保证数据正确性 | 可能丢包 |
(4)UDP应用场景
- 面向数据报方式
- 网络数据大多为短消息
- 拥有大量Client
- 对数据安全性无特殊要求
- 网络负担非常重,但对响应速度要求高
(5)编程应用步骤
(A)TCP:
TCP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt(); * 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、开启监听,用函数listen();
5、接收客户端上来的连接,用函数accept();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
8、关闭监听;
TCP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置要连接的对方的IP地址和端口等属性;
5、连接服务器,用函数connect();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
(B)UDP:
与之对应的UDP编程步骤要简单许多,分别如下:
UDP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、循环接收数据,用函数recvfrom();
5、关闭网络连接;
UDP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置对方的IP地址和端口等属性;
5、发送数据,用函数sendto();
6、关闭网络连接;
TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。
(6)补充说明
(A)TCP补充:
TCP充分实现了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。
(B)UDP补充:
UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用户充分考虑好上层协议类型并制作相应的应用程序。
2.TCP三次握手和四次挥手
(1)TCP报文中的字段含义
(1)序列号:seq序列号,占32位,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。
(2)确认序号:ack序号,占32位,只有ACK标志位为1时,确认字段才有效,ack=seq+1。期待收到对方下一个报文段的第一个数据字节的序号;
序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。
(3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:
- URG:紧急指针是否有效。为1,表示某一位需要被优先处理
- ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效
- PSH:接收方应该尽快将这个报文交给应用层。
- RST:对方要求重新建立连接,复位。
- SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。
- FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接
需要注意的是:
(A)不要将确认序号Ack与标志位中的ACK搞混了。
(B)确认方Ack=发起方Seq+1,两端配对。
(2)TCP三次握手过程
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值 seq = X,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=X+1,随机产生一个值seq=Y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为X + 1,ACK是否为1,如果正确则将标志位ACK置为1,ack=Y+1,并将该数据包发送给Server,Server检查ack是否为Y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
简化过程:
在第一次消息发送中,C随机选取一个序列号作为自己的初始序号发送给B;
第二次消息S使用ack对C的数据包进行确认,因为已经收到了序列号为x的数据包,准备接收序列号为x+1的包,所以ack=x+1,同时S告诉C自己的初始序列号,就是seq=y;
第三条消息C告诉S收到了S的确认消息并准备建立连接,C自己此条消息的序列号是x+1,所以seq=x+1,而ack=y+1是表示C正准备接收S序列号为y+1的数据包。
握手次数 | 方向 | SYN | seq | ACK | ack |
---|---|---|---|---|---|
1 | C->[SYN]->S | 1 | X | 没有 | 没有 |
2 | S->[SYN/ACK]->C | 1 | Y | 1 | X + 1 |
3 | C->[ACK]->S | 不管 | X + 1 | 1 | Y + 1 |
(3)TCP三次握手的必要性
必要性一:防止已失效的请求报文段突然又传送到了服务端而造成连接的误判。
1)第一次握手和第二次握手(ACK部分)建立了从客户端到服务器传送数据的可靠连接;
2)第二次握手(SYN部分)和第三次握手建立了从服务器到客户端传送数据的可靠连接;
3)由于我们期望建立全双工连接,所以两个方向的通信都是需要的,于是合并了服务器发送的ACK和SYN。
eg:假如客户端发出连接请求A,由于网络原因,服务端并没有收到A,于是客户端又发送了连接请求B,并建立了连接,完成通信,断开连接。这时候,服务端突然又收到了A,于是看作是一次新的连接请求,进行第二次握手,由于不存在第三次握手,所以这时已经建立了TCP连接。但实际上客户端并没有发起连接,所以不会传递数据,那么这条连接就会变成一条死连接。
必要性二:实现可靠的全双工通信
为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。
三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤。
如果只是两次握手,至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认 (可以接受到序号,但是没有确认序号是否准确)
(4)三次握手错误分析
第一次握手A发送SYN传输失败,A,B都不会申请资源,连接失败。如果一段时间内发出多个SYN连接请求,那么A只会接受它最后发送的那个SYN的SYN+ACK回应,忽略其他回应全部回应,B中多申请的资源也会释放。
第二次握手B发送SYN+ACK传输失败,A不会申请资源,B申请了资源,但收不到A的ACK,过一段时间释放资源。如果是收到了多个A的SYN请求,B都会回复SYN+ACK,但A只会承认其中它最早发送的那个SYN的回应,并回复最后一次握手的ACK。
第三次握手ACK传输失败,B没有收到ACK,释放资源,对于后序的A的传输数据返回RST。实际上B会因为没有收到A的ACK会多次发送SYN+ACK,次数是可以设置的,如果最后还是没有收到A的ACK,则释放资源,对A的数据传输返回RST
(5)SYN攻击
- 在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect)。
- 此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。
- SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将产时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。
- SYN攻击时一种典型的DDOS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了,使用如下命令可以让之现行: #netstat -nap | grep SYN_RECV
(6)TCP四次挥手
- 由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
- 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
- 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
(7)四次挥手必要性
必要性:为保证单向通信的可行性,所以多一次挥手。
主动断开方发送FIN时,被动断开方要回复ACK,意思是“我收到你的FIN了”;
主动断开方发送FIN并不意味着立即关闭TCP连接,而是告诉对方自己没有更多的数据要发送了,只有当对方发完自己的数据再发送FIN后,才意味着关闭TCP连接;
被动断开方收到FIN并回复ACK后,此时TCP处于“半关闭”状态,为保证被动断开方可以继续发送数据,所以第二个FIN并不会伴随ACK发送,所以比连接时多一个报文段。
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送
(8)计网面试常见题
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
3.HTTP & HTTPS
(1)基本概念
HTTP:是互联网上应用最为广泛的一种网络协议 ,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
HTTPS:是以安全为目标的HTTP通道,简单讲就是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详情内容就需要SSL。
HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
(2)HTTP & HTTPS的区别
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。
简单来说,HTTP协议是有SSL+HTTP协议构建的可进行加密传输,身份认证的网络协议,要比HTTP协议安全。
HTTPS和HTTP的区别主要如下
HTTPS协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议。
HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输,身份认证的网络协议,比HTTP协议安全。
(3)HTTPS工作流程
1、客户端发起HTTPS请求
用户在浏览器里输入一个https网址,然后连接到server的443端口。
2、服务端的配置
采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请,区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面。
这套证书其实就是一对公钥盒私钥,如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,全世界只有你一个人有这一把钥匙,你可以把锁头给别人,别人可以用这把锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只要你才能看到被这把锁锁起来的东西。
3、传送证书
这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。
4、客户端解析证书
这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。
如果证书没有问题,那么就生成一个随机值,然后用证书对该随机值进行加密,就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。
5、传送加密信息
这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的同学就可以通过这个随机值进行加密解密了。
6、服务端解密信息
服务端用私钥解密后,得到了客户端传过来的的随机值(私钥),然后把内容通过该值进行对称加密,所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。
7、传输加密后的信息
这部分信息是服务端用私钥加密后的信息,可以在客户端被还原。
8、客户端解密信息
客户端用之前生产的私钥解密服务段传过来的信息,于是获取了解密后的内容,整个过程第三方即使监听到了数据,也束手无策。
(4)HTTPS优缺点
a.优点
1、SEO方面
谷歌曾在2014年8月份调整搜索引擎算法,并称“比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高”
2、安全性
尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个好处:
(1)、使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
(2)、HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
(3)、HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本
b.缺点
1、SEO方面
据ACM CoNEXT数据显示,使用HTTPS协议会使页面的加载时间延长近50%,增加10%到20%的耗电,此外,HTTPS协议还会影响缓存,增加数据开销和功耗,甚至已有安全措施也会受到影响也会因此而受到影响。
而且HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。
最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。
2、经济方面
(1)、SSL证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。
(2)、SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗(SSL有扩展可以部分解决这个问题,但是比较麻烦,而且要求浏览器、操作系统支持,Windows XP就不支持这个扩展,考虑到XP的装机量,这个特性几乎没用)。
(3)、HTTPS连接缓存不如HTTP高效,大流量网站如非必要也不会采用,流量成本太高。
(4)、HTTPS连接服务器端资源占用高很多,支持访客稍多的网站需要投入更大的成本,如果全部采用HTTPS,基于大部分计算资源闲置的假设的VPS的平均成本会上去。
(5)、HTTPS协议握手阶段比较费时,对网站的相应速度有负面影响,如非必要,没有理由牺牲用户体验。
(5)SSL证书
1、SSL的作用
(1)、认证用户和服务器,确保数据发送到正确的客户机和服务器;
(2)、加密数据以防止数据中途被窃取;
(3)、维护数据的完整性,确保数据在传输过程中不被改变。
而SSL证书指的是在SSL通信中验证通信双方身份的数字文件,一般分为服务器证书和客户端证书,我们通常说的SSL证书主要指服务器证书,SSL证书由受信任的数字证书颁发机构CA(如VeriSign,GlobalSign,WoSign等),在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能,分为扩展验证型(EV)SSL证书、组织验证型(OV)SSL证书、和域名验证型(DV)SSL证书。
2、SSL证书申请的3个主要步骤
(1)、制作CSR文件
所谓CSR就是由申请人制作的Certificate Secure Request证书请求文件,制作过程中,系统会产生2个密钥,一个是公钥就是这个CSR文件;另外一个是私钥,存放在服务器上。
要制作CSR文件,申请人可以参考WEB SERVER的文档,一般APACHE等,使用OPENSSL命令行来生成KEY+CSR2个文件,Tomcat,JBoss,Resin等使用KEYTOOL来生成JKS和CSR文件,IIS通过向导建立一个挂起的请求和一个CSR文件。
(2)、CA认证
将CSR提交给CA,CA一般有2种认证方式:
①、域名认证:一般通过对管理员邮箱认证的方式,这种方式认证速度快,但是签发的证书中没有企业的名称。
②、企业文档认证:需要提供企业的营业执照,一般需要3-5个工作日。
也有需要同时认证以上2种方式的证书,叫EV证书,这种证书可以使IE7以上的浏览器地址栏变成绿色,所以认证也最严格。
(3)、证书的安装
在收到CA的证书后,可以将证书部署上服务器,一般APACHE文件直接将KEY+CER复制到文件上,然后修改HTTPD.CONF文件;TOMCAT等,需要将CA签发的证书CER文件导入JKS文件后,复制上服务器,然后修改SERVER.XML;IIS需要处理挂起的请求,将CER文件导入
基础算法
快速排序
import Cocoaclass Sort {/** array:传入参数的数组,注意是传址不是传值* p:排序区间起点下标* r:排序区间终点点下标*/private func partition(_ array:inout [Int], _ p:Int, _ r:Int) -> Int {let pivot = array[r] // 分界值,一般选数组排序区间末尾元素var i:Int = pfor j in p...r-1 {if array[j] < pivot{ array.swapAt(i, j) // 交换数组下标为 i、j 的元素 i += 1 // 有小于pivot的数字i++}}// 交换 a[i] 与 a[r] 即:将pivot放到相应位置array[r] = array[i]array[i] = pivotreturn i // 获取分界值(pivot)排序后下标}/* array:传入参数的数组,注意是传址不是传值* p:排序区间起点下标* r:排序区间终点点下标*/private func qucikSortC (_ array:inout [Int], _ p:Int, _ r:Int) -> [Int]{// 需要排序的区间只包含一个数字,则不需要重排数组,直接返回if p >= r {return array}let i = partition(&array, p, r)qucikSortC(&array, p, i-1)qucikSortC(&array, i+1, r)return array}public func quickSort(_ array: [Int]) -> [Int] {var a = arrayreturn qucikSortC(&a, 0, array.count-1)}}
设计思路
Runtime
1.什么是Runtime?
- Rumtime是Objective-C语言动态的核心,Objective-C的对象一般都是基于Runtime的类结构,达到很多在编译时确定方法推迟到了运行时,从而达到动态修改、确定、交换…属性及方法.
- 运行时(Runtime)是指将数据类型的确定由编译时推迟到了运行时
- Runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API
- 平时编写的OC代码,在程序运行过程中,其实最终会转换成Runtime的C语言代码,Runtime是Object-C的幕后工作者
- Object-C需要Runtime来创建类和对象,进行消息发送和转发
- 特性: 编写的代码具有运行时、动态特性
2.Runtime的用法以及所使用的地方
(1)用来干什么 基本作用
- 在程序运行过程中,动态的创建类,动态添加、修改这个类的属性和方法;
- 遍历一个类中所有的成员变量、属性、以及所有方法
- 消息传递、转发
(2)用在哪些地方 Runtime的典型事例
- 给系统分类添加属性、方法
- 方法交换
- 获取对象的属性、私有属性
- 字典转换模型
- KVC、KVO
- 归档(编码、解码)
- NSClassFromString class<->字符串
- block
- 类的自我检测
……
多线程
1.进程与线程
(1)进程:
进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元.
进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app.
每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源
(2)线程
程序执行流的最小单元,线程是进程中的一个实体.
一个进程要想执行任务,必须至少有一条线程.应用程序启动的时候,系统会默认开启一条线程,也就是主线程
(3)进程和线程的关系
线程是进程的执行单元,进程的所有任务都在线程中执行
线程是 CPU 分配资源和调度的最小单位
一个程序可以对应多个进程(多进程),一个进程中可有多个线程,但至少要有一条线程
同一个进程内的线程共享进程资源
2.多线程的含义
多线程的实现原理:事实上,同一时间内单核的CPU只能执行一个线程,多线程是CPU快速的在多个线程之间进行切换(调度),造成了多个线程同时执行的假象。
如果是多核CPU就真的可以同时处理多个线程了。
多线程的目的是为了同步完成多项任务,通过提高系统的资源利用率来提高系统的效率。
3.多线程的优缺点
优点:
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)
缺点:
开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
程序设计更加复杂:比如线程之间的通信、多线程的数据共享
4.多线程的 并行 和 并发
并行:充分利用计算机的多核,在多个线程上同步进行
并发:在一条线程上通过快速切换,让人感觉在同步进行
5.IOS多线程方案
NSThread 面向对象的,需要程序员手动创建线程,但不需要手动销毁。子线程间通信很难。
GCD c语言,充分利用了设备的多核,自动管理线程生命周期。比NSOperation效率更高。
NSOperation 基于gcd封装,更加面向对象,比gcd多了一些功能。
6.死锁
死锁是由于多个线程(进程)在执行过程中,因为争夺资源而造成的互相等待现象,你可以理解为卡主了。产生死锁的必要条件有四个:
互斥条件 : 指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
请求和保持条件 : 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
不可剥夺条件 : 指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
环路等待条件 : 指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
最常见的就是 同步函数 + 主队列 的组合,本质是队列阻塞。
7.GCD执行原理
GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?) 而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。
如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。
如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。
这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开5–8条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:3–5条最为合理。
同步和异步
- 同步:发送一个请求,等待返回,然后再发送下一个请求
- 异步:发送一个请求,不等待返回,随时可以再发送下一个请求
- 同步和异步最大的区别就在于。一个需要等待,一个不需要等待。
(1)Swift中Alamofire的异步请求思路
闭包不好保持,但结果数据易于保持,具体就是网络请求回来的数据对象,我们在网络请求代理回调里,可以把拿到的数据对象保持住,然后再告知上层去获取解析这个数据对象,于是问题就从如何保持一个闭包转化为了如何协调:
故而流程为:调用者发起请求->调用者等待结果->异步请求,拿到结果,通过对象保持住结果->告知调用者(通过对象)取结果,并将结果进一步反馈。
调用者等待的时候完全不用离开发起请求的函数体,唤醒后继续执行当前函数体,于是也就规避了闭包的保持问题.
问题从保持闭包转化为了流程协调,Alamofire的解决方式就是通过OperationQueue,具体一点就是借助OperationQueue的suspend.
其大概流程是这样的:
发起请求,建立一个队列
将队列暂停住
把调用者的回调处理加入队列
在网络数据返回后,通过对象保持住返回的数据,取消队列暂停
调用者的回调处理被队列执行,调用者此时可以借助保持对象拿到返回的数据结果,进行后续的处理.
补充
- 堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。
- 要点:堆:顺序随意 栈:后进先出(Last-In/First-Out)
堆
堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
·堆中某个节点的值总是不大于或不小于其父节点的值;
·堆总是一棵完全二叉树。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。
堆是应用程序在运行的时候请求操作系统分配给自己内存,一般是申请/给予的过程。
堆是指程序运行时申请的动态内存,而栈只是指一种使用堆的方法(即先进后出)。
栈
栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。
栈就是一个桶,后放进去的先拿出来,它下面本来有的东西要等它出来之后才能出来(先进后出)
栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有FIFO的特性,在编译的时候可以指定需要的Stack的大小。
堆栈区别
1.堆栈空间分配
栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
2.堆栈缓存方式
栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。
堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
3.堆栈数据结构区别
①堆(数据结构):堆可以被看成是一棵树,如:堆排序。
②栈(数据结构):一种后进先出的数据结构。
4.例子
char s1[] = “aaaaaaaaaaaaaaa”;
char *s2 = “bbbbbbbbbbbbbbbbb”;
aaaaaaaaaaa是在运行时刻赋值的;放在栈中。
而bbbbbbbbbbb是在编译时就确定的;放在堆中。
数组链表
数组静态分配内存,链表动态分配内存;
数组在内存中连续,链表不连续;
数组元素在栈区,链表元素在堆区;
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
内存释放时间
栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。程序结束时由编译器自动释放。
堆区(heap) — 在内存开辟另一块存储区域。一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上
全局区(静态区)(static)—编译器编译时即分配内存。全局变量和静态变量的存储是放在一块的。对于C语言初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。而C++则没有这个区别 - 程序结束后由系统释放
文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
程序代码区—存放函数体的二进制代码。
TCP的CS
- 服务器初始化
- 1)调用socket,创建文件描述符fd
- 调用bind将fd与服务器的IP与PORT绑定
- 3)调用listen将套接字设为监听模式,准备接收客户端连接请求
4)调用accept等待并接收客户端的连接请求,建立好TCP连接后,该函数会返回一个新的已连接套接字newfd
建立连接
1)客户端调用socket创建文件描述符
2)调用connect,向服务器发送连接请求
3)connect会发送一个请求SYN段并阻塞等待服务器应答(第一次握手)
4)服务器收到SYN,会给客户端发送一个确认应答ACK,同时发送一个请求(SYN)建立连接(第二次握手)
5)客户端收到服务器发的SYN+ACK段,表明客户端连接已建立成功,进入已连接状态。客户端再向服务器
发送一个ACK段,服务器收到后则服务器连接成功。
数据传输
1)连接建立成功后,在同一连接、同一时刻,通信双方可同时写数据(全双工)
2)服务器端从accept()返回后调用read()开始读数据,若没有数据则阻塞等待
3)客户端调用write()向服务器发送数据请求,客户端收到之后调用read()处理请求,此过程服务器调用read()阻塞等待
4)服务器调用write()将处理好的请求发送给客户端,再次调用read()等待下一个请求
5)客户端收到后从read()返回,发送下一条请求,如此循环下去
断开连接
1)没有数据处理了,则客户端调用close()关闭连接,给服务器发送一个断开连接请求FIN段(第一次握手)
2)服务器收到客户端的FIN段,给客户端发送一个确认应答ACK段,表明同一断开连接。客户端收到ACK段并
调用read()返回0,表明客户端连接已经断开(第二次握手)
3)read()返回0后,服务器知道客户端已经断开连接,它也调用close()关闭连接,给客户端发送一个断开连接
请求FIN段(第三次握手)
4) 客户端收到服务器发送的FIN段,就给服务器一个确认应答ACK段,表明同意断开连接。客户端进入TIME_WAIT
状态,服务器收到客户端的ACK段后也断开连接。
1、TCP建立连接
HTTP协议是基于TCP协议来实现的,因此首先就是要通过TCP三次握手与服务器端建立连接,一般HTTP默认的端口号为80;
2、浏览器发送请求命令
在与服务器建立连接后,Web浏览器会想服务器发送请求命令
3、浏览器发送请求头消息
在浏览器发送请求命令后,还会发送一些其它信息,最后以一行空白内容告知服务器已经完成头信息的发送;
4、服务器应答
在收到浏览器发送的请求后,服务器会对其进行回应,应答的第一部分是协议的版本号和应答状态码;
5、服务器回应头信息
与浏览器端同理,服务器端也会将自身的信息发送一份至浏览器
6、服务器发送数据
在完成所有应答后,会以Content-Type应答头信息所描述的格式发送用户所需求的数据信息
7、断开TCP连接
在完成此次数据通信后,服务器会通过TCP四次挥手主动断开连接。但若此次连接为长连接,那么浏览器或服务器的头信息会加入keep-alive的信息,会保持此连接状态,在有其它数据发送时,可以节省建立连接的时间;
c++编译过程
C/C++程序编译流程:
预处理->编译->汇编->链接
具体的就是:
源代码(source coprede)→预处理器(processor)→编译器(compiler)→汇编程序(assembler)→目标程序(object code)→链接器(Linker)→可执行程序(executables)
网络错误开头
201-206都表示请求成功
300-307表示要完成请求,需要进一步操作,代码状态通常为重定向
400-417表示请求可能出错了,妨碍服务器处理
500-505表示:服务器在尝试请求处理时发生内部错误,是服务器的错,不是请求的错
IOS-Swift面试相关基础相关推荐
- JAVA面试相关基础知识
1.面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节.抽 ...
- 【秋招总结】面试相关-基础概念
##操作系统: 进程与线程的区别: 进程就是运行的一个程序,一个进程可以包含多个线程:进程是资源分配的最小单位,线程是CPU调度的最小单位. 线存共享 进程同步 进程调度的算法:先来先服务等 进程和线 ...
- 【C++后台开发面经】面试总结第三波:针对后台开发相关基础知识分类总结
前言 面试总结第三波,关于后台开发面试相关基础知识,数据结构.算法.linux操作系统.计算机网络.C++.数据库进行分类总结. 后端面试总结 目录 后端面试总结 1.数据结构 链表和数组的区别 树的 ...
- Swift面试资料 (一) —— Questions 和 Answers(一)
前言 Swift作为一门开发语言,它到目前为止也四岁了,接下来这个专题主要收集一下Swift面试相关的问题. 开始 首先看下写作环境 Swift 5, iOS 12, Xcode 10 Swift只有 ...
- ios 与web服务器传值_中高级iOS大厂面试宝典,拿到offer率80%,金三银四将是你的新起点...
引言 过年之后相信有一部分的人,早已磨刀霍霍向大厂.势必要大展拳脚,必将在大厂内创出一片天地.但是,想必大家都知道:最近几年的最严重的互联网寒冬来临,各位兄弟都会说"江湖再见".耳 ...
- 中高级iOS大厂面试宝典,进厂率80%,金三银四将是你的新起点
引言 过年之后相信有一部分的人,早已磨刀霍霍向大厂.势必要大展拳脚,必将在大厂内创出一片天地.但是,想必大家都知道:最近几年的最严重的互联网寒冬来临,各位兄弟都会说"江湖再见".耳 ...
- Swift 2.2 基础语法
Swift 介绍 简介 Swift 语言由苹果公司在 2014 年推出,用来撰写 OS X 和 iOS 应用程序 2014 年,在 Apple WWDC 发布 几家欢喜,几家愁 愁者:只学Object ...
- 最新2022【iOS开发面试真题】初级,中级,高级
前言: 我是一名开发者,一个iOS交流圈子的维护者,对于程序员来说,要学习的知识内容.技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们! 标语:不要浪费 ...
- 如何实现一个直播平台的相关基础及开源软件推荐
如何实现一个直播平台的相关基础及开源软件推荐 首先我们要大致知道一个直播系统核心的构成要件主要是三部分: 采集--主播使用 OBS 之类的软件来录制视频,并能够将数据发向服务器 转播--服务器使用 S ...
最新文章
- tabcontainer控件太长_asp.net AjaxControlToolKit--TabContainer控件的介绍
- 人脸和性别识别(基于OpenCV)
- java中的强制类型转换:int和byte
- How run Powershell ISE on WIndows Server2008
- cad中简单流程图制作_想要又快又好的画出世界级的流程图,这样牛掰的方法一定要知道...
- Arduino I/O Expansion Shield V7.1
- Snmp linux
- 数学哲学与科学哲学和计算机科学的能动作用,数学哲学与科学哲学和计算机科学的能动作用...
- Maximum sum(信息学奥赛一本通-T1305)
- 满满的干货!mysql技术介绍
- Java内存管理及GC算法
- 软件考试网络工程师系列博文索引
- 共享WiFi码项目一天赚3000,一个月6W,背后逻辑与源代码分析
- 东方精工、普莱德商誉“罗生门”,谁在扯谎?
- Android源码目录结构
- 我的世界服务器插件文件夹,各位大神服务器没有插件文件夹怎么办
- Python面向对象版学员管理系统
- ipsec-***过程
- UCloud成首个通过乌云认证的云计算平台
- 爬虫入门 ---- CSDN查看文章全部评论