Swift 中 Protocol 和 泛型
前言
一般在 Swift 中使用 泛型 的时候我们会这么写:
/// 类
class AClass<T> {}
/// 结构体
struct ASctuct<T> {}
/// 枚举
enum AEnum<T> {}
但是如果想在 协议 中使用泛型的时候这么写就会报错:
protocol AProtocol<T> {}
报错信息:
Protocols do not allow generic parameters; use associated types instead
虽然 泛型 可以在 类, 结构体, 枚举 中使用, 但是某些使用场景中, 如果在 协议 中加入 泛型 的话, 会使我们的代码更加灵活.
尽管 协议 中不支持 泛型, 但是却有个 associatedtype, 各种文章和书籍中都把它翻译为 关联类型. 我们可以使用 associatedtype 来达成 泛型 的目的.
正文
假设现在有如下 2 个接口:
/// 请求老师数据列表
/// - page: 分页页码
/// - limit: 分页页面容量
/// - return: 老师列表数据
[POST] https://example.com/teachlist
/// 请求老师所教授的科目
/// - id: 老师 id
/// - page: 分页页码
/// - limit: 分页页面容量
/// - return: 老师教授的科目数据列表
[POST] https://example.com/subjectlist
PListable 协议
此处定义协议 PListable.
Parameters 为网络请求的参数类型, 由于其需要使用 JSONEncoder 对其进行编码, 因此需要实现 Encodable 协议.
Result 作为请求方法的返回类型, 由于需要使用 JSONDecoder 对请求到的 Data 进行解码, 因此需要实现 Decodable 协议.
requestURL 返回结果为网络请求的 URL 地址.
protocol PListable {
/// 参数类型
associatedtype Parameters: Encodable
/// 请求结果类型
associatedtype Result: Decodable
/// 请求地址
static var requestURL: URL? { get }
}
在协议的 extension 中实现了 static func plist(parameters: Parameters) -> Result? , 该方法为实现该协议的类型提供网络请求的功能实现.
extension PListable {
/// 分页的方式请求数据列表
/// - Parameter parameters: 参数对象
/// - Returns: 请求结果
static func plist(parameters: Parameters) -> Result? {
/*
网络请求代码
...
*/
/// 网络请求取到的数据
let data: Data = ...
/// 解析数据
return try? JSONDecoder().decode(Result.self, from: data)
}
}
此方法为了更加清晰的表达意图, 未使用 异步, 而是使用了 同步 的直接返回请求结果的写法.
如果了解 协程 的话, 应该就很容易理解这种写法了.
参数类型数据结构
PLimit 结构为需要 page 和 limit 参数类型的接口提供参数. 依据 PListable 协议中 Parameters 的约束要求实现了 Encodable 协议.
struct PLimit: Encodable {
/// 分页页码
let page: Int
/// 分页数据容量
let limit: Int
}
PLimitWithId 结构对应的为需要 id, page, limit 参数类型的接口提供参数, 同样的实现了 Encodable 协议.
struct PLimitWithId: Encodable {
/// 数据查询依赖的 id
let id: Int
/// 分页页码
let page: Int
/// 分页数据容量
let limit: Int
}
Teacher 为接口 https://example.com/teachlist 返回的数据体部分的数据结构. 根据 PListable 协议中 Result 类型约束的要求实现了 Decodable 协议.
数据体数据结构
/// 老师对象
struct Teacher: Decodable {
/// 姓名
var name: String?
/// 教学科目列表
var subject: [Subject]?
}
Teacher 实现 PListable 协议, 并在 extension 中给 Parameters 类型关联为 PLimit, Result 类型关联为 [Teacher] 类型.
extension Teacher: PListable {
typealias Parameters = PLimit
typealias Result = [Teacher]
static var requestURL: URL? { URL(string: "http://example.com/teachlist") }
}
这样 Teacher 就可以调用 static func plist(parameters: Parameters) -> Result? 方法了, 并且其参数类型为 PLimit, 返回类型为 [Teacher] 返回一组 Teacher 类型的数据.
对应的, Subject 也与 Teacher 做相同的操作.
/// 科目对象
struct Subject: Decodable {
/// 科目名称
var name: String?
}
不同的是 Subject 中 Parameters 绑定为 PLimitWithId 类型, Result 绑定为 [Subject] 类型.
extension Subject: PListable {
typealias Parameters = PLimitWithId
typealias Result = [Subject]
static var requestURL: URL? { URL(string: "http://example.com/subjectlist") }
}
这样 Subject 就同样可以调用 static func plist(parameters: Parameters) -> Result? 方法了, 并且其参数类型为 PLimitWithId, 返回类型为 [Subject] 返回一组 Subject 类型的数据.
调用的代码如下:
Teacher.plist(parameters: PLimit(page: 0, limit: 20))
Subject.plist(parameters: PLimitWithId(id: 101, page: 0, limit: 20))
扩展
同时 protocol + associatedtype 还可以与 泛型 组合使用:
如果我们有如下 Animal 协议 和 结构体 Cat:
protocol Animal {
associatedtype `Type`
}
struct Cat<T> {}
extension Cat: Animal {
typealias `Type` = T
}
Cat 类型接收一个 T 类型的泛型, Cat 在实现 Animal 协议后, 可以把 T 设置为 Type 的关联类型.
结语
虽然使用 class 的 继承 也能达到类似的效果, 但是 struct 和 enum 却不支持 继承.
通过 协议 任何实现 PListable 的类型都拥有了 分页获取数据 的能力.
在项目开发中我们往往可能还要有 Deleteable, Updateable … 等等诸多类型的接口, 如果我们都通过 protocol + associatedtype 的方式来为对应类型进行扩展, 不仅能够提升开发效率, 还能降低维护成本.
代码自动格式化
前言
每个团队都应该有统一的代码风格和规范,这带来的好处我相信不言而喻,具体我就不多说了,大家都懂的😁。如何更有效率的去做这件事呢,我这次就来说说如何更好的自动格式化你的代码。
现状
大多数 iOS 开发者应该都知道 Xcode 的插件 Clang Format,它是基于 clang-format 命令行工具的一个 Xcode 插件,但是这款插件在Xcode9上已经无法使用了,因为Xcode9的插件机制已经变了。
现在可以使用这一款XcodeClangFormat,具体的使用方式点击链接,大家自行去看吧。这款有个缺点,就是不能像之前那款插件可以设置在保存时自动格式化(这其实也不能怪作者,Xcode新的机制不允许)。 不过使用这种插件还是不够方便,你还得手动选中文件或者代码再按快捷键来格式化,很容易忘,而导致把不规范的代码直接提交到仓库里了。
那么有没有一种方式,可以让我在敲代码的时候随心所欲,提交时又能提醒我然后自动帮我格式化吗?
该怎么做
这里我直接介绍一款神器Space Commander,它利用 Git Hooks ,在 commit 之前检查代码风格是否符合规范,只有符合规范的代码才允许提交,列出不符合规范的文件,同时提供 Shell 脚本来自动格式化。接下来我介绍下如何使用。
- 1 clone Space Commander
git clone https://github.com/square/spacecommander.git
- 2 在项目中安装Space Commander
cd到你的项目根目录,执行setup-repo.sh脚本发(在你clone下来的项目中,所以要全路径),执行完后会在项目根目录多一个隐藏文件.clang-format,这是一个替身,指向Space Commander仓库中的.clang-format文件,里面默认包含了一系列代码规则,如果你想要用自己的规则,可以去Space Commander仓库中改真身,也可以用新的.clang-format文件替换掉这个替身。 - 3 让我们提交代码试试
BasedOnStyle: Chromium
IndentWidth: 4
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
ObjCSpaceAfterProperty: true
PointerAlignment: Right
BreakBeforeBraces: Attach
这是我自定义的一些规则,具体.clang-format的写法请参照这个。
好,让我们写一下代码
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, copy) NSString* p;
@property(nonatomic, strong) UITextView * textview;
@end
@implementation ViewController
-(void)formatTest:(NSString *)param{
if (param) {
NSLog(@"sss");
}
int a=0;
int b = 1;
int c= 2;
NSLog(@"%d%d%d",a,b,c);
}
-(void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)viewDidAppear:(BOOL)animated {
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
复制代码
这一看,就很不规范吧,先让我们提交看看
提交的时候明确提示ViewController文件需要格式化,这时候我们可以使用format-objc-file.sh脚本单独格式化某个文件,也可以format-objc-files.sh格式化所有的暂存文件,甚至使用format-objc-files-in-repo.sh格式化整个仓库的文件。
再提交一遍
好,接下来我们在看看代码变成什么样子了
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, copy) NSString * p;
@property (nonatomic, strong) UITextView *textview;
@end
@implementation ViewController
- (void)formatTest:(NSString *)param {
if (param) {
NSLog(@"sss");
}
int a = 0;
int b = 1;
int c = 2;
NSLog(@"%d%d%d", a, b, c);
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidAppear:(BOOL)animated {
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
复制代码
完美!😝
后续
可以看到上面的命令太长了,必须要指定shell脚本的全路径才能执行,我们可以简化命令,如果你用的zsh,去修改 ~/.zshrc,如果是bash,则修改~/.bash_profile,
// 初始化
alias clangformatsetup="/你自己的路径/spacecommander/setup-repo.sh"
// 格式化对应文件
alias clangformatfile="/你自己的路径/spacecommander/format-objc-file.sh"
// 格式化所有暂存文件
alias clangformatfiles="/你自己的路径/spacecommander/format-objc-files.sh"
// 格式化整个仓库
alias clangformatall="/你自己的路径/spacecommander/format-objc-files-in-repo.sh
复制代码
如果你还想知道更多的用法,直接去spacecommander的github主页查看。
总结
这是我第一次在掘金上写文章(别的地方也没写过多少😂),写的不好,大家海涵呐,多多提意见哈😁。
使用Artifactory 1分钟搭建 CocoaPod 私服
痛点
目前的 CocoaPod 私服,很多公司使用 Git 仓库进行搭建,这导致的问题是,CocoaPod 的构建产出物通常较大,上传到 Git 仓库时,会导致 Git 仓库持续增大, Git Clone 的速度大大降低,进而导致软件部署,交付的时间变长,影响了研发上线的效率。
不仅如此,您可能还需要为安卓的开发者搭建 Gradle 仓库,Java 开发者搭建 Maven 私服,容器团队搭建 Docker 私服,各个私服独立维护,占用大量系统资源,维护成本呈几何指数增长。
JFrog Artifactory 能够解决这个问题,通过搭建 Artifactory,能够在内网建立统一全语言的私有制品仓库,支持 CocoaPod,Gradle,Maven,Docker 等等。程序员通过 Artifactory 可以实现全语言的依赖下载,并且可以将构建产出物上传到 Artifactory 进行管理。
下载 Artifactory
获得 Artifactory 的安装文件很简单,访问https://jfrog.com/download-artifactory-pro/, 然后在http://www.jfrogchina.com/artifactory/free-trial/ 申请免费试用版 License 即可。可以用 StandAlone 方案安装,无需配置数据库即可使用。也支持 RPM,Debian,Docker 的安装方式。
创建 CocoaPod 仓库
解压下载的安装包后,进入 bin 目录,执行 artifactory.sh文件,随后访问 localhost:8081即可进入 Artifactory 页面:
输入 License 信息,即可开始使用 Artifactory。创建仓库时,选择 CocoaPod:
在此,我们创建两个仓库,一个是 CocoaPod Local,目的是存储所有本地的CocoaPod 构建产出物,另一个是 CocoaPod Remote,能够作为外网 CocoaPod 源的本地代理,在内网提供服务。
在使用 CocoaPod 仓库之前,需要安装 cocoapod-art 插件:gem install cocoapod-art。安装完之后,选择 CocoaPod Remote 仓库,右上角点击 Set Me Up,会弹出如下对话框:
该对话框里会提示如何使用该仓库,包括如何安装 cocoapod-art 插件,如何在 pod 添加 Artifactory 作为源:
然后将Podfile 中添加该源作为 pod 的依赖解析源:
下载依赖,上传构建包到 Artifactory
完成之前步骤之后,再执行 pod install 的时候,可以看到依赖已经被缓存在远程仓库:
在打包 CocoaPod 项目时,我们执行 pod spec create jfrogapp,并且通过 JFrog 的Rest API 上传到 CocoaPod Local 仓库,供后面的测试,运维团队使用该构建包。
上传完成之后,可以看到构建的 tar 包已经被存储到 Artifactory 的 local 仓库,而不需要存储到 Git 仓库。
除了全语言的包管理支持,Artifactory 还支持构建包的元数据和漏洞扫描。通过元数据的能力,能够展示包相关的生命周期数据信息,例如需求 ID,和单元测试覆盖率,通过率等等指标。
总结
通过Artifactory CocoaPod 仓库的使用,能够快速在公司内网搭建一套 CocoaPod 私服,既可以代理外网依赖,也可以作为本地私服存储构建包,并且记录该构建包管理的需求 ID,单元测试,性能测试等结果,Artifactory 企业版也支持高可用架构的搭建,实现0宕机的私服服务,更重要的是您也可以将 Maven,Docker,NPM 等30多种语言包都存储在 Artifactory 进行全公司统一管理,标准化交付流水线,提高软件交付的速度。
试用 JFrog Artifactory 地址:
使用fastlane match自动化管理证书和描述文件
转载:https://juejin.cn/post/7121617118100979748
在我们进行团队开发的时候,避免不了证书和描述文件的管理,常规的有自动管理和手动管理两种方式。
1. 常规的管理方式
1.1 自动管理签名
需要在Xcode的 Targets->Signing & Capabilities
勾选 Automatically manage signing。用这种方式,所有的工作包括AppId、证书、描述文件(Provisioning Profile)的创建都由Xcode包办了,非常的方便。
这种方式对个人开发者非常友好,但是对团队开发来说有比较大的弊端,具体如下。
1.1.1 每个开发成员都有单独的证书和描述文件,会导致存在大量重复的文件,管理起来非常混乱
如图所示:
1.1.2 证书的创建是有上限的,可能会导致其他人无法创建证书。
1.1.3 每次添加新设备或证书过期时,都必须手动更新和下载最新的配置文件集
比如说,添加新设备后,如果描述文件配置里面没有勾选这个设备,这个设备是无法安装我们的应用的,所有每次都需要确保描述文件的Select All是否已勾选。
1.2 手动管理签名
首先,需要某个团队成员先在 Apple Developer 后台 分别创建开发环境和生产环境的证书和配置文件,然后将这些文件下载安装到本地。当其他人参与开发时,需要这个人将相关的文件导出给其他人。
然后需要在 Xcode上 取消勾选 Automatically manage signing
,同时设置对应的证书和描述文件(Provisioning Profile)。
这种方式的优点是所有开发人员都共用一份证书和描述文件,缺点也非常明显:每次证书过期或者添加新的设备后,都需要手动去更新,然后重新分发其他人,操作起来非常麻烦。
那么,有没有这么一个方案:在一个公共的地方存取这些证书和配置文件,自动化去处理整个流程呢?这就是我们今天要讲的 match 工具。
2. 使用 fastlane match 自动化管理证书和配置文件
match 是 fastlane 工具套装其中的一个工具,它是 codesigning.guide 概念的实现。 它提供了一种全新的管理证书的方式,使团队所有成员共享一份代码签名,以减免不必要的证书创建、配置文件失效等问题。
2.1 使用 match 有什么优势?
- 所有的团队成员共享同一份证书和配置文件,减少了管理和维护成本
- 简化请求证书,生成描述文件,注册设备等一系列繁杂工作
- 能自动识别已过期的证书和失效的描述文件,对这些文件进行重置
- 对新开发者极其友好,match使用git管理所有的证书和描述文件,所以只要新人拥有git的访问权限,安装了fastlane,就能快速同步现有的证书配置,远离证书配置的大坑
2.2 如何使用 match ?
下面我将详细介绍 match 的使用流程。
2.2.1 准备一个私有的 git仓库
创建一个私有git仓库来存储证书和描述文件。建议在git账号中配置好SSH Key,这样就可以省去身份校验这一步。至于如何配置SSH Key,请参考:使用 SSH 连接到 GitHub。
另外,当有多个App时,建议一个git分支对应一个App,这样,我们所有App的证书都在一个仓库里面,便于管理。
2.2.2 初始化 match
在终端定位到项目目录,注意先看项目目录下是否存在fastlane目录,如果没有的话,先执行 fastlane init
命令来初始化fastlane服务(后面会用到)。然后再执行 fastlane match init
命令,首先会提示让你选择存储方式,我们选git,然后再输入git仓库地址,最后会生成一个 Matchfile 的配置文件。接下来,我们修改一下 Matchfile。
git_url("git@github.com:YourUserName/certificates.git") # git仓库地址
storage_mode("git") # 存储方式
git_branch("app1") # git分支名称,暂时以app名称作为分支名
# 默认的Profile类型, 可以为: appstore, adhoc, enterprise or development
type("development")
# bundleId,可以填多个bundleId,如App内包含 extension
app_identifier(["tools.fastlane.app", "tools.fastlane.app2"])
ENV["MATCH_PASSWORD"] = "your match password" # 导出和打开 .p12文件的密码
# username("user@fastlane.tools") # Your Apple Developer Portal username
# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options
# The docs are available on https://docs.fastlane.tools/actions/match
复制代码
配置完 Matchfile 后,大部分教程都是让你通过以下三条命令来同步证书和配置文件。
fastlane match development
fastlane match adhoc
fastlane match appstore
复制代码
但是这种方式使用起来非常不方便,特别是用于自动化构建脚本,因为每次都要输入AppleId和密码;有的团队的做法是在Matchfile配置一个公用的AppleId,这样就不用每次输入账户和密码。
git_url("git@github.com:YourUserName/certificates.git") # git仓库地址
# 省略其他内容...
username("APPLE_ID") # 公用的AppleId
ENV["FASTLANE_PASSWORD"] = "AppleId密码"
复制代码
但是,还会遇到另外一个问题,需要进行双重验证,要求输入6位验证码,所以我个人更推荐用 App Store Connect API 的方式,下面我将对这种方式做详细介绍。
2.2.3 创建 App Store Connect API 秘钥
App Store Connect API 是官方提供的一套 REST API,可让您在 App Store Connect 中执行的操作自动化。它主要提供了以下功能(包含了证书和描述文件的管理):
为什么要使用 App Store Connect API?
因为如果按照常规的方式的话,需要先用账号密码登录,登录过程中还要做双重验证,非常的不方便。 你可能会说 fastlane 不是还有一个强大的 spaceship 工具么,但是它还是无法绕过双重验证这个流程。具体可以看下这个文章:Spaceship VS App Store Connect API,这里就不做详细介绍了。
而 App Store Connect API 是通过 JSON Web Tokens (JWT) 进行授权,无需登录开发者账号,也无需做双重验证,非常适合在脚本内做自动化操作。
fastlane 内部也集成了 App Store Connect API,它那边也是推荐使用 API key的方式进行身份验证,具体请参考文档:Using App Store Connect API。
创建 App Store Connect API 密钥
用有管理员权限的AppleID登录 AppStoreConnect 后台,选择 用户和访问->秘钥,点击添加按钮来生成API秘钥。
然后下载API秘钥(一个.p8文件),保存到项目的fastlane目录。注意:私钥只能下载一次,永远不会过期,保管好,如果丢失了,去App Store Connect后台撤销密钥,否则别人拿到也可以用。
2.2.4 创建自动化脚本
为了更加方便使用,我们通过 fastlane 来配置几个常用的命令,将以下内容添加到你的 fastlane目录下的 Fastfile 文件中:
# 定义一个全局变量api_key,下面都会要用到这个 api_key
# key_id 和 issuer_id 都可以在 AppStoreConnect后台 -> 用户和访问 -> 秘钥 这里找到
api_key = app_store_connect_api_key(
key_id: "D383SF739",
issuer_id: "6053b7fe-68a8-4acb-89be-165aa6465141",
key_filepath: "./AuthKey_D383SF739.p8", # 上面下载的p8文件路径
duration: 1200, # optional (maximum 1200)
in_house: false # optional but may be required if using match/sigh
)
desc "下载所有需要的证书和描述文件到本地,不会重新创建证书和描述文件(只读方式)"
lane :match_all do
match(api_key: api_key, type: "development", readonly: true)
match(api_key: api_key, type: "adhoc", readonly: true)
match(api_key: api_key, type: "appstore", readonly: true)
end
desc "同步证书,如果证书过期或新增了设备,会重新创建证书和描述文件"
desc "该方法仅限管理员使用,其他开发成员只需要使用 match_all 方法即可"
lane :force_match do
match(api_key: api_key, type: "development", force_for_new_devices: true)
match(api_key: api_key, type: "adhoc", force_for_new_devices: true)
match(api_key: api_key, type: "appstore")
end
desc "注册设备,并更新描述文件"
lane :sync_devices do
# devices.txt模板:
# http://devimages.apple.com/downloads/devices/Multiple-Upload-Samples.zip
register_devices(api_key: api_key, devices_file: "./devices.txt")
match(api_key: api_key, type: "development", force_for_new_devices: true)
match(api_key: api_key, type: "adhoc", force_for_new_devices: true)
end
# 构建测试包
lane :beta do
# 先同步adhoc证书和描述文件
match(api_key: api_key, type: "adhoc", readonly: true)
# 省略其他步骤...
build_app(scheme: "MyApp",
workspace: "Example.xcworkspace",
include_bitcode: true)
end
lane :release do
# 先同步appstore证书和描述文件
match(api_key: api_key, type: "appstore", readonly: true)
# 省略其他步骤...
build_app(scheme: "MyApp")
# 上传应用到AppStore
upload_to_app_store(
api_key: api_key,
force: true, # Skip HTMl report verification
skip_screenshots: true,
skip_metadata: true,
submit_for_review: false,
)
end
复制代码
通过上面这个模板我定义了以下几个常用的命令:
fastlane match_all
:下载所有需要的证书和描述文件到本地,不会重新创建证书和描述文件(只读方式)fastlane force_match
:强制同步证书和描述文件,如果证书过期或新增了设备,会重新创建证书和描述文件sync_devices
:注册设备,会同步更新描述文件,需要先在 devices.txt 文件录入新增的设备UDID。fastlane beta
:构建测试包,先通过 match 确保adhoc证书和描述文件都是最新且有效的fastlane release
:构建且上传到AppStore,先通过 match 确保 appstore证书和描述文件都是最新且有效的。
当我们有新同事入职,或者需要在新的电脑上配置开发证书和描述文件,我们仅仅只需要一条 fastlane match_all
命令即可。
3. 其他
3.1 如何撤销所有的证书和描述文件
很少会有这种需求,如果确实需要清空所有证书和描述文件的话,可以通过 fastlane match_nuke
工具来处理:
desc "清空所有的证书和描述文件,慎用"
lane :nuke_all do
match_nuke(api_key: api_key, type: "development")
match_nuke(api_key: api_key, type: "adhoc")
match_nuke(api_key: api_key, type: "appstore")
end
复制代码
注意:清空完所有的证书和描述文件后,已安装的测试包是无法使用的,谨慎使用。
3.2 如何查看设备的UDID
1. 通过Xcode来查看
先通过USB在电脑上连接iOS设备,然后在Xcode中打开菜单:Window -> Device and Simulators,上面显示的 Identifier 这一项就是我们所需要的设备UDID。
2. 通过第三方工具
如果当前无法使用Xcode来查看,如其他地区的同事,可以使用第三方工具。大部分提供应用分发的平台都支持获取UDID,如 Fir获取UDID、蒲公英-快速获取iOS设备的UDID。
3.3 如何判断某个测试包是否能在设备上运行
原理:描述文件里面包含了所有的支持安装的设备的UDID,所以我们只需要看描述文件里面是否包含该设备的UDID就行了。在我们构建IPA包时,里面会嵌入一个叫 embedded.mobileprovision 文件(其实就是描述文件),判断我们的设备UDID是否在包含这个文件中,就能判断是否能安装(当然这只是其中的一个条件,其他的没在本文范围内,不做过多介绍)。
但是,这个文件是无法直接打开查看的,因为它经过了特殊的编码,其实质是一个plist文件,我们可以通过以下方式来查看它:
1. 通过 security 命名解码查看
security cms -D -i embedded.mobileprovision > result.plist
open result.plist
复制代码
2. 使用预览插件 ProvisionQL 查看
可以通过 brew 来安装 ProvisionQL,安装命令为: brew install --cask provisionql
。 在文件扩展名为 .ipa
、 .xcarchive
或 .mobileprovision
上可通过空格键来快速预览。
4. 参考资料
Copyright © 2015 Powered by MWeb, 豫ICP备09002885号-5