第三方库SDWebImage②请求-①简介

必备知识架构-第三方库SDWebImage②请求-①简介

[toc]

2、SDWebImageDownloader 异步的图片下载器

SDWebImageDownloader是一个异步的图片下载器,它是一个单例类,主要负责图片的下载操作的管理。图片的下载是放在一个NSOperationQueue操作队列中来完成的,默认情况下,队列最大并发数是6。如果需要的话,我们可以通过SDWebImageDownloader类的maxConcurrentDownloads属性来修改。其声明如下:

3、SDWebImageDownloaderOperation 下载操作

下面我们来说一说SDWebImage的下载操作SDWebImageDownloaderOperation。该类继承自NSOperation,并且采用了 SDWebImageDownloaderOperationInterface, SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate 四个协议方法。

①、SDWebImageDownloaderOperation的下载请求

先通过URL等生成NSURLRequest,并设置给operation属性。

1
2
3
4
5
6
7
声明:
@property (strong, nonatomic, readonly, nullable) NSURLRequest *request;

定义:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];

有时候下我们希望它支持后台下载。所以在operation的start方法中,如果支持后台shouldContinueWhenAppEntersBackground,则将当前请求添加到后台任务中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;

if (sself) {
[sself cancel];

[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}

对于图片的下载,SDWebImageDownloaderOperation的下载使用NSURLSession类。

1
self.dataTask = [session dataTaskWithRequest:self.request];
1
2
3
4
5
6
7
8
9
10
11
12
if (self.dataTask) {
[self.dataTask resume];

......
// 任务开始后,会在主线程抛出下载开始通知
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
......
}

END

第三方库SDWebImag②请求-①避免重复请求问题

必备知识架构-第三方库SDWebImag②请求-①避免重复请求问题

[toc]

iOS 如何避免在短时间内频繁发出相同的网络请求?

SDWebImage框架特征

  • 类别UIImageView,UIButton,MKAnnotationView- - 添加Web图像和高速缓存管理
  • 异步图像下载器
  • 具有自动缓存到期处理的异步内存+磁盘映像缓存
  • 背景图像解压缩
  • 保证相同的URL不会被下载多次
  • 保证虚假网址不会重复重试
  • 保证主线程永远不会被阻止
  • 使用GCD和ARC

一、SDWebImage是如何避免在短时间内频繁发出相同的网络请求?

SDWebImage是如何避免在短时间内频繁发出相同的网络请求?

设想:一个列表所有的图片请求都是同一个url。

答:

1、如何确保不生成重复的网络请求

图片下载的回调信息存储在SDWebImageDownloader类的URLOperations属性中,该属性是一个字典,key是图片的URL地址,value则是一个SDWebImageDownloaderOperation对象,包含每个图片的多组回调信息。由于我们允许多个图片同时下载,因此可能会有多个线程同时操作URLOperations属性。需要保证URLOperations操作(添加、删除)的线程安全性。

为了避免同一个URL在被多次任务请求的时候,进行多次的重复网络下载。

1、将下载地址URL与其对应的网络下载请求,通过下载管理器SDWebImageDownloader的URLOperations属性管理起来。(该属性是一个字典,key是图片的URL地址,value是operation)

2、我们并不会对每次操作一进来就进行创建请求任务,而是先通过之前缓存的下载任务URLOperations,通过URL寻找是否有该操作了,①如果有则不创建,而是直接取出来使用;②如果没有才创建operation来使用,并添加到URLOperations中。以备后续有同样URL请求的时候,能会从URLOperations中得到operation,就不会导致重复创建和添加到队列中了。(通过URLOperations,我们以此保证一个URL同时在被请求多次的情况下,生成/取到的是同一个,也只有一个SDWebImageDownloaderOperation,从而也就只会被下载一次。)。

在SDWebImage版本5.8.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
SD_LOCK(self.operationsLock);	// 6、加锁,防止添加过程中,又有数据需要添加,保证线程安全
id downloadOperationCancelToken;
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url]; // 1、从进行中的 URLOperations 获取
if (!operation || operation.isFinished || operation.isCancelled) {
operation = [self createDownloaderOperationWithUrl:url options:options context:context]; // 2、如果之前没有下载操作,则创建,并加入到 URLOperations 中
// ....省略一堆代码
@weakify(self);
operation.completionBlock = ^{
@strongify(self);
if (!self) {
return;
}
SD_LOCK(self.operationsLock);
[self.URLOperations removeObjectForKey:url]; // 3、下载完成后,记得删除操作
SD_UNLOCK(self.operationsLock);
};
self.URLOperations[url] = operation; // 4、保存到 URLOperations 中,用于判断
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
[self.downloadQueue addOperation:operation]; // 4、添加操作

} else {
// ....省略一堆代码
@synchronized (operation) {
// 5、如果之前有下载操作,则不用创建操作,但是仍然需要保存回调,不然其他位置的图片的请求就丢失了(虽然说它们是同一张图片)
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
}
SD_UNLOCK(self.operationsLock); // 6、解锁

3、问:重复的url请求,没有创建operation了,那operation的回调怎么办?

答:虽然重复的URL只有一个SDWebImageDownloaderOperation。但是这个opeartion的callbackBlocks是个数组,所有的回调都用这个数组保存起来的,所以不会丢失的(这里callbackBlocks数组里存放字典,对应进行时候的ProgressCallback回调和结束时候的CompletedCallback回调)。

4、问:对同一个operation的callbackBlocks数组操作,有什么要注意的?

答:因为不管同个url不管有没有新的operation生成,我们都会有一个当前任务对应downloadOperationCancelToken生成,来给这个operation添加回调。即同一个url所对应opeartion里的callbackBlocks数组,在有多次图片任务时候,就会有多次操作callbackBlocks的情况。所以对callbackBlocks的操作,也要保证线程安全。

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
// SDWebImageDownloader.m

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// ....省略一堆代码

SD_LOCK(self.operationsLock);
id downloadOperationCancelToken;
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
if (!operation || operation.isFinished || operation.isCancelled) {
// ....省略一堆代码
} else {
// When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
// So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
@synchronized (operation) {
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
// ....省略一堆代码
}
SD_UNLOCK(self.operationsLock);

// 虽然重复的URL只有一个SDWebImageDownloaderOperation。但是SDWebImageDownloadToken是每个URL都会有一个的,只是他们的SDWebImageDownloaderOperation是同一个。
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
token.downloadOperationCancelToken = downloadOperationCancelToken;

return token;
}

对callbackBlocks的操作,也要保证线程安全,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// SDWebImageDownloaderOperation.m

@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;

- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
@synchronized (self) {
[self.callbackBlocks addObject:callbacks];
}
return callbacks;
}

2、它是如何保证请求管理的线程安全

说明:由于我们允许多个任务同时进行,也就造成了会有多个线程同时操作URLOperations属性。为了保证URLOperations操作(添加、删除)的线程安全性,我们添加了一个锁,且考虑到各种具备锁功能的性能问题,这里我们使用信号量semaphore。所以,我们控制线程安全的锁是使用信号量实现的operationsLock,且其初始值为1,用它来确保同一时间只有一个线程操作URLOperations属性

在SDWebImage版本5.8.1中的相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// SDWebImageDownloader.m

- (nonnull instancetype)init {
return [self initWithConfig:SDWebImageDownloaderConfig.defaultDownloaderConfig];
}

- (instancetype)initWithConfig:(SDWebImageDownloaderConfig *)config {
self = [super init];
if (self) {
// ....省略一堆代码
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = _config.maxConcurrentDownloads;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new];
// ....省略一堆代码
_operationsLock = dispatch_semaphore_create(1);
// ....省略一堆代码
}
return self;
}

3、其它知识说明

3.1、SDWebImageDownloader 是什么?(异步的图片下载器)

SDWebImageDownloader是一个异步的图片下载器,它是一个单例类,主要负责图片的下载操作的管理。

图片的下载是放在一个NSOperationQueue操作队列中来完成的,默认情况下,队列最大并发数是6。

1
2
3
4
5
6
7
8
9
10
11
12
13
// SDWebImageDownloader.m
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;


_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = _config.maxConcurrentDownloads;

// SDWebImageDownloaderConfig.h
/**
* The maximum number of concurrent downloads.
* Defaults to 6.
*/
@property (nonatomic, assign) NSInteger maxConcurrentDownloads;

如果需要的话,我们可以通过修改maxConcurrentDownloads属性来修改并发下载数。

二、其他版本的SDWebImage的代码解析

1、SDWebImageDownloader 是什么?(异步的图片下载器)

其声明及定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
//SDWebImageDownloader.m

@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;

- (id)init {
if ((self = [super init])) {
...
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT); // 并发队列
...
}
return self;
}

每有一个图片下载请求,SDWebImageDownloader图片下载管理器就会生成一个继承自NSOperation的下载操作SDWebImageDownloaderOperation,并添加到下载队列downloadQueue中。

[sself.downloadQueue addOperation:operation];

该队列允许修改最大并发数。

1、由于我们允许多个任务同时进行,也就造成了会有多个线程同时操作URLOperations属性。为了保证URLOperations操作(添加、删除)的线程安全性,我们添加了线程操作的信号量operationsLock,其初始值为1,来确保同一时间只有一个线程操作URLOperations属性,我们以添加操作为例,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LOCK(self.operationsLock);
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
if (!operation) {
operation = createCallback();
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};
[self.URLOperations setObject:operation forKey:url];
}
UNLOCK(self.operationsLock);

2、同样由于为了避免同一个URL的图片被下载多次,所以我们并不会对每次操作一进来就进行创建请求任务,而是先通过之前缓存的下载任务URLOperations中通过URL寻找是否有该操作了,如果有则不创建,如果没有才创建。因此,这里就会有一个URL同时在被请求多次的情况下,生成/取到的是同一个SDWebImageDownloaderOperation。

三、其他

对于下载任务的执行,SDWebImage还允许我们设置是默认的先进先出还是后进先出。该功能的实现,其自然是通过队列操作的依赖来完成的(其中lastAddedOperation指的是上一次的操作)。源代码如下:

1
2
3
4
5
6
[sself.downloadQueue addOperation:operation];
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}

其他参考文章

END

第三方库SDWebImage①缓存-①缓存不更新问题

必备知识架构-第三方库SDWebImage③其他

[toc]

iOS - 主线程调度在应用中的小技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma mark - 宏定义
#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {\
block();\
} else {\
dispatch_async(queue, block);\
}
#endif

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block)
#endif

dispatch_queue_get_label:返回创建队列时为队列指定的标签。

END

数据库-①FMDB

必备知识架构-数据库-①FMDB

[toc]

一、FMDB的多线程问题

你项目用FMDB吗?你写的数据库类是线程安全的吗?如果不是,希望这篇小文章会对你有点帮助

1、FMDB如何保证多线程处理的数据安全

对于数据操作,最重要的一点就是数据安全的问题,在多线程中,线程安全是数据安全的首要前提,下面谈谈FMDB 是如何对多线程进行处理的。

详情参考:FMDB 中的多线程处理

FMDatabaseQueue为何会线程安全的一点心得

2、FMDatabaseQueue为何能保证线程安全

在做iOS项目的时候,会用到许多第三方的框架,比如用FMDB来处理数据库的东西。但是FMDatabase并不是线程安全的,当你在不同的线程里同时操作一个Database的时候,就容易产生崩溃。但是好在FMDB提供了一个解决的方法,就是FMDatabaseQueue。通过它来操作数据库就是线程安全的了。
即:FMDB是使用databaseQueue实现数据库操作线程安全

1
2
3
4
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:_dbPath];
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdateWithFormat:@"INSERT INTO TABLENAME(id, name) VALUES (%zd, %@);", @1, @"idage"];
}];

queue inDatabase:的本质如下:

FMDB数据安全1

_queue的初始化为:

1
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);

FMDB是怎么处理线程安全的?

综上所述:FMDB的线程处理就是强行就所有的多线程逻辑在一个串行队列中同步处理。

二、FMDB的数据库事务(Database Transaction)

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。简单的来说就是可以同时处理多个数据库操作,并且速度很快。

FMDB是支持数据库事务的。

1
2
3
4
5
6
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @4];
}];

三、为什么要从FMDB迁移到WCDB?

为什么要从FMDB迁移到WCDB?

表结构

WCDB 提供了 ORM 的功能,将类的属性绑定到数据库表的字段。在日常实践中,类的属性名和表的字段名通常不一致。因此,WCDB 提供了 WCDB_SYNTHESIZE_COLUMN(className, propertyName, columnName) 宏,用于映射属性名。

END

第三方网络库AFNetworking

必备知识架构-第三方网络库AFNetworking

[toc]

知识架构

iOS知识库

Android知识库

AFNetworking源码解析与面试考点思考

AFNetworking源码解析与面试考点思考

八、多线程–第三方库AFNetworking

< 返回目录

1、AFNetworking源码解析

2、AFNetworking的线程和信号量问题

AFNetworking3.0后为什么不再需要常驻线程?

AFNetworking3.0后为什么不再需要常驻线程?

B.一个请求一条线程

如果来一个请求开辟一条线程,设置runloop保活线程,等待结果回调。这种方式理论上是可行的,但是你也看到了,线程开销太大了。(PASS)

C.一条常驻线程

只开辟一条子线程,设置runloop使线程常驻。所有的请求在这个线程上发起、同时也在这个线程上处理回调

线程保活的代码

1
2
3
4
5
6
7
8
9
+ (void)aliveThread {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}

我们知道主线程一直是保活的;而新建的子线程默认是没有添加Runloop的,因此给这个线程添加了一个runloop,并且加了一个NSMachPort,来防止这个新建的线程由于没有活动直接退出。

AFNetworking一些API介绍

必知点:AFNetworking框架默认请求类型requestSerializer和响应类型responseSerializer都是JSON格式的,即默认请求类型为AFJSONRequestSerializer,默认相应类型为AFJSONResponseSerializer

1、关于请求类型及请求参数的书写

所以在进行请求时候,我们必须根据自己请求的参数类型parameters,对AFNetworking的请求类型进行设置。主要设置为:

  1. 如果请求参数是字典类型NSDictionary,则请求类型应设置AFHTTPRequestSerializer
  2. 如果请求参数是JSON类型,则请求类型应设置AFJSONRequestSerializer

因为设置的类型不同,AFNetworking会根据设置的类型执行该类型下面对应的- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error方法。

其中

AFHTTPRequestSerializer对参数会有如下第496行的处理query = AFQueryStringFromParameters(parameters);该处理为将字典类型转为一串请求的字符串格式。

AFJSONRequestSerializer对参数的处理主要为第1260行的
[mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];

2、关于响应类型及响应response

响应的时候,AFURLSessionManager其会调用AFURLSessionManagerTaskDelegate协议,执行该协议里第292的responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];可以看出这里会根据我们设置的不同响应类型,调用该响应类型下的

1
2
3
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error

所以,这里我们衍生出一个继承自AFJSONResponseSerializer的CJJSONResponseSerializer的新响应类型,重写该方法,用来处理服务端返回的JSON不是标准的json格式的问题,即主要处理AFNetworking 3840的错误。

END

< 返回目录

提示语优化

[toc]

提示语优化

一、提示语优化

如果有一个提示语在显示中,其他同类提示语不再显示

提示语消失后,如果异常仍然存在,按上述规则显示第一个错误提示语

如果两类提示语需要出现,后面的覆盖前面。任何时间,只显示一条提示语

End

iOS进阶_打包脚本

[TOC]

一、认识Mac的keychain机制

要使用Keychain中的证书,请先unlock-keychain

完整的命令如下:

1
security unlock-keychain -p "lichaoqian" "/Users/lichaoqian/Library/Keychains/login.keychain"

如果你是在Jenkins中执行的,请确保你的权限问题。具体的解决可进入《使用工具->Jenkins->Jenkins使用问题常见》中的”二、权限问题”中查看解决。

其他参考文章:

二、认识几个命令

  • 查看SDK版本
1
xcodebuild -showsdks

查询结果如下:

xcodebuild -showsdks的执行结果

三、认识执行命令

在终端执行xcodebuild --help

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
Beyond-MacBook-Pro:~ lichaoqian$ xcodebuild --help
Usage: xcodebuild [-project <projectname>] [[-target <targetname>]...|-alltargets] [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings [-json]] [<buildsetting>=<value>]... [<buildaction>]...
xcodebuild [-project <projectname>] -scheme <schemeName> [-destination <destinationspecifier>]... [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings [-json]] [-showdestinations] [<buildsetting>=<value>]... [<buildaction>]...
xcodebuild -workspace <workspacename> -scheme <schemeName> [-destination <destinationspecifier>]... [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings] [-showdestinations] [<buildsetting>=<value>]... [<buildaction>]...
xcodebuild -version [-sdk [<sdkfullpath>|<sdkname>] [-json] [<infoitem>] ]
xcodebuild -list [[-project <projectname>]|[-workspace <workspacename>]] [-json]
xcodebuild -showsdks [-json]
xcodebuild -exportArchive -archivePath <xcarchivepath> [-exportPath <destinationpath>] -exportOptionsPlist <plistpath>
xcodebuild -exportNotarizedApp -archivePath <xcarchivepath> -exportPath <destinationpath>
xcodebuild -exportLocalizations -localizationPath <path> -project <projectname> [-exportLanguage <targetlanguage>...[-includeScreenshots]]
xcodebuild -importLocalizations -localizationPath <path> -project <projectname>
xcodebuild -resolvePackageDependencies [-project <projectname>|-workspace <workspacename>] -clonedSourcePackagesDirPath <path>
xcodebuild -create-xcframework [-help] [-framework <path>] [-library <path> [-headers <path>]] -output <path>

Options:
-usage print brief usage
-help print complete usage
-verbose provide additional status output
-license show the Xcode and SDK license agreements
-checkFirstLaunchStatus Check if any First Launch tasks need to be performed
-runFirstLaunch install packages and agree to the license
-project NAME build the project NAME
-target NAME build the target NAME
-alltargets build all targets
-workspace NAME build the workspace NAME
-scheme NAME build the scheme NAME
-configuration NAME use the build configuration NAME for building each target
-xcconfig PATH apply the build settings defined in the file at PATH as overrides
-arch ARCH build each target for the architecture ARCH; this will override architectures defined in the project
-sdk SDK use SDK as the name or path of the base SDK when building the project
-toolchain NAME use the toolchain with identifier or name NAME
-destination DESTINATIONSPECIFIER use the destination described by DESTINATIONSPECIFIER (a comma-separated set of key=value pairs describing the destination to use)
-destination-timeout TIMEOUT wait for TIMEOUT seconds while searching for the destination device
-parallelizeTargets build independent targets in parallel
-jobs NUMBER specify the maximum number of concurrent build operations
-maximum-concurrent-test-device-destinations NUMBER the maximum number of device destinations to test on concurrently
-maximum-concurrent-test-simulator-destinations NUMBER the maximum number of simulator destinations to test on concurrently
-parallel-testing-enabled YES|NO overrides the per-target setting in the scheme
-parallel-testing-worker-count NUMBER the exact number of test runners that will be spawned during parallel testing
-maximum-parallel-testing-workers NUMBER the maximum number of test runners that will be spawned during parallel testing
-dry-run do everything except actually running the commands
-quiet do not print any output except for warnings and errors
-hideShellScriptEnvironment don't show shell script environment variables in build log
-showsdks display a compact list of the installed SDKs
-showdestinations display a list of destinations
-showTestPlans display a list of test plans
-showBuildSettings display a list of build settings and values
-list lists the targets and configurations in a project, or the schemes in a workspace
-find-executable NAME display the full path to executable NAME in the provided SDK and toolchain
-find-library NAME display the full path to library NAME in the provided SDK and toolchain
-version display the version of Xcode; with -sdk will display info about one or all installed SDKs
-enableAddressSanitizer YES|NO turn the address sanitizer on or off
-enableThreadSanitizer YES|NO turn the thread sanitizer on or off
-enableUndefinedBehaviorSanitizer YES|NO turn the undefined behavior sanitizer on or off
-resultBundlePath PATH specifies the directory where a result bundle describing what occurred will be placed
-resultStreamPath PATH specifies the file where a result stream will be written to (the file must already exist)
-resultBundleVersion 3 [default] specifies which result bundle version should be used
-clonedSourcePackagesDirPath PATH specifies the directory to which remote source packages are fetch or expected to be found
-derivedDataPath PATH specifies the directory where build products and other derived data will go
-archivePath PATH specifies the directory where any created archives will be placed, or the archive that should be exported
-exportArchive specifies that an archive should be exported
-exportNotarizedApp export an archive that has been notarized by Apple
-exportOptionsPlist PATH specifies a path to a plist file that configures archive exporting
-enableCodeCoverage YES|NO turn code coverage on or off when testing
-exportPath PATH specifies the destination for the product exported from an archive
-skipUnavailableActions specifies that scheme actions that cannot be performed should be skipped instead of causing a failure
-exportLocalizations exports completed and outstanding project localizations
-importLocalizations imports localizations for a project, assuming any necessary localized resources have been created in Xcode
-localizationPath specifies a path to XLIFF localization files
-exportLanguage specifies multiple optional ISO 639-1 languages included in a localization export
-xctestrun specifies a path to a test run specification
-testPlan specifies the name of the test plan associated with the scheme to use for testing
-only-testing constrains testing by specifying tests to include, and excluding other tests
-only-testing:TEST-IDENTIFIER constrains testing by specifying tests to include, and excluding other tests
-skip-testing constrains testing by specifying tests to exclude, but including other tests
-skip-testing:TEST-IDENTIFIER constrains testing by specifying tests to exclude, but including other tests
-only-test-configuration constrains testing by specifying test configurations to include, and excluding other test configurations
-skip-test-configuration constrains testing by specifying test configurations to exclude, but including other test configurations
-testLanguage constrains testing by specifying ISO 639-1 language in which to run the tests
-testRegion constrains testing by specifying ISO 3166-1 region in which to run the tests
-resolvePackageDependencies resolves any Swift package dependencies referenced by the project or workspace
-disableAutomaticPackageResolution prevents packages from automatically being resolved to versions other than those recorded in the `Package.resolved` file
-json output as JSON (note: -json implies -quiet)
-allowProvisioningUpdates Allow xcodebuild to communicate with the Apple Developer website. For automatically signed targets, xcodebuild will create and update profiles, app IDs, and certificates. For manually signed targets, xcodebuild will download missing or updated provisioning profiles. Requires a developer account to have been added in Xcode's Accounts preference pane.
-allowProvisioningDeviceRegistration Allow xcodebuild to register your destination device on the developer portal if necessary. This flag only takes effect if -allowProvisioningUpdates is also passed.
-showBuildTimingSummary display a report of the timings of all the commands invoked during the build
-create-xcframework create an xcframework from prebuilt libraries; -help for more information.

Available keys for -exportOptionsPlist:

compileBitcode : Bool

For non-App Store exports, should Xcode re-compile the app from bitcode? Defaults to YES.

destination : String

Determines whether the app is exported locally or uploaded to Apple. Options are export or upload. The available options vary based on the selected distribution method. Defaults to export.

embedOnDemandResourcesAssetPacksInBundle : Bool

For non-App Store exports, if the app uses On Demand Resources and this is YES, asset packs are embedded in the app bundle so that the app can be tested without a server to host asset packs. Defaults to YES unless onDemandResourcesAssetPacksBaseURL is specified.

generateAppStoreInformation : Bool

For App Store exports, should Xcode generate App Store Information for uploading with iTMSTransporter? Defaults to NO.

iCloudContainerEnvironment : String

If the app is using CloudKit, this configures the "com.apple.developer.icloud-container-environment" entitlement. Available options vary depending on the type of provisioning profile used, but may include: Development and Production.

installerSigningCertificate : String

For manual signing only. Provide a certificate name, SHA-1 hash, or automatic selector to use for signing. Automatic selectors allow Xcode to pick the newest installed certificate of a particular type. The available automatic selectors are "Mac Installer Distribution" and "Developer ID Installer". Defaults to an automatic certificate selector matching the current distribution method.

manifest : Dictionary

For non-App Store exports, users can download your app over the web by opening your distribution manifest file in a web browser. To generate a distribution manifest, the value of this key should be a dictionary with three sub-keys: appURL, displayImageURL, fullSizeImageURL. The additional sub-key assetPackManifestURL is required when using on-demand resources.

method : String

Describes how Xcode should export the archive. Available options: app-store, validation, ad-hoc, package, enterprise, development, developer-id, and mac-application. The list of options varies based on the type of archive. Defaults to development.

onDemandResourcesAssetPacksBaseURL : String

For non-App Store exports, if the app uses On Demand Resources and embedOnDemandResourcesAssetPacksInBundle isn't YES, this should be a base URL specifying where asset packs are going to be hosted. This configures the app to download asset packs from the specified URL.

provisioningProfiles : Dictionary

For manual signing only. Specify the provisioning profile to use for each executable in your app. Keys in this dictionary are the bundle identifiers of executables; values are the provisioning profile name or UUID to use.

signingCertificate : String

For manual signing only. Provide a certificate name, SHA-1 hash, or automatic selector to use for signing. Automatic selectors allow Xcode to pick the newest installed certificate of a particular type. The available automatic selectors are "Mac App Distribution", "iOS Distribution", "iOS Developer", "Developer ID Application", "Apple Distribution", "Mac Developer", and "Apple Development". Defaults to an automatic certificate selector matching the current distribution method.

signingStyle : String

The signing style to use when re-signing the app for distribution. Options are manual or automatic. Apps that were automatically signed when archived can be signed manually or automatically during distribution, and default to automatic. Apps that were manually signed when archived must be manually signed during distribtion, so the value of signingStyle is ignored.

stripSwiftSymbols : Bool

Should symbols be stripped from Swift libraries in your IPA? Defaults to YES.

teamID : String

The Developer Portal team to use for this export. Defaults to the team used to build the archive.

thinning : String

For non-App Store exports, should Xcode thin the package for one or more device variants? Available options: <none> (Xcode produces a non-thinned universal app), <thin-for-all-variants> (Xcode produces a universal app and all available thinned variants), or a model identifier for a specific device (e.g. "iPhone7,1"). Defaults to <none>.

uploadBitcode : Bool

For App Store exports, should the package include bitcode? Defaults to YES.

uploadSymbols : Bool

For App Store exports, should the package include symbols? Defaults to YES.

1、cd到代码目录

2、开始打包

  • 执行命令

写在终端进行测试的命令:

1
xcodebuild -workspace CJAutoPackageDemo.xcworkspace -scheme App1Enterprise -configuration Debug -sdk iphoneos12.2 ARCHS='arm64 armv7' IOS_DEVELOPMENT_TARGET=8.0 -derivedDataPath ../output/build/Debug-iphoneos archive -archivePath ../output/Debug-iphoneos/App1Enterprise.xcarchive

写在脚本中的命令:

1
2
3
4
5
echo ""
echo ""
echo ">>>>>>>>>>>>>>>>>>>>>>>>>> step5:begin compile project >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
xcodebuild -workspace ${PROJECT_DIR}/${APPPROJECT_NAME}.xcworkspace -scheme ${APPTARGET_NAME} -configuration ${BUILD_CONFIGURATION_NAME} -sdk ${SIMULATOR_OR_IOS_SDK}${SDK_VERSION} ARCHS='arm64 armv7' IOS_DEVELOPMENT_TARGET=${DEVELOPMENT_TARGET} -derivedDataPath ${BUILD_OUTPUT_PATH} archive -archivePath "${ARCHIVE_OUTPUT_PATH}/${APPTARGET_NAME}.xcarchive"
echo "<<<<<<<<<<<<<<<<<<<<<<<<<<< step5:end compile project <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"

参数介绍:

参数 含义
-workspace 工程文件名(用cocopods集成的项目,没有的话 整个改为-xcodeproj xxx.xcodeproj)
-scheme 通过scheme指定不同的target
-configuration 对应的环境配置,就是编译的时候执行的模式
(测试Debug、预生产PreRelease、生产Release)
-archivePath 导出的.xcarchive的路径
-sdk
-derivedDataPath
-archivePath 导出的.xcarchive的路径

3、导出ipa

  • 执行命令
1
2
3
4
5
6
7
8
9
10
11
echo ""
echo ""
echo ">>>>>>>>>>>>>>>>>>>>>>>>>> step6:begin archiving app to ipa >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
echo "PWD=$PWD"
cd ${ROOT_DIR}
echo "PWD=$PWD"
echo "archivePath ==>>> ${ARCHIVE_OUTPUT_PATH}/${APPTARGET_NAME}.xcarchive"
echo "exportPath ==>>> ${ARCHIVE_OUTPUT_PATH}/${APPTARGET_NAME}"
echo "exportOptionsPlist ==>>> ${ExportOptionsPlist_PATH}"
xcodebuild -exportArchive -archivePath "${ARCHIVE_OUTPUT_PATH}/${APPTARGET_NAME}.xcarchive" -exportPath "${ARCHIVE_OUTPUT_PATH}/${APPTARGET_NAME}" -exportOptionsPlist "${ExportOptionsPlist_PATH}" -allowProvisioningUpdates
echo "<<<<<<<<<<<<<<<<<<<<<<<<<<< step6:end archiving app to ipa <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
  • 参数解读:
参数 含义
-archivePath .xcarchive文件的路径
-exportPath 导出的ipa的路径
-exportOptionsPlist

三、完整脚本内容(含使用Jenkins打包)

  • 测试项目工程详见:gitee中的AutoPackage-iOS工程

  • 测试Jenkins工程详见:本地Jenkins中的CJAutoPackage工程

  • 最终Jenkins脚本如下图:

    iOS打包_Jenkins脚本

附:没有权限问题的Jenkins正确的启动方式如下(按下面方式打开,才不会出现权限问题):

1
2
$ sudo launchctl unload /Library/LaunchDaemons/org.jenkins-ci.plist
$ java -jar /Applications/Jenkins/jenkins.war --httpPort=8080

当你执行完这两行命令的时候,你可以在浏览器上输入http://localhost:8080来访问Jenkins了。(如果你只执行了第一行,没执行第二行,会出现无法访问)

更多Jenkins知识请查看:实用工具->Jenkins->Jenkins的安装与启动

四、注意点

1、多环境的打包

主要注意为xcodebuild -workspace中的-configuration参数即可。

2、多Target的打包注意

多Target的Podfile文件ruby脚本内容,大概如下:

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
43
44
45
source 'https://github.com/CocoaPods/Specs.git'
source 'https://gitee.com/dvlproad/dvlproadSpecs'

platform :ios, '8.0'
#use_frameworks!
inhibit_all_warnings!

post_install do |installer|

puts 'Determining pod project minimal deployment target'

pods_project = installer.pods_project
deployment_target_key = 'IPHONEOS_DEPLOYMENT_TARGET'
deployment_targets = pods_project.build_configurations.map{ |config| config.build_settings[deployment_target_key] }
minimal_deployment_target = deployment_targets.min_by{ |version| Gem::Version.new(version) }

puts 'Minimal deployment target is ' + minimal_deployment_target
puts 'Setting each pod deployment target to ' + minimal_deployment_target

installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings[deployment_target_key] = minimal_deployment_target
end
end
end


# There are no targets called "App1Common" in any Xcode projects
abstract_target 'App1Common' do
# Has its own copy of App1Common + App1Enterprise
target 'App1Enterprise' do

end

# Has its own copy of App1Common + App1AppStore
target 'App1AppStore' do

end
end



target 'CJAutoPackageDemoTests' do

end

通过脚本自动化打包的时候的注意点如下:

  1. 主要注意为xcodebuild -workspace中的-scheme参数即可。

  2. 多Target后,记得在Linked Frameworks and Libraries中删除已经不存在的原本旧的libPods-xxx.a文件。否则,Jenkins脚本打包Build的时候会报错。

    • Jenkins脚本打包Build的时候报错的信息如下:

    Jenkins脚本打包Build的时候报错的信息

    • 多Target工程打包时候要删除的旧引用文件如下:

      多Target工程打包时候要删除的旧引用文件

iOS证书

[TOC]

iOS证书

一、证书p12

1、制作

image-20230302172748572

2、导出

请一定要切换到“我的证书”目录下

image-20230302173554802

3、导入

拖动p12文件到钥匙串中的登录

问题1、iOS证书不受信任解决办法

问题如下:

image-20230302173013788

解决方法:

重新下载最新的AppleWWDR

进入 https://developer.apple.com/support/expiration/

image-20230302173236106

进入后 https://www.apple.com/certificateauthority/

image-20230302173318271

这里我们下载个G4就够了。

之后再对未信任的证书,右键显示简介,在简介里选中”始终信任”即可。

其他参考:iOS证书出现证书不受信任的解决方法

二、mobileprovision 文件

将 mobileprovision 文件放到以下目录

1
2
open ~/Library/MobileDevice/Provisioning\ Profiles							# Xcode 15
open ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles # Xcode 16

End

第4节、Jenkins使用问题常见

[TOC]

Jenkins上显示二维码图片

一、在Jenkins上显示二维码图片

持续集成 从零开始在 Jenkins 上显示打包二维码

1. 安装Jenkins插件

  • 进入Jenkins工作台系统管理>插件管理>可选插件>过滤>build-name-setter,勾选对应插件直接安装

    showQR1

  • 返回工作台首页,选择一个Build工程>配置

  • 可以看到构建环境处多了一个Set Build Name的选项,即插件安装成功

    showQR2

2. 配置Set Build Name插件

查看Jekins的环境变量参数,我们可以看到

1
2
3
4
BUILD_NUMBER
The current build number, such as "153"
BUILD_DISPLAY_NAME
The display name of the current build, which is something like "#153" by default.
  • Set Build Name就是修改这个BUILD_DISPLAY_NAME,默认为#${BUILD_NUMBER}

  • Jenkins的默认安全设置,会把所有的输入都当成纯文本,为了使图片链接生效,需要更改一下系统管理>全局安全配置>标记格式器,将纯文本选项改为Safe HTML,保存设置

    showQR3

  • 点击Set Build Name的高级,会出现Build Description的设置,可以在这里写入一个图片的链接 <img src="http://ww1.sinaimg.cn/large/007x9vWyly1g2zgykchtmj30a7064dk4.jpg" height="200" width="200/>

  • Build完成之后即可在Build历史中看到对应的图片链接

    img

showQR4_update1

End