必备知识架构-线程与网络-①线程GCD

[toc]

知识架构

iOS知识库

Android知识库

六、GCD

< 返回目录

精: iOS开发多线程篇—GCD介绍

0、dispatch_sync、dispatch_async

1.GCD中有2个用来执行任务的函数

说明:把右边的参数(任务)提交给左边的参数(队列)进行执行

(1)用同步的方式执行任务 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

(2)用异步的方式执行任务 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

以下参考自:dispatch_group_async 使用详解

1、dispatch_group_async (可实现队列同步)

要实现监听一组任务是否全部完成,等到全部完成后执行其他的操作可以使用dispatch_group_async(这个方法很有用)。比如异步下载多张图片,等所有图片下载完成之后,再去做一些事情(比如才通知界面说完成的)。

如果想在dispatch_queue中所有的任务执行完成后在做某种操作,在串行队列中,我们只需把该操作放到最后一个任务来执行就可以,但是在并行队列中怎么做呢。答案是用dispatch_group任务组来做。

思路:创建一个任务组,然后将异步操作都放进任务组里面,在最后用notify 告知所有任务完成,并做相应处理,一般最后的处理都是在主线程里面刷新UI来提示用户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //创建并行队列queue
dispatch_group_t group = dispatch_group_create(); //创建任务组group

dispatch_group_async(group, queue, ^{
NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"group3");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"updateUi");
});

dispatch_release(group);

附:下面的两种调用其实是等价的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
A)

dispatch_group_async(group, queue, ^{

  // 。。。

});

B)

dispatch_group_enter(group);

dispatch_async(queue, ^{

  //。。。

  dispatch_group_leave(group);

});

其他:

使用gcd怎么让两个线程执行完结束后再去执行另一个线程

多线程并发流程控制之dispatch_group 有关函数

2、dispatch_barrier_async(可实现队列同步)

dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
dispatch_queue_t queue = dispatch_queue_create("**test.abc.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@"dispatch_async2");
});

dispatch_barrier_async(queue, ^{ //dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
NSLog(@"dispatch_barrier_async");
[NSThread sleepForTimeInterval:4];
});

dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3");
});

3、dispatch_apply

dispatch_apply执行某个代码片段N次。

1
2
3
dispatch_apply(5, globalQ, ^(size_t index) {
// 执行5次
});

4、小结dispatch_group_async、dispatch_barrier_async、dispatch_apply分别是什么?

dispatch_group_async 可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了
dispatch_barrier_async 是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
dispatch_apply 执行某个代码片段N次。

5、GCD四大队列

iOS GCD四大队列

(1)、串行队列
(2)、并发队列
(3)、全局队列

全局队列:
和并发队列,执行效果一样,只是说,并发队列,需要我们程序员,自己创建
而全局队列,是由于系统提供

(4)、主队列

END

< 返回目录

必备知识架构-线程与网络-①线程

[toc]

二、谈谈你对线程间的通信的了解

< 返回目录

在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信。

线程间通信的体现:

1
2
1. 一个线程传递数据给另一个线程
2. 在一个线程中执行完特定任务后,转到另一个线程继续执行任务

线程间通信应用举例:(即在一个线程中执行完特定任务后,转到另一个线程继续执行任务,并将该线程数据传递给另一个线程的应用举例)

以下例子摘自:线程间通讯

NSThread可以先将自己的当前线程对象注册到某个全局的对象中去,这样相互之间就可以获取对方的线程对象,然后就可以使用下面的方法进行线程间的通信了。

1
2
3
4
5
6
7
8
9
10
11
>回到主线程执行,执行self的showImage方法,参数是arg
>
>- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
>
>回到后台线程执行aSelector方法,参数是arg
>- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg
>
>回到xx线程执行aSelector方法,参数是arg
>
>- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
>

②、GCD一个线程传递数据给另一个线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
> dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
> NSLog(@"donwload---%@", [NSThread currentThread]);
> // 1.子线程下载图片
> NSURL *url = [NSURL URLWithString:@"http://d.jpg"];
> NSData *data = [NSData dataWithContentsOfURL:url];
> UIImage *image = [UIImage imageWithData:data];
> // 2.回到主线程设置图片
> dispatch_async(dispatch_get_main_queue(), ^{
> NSLog(@"setting---%@ %@", [NSThread currentThread], image);
> [self.button setImage:image forState:UIControlStateNormal];
> });
> });
>

③、NSOperation线程通信的举例:在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。这里有两种方法,一种是线程里转线程,另一种是线程间使用依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> // 1.创建一个新的队列
> NSOperationQueue *queue = [[NSOperationQueue alloc] init];
> // 2.添加任务(操作)
> [queue addOperationWithBlock:^{
> // 2.1在子线程中下载图片
> NSURL *url = [NSURL URLWithString:@"http://imgcache.mysodao.com/img2/M04/8C/74/CgAPDk9dyjvS1AanAAJPpRypnFA573_700x0x1.JPG"];
> NSData *data = [NSData dataWithContentsOfURL:url];
> UIImage *image = [UIImage imageWithData:data];
> // 2.2回到主线程更新UI
> [[NSOperationQueue mainQueue] addOperationWithBlock:^{
> self.imageView.image = image;
> }];
> }];
>

首先我们先来说说线程与线程间的关系,其引入了我们下面的第二点线程等待(线程同步)问题、线程依赖问题。

然后我们再来说说,有时候我们还常经常在多个线程中操作一个全局变量,这个的话,也算线程间通信吗?不管他算不算(应该是不算的,个人觉得),但是他的存在使得我们引入了另一个线程安全的问题,即多个线程操作同一个变量。也就是下面说的第三点,线程安全问题。

附:其他传值方法总结

参考:其他传值方法总结

1
2
1、A界面到B界面的正向传值,即A界面传值给B界面使用
2、。。。

END

< 返回目录

必备知识架构-线程与网络-①队列与任务或线程

[toc]

知识架构

iOS知识库

Android知识库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>1、死锁探究:GCD死锁及报错提示(EXC_BAD_INSTRUCTION)
>(1)、死锁举例:通过串行队列里的任务,往这个串行队列里添加同步任务,会造成死锁
>(2)、死锁结论:往串行队列里添加的同步任务不能卡住该串行队列,否则会造成死锁(这句话非常重要)
>(3)、死锁原因分析
>
>2、主队列中的死锁:在主队列开启同步任务,一定为什么会阻塞线程?
>
>3、iOS多线程中,队列和执行的排列组合结果分析
>(1)、队列的类型
>(2)、四个比较容易混淆的术语:同步、异步、并发、串行
>(3)、队列和线程的关系
>(4)、自己延伸的问题:一个队列同时存在同步任务和异步任务?
>
>4、并发是并发队列,并行又是什么鬼???
>(1)、多个处理器和多核处理器的区别
>(2)、并发和并行的区别
>
>5、进程和线程的区别
>(1)、进程和线程的区别
>(2)、一个app运行时只有一个进程吗?有没有多进程?
>(3)、什么时候用进程?什么时候用线程?
>

五、队列与任务执行

< 返回目录

1、队列的类型(并发队列(Concurrent Dispatch Queue)& 串行队列(Serial Dispatch Queue))

GCD的队列可以分为2大类型:并发队列和串行队列。

(1)并发队列(Concurrent Dispatch Queue)

可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效
并发队列

####(2)串行队列(Serial Dispatch Queue)

让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
串行队列

问:并发是并发队列,并行又是什么鬼???

答案:下文会详细介绍。

2、队列是用来干嘛的?说一说【队列与任务 】或【队列与线程】的关系?(重要)

答:

1、队列是用来添加任务/保存以及管理任务的,它只是负责任务的调度(是按串行队列还是按并发队列调度),而不负责任务的执行
2、任务是添加到队列里的,可以添加同步任务或者异步任务。

往队列里面添加任务,
①如果是添加异步任务,则会开启新的线程工作,开启的线程数根据队列类型决定。串行只开一条,并行开多条。
②如果是添加同步任务,会在当前线程内工作,不会创建新的线程。

3、对队列所添加的任务是在线程中执行的/由线程负责进行执行,至于在什么线程根据是同步任务还是异步任务,以及当前是什么队列共同决定。

(所以不管是将同步任务添加到串行队列还是并行队列,因为同步任务都不会创建新线程。所以并行同步队列和串行同步队列其实是一样的,它们都不会创建新的线程而且会是顺序执行)

4、补充:在一个线程内可能有多个队列。

2.1、理解队列是用来添加任务/保存以及管理任务的,它只是负责任务的调度,而不负责任务的执行的代码

看以下代码,我们通过它来方便我们理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//  NSOperationQueueCJHelper.m

/**
* 创建队列
*
* @param operations 队列的操作数组
* @param lastOperation 队列的最后一条(最后一条,会等到前面都结束后才会结束)
*
* return 队列
*/
+ (NSOperationQueue *)createOperationQueueWithOperations:(NSArray<NSOperation *> *)operations lastOperation:(NSOperation *)lastOperation {

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"this is a queue";

for (NSBlockOperation *operation in operations) {
[queue addOperation:operation];

[lastOperation addDependency:operation];
}

[queue addOperation:lastOperation];

return queue;
}

// 或者
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(myTask) object:nil];
[operationQueue addOperation:operation];

附:队列的取消、暂停、恢复
取消队列的所有操作使用NSOperationQueue的 - (void)cancelAllOperations;

也可以调用NSOperation的- (void)cancel方法取消单个操作

暂停和恢复队列

- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列

2.2、iOS主线程、主队列(串行队列)、全局队列

iOS 主队列 全局队列

主队列是系统为我们创建的串行队列,而且每个APP只有一个主队列,负责调度处理APP的唯一主线程的UI事件。

注意:主队列没有办法开辟新的线程。所以,在主队列下的任务不管是异步任务还是同步任务都不会开辟线程,任务只会在主线程顺序执行。

主队列异步任务:先将任务放在主队列中,但是不是马上执行,等到主队列中的其它所有除我们使用代码添加到主队列的任务的任务都执行完毕之后才会执行我们使用代码添加的任务。

主队列同步任务:容易阻塞主线程,所以不要这样写。原因:我们自己代码任务需要马上执行,但是主线程正在执行代码任务的方法体,因此代码任务就必须等待,而主线程又在等待代码任务的完成好去完成下面的任务,因此就形成了相互等待。整个主线程就被阻塞了。

全局队列:本质是一个并发队列,由系统提供,方便编程,可以不用创建就直接使用。

2.3、插入问:NSOperationQueue是什么队列?

答:NSOperationQueue是操作队列。结合实际才知道它是并发队列,还是串行队列。

[NSOperationQueue mainQueue]是主队列,和GCD中的主队列一样,是串行队列

[[NSOperationQueue alloc]init]是非主队列,非常特殊(同时具备并发和串行的功能)。默认情况下,非主队列是并发队列。

NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
当operationQueue.maxConcurrentOperationCount > 1,那么operationQueue就是并发队列;
当operationQueue.maxConcurrentOperationCount == 1,那么operationQueue就是串行队列。

3、总结四个比较容易混淆的术语:并发队列、串行队列、同步任务、异步任务

3.1、并发和串行决定了任务的执行方式(队列)。

并发队列:多个任务并发(同时)执行,指一个处理器同时处理多个任务
串行队列:一个任务执行完毕后,再执行下一个任务

3.2、同步和异步决定了要不要开启新的线程(线程)

1
2
3
>同步:在当前线程中执行任务,不具备开启新线程的能力
>异步:在新的线程中执行任务,具备开启新线程的能力
>

4、并发是并发队列,并行又是什么鬼(多核处理器同时执行任务)???

(1)、多个处理器和多核处理器的区别

多个处理器:多个单核处理器,就是说电脑和处理器有多个,但是这个电脑的处理器是单核的;

多核处理器:单个多核处理器,也就是说电脑有一个处理器,但是这个处理器是多核的;

附:在生活中,我们的电脑上常见的处理器都是单处理器,但是这个处理器是多核的,一般是四核,有些需要玩高级游戏的,可能还是八核的处理器。

详情查看:多个处理器和多核处理器的区别

(2)、并发和并行的区别

并发(concurrency)是指一个处理器同时处理多个任务。即并发指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。

并发(concurrency).jpg)

并行(parallel)是指多个处理器或者是多核的处理器同时处理多个不同的任务。即并行是指在同一时刻,有多条指令在多个处理器上同时执行。

并行(parallel).jpg)

个人理解:是否可以理解为在出现多核处理器或者只有一个处理器的时候,我们那时候就只还有并发的概念。

并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)

5、iOS多线程中,队列和执行的排列组合结果分析

该文章非常重要,且主要总结点有

1
2
3
4
5
6
7
1. 开不开线程,取决于执行任务的函数,同步不开,异步开。
2. 开几条线程,取决于队列,串行开一条,并发可以开多条(即并发队列里有异步任务,则可以开多条,如果都只有同步任务,那由于同步任务并不会开新线程,所以此时也就不会开新线程)
3. 主队列:专门用来在主线程上调度任务的"队列",主队列不能在其他线程中调度任务!
4. 如果主线程上当前正在有执行的任务,主队列暂时不会调度任务的执行!主队列同步任务,会造成死锁。原因是循环等待
5. 同步任务可以队列调度多个异步任务前,指定一个同步任务,让所有的异步任务,等待同步任务执行完成,这是依赖关系。
6. 全局队列:并发,能够调度多个线程,执行效率高,但是相对费电。 串行队列效率较低,省电省流量,或者是任务之间需要依赖也可以使用串行队列。
7. 也可以通过判断当前用户的网络环境来决定开的线程数。WIFI下6条,3G/4G下2~3条。

6、代码:使用GCD为队列添加任务

实例1:并发队列添加异步任务/用异步函数往并发队列中添加任务的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)viewDidLoad
{
[super viewDidLoad];

//1.获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//2.添加任务到队列中,就可以执行任务
//异步函数:具备开启新线程的能力
dispatch_async(queue, ^{
​ NSLog(@"下载图片1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
​ NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
​ NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
//打印主线程
NSLog(@"主线程----%@",[NSThread mainThread]);

}
上述方法说明.GCD中有2个用来执行任务的函数

说明:把右边的参数(任务)提交给左边的参数(队列)进行执行

(1)用同步的方式执行任务 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

(2)用异步的方式执行任务 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

常见笔试/面试题

< 返回目录

2、以下两种GCD队列创建有什么不同?
1
2
3
4
5
6
7
dispatch_queue_t queue = dispatch_queue_create("MyQueue",DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue =dispatch_queue_create(@“MyQueue", DISPATCH_QUEUE_CONCURRENT);

//生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。

//生成一个并发执行队列,block被分发到多个线程去执行

END

< 返回目录

必备知识架构-线程与网络-①线程

[toc]

知识架构

iOS知识库

Android知识库

5、进程和线程的区别

(1)、进程和线程的区别

以下区别摘自:进程和线程的区别

一个程序至少一个进程,一个进程至少一个线程。

一个线程只能属于一个进程,而一个进程可以有多个线程,且至少有一个线程。

进程线程的区别:

1
2
3
4
5
6
7
8
9
资源拥有:同一进程内的所有线程共享该进程的资源。如内存、I/O、cpu等,但是进程之间的资源是独立的。
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
>
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
>
进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
>
两者均可并发执行。
>
(2)、一个app运行时只有一个进程吗?有没有多进程?

一个正在运行的程序可以看做一个进程。(例如:正在运行的QQ就是一个进程),进程拥有独立运行所需的全部资源。

iOS (APP)进程间8中常用通信方式总结

(3)、什么时候用进程?什么时候用线程?

进程与线程的选择取决以下几点:

1、需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。

2、线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应

3、因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;

4、并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求;

5、需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。

END

通讯-②跨端间的通讯

[toc]


Flutter 与 Android iOS 原生的通信有以下三种方式

使用场景
BasicMessageChannel 调用原生端的相机功能来拍照,并且获取拍摄的照片数据 支持双向通信,即Flutter可以向原生发送消息,并且原生可以回复这些消息。
MethodChannel 向原生发送一次性的命令或请求,并等待响应
EventChannel 适合用于数据流的通信,如监听传感器数据、网络变化等事件。 单向通信,只能由原生平台向Flutter发送事件流

参考文档:

Flutter通过BasicMessageChannel与Android iOS 的双向通信

纹理Texture

纹理Texture可以理解为GPU内代表图像数据的一个对象

Flutter展示Native端大数据:外接纹理

背景

问:在Flutter与原生的混合开发中,资源在Native和Flutter重复加载,导致内存占了双份的性能问题怎么解决?

答:用Texture外接纹理的方式缓解内存压力。

Flutter 多引擎渲染,外接纹理实践

二、外接纹理

三、共享纹理

ShareGroup是OpenGL ES中的一个概念,用于在不同的EAGLContext之间共享纹理和其他资源。即在Flutter和iOS之间实现纹理共享,可以通过使用ShareGroup来实现。(在iOS开发中,ShareGroup对应于EAGLSharegroup,而在Flutter中,可以通过Texture控件来使用共享的纹理 。)

使用ShareGroup实现Flutter和iOS原生纹理共享的基本步骤:

1
2
3
4
5
6
7
8
9
10
11
12
// 1、创建一个 EAGLContext 并设置其 sharegroup,这个 sharegroup 将被用于后续创建共享纹理。
EAGLSharegroup *sharegroup = [[EAGLSharegroup alloc] init];
EAGLContext *sharedContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3 sharegroup:sharegroup];

// 2、创建纹理并设置为共享:使用上述创建的 EAGLContext 来生成纹理,并确保这些纹理可以被共享。
// 创建纹理,配置纹理参数(省略具体参数设置)
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
.......
// 确保纹理可以被共享
[sharedContext presentRenderbuffer:GL_RENDERBUFFER];
1
2
3
4
5
6
7
8
9
10
11
12
// 在 Flutter 中使用
import 'package:flutter/widgets.dart';

class SharedTextureWidget extends StatelessWidget {
SharedTextureWidget({required this.textureId});

@override
Widget build(BuildContext context) {
int textureId = getNativeTextureId(); // 调用原生方法获取纹理ID
return Texture(textureId: textureId);
}
}
  1. 配置Flutter的Texture控件:在Flutter代码中,创建一个Texture控件,并通过PlatformView注册一个视图工厂,该工厂将返回一个实现了FlutterTexture协议的对象。这个对象负责管理原生纹理,并实现copyPixelBuffer方法来提供纹理数据给

以下内容摘自:谈一谈Flutter外接纹理

1 背景知识

当我们用flutter做实时视频渲染时,往往是要对视频或者相机画面做滤镜处理的,如图:

img

如果我们要用flutter定义的消息通道机制来实现这个功能,就需要将摄像头采集的每一帧图片都要从原生传递到flutter中,这样做代价将会非常大,因为将图像或视频数据通过消息通道实时传输必然会引起内存和CPU的巨大消耗。为此,flutter提供了两种机制实现这一功能:

  • PlatformView
  • Texture Widget

PlatformView实质上是将原有的NativeView嵌入到Flutter中显示,虽然使用和移植很简单,但并不是性能最优的做法。

Texture Widget是flutter提供的另一种机制,可以将native纹理共享给flutter进行渲染。但由于native纹理与flutter是两个OpenGL Context,如果直接使用的话,需要经过GPU -> CPU -> GPU的转换开销,这对于实时视频渲染是很难令人接受的。

所以解决方案就是共享纹理

一个方案提升Flutter内存利用率(干货)

NSTimer、NSDate

###iOS时间那点事
经典看前必先看:iOS时间那点事NSCalendar + NSDateComponents
iOS 时区日期处理及定时 (NSDate,NSCalendar,NSTimer,NSTimeZone)

NSTimer

NSTimer 定时器用法总结

自动加一秒(dateByAddingComponents和dateByAddingTimeInterval)

参考:时间与日期处理
涉及知识点:
NSDate的生成
方法①通过NSDateFormatterdateFromString:方法对dateString生成NSDate;

1
2
3
4
5
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];

NSString *dateString = @"2016-08-15 22:55:03";
NSDate *date = [dateFormatter dateFromString:dateString];

方法②通过NSCalendardateFromComponents:方法对dateComponents生成NSDate;(dateComponents由分个字段生成)
[greCalendar dateFromComponents:dateComponents];

1
2
3
4
5
6
7
    NSCalendar *greCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
[dateComponents setYear:2016];
[dateComponents setMonth:8];
[dateComponents setDay:15];
NSDate *operationDate = [greCalendar dateFromComponents:dateComponents];

NSDate转NSString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
NSDate *operationDate;

- (void)updateTimeExample {
NSCalendar *greCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
[dateComponents setYear:2016];
[dateComponents setMonth:8];
[dateComponents setDay:15];
operationDate = [greCalendar dateFromComponents:dateComponents];

if (!self.timer) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target: self selector:@selector(addOneSecondToDate:) userInfo:nil repeats:YES];
}
}

- (void)addOneSecondToDate:(NSTimer *)timer {
// NSDate *operationDate = [timer userInfo];
NSCalendar *greCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

NSDateComponents *offsetDateComponents = [[NSDateComponents alloc] init];
[offsetDateComponents setMinute:1];
[offsetDateComponents setSecond:1];
NSDate *finalDate = [greCalendar dateByAddingComponents:offsetDateComponents toDate:operationDate options:0];
operationDate = finalDate;

//NSDate转NSString
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy年MM月dd HH:mm:ss"];
NSString *dateString = [dateFormatter stringFromDate:finalDate];

self.networkTimeLabel.text = dateString;
}

#####NSJSONSerialization 去除不必要的空格回车
NSJSONSerialization介绍
NSJSONSerialization四个枚举什么意思

1
2
3
4
5
6
// 要去除不必要的空格回车,只需将参数Option设置为0即可。如果设置NSJSONWritingPrettyPrinted会使格式化的json更加可读,即添加空格和换行,但如果设为0,则不添加空格和换行
NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary
options:0
error:nil];
NSString *JSON = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];

######颜色渐变 CAGradientLayer
CAGradientLayer简介

必备知识架构-①语言

[Toc]

知识架构

iOS知识库

Android知识库

面向对象(OOP)

面向对象的三个特性:封装、继承和多态。

所谓封装,也就是把客观事物封装成抽象的类;

所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。(或者说是不同对象以自己的方式响应相同的消息的能力叫做多态。)

继承的问题:如果架构工程师写父类,业务工程师实现子类。那么业务工程师很可能不清楚:哪些方法需要被覆盖重载,哪些不需要。如果子类没有覆重方法,而父类提供的只是空方法,就很容易出问题。如果子类在覆重的时候引入了其他不相关逻辑,那么子类对象就显得不够单纯,角色复杂了。

多态面临的四个问题:

  • 父类有部分public的方法是不需要,也不允许子类覆重。

    答:公司内部可以规定:不允许覆盖重载父类中的方法子类需要实现接口协议中的方法

  • 父类有一些特别的方法是必须要子类去覆重的,在父类的方法其实是个空方法。

  • 父类有一些方法即便被覆重,父类原方法还是要执行的。

  • 父类有一些方法是可选覆重的,一旦覆重,则以子类为准。

常见笔试/面试题

< 返回目录

2、const 与define使用的区别

  • define修饰的变量不指定类型,const的指定类型
  • defien修饰的变量每次引用都开辟一次内存,而const只有一份内存
  • 如果修饰的是代码片段适合用define,如果修饰的是变量适合用const

6、nil, Nil,NULL 与 NSNull 的区别

  • nil 指向一个对象的指针为空,在objc.h 的定义如下: NSString *name = nil;
  • Nil 指向一个类的指针为空,定义如下: Class aClass = Nil;
  • NULL 指向C类型的指针为空, 例如: int*pInt = NULL;
  • NSNull 在Objective-C中是一个类,使用为生成一个空对象[NSNull null];,多用于集合(NSArray,NSDictionary)中值为空的对象

END

< 返回目录

1
2


必备知识架构-①语言Swift

[Toc]

一、iOS开发中使用OC和swift的对比

1
2
3
1.import的类
OC:某个只要要使用某个类就要将该类import。
swift:如果是用户自己创建类,其他类无需import可以直接使用。pod的一些三方类和系统的一些类,在使用的时候需要import

Guard语句的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
guard expression else { 
//语句
//必须包含一个控制语句:return,break,continue或throw。
}
这里,expression是一个布尔表达式(返回true或者false)。
如果对表达式求值falseguard则执行代码块内的语句。
如果对表达式求值trueguard则从执行中跳过代码块内的语句

func someFunction() {
guard false else {
print("Condition not met")
return
}
print("Condition met")
}

二、【iOS】Swift和OC协议的区别?为何Swift是面向协议的?

【iOS】Swift和OC协议的区别?为何Swift是面向协议的?

相比于OC,Swift 可以做到协议方法的具体实现,而 OC 则不行。

OC中协议(Protocol)类似一个类的接口,只声明,不实现。
只能定义公用的一套接口,但不能提供具体的实现方法。也就是说,它只告诉你要做什么,但具体怎么做不关心。具体的实现要在遵守这个协议的类中实现。

OC的Protocol与继承的区别

继承连同方法的实现也继承了,而protocol只有声明没有实现;
protocol能够作用到不同类型的类上。

OC的Protocol与Category的区别

Category可以给一个类扩充方法,既有申明也有实现;而Protocol只有声明,没有实现。
同:CategoryProtocol都可以声明方法,不能声明属性。

Swift 面试题解

?? 的作用

可选值的默认值, 当可选值为nil 的时候, 会返回后面的值. 如
let someValue = optional1 ?? 0

Runtime_Interview

Runtime

前言:

Objective C的runtime技术功能非常强大,能够在运行时获取并修改类的各种信息,包括获取方法列表、属性列表、变量列表,修改方法、属性,增加方法,属性等等,本文对相关的几个要点做了一个小结。

目录:

(1)使用class_replaceMethod/class_addMethod函数在运行时对函数进行动态替换或增加新函数

(2)重载forwardingTargetForSelector,将无法处理的selector转发给其他对象

(3)重载resolveInstanceMethod,从而在无法处理某个selector时,动态添加一个selector

(4)使用class_copyPropertyList及property_getName获取类的属性列表及每个属性的名称

(5) 使用class_copyMethodList获取类的所有方法列表

应用:

viewWillAppear 埋点

KVO的本质

问: Category的实现原理,以及Category为什么只能加方法不能直接加属性(需要自己在分类中实现setter、getter方法)?

答:分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。 Category可以添加属性,但是并不会自动生成成员变量及set/get方法。因为category_t结构体中并不存在成员变量。通过之前对对象的分析我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。

一些参考

Objective C运行时(runtime)技术总结

需求1、当项目中,需要继承某一个父类,但是父类中并没有提供我们需要的调用方法,一般情况下,这种时候,我们可能会再派生一个类,或者会直接为这个类写一个分类(category),在这个分类中,我们可以新增或者替换某个方法(注意:不推荐在分类中重写方法,而且也无法通过 super 来获取所谓父类的方法)。大致在这两种情况下,我们可以通过 class_addMethod 来实现我们想要的效果。

先从一个场景问题带出吧,比如一个应用都是继承于UIViewController,刚开始时候不要求要转屏,到后面才决定加上旋转屏适配,这个时候如果我们去创建一个父类,并让去修改每个类的继承这个方法显然是效率太低了。而且有时候,我们也不想多创个父类,于是我们就只能决定一次过替换掉这些controller里的viewWillAppear:和 willAnimateRotationToInterfaceOrientation:duration:,换成自己的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class clazz = object_getClass((id)self);

//将系统的resolveInstanceMethod替换成自己的myResolveInstanceMethod,而系统的resolveInstanceMethod方法就是当运行时对象调用了一个找不到的方法的时候系统会去寻找的机制。
[clazz swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];
});
}


/**
* 用什么方法替换掉什么方法
*
* @param origSel 原方法选择子
* @param aftSel 新方法选择子
*/
+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {
//通过class_getInstanceMethod()拿到对应的Method
Method originMethod = class_getInstanceMethod(self, origSel);
Method newMethod = class_getInstanceMethod(self, aftSel);

if(originMethod && newMethod){ //必须两个Method都要拿到
//class_addMethod将本来不存在于被操作的Class里的newMethod的实现添加在被操作的Class里,并使用origSel作为其选择子
if(class_addMethod(self, origSel, method_getImplementation(newMethod),method_getTypeEncoding(newMethod))) {
//当addMethod成功完成后,利用class_replaceMethod换掉method_getImplaementation(oiginMethod)的选择子,将原方法的实现的SEL换成新方法的SEL:aftSel,ok目的达成了。想一想,现在通过旧方法SEL来调用,就会实现新方法的IMP,通过新方法的SEL来调用,就会实现旧方法的IMP。
class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
}
return YES;
}
return NO;
}