轩辕十四

探索科技与创新的个人博客

在 iOS 开发中,自动化构建和打包流程是提高效率的关键。Fastlane 的 Gym 工具为我们提供了强大的自动化能力,但有时也会带来一些令人头疼的问题。今天,我们就来探讨一个我遇到的 Gym 打包问题,并分享一个巧妙的解决方案。

问题描述

在使用 Fastlane 的 Gym 工具进行批量 iOS 打包时,我们可能会遇到一个棘手的问题。特别是当我们将 Gym 的 clean 参数设置为 true 时,可能会导致构建过程失败。

在执行打包命令时,Gym 会运行类似这样的命令:

1
2
3
4
5
6
$ /usr/bin/xcrun \
/opt/homebrew/lib/ruby/gems/3.3.0/gems/fastlane-2.222.0/gym/lib/assets/wrap_xcodebuild/xcbuild-safe.sh \
-exportArchive \
-exportOptionsPlist '/var/folders/64/8v_9fcln0_g76s0ry0j6pwt40000gn/T/gym_config20240929-78449-4vb1zz.plist' \
-archivePath build/beta/zestbuy_beta.xcarchive \
-exportPath '/var/folders/64/8v_9fcln0_g76s0ry0j6pwt40000gn/T/gym_output20240929-78449-k1ep6q'

问题出现在这里:当 clean 参数设置为 true 时,Gym 会在构建过程中清理临时文件和目录。这可能导致 exportOptionsPlist 指定的文件路径被删除,从而引发 “文件或目录不存在”的错误,最终导致整个打包过程失败。

更具体地说,问题的根源在于:

  1. Gym 使用系统的临时目录来存储 exportOptionsPlist 文件。
  2. clean 参数为 true 时,这些临时文件可能在需要使用之前就被清理掉了。
  3. Gym 没有提供直接的方法让我们自定义这个文件的存储路径。

这个问题特别棘手,因为它只在特定配置下出现(即 clean: true),而这个配置在某些情况下是必要的,比如为了确保每次构建都是从一个干净的状态开始。

阅读全文 »

在 iOS 开发中,证书管理一直是一个重要且复杂的话题。随着 fastlane 的更新,我们处理证书信息的方式也在不断演进。今天,我想分享我在使用 fastlane 读取和加密 iOS 证书信息时的经历,以及如何应对 fastlane 更新带来的变化。

初始方法:直接读取证书文件

最初,我创建了一个自定义的 fastlane Action 来直接读取 .cer 文件,这里使用的是 OpenSSL 进行解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ReadSpecificFileAction < Action
def self.decrypt_specific_file(path: nil, password: nil, hash_algorithm: "MD5")
stored_data = Base64.decode64(File.read(path))
salt = stored_data[8..15]
data_to_decrypt = stored_data[16..-1]

decipher = ::OpenSSL::Cipher.new('AES-256-CBC')
decipher.decrypt
decipher.pkcs5_keyivgen(password, salt, 1, hash_algorithm)

decrypted_data = decipher.update(data_to_decrypt) + decipher.final

File.binwrite(path, decrypted_data)
rescue => error
fallback_hash_algorithm = "SHA256"
if hash_algorithm != fallback_hash_algorithm
decrypt_specific_file(path: path, password: password, hash_algorithm: fallback_hash_algorithm)
else
UI.error(error.to_s)
UI.crash!("Error decrypting '#{path}'")
end
end
end

这种方法简单直接,但随着 fastlane 的更新,fastlane 应该是修改了加密解密的逻辑,上面的方案已经无法使用,我们需要寻找新的实现方案。

阅读全文 »

函数

函数是任何应用程序的基本构建块,无论它们是本地函数、从另一个模块导入的函数,还是类中的方法。 它们也是值,就像其他值一样,TypeScript 有很多方法来描述如何调用函数。 让我们学习如何编写描述函数的类型。

阅读全文 »

类型缩小

假设我们有一个名为 padLeft 的函数。

1
2
3
function padLeft(padding: number | string, input: string): string {
throw new Error("Not implemented yet!");
}

如果 paddingnumber,它会将其视为我们想要添加到 input 的空格数。如果 paddingstring,它应该只是将 padding 前置到 input。让我们尝试实现当 padLeftpadding 传递 number 时的逻辑。

1
2
3
function padLeft(padding: number | string, input: string) {
return " ".repeat(padding) + input;
}

哦,我们在 padding 上遇到错误。TypeScript 警告我们,将 number | string 添加到 number 可能不会给我们想要的东西,这是正确的。换句话说,我们没有先明确检查 padding 是否是 number,也没有处理它是 string 的情况,所以让我们这样做。

1
2
3
4
5
6
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
return " ".repeat(padding) + input;
}
return padding + input;
}

如果这看起来像是无趣的 JavaScript 代码,那就是重点。除了我们放置的注释之外,这个 TypeScript 代码看起来像 JavaScript。这个想法是 TypeScript 的类型系统旨在使编写典型的 JavaScript 代码尽可能容易,而无需向后兼容以获得类型安全。

阅读全文 »

字符串

JavaScript 程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样,我们使用string表示文本数据类型。 和 JavaScript 一样,可以使用双引号(")或单引号(')表示字符串。

1
2
let name: string = "bob";
name = "smith";
阅读全文 »

当用户浏览、退出和返回到您的应用时,应用中的 Activity 实例会在其生命周期的不同状态间转换。Activity 类会提供许多回调,这些回调会让 Activity 知晓某个状态已经更改:系统正在创建、停止或恢复某个 Activity,或者正在销毁该 Activity 所在的进程。

阅读全文 »

在 C 语言中,结构体是一种非常重要的数据类型。它可以将不同的数据类型组合成一个整体,方便程序员进行操作。比如,我们可以定义一个结构体来表示一个人的信息,包括姓名、年龄、性别等等。使用结构体可以使得程序更加清晰易懂,提高代码的可读性和可维护性。

阅读全文 »
0%