0%

最近遇到Transporter上传ipa文件到AppStore Connect的时候一直卡在正在验证APP - 正在通过App Store进行认证状态。

解决办法:
1.删除Transporter的缓存目录,具体的目录路径在:

1
/Users/kris/Library/Caches/com.apple.amp.itmstransporter

2.使用命令行进入Transporter目录,执行命令:

1
2
$ cd /Applications/Transporter.app/Contents/itms/bin
$ ./iTMSTransporter

3.当命令行出现以下内容,重新再打开Transporter交付App就可以了。

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
33
34
[2020-12-03 12:55:39 CST] <main> DEBUG: Saving configuration to local path: /Users/kris/Library/Caches/com.apple.amp.itmstransporter/Defaults.properties
usage: iTMSTransporter [-help <arg> | -info | -m <arg> | -version] [-o <arg>] [-v
<arg>] [-WONoPause <arg>] [-Xmx4096m]
iTMSTransporter : iTunes Store Transporter 2.1.0
-help <arg> Show this help. If a mode value is specified, show help specific
to that mode.
-info The -info option should be used by itself and returns the
copyright notice and acknowledgements.
-m <arg> The -m option specifies the tool's mode. The valid values are:
verify, upload, provider, diagnostic, lookupMetadata,
createArtist, lookupArtist, status, statusAll,
createMetadataTicket, queryTickets, generateSchema, transferTest,
downloadMetadataGuides, listReports, requestReport
-o <arg> The -o option specifies the directory and filename you want to use
to log output information. By default, Transporter logs output
information to standard out. If you specify a filename,
Transporter logs the output to the specified file, as well as to
standard out.
-v <arg> The -v option specifies the level of logging. The five values
are: off, detailed, informational, critical, eXtreme.
-version The -version option should be used by itself and returns the
version of the tool.
-WONoPause <arg> The -WONoPause option is only valid on Windows and its value can
be 'true' or 'false'. If an error occurs during script execution,
the process idles because the message 'Press any key...' is
displayed on the console and the system awaits a keypress. To
avoid this behavior, set this property to true
-Xmx4096m Specifies that you want to change the Java Virtual Machine's (JVM)
allocated memory by increasing the JVM heap size. By default,
Transporter uses a 2048MB heap size. You can use the -Xmx4096m
option to specify a 4-gigabyte (GB) heap size. Apple recommends,
if needed, increasing the heap size to 4096MB by specifying the
-Xmx4096m (or -Xmx4g) option and adjusting as needed.
[2020-12-03 12:55:39 CST] <main> DBG-X: Returning 0

A serial dispatch queue runs only one task at a time, waiting until that task is complete before dequeuing and starting a new one. By contrast, a concurrent dispatch queue starts as many tasks as it can without waiting for already started tasks to finish.

串行队列-同步-同步

运行结果:死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
条件:串行队列,同步执行,同步执行。
结果:死锁。
*/
- (void) testMethod2 {
dispatch_queue_t queue = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
NSLog(@"11");
dispatch_sync(queue, ^{
NSLog(@"22");
dispatch_sync(queue, ^{
NSLog(@"33");
});
NSLog(@"44");
});
NSLog(@"55");
}

运行截图:

串行队列-同步-异步

运行结果:不死锁,同步操作在主线程中执行,异步操作在子线程执行。为什么第一个同步操作没有死锁,因为第一个同步操作在“com.serial”这个队列里,而testMethod10在主队列里,虽然是同步操作,但是二者不属于同一个队列,不会互相阻塞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
条件:串行队列,同步执行,异步执行。
结果:死锁。
*/
- (void) testMethod10 {
dispatch_queue_t queue = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

串行队列-异步-同步

运行结果:死锁。打印“22”、“44”的操作已经在串行队列上执行了,此时要求插入打印“33”的操作,这个同步操作造成“com.serial”串行队列死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
条件:串行队列,异步执行,同步执行。
结果:死锁。
*/
- (void) testMethod5 {
dispatch_queue_t queue = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

串行队列-异步-异步

运行结果:不死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
条件:串行队列,异步执行,异步执行。
结果:不死锁。
线程:创建了一个线程。
*/
- (void) testMethod8 {
dispatch_queue_t queue = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

并发队列-同步-同步

运行结果:不死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
条件:并发队列,同步执行,同步执行。
结果:不死锁。
*/
- (void) testMethod3 {
dispatch_queue_t queue = dispatch_queue_create("com.serial.11", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

并发队列-同步-异步

运行结果:不死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
条件:并发队列,同步执行,异步执行。
结果:不死锁。
*/
- (void) testMethod11 {
dispatch_queue_t queue = dispatch_queue_create("com.serial.11", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

并发队列-异步-同步

运行结果:不死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
条件:并发队列,异步执行,同步执行。
结果:不死锁。
*/
- (void) testMethod6 {
dispatch_queue_t queue = dispatch_queue_create("com.serial.11", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

并发队列-异步-异步

运行结果:不死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
条件:并发队列,异步执行,异步执行。
结果:不死锁。
线程:创建了一个线程。
*/
- (void) testMethod9 {
dispatch_queue_t queue = dispatch_queue_create("com.serial.11", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

主队列-同步-同步

运行结果:死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
条件:主队列,同步执行,同步执行。
结果:死锁。
*/
- (void) testMethod1 {
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

主队列-同步-异步

运行结果:死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
条件:主队列,同步执行,异步执行。
结果:死锁。
*/
- (void) testMethod12 {
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

主队列-异步-同步

运行结果:死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
条件:主队列,异步执行,同步执行。
结果:死锁。
*/
- (void) testMethod4 {
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

主队列-异步-异步

运行结果:不死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
条件:主队列,异步执行,异步执行。
结果:不死锁。
线程:主线程执行。
*/
- (void) testMethod7 {
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"11, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"22, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"33, %@", [NSThread currentThread]);
});
NSLog(@"44, %@", [NSThread currentThread]);
});
NSLog(@"55, %@", [NSThread currentThread]);
}

运行截图:

总结:在并发队列上,无论是同步还是异步都不会发生死锁;主队列上只有异步执行的时候才不会发生死锁;串行队列是否发生死锁需要具体分析。

参考链接

官方文档

在使用SourceTree来管理Git项目的时候,有时需要回滚项目,具体的回滚操作包含本地回滚和远程回滚。建议回滚前先做好代码备份。

本地回滚

使用命令git reset进行本地回滚操作,本地回滚操作一步即可完成。

1
2
3
4
5
# 回退到指定的提交版本,22f8aae 为某次提交的提交号。
$ git reset --hard   22f8aae

#(回退3次提交)
$ git reset --hard HEAD~3 //(回退3次提交)

–hard:本地的源码和本地未提交的源码都会回退到某个版本,包括commit内容,和git自己对代码的索引都会回退到某个版本,any local changes will be lost。
–soft:保留源码,只能回退到commit信息到某个版本,不涉及到index的回退,如果还需要提交,直接commit即可。比如我选择soft方式来进行回退,我的本地代码和本地新添加的尚未commit的代码都没有改变。
–mixed:会保留源码,只是将git commit和index信息回退到某个版本。

远程回滚

远程回滚的操作流程是,先回滚本地操作,然后将本地的修改强行push到远程仓库。具体操作分为以下2步:

1.在需要回滚的提交记录上右键选择将master重置到这次提交,选择重置(强行合并)到需要回滚的历史节点。

2.打开终端,cd到项目目录,执行git push origin master -f,即可。
origin:远程仓库名
master:分支名称
-f:force,意为强制、强行

3.如果出现 ! [remote rejected] master -> master (pre-receive hook declined)错误,需要去gitlab网站去除master分支保护,才可以强制推送。
具体错误信息如下:

1
2
3
4
5
6
$ git push origin master -f
总共 0 (差异 0),复用 0 (差异 0)
remote: GitLab: You are not allowed to force push code to a protected branch on this project.
To https://test.com.cn/test.git
! [remote rejected] master -> master (pre-receive hook declined)
error: 推送一些引用到 'https://test.com.cn/test.git' 失败

需要去到gitlab的网站上去除master分支保护。

参考链接

git 远程代码回滚master
使用sourceTree回滚git代码到历史节点

NSTimer如何使用

方法1: Creates a timer and schedules it on the current run loop in the default mode.

1
2
3
4
5
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti 
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)yesOrNo;

方法2: Initializes a timer object with the specified object and selector.

You must add the new timer to a run loop, using addTimer:forMode:. Then, after ti seconds have elapsed, the timer fires, sending the message aSelector to target. (If the timer is configured to repeat, there is no need to subsequently re-add the timer to the run loop.)

1
2
3
4
5
6
7
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti 
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)yesOrNo;

[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];

这两个方法是等价的,区别是第一个方法默认创建了一个NSTimer并自动添加到了当前线程的Runloop中去,第二个需要我们手动添加。如果当前线程是主线程的话,某些UI事件,比如UIScrollView的拖拽操作,会将Runloop切换成UITrackingRunLoopMode,这时候,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。所以为了设置一个不会被UI干扰的Timer,我们需要手动将timer的当前RunloopMode设置为NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopMode和UITrackingRunLoopMode的结合。

由于NSTimer会强引用Target,RunLoop会强引用NSTimer,若ViewController再强引用NSTimer,NSTimer找不到合适的时机释放的时候,很容易造成内存泄漏,这个问题如何解决?

如何解决NSTimer找不到释放时机的问题?

方案1,使用官方API带Block的方式来创建NSTimer

具体的创建方法如下,但是仅支持iOS 10以上的系统。

1
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

在ViewController中的使用方法:

1
2
3
4
5
6
__weak typeof(self)weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"第%ld次执行", (long)weakSelf.count);
weakSelf.count ++;
}];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

方案2,使用Block的方式来创建NSTimer

具体实现方式是,需要创建一个NSTimer的分类,让外部传入一个Block,NSTimer每次到时间就执行传入进来的Block。新建一个分类:
NSTimer+BlockSupport.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSTimer (BlockSupport)

+ (NSTimer *)cd_timerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void(^)(void))block;

@end

NS_ASSUME_NONNULL_END

NSTimer+BlockSupport.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import "NSTimer+BlockSupport.h"

@implementation NSTimer (BlockSupport)

+ (NSTimer *)cd_timerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void(^)(void))block {
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(cd_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}

+ (void)cd_blockInvoke:(NSTimer *)timer {
void (^block)(void) = timer.userInfo;
if(block) {
block();
}
}

@end

在ViewController中使用方法:

1
2
3
4
5
6
__weak typeof(self)weakSelf = self;
self.timer = [NSTimer cd_timerWithTimeInterval:1.0 repeats:YES block:^{
NSLog(@"第%ld次执行", (long)weakSelf.count);
weakSelf.count ++;
}];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

方案3,自定义NSTimer的Target

封装自定义NSTimer的Target和NSTimer的工具类,让NSTimer在合适的时机释放。
CDTimerTarget.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface CDTimerTarget : NSObject

// 目标对象
@property (nonatomic,weak) id target;
// 目标方法
@property (nonatomic,assign) SEL selector;

@end

NS_ASSUME_NONNULL_END

CDTimerTarget.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "CDTimerTarget.h"

@implementation CDTimerTarget

- (void)fire:(NSTimer *)timer {
// target为弱引用,所以当target对象销毁时,target 为nil
if (self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.target performSelector:self.selector withObject:timer.userInfo];
#pragma clang diagnostic pop
}else {
[timer invalidate];
}
}

@end

CDTimer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface CDTimer : NSObject

+ (NSTimer *)scheduleTimerWithTimerInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats;

@end

NS_ASSUME_NONNULL_END

CDTimer.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "CDTimer.h"
#import "CDTimerTarget.h"

@implementation CDTimer

+ (NSTimer *)scheduleTimerWithTimerInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats {

CDTimerTarget *timerTarget = [[CDTimerTarget alloc]init];
timerTarget.target = aTarget;
timerTarget.selector = aSelector;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:timerTarget
selector:@selector(fire:)
userInfo:userInfo
repeats:repeats];
return timer;
}

@end

在ViewController中的用法:

1
self.timer = [CDTimer scheduleTimerWithTimerInterval:1.0 target:self selector:@selector(repeatAction) userInfo:nil repeats:YES];

方案4,自定义NSProxy子类,作为NSTimer的Target

TargetProxy.h

1
2
3
4
5
6
7
8
9
10
11
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface TargetProxy : NSProxy

+ (instancetype)proxyWithTarget:(id)target;

@end

NS_ASSUME_NONNULL_END

TargetProxy.m

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
33
34
#import "TargetProxy.h"

@interface TargetProxy()

//target, 这里必须要用weak, 因为某个对象会强引TimeProxy对象, TimeProxy对象不能再强引target, 否则会形成循环引用
@property (nonatomic, weak)id target;

@end

@implementation TargetProxy

+ (instancetype)proxyWithTarget:(id)target {
TargetProxy *proxy = [[self class] alloc];
proxy.target = target;
return proxy;
}

//获取target类中的sel方法的方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
//判断target是否实现了该方法
if ([self.target respondsToSelector:invocation.selector]) {
//让target调用该方法
[invocation invokeWithTarget:self.target];
}else {
//找不到该方法
[invocation doesNotRecognizeSelector:invocation.selector];
}
}

@end

在ViewController中的使用方法:

1
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[TargetProxy proxyWithTarget:self] selector:@selector(repeatAction) userInfo:nil repeats:YES];

参考链接

NSTimer

NSProxy解决NSTimer和CADisplayLink的循环引用

《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》

算法

算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。

主要还是从算法所占用的「时间」和「空间」两个维度去考量。

  • 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
  • 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。

什么是时间复杂度,如何表示?

一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。

常见时间复杂度:

时间复杂度
O(1) 常数复杂度
O(logn) 对数复杂度
O(n) 线性时间复杂度
O(n^2) 平方复杂度
O(n^3) 立方复杂度
O(2^n) 指数复杂度
O(n!) 阶乘复杂度

主定理

在算法分析中,主定理(Master Theorem)提供了用渐近符号(大O符号)表示许多由分治法得到的递推关系式的方法。
——维基百科

主定理可以用来分析递归算法的时间复杂度(也叫渐进复杂度)。

常见时间复杂度问题分析

  • 看一段代码根据n的不同情况,运行多少次。
  • 画出递归状态树,第一层1个节点,第二层2个节点,第三层2^2个节点,第四层2^3个节点,最终根据等比数列求和公式计算所有的节点数,就是它的时间复杂度。

二叉树遍历,前序、中序、后序:时间复杂度是多少?

遍历二叉树,每个节点都会访问一次,所以最终时间复杂度都为O(n),n代表节点总数。
也可以通过主定理推算出来。

图的遍历,时间复杂度是多少?

遍历图的时候,每个节点都会访问一次,所以时间复杂度都是O(n),n代表节点总数。

搜索算法,DFS、BFS时间复杂度是多少?

无论是深度优先搜索还是广度优先搜索,每个节点都会遍历一次,最终的时间复杂度是O(n),n代表节点总数。

二分查找,时间复杂度是多少?

二分查找,时间复杂度是O(logn),n代表节点总数。

数组的时间复杂度?

操作 时间复杂度
头部追加 O(n)
尾部追加 O(1)
查找元素 O(1)
插入元素 O(n)
删除元素 O(n)

链表的时间复杂度?

操作 时间复杂度
头部追加 O(1)
尾部追加 O(1)
查找元素 O(n)
插入元素 O(1)
删除元素 O(1)

跳表的时间复杂度?

跳表处理的是有序链表,被称为“跳跃链表”。跳表的空间复杂度为O(n)。

操作 时间复杂度
头部追加 O(logn)
尾部追加 O(logn)
查找元素 O(logn)
插入元素 O(logn)
删除元素 O(logn)

空间复杂度

  • 1.数组的长度
  • 2.递归的深度

如果程序里面开辟了数组,空间复杂度就是O(n),如果开启的是二维数组,空间复杂度是O(n^2)。如果程序用到了递归,则递归的最大深度就是程序的空间复杂度。如果又开辟了数组又有递归,则空间复杂度为数组长度和递归最大深度之中的较大值。

参考链接

主定理与递归程序时间复杂度的计算
算法的时间复杂度和空间复杂度-总结
跳表──没听过但很犀利的数据结构
算法的时间与空间复杂度(一看就懂)

创建私有库模版

使用命令进入到要建立私有库工程的目录,执行以下命令,CDUtils是私有库项目名。

1
$ pod lib create CDUtils

接下来终端会询问几个问题,请根据实际情况设置:

根据提示最终就会创建一个私有工程。

创建私有库Git地址,这里以Github为例

修改配置文件CDUtils.podspec

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
33
34
35
36
37
38
39
40
41
42
#
# Be sure to run `pod lib lint CDUtils.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#

Pod::Spec.new do |s|
s.name = 'CDUtils'
s.version = '0.0.1'
s.summary = 'CDUtils 是一个公共方法库。'

# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!

s.description = <<-DESC
TODO: Add long description of the pod here.
DESC

s.homepage = 'https://github.com/DavidWanderer/CDUtils.git'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { '1516715172@qq.com' => 'jsjhyp@gmail.com' }
s.source = { :git => 'https://github.com/DavidWanderer/CDUtils.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

s.ios.deployment_target = '8.0'

s.source_files = 'CDUtils/Classes/**/*'

# s.resource_bundles = {
# 'CDUtils' => ['CDUtils/Assets/*.png']
# }

# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
end

安装依赖

命令行进入私有库工程所在目录下的Example文件夹,执行pod install,安装依赖项。

1
2
3
4
5
6
7
PoetMacBook-Pro:Example kris$ pod install
Analyzing dependencies
Downloading dependencies
Generating Pods project
Integrating client project
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.
PoetMacBook-Pro:Example kris$

添加库的源代码文件

将源码文件放入CDUtils/Classes目录下,与podspec文件保持一致。

验证私有库的正确性

使用命令验证私有库的正确性。

1
$ pod lib lint CDUtils.podspec

提交源码到github,并打Tag。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git remote add origin https://github.com/DavidWanderer/CDUtils.git

$ git add .

$ git commit -m "0.0.1"

$ git pull origin master

$ git push origin master

$ git tag 0.0.1

$ git push origin 0.0.1

创建私有库的索引库

在Github上创建私有库的索引库,并进行初始化操作。

将创建的索引库添加到本地cocoapods仓库

  • 在自己的私有库目录下执行命令,将创建的索引库克隆到本地的cocoapods的仓库中。
    1
    $ pod repo add CDSpecs https://github.com/DavidWanderer/CDSpecs.git
  • cocoapods本地仓库路径为:
    1
    $ ~/.cocoapods/repos

发布私有库

在自己的私有库目录下执行命令,把当前私有库的索引同步到本地索引库,并同步给远程索引库。

1
$ pod repo push CDUtils CDUtils.podspec

终端截图:

在自己的项目中引用私有库

Podfile如下:

1
2
3
4
5
6
7
8
9
10
11
# Uncomment the next line to define a global platform for your project
source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/DavidWanderer/CDSpecs.git'
platform :ios, '8.0'

target '测试' do
# Comment the next line if you don't want to use dynamic frameworks
#use_frameworks!
pod 'CDUtils', '~> 0.0.1'

end

为什么添加gitalk评论?

Next主题支持的评论有disqusdisqusjschangyangitalkliverevaline。前面两个都需要翻墙才可以使用;畅言需要注册;valine需要注册,并且绑定身份证号后才可以使用;livere是韩国的,注册页面没有国际化,看不懂。综合下来选了gitalk,gitalk是使用github上仓库的issue来存储用户的评论,非常方便。

如何给自己的博客添加gitalk评论呢?

参考gitalk官网gitalk的Demo来尝试在Next中接入gitalk。
1.首先需要选择一个公共的github仓库(已存在或创建一个新的github存储库)用于存储评论。
2.需要创建GitHub Application, 如果没有点击这里申请,Authorization callback URL填写当前博客的首页地址。

3.修改Next主题的_config.yml,开启gitalk评论。配置内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Gitalk
# For more information: https://gitalk.github.io, https://github.com/gitalk/gitalk
gitalk:
enable: true
github_id: DavidWanderer(从博客地址中获取:https://davidwanderer.github.io/Blog/,自己的依次类推)
id: location.pathname
repo: Blog(这里只需要最终的仓库名,不需要全路径,我把评论直接存储在自己的博客仓库里)
client_id: ce08****8271(此id从第2步申请结果中获取)
client_secret: 1d47***9a608(此secret从第二步申请结果中获取)
admin_user: DavidWanderer(此处需要和github_id保持一致)
distraction_free_mode: true # Facebook-like distraction free mode
# Gitalk's display language depends on user's browser or system environment
# If you want everyone visiting your site to see a uniform language, you can set a force language value
# Available values: en | es-ES | fr | ru | zh-CN | zh-TW
language: zh-CN

遇到的问题

1.博客的评论在使用之前需要登录配置的admin_user进行初始化评论,包括github授权等等操作,否则会出现如下问题。

2.博客的评论有时候会出现Network Error的问题,一般情况下,使用自己的Github账号登录一下,就不会出现问题了。但是如果问题还是没有消除,请检查Next配置文件中的Gitalk配置中的github_idadmin_userrepo填写的是否正确,可以结合Chrome的网页调试工具调试查看是否是认证失败,如果是认证失败,请检查Gitalk配置。
评论Network Error截图:

评论接入成功后的截图:

iPA解压排查

iOS安装包瘦身时,最直接的办法就是,解压打包完的iPA,查找大文件,优化对应的大文件。

  • _CodeSignature:文件hash列表。里面有一个属性列表文件CodeResources,包含.app中所有文件的列表,里面是一个字典,key是文件名,value通常是Base64格式的散列值。它的作用是用来判断应用程序是否完好无损,防止资源文件修改损坏。

  • Assets.car:由.xcassets统一打包生成。包含所有.xcassets下的资源文件.xcassets可以存放图片资源,组织清晰,便于管理,Xcode也会对一些资源做优化处理。如果直接放在Xcode工程目录下,打包后这些资源会在包的根目录下面展示。Assets.car可以通过Asset Catalog Tinkerer软件打开,并导出相关资源。

  • embedded.mobileprovision:描述文件

  • Info.plist:工程信息配置文件

  • YXTDemo:与工程同名的可执行文件。可以通过file命令查看类型。

图片资源优化

删除未使用的图片资源

可以使用工具LSUnusedResources工具查找未使用的图片资源,工具下载地址:https://github.com/tinymind/LSUnusedResources。

压缩图标和图片

对于一些较大的图标和图片可以使用工具ImageOptim压缩一下。

特殊大图放工程目录下

大图(例如游戏界面背景图)若放在.xcassets文件夹中,工程打包后大图的尺寸会变大,因此建议特定大图不要放在.xcassets中,放在工程目录下即可。

删除冗余代码

删除未使用的类文件

使用CATClearProjectTool工具查找未使用的类,删除即可。

使用LinkMap分析类文件和静态库的大小

使用LinkMap工具可以分析具体类文件和静态库大小,工具下载地址:https://github.com/huanxsd/LinkMap。

数组和可选项都拥有一个map函数,数组的map函数可以用来对数组的每一个元素都做处理,处理完的值放到一个新的数组中存储;可选项的map函数可以把可选项中的值解包后进行运算,将运算完的值再放到一个新的可选项中,使用可选项的map函数可以在不解包的情况下对可选项的值进行运算,非常方便。如果可选项值为nil,就不会进行运算。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 可选项不为空时,可以使用map函数对可选项内容进行运算
let num1: Int? = 10
print("num1 = \(String(describing: num1))")
let num2 = num1.map { $0 * 2 }
print("num2 = \(String(describing: num2))")

// 可选项为空时执行map函数结果
let num3: Int? = nil
print("num3 = \(String(describing: num3))")
let num4 = num3.map { $0 * 2 }
print("num4 = \(String(describing: num4))")

// 数组使用map函数
let arr1 = [1, 2, 3]
print("arr1 = \(arr1)")
let arr2 = arr1.map { $0 * 2 }
print("arr2 = \(arr2)")

执行结果:

1
2
3
4
5
6
7
num1 = Optional(10)
num2 = Optional(20)
num3 = nil
num4 = nil
arr1 = [1, 2, 3]
arr2 = [2, 4, 6]
Program ended with exit code: 0

如果不使用map函数,而需要达到和上面相同的结果,代码如下:

1
2
3
4
5
var num1: Int? = 10
if let num = num1 {
num1 = num * 2
}
print(String(describing: num1))

由此可见,使用map函数在特定情况下可以简化操作。所以推荐可选项使用map函数简化操作。

NSOperation介绍

NSOperation、NSOperationQueue 是苹果提供给我们的一套多线程解决方案。实际上 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。

好处:

  • 1、可添加完成的代码块,在操作完成后执行
  • 2、添加操作之间的依赖关系,方便的控制执行顺序
  • 3、设定操作执行的优先级
  • 4、可以很方便的取消一个操作的执行
  • 5、使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled

既然是基于 GCD 的更高一层的封装。那么,GCD 中的一些概念同样适用于 NSOperation、NSOperationQueue。在 NSOperation、NSOperationQueue 中也有类似的任务(操作)和队列(操作队列)的概念。

操作(Operation)

  • 1、执行操作的意思,换句话说就是你在线程中执行的那段代码
  • 2、在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperationNSBlockOperation,或者自定义子类来封装操作

操作队列(Operation Queues)

  • 1、这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
  • 2、操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行
  • 3、NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行

常用API

NSOperation常用属性和方法

  • 1、开始取消操作

    • - (void)start;:对于并发Operation需要重写该方法,也可以不把operation加入到队列中,手动触发执行,与调用普通方法一样
    • - (void)main;:非并发Operation需要重写该方法
    • - (void)cancel;:可取消操作,实质是标记 isCancelled 状态
  • 2、判断操作状态方法

    • - (BOOL)isFinished; 判断操作是否已经结束
    • - (BOOL)isCancelled; 判断操作是否已经标记为取消
    • - (BOOL)isExecuting;判断操作是否正在在运行
    • - (BOOL)isReady;判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
  • 3、操作同步

    • - (void)waitUntilFinished;阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步
    • - (void)setCompletionBlock:(void (^)(void))block; 会在当前操作执行完毕时执行 completionBlock
    • - (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成
    • - (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
    • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

NSOperationQueue 常用属性和方法

  • 1、取消/暂停/恢复操作

    • - (void)cancelAllOperations; 可以取消队列的所有操作
    • - (BOOL)isSuspended; 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态
    • - (void)setSuspended:(BOOL)b; 可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列
  • 2、操作同步

    • - (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕。
  • 3、添加/获取操作

    • - (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象
    • - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
    • - (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)
    • - (NSUInteger)operationCount; 当前队列中的操作数
  • 4、获取队列

    • + (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
    • + (id)mainQueue; 获取主队列。

简单使用

NSOperation 需要配合 NSOperationQueue 来实现多线程。因为默认情况下,NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue 我们能更好的实现异步执行。

实现步骤:

  • 1、创建操作:先将需要执行的操作封装到一个 NSOperation 对象中
  • 2、创建队列:创建 NSOperationQueue 对象
  • 3、将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中

NSOperation 是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作:

  • 1、使用子类 NSInvocationOperation
  • 2、使用子类 NSBlockOperation
  • 3、自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作

使用子类 NSInvocationOperation

在主线程中操作:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)Operation1 {
//1、创建NSInvocationOperation对象
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
//2、开始调用
[op start];
}

- (void)test {
for (NSInteger i = 0; i < 2; i++) {
NSLog(@"当前线程:%@",[NSThread currentThread]);
}
}

运行结果:

1
2
2020-07-04 15:23:23.108524+0800 NSOperation[2211:95115] 当前线程:<NSThread: 0x6000028c2d00>{number = 1, name = main}
2020-07-04 15:23:23.108688+0800 NSOperation[2211:95115] 当前线程:<NSThread: 0x6000028c2d00>{number = 1, name = main}

如果在其他线程中操作:

1
2
3
4
5
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(Operation1) object:@"Hello, World"];
//设置线程的名字,方便查看
[thread setName:@"firstThread"];
//启动线程
[thread start];

运行结果:

1
2
2020-07-04 15:33:54.722270+0800 NSOperation[2373:105328] 当前线程:<NSThread: 0x60000242f9c0>{number = 6, name = firstThread}
2020-07-04 15:33:54.722442+0800 NSOperation[2373:105328] 当前线程:<NSThread: 0x60000242f9c0>{number = 6, name = firstThread}

总结:在没有使用 NSOperationQueue、在主线程中单独使用使用子类 NSInvocationOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程

使用子类 NSBlockOperation

在主线程中:

1
2
3
4
5
6
7
8
- (void)Operation2{
//1、使用NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程:%@",[NSThread currentThread]);
}];
//2、开始调用
[op start];
}

运行结果:

1
2020-07-04 15:31:49.650987+0800 NSOperation[2329:102582] 当前线程:<NSThread: 0x600001d5ad40>{number = 1, name = main}

如果在其他线程中操作:

1
2
3
4
5
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(Operation2) object:@"Hello, World"];
//设置线程的名字,方便查看
[thread setName:@"firstThread"];
//启动线程
[thread start];

运行结果:

1
2
2020-07-04 15:26:06.006847+0800 NSOperation[2264:97942] 当前线程:<NSThread: 0x600003f63080>{number = 6, name = firstThread}
2020-07-04 15:26:06.006998+0800 NSOperation[2264:97942] 当前线程:<NSThread: 0x600003f63080>{number = 6, name = firstThread}

总结:在没有使用 NSOperationQueue、在主线程中单独使用使用子类 NSBlockOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程

addExecutionBlock

NSBlockOperation 还提供了一个方法 addExecutionBlock:,通过 addExecutionBlock: 就可以为 NSBlockOperation 添加额外的操作。这些操作(包括 blockOperationWithBlock 中的操作)可以在不同的线程中同时(并发)执行。只有当所有相关的操作已经完成执行时,才视为完成。每添加一个addExecutionBlock:就是开启一个异步并发执行事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//1、使用NSBlockOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程:%@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
NSLog(@"当前线程2:%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
NSLog(@"当前线程3:%@", [NSThread currentThread]); // 打印当前线程
}
}];
[op addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
NSLog(@"当前线程4:%@", [NSThread currentThread]); // 打印当前线程
}
}];

//2、开始调用
[op start];

运行结果:

1
2
3
4
5
6
7
2020-07-04 15:40:00.036304+0800 NSOperation[2449:111971] 当前线程2:<NSThread: 0x600001201600>{number = 4, name = (null)}
2020-07-04 15:40:00.036307+0800 NSOperation[2449:111970] 当前线程:<NSThread: 0x600001226100>{number = 3, name = (null)}
2020-07-04 15:40:00.036317+0800 NSOperation[2449:111972] 当前线程4:<NSThread: 0x600001226e00>{number = 5, name = (null)}
2020-07-04 15:40:00.036328+0800 NSOperation[2449:111776] 当前线程3:<NSThread: 0x600001249e40>{number = 1, name = main}
2020-07-04 15:40:00.036459+0800 NSOperation[2449:111971] 当前线程2:<NSThread: 0x600001201600>{number = 4, name = (null)}
2020-07-04 15:40:00.036460+0800 NSOperation[2449:111972] 当前线程4:<NSThread: 0x600001226e00>{number = 5, name = (null)}
2020-07-04 15:40:00.036533+0800 NSOperation[2449:111776] 当前线程3:<NSThread: 0x600001249e40>{number = 1, name = main}

自定义继承自 NSOperation 的子类

如果使用子类 NSInvocationOperationNSBlockOperation 不能满足日常需求,我们可以使用自定义继承自 NSOperation 的子类。可以通过重写 main 或者 start 方法 来定义自己的 NSOperation 对象。重写main方法比较简单,我们不需要管理操作的状态属性 isExecutingisFinished。当 main 执行完返回的时候,这个操作就结束了。
HHOperation.h

1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface HHOperation : NSOperation

@end

NS_ASSUME_NONNULL_END

HHOperation.m

1
2
3
4
5
6
7
8
9
10
11
12
13
#import "HHOperation.h"

@implementation HHOperation

- (void)main {
if (!self.isCancelled) {
for (int i = 0; i < 2; i++) {
NSLog(@"当前线程:%@", [NSThread currentThread]);
}
}
}

@end

调用:

1
2
3
4
5
6
- (void)Operation3{
// 1.创建 HHOperation 对象
HHOperation *op = [[HHOperation alloc] init];
// 2.调用 start 方法开始执行操作
[op start];
}

NSOperationQueue 创建队列

NSOperationQueue 一共有两种队列:主队列、自定义队列。其中自定义队列同时包含了串行、并发功能。

主队列
凡是添加到主队列中的操作,都会放到主线程中执行(注:不包括操作使用addExecutionBlock:添加的额外操作)。

1
2
// 主队列获取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];

自定义队列
添加到这种队列中的操作,就会自动放到子线程中执行。同时包含了:串行、并发功能。

1
2
// 自定义队列创建方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

将操作加入到队列中

NSOperation 需要配合 NSOperationQueue 来实现多线程,总共有两种方法:

  • 1、- (void)addOperation:(NSOperation *)op; 需要先创建操作,再将创建好的操作加入到创建好的队列中去。
  • 2、- (void)addOperationWithBlock:(void (^)(void))block; 无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。

addOperation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)Operation4{
//1、创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2、创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程1:%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程2:%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程3:%@",[NSThread currentThread]);
}];

//3、添加操作
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}

运行结果:

1
2
3
2020-07-04 16:30:38.843822+0800 NSOperation[3307:147785] 当前线程2:<NSThread: 0x6000029a2a40>{number = 5, name = (null)}
2020-07-04 16:30:38.843820+0800 NSOperation[3307:147786] 当前线程3:<NSThread: 0x6000029f19c0>{number = 4, name = (null)}
2020-07-04 16:30:38.843832+0800 NSOperation[3307:147787] 当前线程1:<NSThread: 0x6000029d46c0>{number = 6, name = (null)}

addOperationWithBlock

无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)Operation5{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
NSLog(@"当前线程1:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"当前线程2:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"当前线程3:%@",[NSThread currentThread]);
}];
}

最大并发操作数量 maxConcurrentOperationCount

maxConcurrentOperationCount最大并发操作数量

  • maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。

  • maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。

  • maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    queue.maxConcurrentOperationCount = 1; // 串行队列
    queue.maxConcurrentOperationCount = 2; // 并发队列,一次只能执行两个并发队列
    [queue addOperationWithBlock:^{
    NSLog(@"当前线程1:%@",[NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
    NSLog(@"当前线程2:%@",[NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
    NSLog(@"当前线程3:%@",[NSThread currentThread]);
    }];

NSOperation 操作依赖

NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。

  • - (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
  • - (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
  • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)Operation6{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2、创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"当前线程1:%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"当前线程2:%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程3:%@",[NSThread currentThread]);
}];

//3、添加依赖
[op3 addDependency:op1];
[op3 addDependency:op2];
//4、添加操作
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}

上面op3依赖op1&op2执行完成才能执行:

1
2
3
2020-07-04 16:34:12.396522+0800 NSOperation[3346:151090] 当前线程1:<NSThread: 0x600000363580>{number = 6, name = (null)}
2020-07-04 16:34:13.398032+0800 NSOperation[3346:151088] 当前线程2:<NSThread: 0x600000376cc0>{number = 5, name = (null)}
2020-07-04 16:34:13.398415+0800 NSOperation[3346:151088] 当前线程3:<NSThread: 0x600000376cc0>{number = 5, name = (null)}

参考:iOS 多线程:『NSOperation、NSOperationQueue』详尽总结