Posted in

Go桌面应用如何通过苹果Mac App Store审核?附全套公证(Notarization)自动化脚本与拒审复盘清单

第一章:Go桌面应用上架Mac App Store的合规全景图

将Go编写的桌面应用提交至Mac App Store(MAS)并非仅需打包分发,而是一场涵盖代码签名、沙盒权限、隐私声明、二进制审查与元数据合规的系统性工程。Apple对MAS应用执行严格静态与动态分析,任何违反App Review Guidelines第2.4、2.5、5.1.1及6.3条款的行为均会导致拒绝。

沙盒与权限模型强制约束

Go应用必须运行在启用App Sandbox的macOS沙盒环境中。需在Xcode项目中启用com.apple.security.app-sandbox = true,并通过entitlements.plist显式声明所需权限,例如:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>com.apple.security.app-sandbox</key>
  <true/>
  <key>com.apple.security.files.user-selected.read-write</key>
  <true/>
  <key>com.apple.security.network.client</key>
  <true/>
</dict>
</plist>

未声明却调用os.OpenFile或net.Dial将触发沙盒违规崩溃。

Go二进制合规性要点

Go 1.21+默认启用-buildmode=pie(地址空间布局随机化),但MAS要求所有可执行文件为MH_EXECUTE类型且禁用DYLD_INSERT_LIBRARIES。构建时须显式指定:

CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-w -s -buildid=" -o MyApp.app/Contents/MacOS/MyApp .

-w -s移除调试符号与符号表,避免被拒;-buildid=清空构建ID以满足MAS重复性校验。

隐私清单与网络使用声明

自iOS 14/macOS 11起,所有访问用户数据(如文件选取器、剪贴板、网络)的应用必须在Info.plist中提供对应用途字符串:

权限类型 Info.plist键名 必填用途描述示例
文件系统访问 NSDocumentsFolderUsageDescription “用于保存您的项目文件”
网络连接 NSAppTransportSecurity 配置NSAllowsArbitraryLoads=false
剪贴板读取 NSPasteboardUsageDescription “用于粘贴配置内容到编辑器中”

未提供对应UsageDescription字段将导致审核失败,且无法通过TestFlight绕过。

第二章:Mac App Store审核核心规则深度解析与Go适配实践

2.1 沙盒(Sandbox)机制原理与Go二进制嵌入式资源合规改造

沙盒通过进程级隔离与资源约束(如 seccomp-bpf、capabilities、/proc 隐藏)限制二进制行为,防止嵌入式资源(如 //go:embed)被非法读取或反射调用。

数据同步机制

Go 1.16+ 的 embed.FS 在编译期将资源固化为只读字节切片,运行时无文件系统依赖:

//go:embed config/*.yaml
var configFS embed.FS

func LoadConfig() ([]byte, error) {
  return fs.ReadFile(configFS, "config/app.yaml") // 安全路径解析,不经过 OS 文件系统
}

→ fs.ReadFile 内部通过 embed.FS 的 Open() 实现常量索引查表,规避 openat(2) 系统调用,满足沙盒 syscalls=deny 策略。

合规改造关键项

  • ✅ 移除 os.Open / ioutil.ReadFile 动态路径访问
  • ✅ 禁用 unsafe 和 reflect 对嵌入数据的内存篡改
  • ❌ 禁止 CGO_ENABLED=1(破坏静态链接与沙盒完整性)
改造维度 合规方式 违规示例
资源加载 embed.FS + fs.ReadFile os.ReadFile("config.yaml")
权限模型 CAP_DROP=ALL CAP_SYS_ADMIN
graph TD
  A[源码含 //go:embed] --> B[编译器生成 embedFS 结构体]
  B --> C[链接进 .rodata 段]
  C --> D[沙盒中仅允许 read syscall]
  D --> E[无 open/exec/write 系统调用]

2.2 硬件权限声明(Entitlements)配置:从Info.plist到go build链的自动化注入

iOS/macOS 平台 Go 应用需声明硬件能力(如摄像头、位置),但 go build 原生不支持 entitlements 注入。需桥接 Xcode 构建体系。

Entitlements 文件结构示例

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>com.apple.developer.avfoundation.capture</key>
  <true/>
  <key>com.apple.developer.location-services</key>
  <true/>
</dict>
</plist>

该 plist 声明了 AVFoundation 捕获与定位服务权限;go build -ldflags="-H=ios" 生成 Mach-O 后,必须由 codesign --entitlements 显式绑定,否则系统拒绝访问硬件。

自动化注入流程

graph TD
  A[go build -o app] --> B
  B --> C[sign with entitlements.plist]
  C --> D[verify via codesign -d --entitlements :- app]

关键构建参数对照表

参数 作用 是否必需
-ldflags="-H=ios" 指定 iOS 目标平台 ✅
--entitlements=entitlements.plist 绑定权限声明 ✅
--deep 递归签名嵌套 bundle ⚠️(仅含 Framework 时)

2.3 隐私清单(Privacy Manifest)生成规范与Go应用敏感API调用静态扫描

iOS 18+ 强制要求所有上架App提交 PrivacyManifest.plist,明确声明数据收集行为。Go 应用虽不直接调用 UIKit,但若通过 CGO 调用系统 API(如 CLLocationManager、ASIdentifierManager),仍需合规声明。

敏感API识别规则

静态扫描需匹配以下模式:

  • C.CFUUIDCreateString → 设备标识符(device_id)
  • C.CFLocaleCopyPreferredLanguages → 用户语言偏好(app_usage)
  • C.CFNetworkCopySystemProxySettings → 网络配置(network_info)

自动生成流程

graph TD
    A[Go源码解析AST] --> B[识别CGO调用节点]
    B --> C[映射至隐私分类表]
    C --> D[生成PrivacyManifest.plist]

示例:设备标识符声明片段

<!-- PrivacyManifest.plist 中的条目 -->
<key>NSPrivacyAccessedAPITypes</key>
<array>
  <dict>
    <key>NSPrivacyAccessedAPIType</key>
    <string>NSPrivacyAccessedAPICategoryDeviceID</string>
    <key>NSPrivacyAccessedAPITypeReasons</key>
    <array>
      <string>35A3.1</string> <!-- 用于崩溃诊断 -->
    </array>
  </dict>
</array>

该段声明表明应用通过 CFUUIDCreateString 访问设备唯一标识,35A3.1 是 Apple 官方编码,对应“诊断与崩溃分析”用途,必须精确匹配 App Store Connect 中填写的用途描述。

2.4 无签名依赖与动态库隔离:使用go build -buildmode=pie + strip + codesign全流程验证

现代 macOS 应用分发要求二进制具备 代码签名完整性 与 地址空间随机化(ASLR)兼容性。纯静态链接的 Go 程序默认不满足 hardened runtime 的 library validation 策略,因其隐式依赖系统 dylib(如 libSystem.B.dylib)未显式签名且未隔离。

PIE 构建:启用 ASLR 基础支持

go build -buildmode=pie -o app main.go

-buildmode=pie 强制生成位置无关可执行文件,使加载基址随机化;Go 1.19+ 默认启用,但显式声明可规避旧版构建缓存风险。

符号剥离与签名准备

strip -x -S app  # 移除调试符号与本地符号,减小体积并降低符号泄露风险

多阶段签名验证流程

步骤 命令 验证目标
签名 codesign -s "Developer ID Application: XXX" --entitlements entitlements.plist --deep --force app 绑定所有嵌套 dylib 及资源
验证 codesign --verify --verbose=4 app 检查签名链与库签名一致性
运行时检查 spctl --assess --type execute app 确认 hardened runtime 兼容性
graph TD
    A[源码] --> B[go build -buildmode=pie]
    B --> C[strip -x -S]
    C --> D[codesign --deep]
    D --> E[spctl --assess]

2.5 审核沙箱行为模拟:基于macOS Ventura+系统构建可复现的自动化测试套件

macOS Ventura 引入了更严格的 App Sandbox 运行时策略,需通过 xcodebuild + xctest 构建可审计的行为基线。

测试环境初始化

# 启用沙箱日志并捕获系统调用
sudo sysctl -w kern.maxfiles=1048576
log stream --predicate 'subsystem == "com.apple.sandbox"' --info --debug

该命令启用内核级沙箱事件流监听,--predicate 精确过滤 sandbox 子系统日志,避免噪声干扰;--info --debug 确保捕获 deny 与 allow 两类决策记录。

自动化验证流程

graph TD
    A[启动沙箱化测试App] --> B[注入预设IO/Network/FS操作]
    B --> C[捕获sandboxd日志与syscalls]
    C --> D[比对预期策略白名单]
    D --> E[生成JSON格式合规报告]

关键配置项对照表

配置项 Ventura 默认值 推荐测试值 作用
com.apple.security.app-sandbox YES YES 强制启用沙箱
com.apple.security.files.user-selected.read-write NO YES 模拟用户授权文件访问
com.apple.security.network.client NO YES 启用出站网络能力

使用 codesign --entitlements 注入定制 entitlements.plist 实现策略精准控制。

第三章:公证(Notarization)全链路打通与Go构建流水线集成

3.1 Apple Developer证书与Provisioning Profile的Go友好化管理策略

核心抽象:证书与描述文件即资源对象

在 Go 中建模 Certificate 和 ProvisioningProfile 为可序列化结构体,支持 YAML/JSON 配置驱动:

type Certificate struct {
    ID        string `yaml:"id"`
    Path      string `yaml:"path"`
    ExpiresAt time.Time `yaml:"expires_at"`
    IsValid   bool `yaml:"is_valid"`
}

type ProvisioningProfile struct {
    UUID         string   `yaml:"uuid"`
    Name         string   `yaml:"name"`
    Type         string   `yaml:"type"` // "development", "distribution"
    Certificates []string `yaml:"certificates"` // IDs of trusted certs
}

逻辑分析:Certificates 字段存储证书 ID 引用而非嵌套对象,解耦签名验证与证书生命周期管理;IsValid 为运行时计算字段(非持久化),避免状态不一致。Type 字符串枚举便于策略路由。

自动化同步机制

通过 altool 或 Apple Developer API 封装同步器,支持增量拉取与本地缓存校验:

功能 工具链支持 Go SDK 封装点
证书导出 security find-certificate certutil.Export()
Profile 解析 security cms -D profile.Parse()
过期自动告警 — validator.CheckExpiry()

签名上下文构建流程

graph TD
    A[Load config.yaml] --> B{Validate cert expiry}
    B -->|OK| C[Fetch latest profile via API]
    B -->|Expired| D[Trigger cert renewal]
    C --> E[Bind cert + profile → SigningContext]
    E --> F[Inject into xcbuild]

3.2 xcrun altool → notarytool 迁移实践:兼容性封装与失败诊断日志增强

兼容性封装脚本设计

为平滑过渡,封装 notarytool 调用逻辑,模拟 altool 接口语义:

#!/bin/bash
# 封装脚本:notary-wrapper.sh —— 接收 altool 风格参数,转译为 notarytool 命令
if [[ "$1" == "--notarize-app" ]]; then
  xcrun notarytool submit "$2" \
    --key-id "$ALTOOL_KEY_ID" \
    --issuer "$ALTOOL_ISSUER" \
    --password "$ALTOOL_APP_SPECIFIC_PASSWORD" \
    --wait
fi

此脚本将 --notarize-app <path> 映射为 notarytool submit,关键参数重命名:ALTOOL_* 环境变量适配 Apple ID 密钥链凭证体系;--wait 替代 altool 的轮询逻辑,内置状态同步与超时控制。

失败诊断日志增强策略

notarytool 默认日志精简,需显式启用详细输出:

日志级别 启用方式 输出价值
基础 --verbose 显示请求/响应头、证书链验证
深度 --verbose --log-format json 结构化错误码(如 err-code: 401, notary-status: invalid-bundle)

关键差异流程对比

graph TD
  A[提交归档] --> B{altool}
  A --> C{notarytool}
  B --> D[轮询 API + XML 解析]
  C --> E[单次 submit + 内置 wait + JSON 状态机]
  E --> F[自动重试 + 更细粒度 error.code]

3.3 Go二进制+Bundle签名+公证+ Stapling 四阶原子化脚本设计

为确保 macOS 分发链的完整性与自动化,需将四个强依赖步骤封装为不可分割的原子操作。

核心流程

#!/bin/bash
# 原子化四阶签名流水线(简化版)
go build -o MyApp.app/Contents/MacOS/myapp . && \
codesign --force --options runtime --entitlements entitlements.plist \
  --sign "$IDENTITY" MyApp.app && \
xcrun notarytool submit MyApp.app --keychain-profile "AC_PASSWORD" --wait && \
xcrun stapler staple MyApp.app

逻辑说明:go build 生成原生二进制;codesign 对 Bundle 整体签名(含 --runtime 启用硬化运行时);notarytool 异步提交公证并阻塞等待完成;stapler 将公证票据嵌入 Bundle。所有步骤失败则整条流水线中止。

阶段依赖关系

graph TD
    A[Go构建] --> B[Bundle签名]
    B --> C[Apple公证]
    C --> D[Stapling嵌入]

关键参数对照表

步骤 参数 作用
codesign --options runtime 启用 Library Validation + Hardened Runtime
notarytool --keychain-profile 复用钥匙串中预存的 API 凭据,避免明文密钥
  • 所有命令均设置 set -e 保障失败即退出
  • 输出路径统一采用 .app Bundle 结构,符合 Gatekeeper 要求

第四章:拒审高频场景复盘与Go专属修复方案库

4.1 “Missing Privacy Manifest”拒审根因分析与go generate驱动的自动补全工具

App Store 审核中,“Missing Privacy Manifest”错误源于 iOS 18+ 强制要求所有动态库(.dylib)、框架(.framework)及 Swift Package 依赖必须附带 PrivacyInfo.xcprivacy 文件,声明所访问的敏感数据类型(如照片、位置等)。缺失即触发硬性拒审。

根本症结

  • Xcode 不自动为第三方二进制依赖生成隐私清单
  • 手动维护易遗漏、难同步、违反 CI/CD 原则

自动化解法:go generate 驱动工作流

//go:generate go run privacygen/main.go --input=./Frameworks --output=./PrivacyInfo.xcprivacy
package main

import "flag"
func main() {
    input := flag.String("input", "", "路径:含 .framework 或 .dylib 的目录")
    output := flag.String("output", "", "输出路径:PrivacyInfo.xcprivacy")
    flag.Parse()
    // 扫描二进制 Mach-O header,提取 Info.plist 中 NSPrivacyAccessedAPITypes(若存在)
    // 否则按 bundle ID 查询开源库已知权限(如 SDWebImage → PHPhotoLibrary)
}

该脚本解析 Mach-O 与 Info.plist,结合社区维护的权限映射表(见下表),生成合规 PrivacyInfo.xcprivacy。

库名 访问 API 类型 是否需用户授权
Alamofire NSPrivacyAccessedAPITypesNetwork 否
Photos.framework NSPrivacyAccessedAPITypesPhotos 是
graph TD
    A[扫描 Frameworks/] --> B{是否含 Info.plist?}
    B -->|是| C[提取 NSPrivacyAccessedAPITypes]
    B -->|否| D[查证权限映射表]
    C & D --> E[合并去重 → XML 生成]
    E --> F[写入 PrivacyInfo.xcprivacy]

4.2 “Crash on Launch”问题定位:Go runtime初始化时序与macOS dyld环境变量协同调试

当Go程序在macOS上启动即崩溃(Crash on Launch),常因dyld动态链接器与Go runtime初始化存在竞态——尤其在DYLD_INSERT_LIBRARIES或DYLD_FORCE_FLAT_NAMESPACE干扰下。

关键调试信号链

  • SIGTRAP 在 _rt0_amd64_darwin 入口处被捕获
  • runtime·checkgoarm 调用前,dyld尚未完成__DATA,__mod_init_func段的函数注册
  • Go runtime.init() 依赖的全局符号(如_os_args)被dyld延迟绑定

复现与验证代码

# 启用dyld详细日志,观察符号解析顺序
export DYLD_PRINT_LIBRARIES=1
export DYLD_PRINT_INITIALIZERS=1
./mygoapp

此命令使dyld输出每个库加载及初始化函数调用栈,可确认libsystem_c.dylib是否早于libgo.dylib完成初始化,进而判断runtime.osinit()能否安全访问sysctlbyname。

环境变量影响对照表

环境变量 是否触发崩溃 原因
DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib ✅ 高概率 替换malloc导致runtime.mallocgc未就绪
DYLD_NO_FIX_PREBINDING=1 ❌ 无影响 仅影响prebinding,不干扰runtime时序
graph TD
    A[dyld_load_images] --> B[执行__mod_init_func]
    B --> C[Go runtime·rt0_go]
    C --> D[runtime·checkgoarm]
    D --> E[runtime·osinit]
    E -.->|依赖未就绪| F[crash: EXC_BAD_ACCESS]

4.3 “Hardcoded Paths / Unsafe File Access”检测绕过:基于go:embed + CFBundleResources的安全路径抽象层

传统硬编码路径(如 /var/tmp/config.json)易被 SAST 工具标记为 Hardcoded Paths / Unsafe File Access。现代 macOS Go 应用可利用 go:embed 预编译资源,并通过 CFBundleResources 动态解析 bundle 内部路径,实现静态分析不可见的路径抽象。

资源嵌入与 Bundle 路径桥接

//go:embed assets/*
var assetFS embed.FS

func SafeAssetPath(name string) string {
    bundle := C.CFBundleGetMainBundle()
    cfPath := C.CFBundleCopyResourceURL(bundle,
        C.CFStringCreateWithCString(C.kCFAllocatorDefault, C.CString(name), C.kCFStringEncodingUTF8),
        C.CFStringCreateWithCString(C.kCFAllocatorDefault, C.CString("json"), C.kCFStringEncodingUTF8),
        nil)
    defer C.CFRelease(cfPath)
    return C.GoString(C.CFURLCopyFileSystemPath(cfPath, C.kCFURLPOSIXPathStyle))
}

该函数绕过静态路径扫描:go:embed 将文件编译进二进制,不暴露字符串字面量;CFBundleCopyResourceURL 在运行时从 bundle 中动态查找,路径由 macOS 框架解析,非硬编码。

安全优势对比

方式 静态检测可见性 运行时路径可控性 Bundle 兼容性
/usr/local/conf.json ✅(高危告警) ❌(依赖系统布局) ❌
os.Executable() + "/../Resources" ⚠️(字符串拼接可检) ✅ ✅
CFBundleCopyResourceURL + go:embed ❌(无路径字面量) ✅✅(沙盒安全) ✅✅
graph TD
    A[go:embed assets/*] --> B[编译期固化资源]
    C[CFBundleGetMainBundle] --> D[运行时定位 Bundle]
    B & D --> E[CFBundleCopyResourceURL]
    E --> F[返回 sandbox-safe NSURL]

4.4 “Unexpected Network Activity”误判应对:Go net/http.DefaultTransport定制与ATS合规性白名单配置

iOS App Store审核中,“Unexpected Network Activity”警告常因未声明的明文 HTTP 域名触发。根本解法是双轨并行:服务端强制 HTTPS + 客户端精准白名单。

ATS 白名单配置(Info.plist)

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>api.example.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
      <false/>
      <key>NSTemporaryExceptionRequiresForwardSecrecy</key>
      <true/>
    </dict>
  </dict>
</dict>

此配置仅豁免 api.example.com 及其子域,禁用不安全 HTTP,强制前向保密(FS),符合 Apple 最新 ATS 要求。

Go 客户端 Transport 定制

transport := &http.Transport{
  Proxy: http.ProxyFromEnvironment,
  TLSClientConfig: &tls.Config{
    MinVersion: tls.VersionTLS12,
  },
  // 复用连接、超时控制等生产必需项
}
http.DefaultClient.Transport = transport

强制 TLS 1.2+ 避免握手失败;结合 DefaultClient 全局生效,确保所有 http.Get 等调用均走合规通道。

维度 默认 DefaultTransport 定制后 Transport
TLS 最低版本 不限制(可能降级) 强制 TLS 1.2+
连接复用 启用但无空闲超时 可配 IdleConnTimeout
DNS 缓存 无 支持 DialContext 自定义

graph TD A[发起 HTTP 请求] –> B{DefaultTransport 是否已定制?} B –>|否| C[可能使用 TLS 1.0/1.1 → ATS 拒绝] B –>|是| D[强制 TLS 1.2+ + 白名单域名 → 通过审核]

第五章:通往App Store成功的Go桌面生态演进路径

Go语言长期以服务端与CLI工具见长,但其在桌面应用与App Store分发生态中的突破正悄然发生。2023年Q4,由开源团队Tauri Labs主导的Go-native桌面框架Wails v2.10正式支持macOS签名与公证(Notarization)全流程自动化,成为首个可稳定产出符合Apple Developer Program全部上架要求的Go桌面框架。同一时期,知名开源项目Sourcetrail Go Edition(代码导航工具)完成重构,从Electron迁移至Wails+Go后,安装包体积从128MB压缩至24MB,启动耗时降低67%,并成功通过App Store审核,上架周期仅用5个工作日。

构建可签名的macOS二进制流程

关键在于绕过Go默认静态链接对libSystem的隐式依赖。Wails v2引入-ldflags="-s -w -buildmode=c-archive"配合Xcode工程模板,生成符合Hardened Runtime要求的.app bundle。以下为CI/CD中实际使用的签名脚本片段:

# 在GitHub Actions macOS-14 runner中执行
codesign --force --deep --sign "Developer ID Application: Acme Inc (ABC123XYZ)" \
  --entitlements entitlements.plist \
  --options runtime \
  ./dist/myapp.app

xcrun notarytool submit ./dist/myapp.app \
  --key-id "ACME_NOTARY_KEY" \
  --issuer "ACME Dev Team" \
  --wait

App Store合规性检查清单

检查项 Go实现方式 是否必需 验证工具
基于签名的硬编码运行时 使用runtime.LockOSThread() + Mach-O LC_LOAD_DYLIB白名单校验 是 codesign -dv --verbose=4 ./myapp.app
网络权限声明 在Info.plist中显式配置NSAppTransportSecurity及NSLocalNetworkUsageDescription 是 Xcode Archive验证器
无私有API调用 通过go tool nm ./main | grep -E "_NS.*Private|_objc_.*_internal"扫描符号表 是 自定义CI检查步骤

真实案例:GoDesk笔记应用上架路径

该应用采用Go+Wails构建,核心功能包括Markdown实时渲染(使用blackfriday替代方案goldmark)、本地SQLite加密存储(sqlcipher-go)、以及Spotlight集成(通过osascript桥接)。开发团队在提交前执行三阶段验证:① 使用spctl --assess --type execute ./GoDesk.app确认Gatekeeper兼容性;② 运行App Store Connect预检工具itms transporter;③ 在真机M1 Mac上测试Hardened Runtime下的辅助功能(VoiceOver)无障碍标签渲染。最终版本v1.3.0于2024年3月12日上线,首周下载量达17,400次,用户会话平均时长提升至14分22秒。

跨架构二进制统一策略

为同时支持Intel与Apple Silicon,项目采用Go 1.21+的GOOS=darwin GOARCH=arm64与GOARCH=amd64双编译,再通过lipo合并为通用二进制:

go build -o build/app-arm64 -ldflags="-s -w" -buildmode=exe .
go build -o build/app-amd64 -ldflags="-s -w" -buildmode=exe .
lipo -create build/app-arm64 build/app-amd64 -output build/GoDesk

生态协同演进趋势

随着golang.org/x/exp/shiny实验性GUI模块逐步收敛,以及fyne框架对macOS原生菜单栏(NSStatusBar)与通知中心(UNUserNotificationCenter)的深度封装,Go桌面应用已能无缝接入系统级能力。2024年Q2,Apple Developer Relations团队在WWDC附属技术会议中明确表示:“支持纯Go构建的App Store应用,只要满足App Review Guidelines第2.4、4.2、5.1条,将获得与Swift应用同等审核优先级。”

flowchart LR
    A[Go源码] --> B{构建目标}
    B --> C[macOS arm64]
    B --> D[macOS amd64]
    C --> E[lipo合并]
    D --> E
    E --> F[嵌入entitlements.plist]
    F --> G[codesign签名]
    G --> H[notarytool公证]
    H --> I[App Store Connect上传]
    I --> J[自动审核队列]

传播技术价值,连接开发者与最佳实践。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注