iOS开发精选

iOS知识总结

2021-05-22  本文已影响0人  甘林梦

一、建立TCP连接/ TCP三次握手======

Socket是基于TCP/IP上的封装, 如果要了解Socket的连接, 就要从TPC/IP的连接入手.

我们日夜不分离的手机之所以能联网, 也是因为手机的底层实现了TCP/IP协议, 可以让手机通过WiFi, 4G等无线网络进行通信.

建立起一个TCP连接, 需要通过三次的验证, 我们这里称为三次握手:

第一次握手: 由客户端发送一个叫做SYN(SYN=J)包到服务器, 并且进入SYN_SEND状态, 然后就翘着二郎腿等服务器回应.

第二次握手: 服务器接收到了SYN包, 必须确认客户端的SYN(ACK=J+1), 同时也会发送一个SYN(SYN=K)包, 也就是SYN+ACK, 此时轮到服务器跷二郎腿, 并且进入SYN_RECV状态.

第三次握手: 接收到了服务器发来的SYN+ACK包, 并向服务器发送确认包ACK(ACK=K+1), 发完之后, 客户端和服务器就会收起二郎腿并且进入ESTABLISHED状态, 完成了三次握手

大概的情况就是酱紫:

这里注意一下, 在进行握手的时候, 所传送的包并不包含数据.

只有在完成三次握手之后, 客户端和服务器才会正式开始传输数据, 一般在TCP连接建立成功后, 除非有一方主动关闭连接之前, TCP连接是会一直保持下去的, 比如我们的微信, QQ这些即时聊天App.

关闭TCP连接/TCP四次挥手

刚刚说完了如何建立起TCP的连接, 现在我们来看看怎么关闭.

客户端和服务器都可以发起关闭TCP连接的请求, 但是需要通过四次验证, 我们这里称为四次挥手, 这里我们演示由客户端发起关闭TCP连接:

第一次挥手: 客户端会发送一个FIN的报文给服务器之后就会进入等待服务器的响应.

第二次挥手: 服务器接收到了FIN之后, 并确认是由客户端发起的, 同时也会发送一条ACK=X+1的报文.

第三次挥手: 等到客户端接受到ACK报文之后, 服务器关闭了与客户端的连接, 会发送一条FIN的报文给客户端.

第四次挥手: 客户端接收到了由服务器发送过来的FIN报文, 就会关闭与服务端的连接, 并且发送ACK给服务器.

有人或许有疑问说为啥TCP建立连接的时候是三次握手, 而断开连接的时候却是四次挥手呢?

因为连接时服务端收到了客户端的SYN连接请求的报文后, 可以直接发送SYN+ACK报文, 其中的ACK报文是用来响应, SYN报文是用来同步的.

而当关闭连接时, 服务端收到FIN报文后, 很可能并不会马上就关闭Socket连接, 所以只能先回复一个ACK报文, 告诉客户端, 你发的FIN报文我收到了, 只有等到服务器的所有报文发送完了, 服务端才会发送FIN报文, 所以才需要四次挥手.

第三方的开源库CocoaAsyncSocket

参考:

链接:https://www.jianshu.com/p/5218202e13ff

二、卡顿产生的原因和解决方案======

GPU 通常有一个机制叫做垂直同步(简写也是 V-Sync),当开启垂直同步后,GPU 会等待显示器的 VSync 信号发出后,才进行新的一帧渲染和缓冲区更新。这样能解决画面撕裂现象,也增加了画面流畅度,但需要消费更多的计算资源,也会带来部分延迟。

在 VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在 CPU 中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。随后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。

从上面的图中可以看到,CPU 和 GPU 不论哪个阻碍了显示流程,都会造成掉帧现象。所以开发时,也需要分别对 CPU 和 GPU 压力进行评估和优化。

CPU 资源消耗原因和解决方案

对象调整

当视图层次调整时,UIView、CALayer 之间会出现很多方法调用与通知,所以在优化性能时,应该尽量避免调整视图层次、添加和移除视图。

布局计算

视图布局的计算是 App 中最为常见的消耗 CPU 资源的地方。如果能在后台线程提前计算好视图布局、并且对视图布局进行缓存,那么这个地方基本就不会产生性能问题了。

不论通过何种技术对视图进行布局,其最终都会落到对 UIView.frame/bounds/center 等属性的调整上。上面也说过,对这些属性的调整非常消耗资源,所以尽量提前计算好布局,在需要时一次性调整好对应属性,而不要多次、频繁的计算和调整这些属性。

GPU 资源消耗原因和解决方案

图形的生成

设置了以下属性时,都会触发离屏绘制:

shouldRasterize(光栅化)

masks(遮罩)

shadows(阴影)

edge antialiasing(抗锯齿)

group opacity(不透明)

复杂形状设置圆角等

渐变

CALayer 的 border、圆角、阴影、遮罩(mask),CASharpLayer 的矢量图形显示,通常会触发离屏渲染(offscreen rendering),而离屏渲染通常发生在 GPU 中。当一个列表视图中出现大量圆角的 CALayer,并且快速滑动时,可以观察到 GPU 资源已经占满,而 CPU 资源消耗很少。这时界面仍然能正常滑动,但平均帧数会降到很低。为了避免这种情况,可以尝试开启 CALayer.shouldRasterize 属性,但这会把原本离屏渲染的操作转嫁到 CPU 上去。对于只需要圆角的某些场合,也可以用一张已经绘制好的圆角图片覆盖到原本视图上面来模拟相同的视觉效果。最彻底的解决办法,就是把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。

光栅化有什么好处?

shouldRasterize = YES在其他属性触发离屏渲染的同时,会将光栅化后的内容缓存起来,如果对应的layer及其sublayers没有发生改变,在下一帧的时候可以直接复用。shouldRasterize = YES,这将隐式的创建一个位图,各种阴影遮罩等效果也会保存到位图中并缓存起来,从而减少渲染的频度(不是矢量图)。

而光栅化会导致离屏渲染,影响图像性能,那么光栅化是否有助于优化性能,就取决于光栅化创建的位图缓存是否被有效复用,而减少渲染的频度。

当你使用光栅化时,你可以开启“Color Hits Green and Misses Red”来检查该场景下光栅化操作是否是一个好的选择。

如果光栅化的图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。

三、Block机制======

block本质上也是一个OC对象,它内部也有个isa指针

block是封装了函数调用以及函数调用环境的OC对象

block是封装函数及其上下文的OC对象

block有哪几种类型?

block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

__NSGlobalBlock __ ( _NSConcreteGlobalBlock )

__NSStackBlock __ ( _NSConcreteStackBlock )

__NSMallocBlock __ ( _NSConcreteMallocBlock )

void(^block1)(void) = ^{

    NSLog(@"block1");

};

NSLog(@"%@",[block1class]);

NSLog(@"%@",[[block1class] superclass]);

NSLog(@"%@",[[[block1class] superclass] superclass]);

NSLog(@"%@",[[[[block1class] superclass] superclass] superclass]);

NSLog(@"%@",[[[[[block1class] superclass] superclass] superclass] superclass]);

输出结果:

NSGlobalBlock

__NSGlobalBlock

NSBlock

NSObject

null

上述代码输出了block1的类型,也证实了block是对象,最终继承NSObject

代码展示block的三种类型:

intage =1;void(^block1)(void) = ^{

    NSLog(@"block1");

};void(^block2)(void) = ^{

    NSLog(@"block2:%d",age);

};

NSLog(@"%@/%@/%@",[block1class],[block2class],[^{

    NSLog(@"block3:%d",age);

} class]);

输出结果:

__NSGlobalBlock __/__NSMallocBlock __/__NSStackBlock __

各类型的block在内存中如何分配的?

__NSGlobalBlock __ 在数据区

__NSMallocBlock __ 在堆区

__NSStackBlock __ 在栈区

堆:动态分配内存,需要程序员自己申请,程序员自己管理

栈:自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况

栈空间上的block,不会持有对象;堆空间的block,会持有对象。

如何判断block是哪种类型?

没有访问auto变量的block是__NSGlobalBlock __ ,放在数据段

访问了auto变量的block是__NSStackBlock __

[__NSStackBlock __ copy]操作就变成了__NSMallocBlock __

在全局数据区的Block对象

    {

        NSLog(@"--------------------block的存储域 全局块---------------------");

        void(^blk)(void) = ^{

            NSLog(@"Global Block");

        };

        blk();

        NSLog(@"%@", [blkclass]);

    }

    /*    结果:输出 __NSGlobalBlock__

    *//*    结论:

    全局块:这种块不会捕捉任何状态(外部的变量),运行时也无须有状态来参与。块所使用的整个内存区域,在编译期就已经确定。

    全局块一般声明在全局作用域中。但注意有种特殊情况,在函数栈上创建的block,如果没有捕捉外部变量,block的实例还是会被设置在程序的全局数据区,而非栈上。

    */

在堆上创建的Block对象

    {

        NSLog(@"--------------------block的存储域 堆块---------------------");

        inti =1;

        void(^blk)(void) = ^{

            NSLog(@"Malloc Block, %d", i);

        };

        blk();

        NSLog(@"%@", [blkclass]);

    }

    /*    结果:输出 __NSMallocBlock__

    *//*结论:    堆块:解决块在栈上会被覆写的问题,可以给块对象发送copy消息将它拷贝到堆上。复制到堆上后,块就成了带引用计数的对象了。          在ARC中,以下几种情况栈上的Block会自动复制到堆上: - 调用Block的copy方法

    - 将Block作为函数返回值时(MRC时此条无效,需手动调用copy)

    - 将Block赋值给__strong修饰的变量时(MRC时此条无效)

    - 向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时上述代码就是在ARC中,block赋值给__strong修饰的变量,并且捕获了外部变量,block就会自动复制到堆上。*/

在栈上创建的Block对象    {

        NSLog(@"--------------------block的存储域 栈块---------------------");

        inti =1;

        __weak void(^blk)(void) = ^{

            NSLog(@"Stack Block, %d", i);

        };

        blk();

        NSLog(@"%@", [blkclass]);

    }

    /*    结果:输出 __NSStackBlock__

    *//*    结论:

    栈块:块所占内存区域分配在栈中,编译器有可能把分配给块的内存覆写掉。

    在ARC中,除了上面四种情况,并且不在global上,block是在栈中。

    */

Block总结

在block内部,栈是红灯区,堆是绿灯区。

在block内部使用的是将外部变量的拷贝到堆中的(基本数据类型直接拷贝一份到堆中,对象类型只将在栈中的指针拷贝到堆中并且指针所指向的地址不变。)

__block修饰符的作用:是将block中用到的变量,拷贝到堆中,并且外部的变量本身地址也改变到堆中。

循环引用:分析实际的引用关系,block中直接引用self也不一定会造成循环引用。

__block不能解决循环引用,需要在block执行尾部将变量设置成nil(但问题很多,比如block永远不执行,外面变量变了里面也变,里面变了外面也变等问题)

__weak可以解决循环引用,block在捕获weakObj时,会对weakObj指向的对象进行弱引用。

使用__weak时,可在block开始用局部__strong变量持有,以免block执行期间对象被释放。

块的存储域:全局块、栈块、堆块

全局block不引用外部变量,所以不用考虑。

堆block引用的外部变量,不是原始的外部变量,是拷贝到堆中的副本。

栈block本身就在栈中,引用外部变量不会拷贝到堆中。

参考

链接:https://www.jianshu.com/p/4e79e9a0dd82

iOS ,内存分布、内存管理 、isa 指针,散列表(引用计数表,弱引用表)

内核区 ---- 高地址栈(高地址到低地址,向下扩展,定义的方法或者函数都是存放在栈上)堆(创建的对象或者被 copy 的block)未初始化区域(.bss,未初始化的静态变量或者全局变量)已初始化区域(.data,已经初始化声明的静态变量和全局变量)

代码段(.text,我们写的代码段都存在在此)

保留区

内存管理方案

1.taggedPointer , 对于一些小对象使用,如NSNumber

2.NONPOINTER_ISA,对于64位下的,isa 指针占64个比特位,但是其中可能只有32位够用了,剩余的就浪费了,苹果为了不让内存浪费更好的管理内存,剩下的32位,苹果用来存储和内存管理相关的内容,用来节约内存

3.散列表,引用计数表和弱引用表。

NONPOINTER_ISA:

在64位架构下,如果他的第一位是0 。则代表他是一个 isa 指针,表示当前对象的类对象的地址,如果是1,则不仅代表一个 isa 指针,类对象的地址,里面还存储内存管理相关的内容,第二位代表是否有关联对象,0代表没有,1代表有(has_assoc),第三位,代表当前对象是否含有C++代码(has_cxx_dtor),3-15表示当前对象的类对象内存地址,16-31,也是,32-35位也是,也就是说,13+16+4 = 33位,这些位表示内存地址,接下来的剩余的位数里面,紧跟着6位为 magic 字段,然后下一步,也就是第42位来,来表示是否含有弱引用指针,(weakly_referenced),接下来以为表示当前指针是否正在进行dealloc操作(deallocating),再接下来一位,表示当前isa指针的引用计数是否达到上限(has_sidetable_rc),如果达到了上限需要一个sidetable,来额外存储相关的引用计数内容,剩下的几位(extra_rc),表示额外的引用计数,当引用计数很小的时候就直接存在isa指针当中.

散列表(sideTables):

sideTable 其实是一个 hash 表,下面挂了很多的 sideTable,sidetable 包括自旋锁(spinlock_t),引用计数表(refcountMap),弱引用表(weak_table_t)。

sidetables 为什么是多张表,而不是一张表?:

弱引用表(weak_table_t):

也是一个hash表,key->hash->weak_entry_t,weak_entry_t,其实是一个结构体数组(weakPtr),比如被weak修饰,就存在这个弱引用表中。

iOS Dealloc流程解析 Dealloc 实现原理

总共干了三件事::

执行了object_cxxDestruct 函数

执行_object_remove_assocations,去除了关联对象.(这也是为什么category添加属性时,在释放时没有必要remove)

就是上面写的那个,清空引用计数表并清除弱引用表,将weak指针置为nil

object_cxxDestruct是由编译器生成,这个方法原本是为了++对象析构,ARC借用了这个方法插入代码实现了自动内存释放的工作.

这个释放.

现象:

当类拥有实例变量时,这个方法会出现,且父类的实例变量不会导致子类拥有这个方法.

出现这个方法和变量是否被赋值,赋值成什么没有关系.

所以, 我们可以认为这个方法就是用来释放该类中的属性的. weak修饰的属性应该不包含在内。

iOS中的循环引用

循环引用的实质:多个对象相互之间有强引用,不能施放让系统回收。

解决循环引用一般是将 strong 引用改为 weak 引用。

weak 的实现原理可以概括一下三步:

1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用

IOS 同步异步、串行并行的区别

同步异步是相对于线程来说的。  串行和并行是相对于队列,或者说任务来说的,是任务的执行先后顺序,不关系到线程。

串行:一个任务执行完毕后,再执行下一个任务

并行:多个任务同时执行

同步:顾名思义即为一步一步执行线程内的东西。

异步:不阻塞当前线程操作,等同于后台跑数据。

异步 + 并行:系统会为每个任务开创一个新的线程执行。

异步 + 串行:队列数决定了线程数。

同步 + 串行:不开辟新的线程。

同步 + 并行:不开辟新的线程。

关于队列开启线程数:

假如有2个并行队列,每个分别有2个异步线程。此时会开启4条线程。

假如有2个串行队列,每个分别有2个异步线程。此时会开启2条线程。

因为并行队列为了保证快速响应,会开启多个线程。

串行队列,每个队列只会开启一个线程,里面有多少任务都只开启一个。

异步是指线程的异步。通常异步操作都是开启另一个线程来执行,开启的这个只能是子线程。也有在主线程执行异步的时候。异步的子线程会在后台跑起来,甚至超过了主线程的速度。

举例:

举个生活中的例子高速公路,过收费站的时候排队刷卡这件事本身就像是串行。开多个窗口收费的时候就像是并行。然后用户为了超车进入快车道行驶一段路之后再回归主干道,这是异步。同步和串行有点像,但是是相对于线程而言的。所有的线程默认都是同步。

注意:

1、主线程中不能使用同步。会发生循环等待。主线程和主队列的相互等待。主线程内的东西需要加入到队列执行,而主队列在等待主线程执行完毕再继续执行。因此死循环,且主线程内代码不会执行。

2、关于刷新UI的事情需要回归主线程来做。子线程不具备刷新UI的功能。可以更新的结果只是一个幻像:因为子线程代码执行完毕了,又自动进入到了主线程,执行了子线程中的UI更新的函数栈。


iOS 开发中的八种锁(Lock)

iOS信号量的理解和使用

copy修饰NSArray strong修饰NSMutableArray

strong与copy修饰符

strong修饰的属性,对该属性赋值时发生指针拷贝,即浅拷贝;

copy修饰的属性,对该属性赋值时发生内容拷贝,即深拷贝。(存在特殊Case)

上一篇下一篇

猜你喜欢

热点阅读