Cocoa 文档注释与生成工具使用 您所在的位置:网站首页 文档生成工具怎么用 Cocoa 文档注释与生成工具使用

Cocoa 文档注释与生成工具使用

2023-07-06 12:27| 来源: 网络整理| 查看: 265

2020-06-18 土土Edmond木

Cocoa 文档注释与生成工具使用 - Jazzy + SourceKitten

Cocoa Documentation By Jazzy & SourceKitten.png

我们在前文 《Cocoa 代码注释与文档生成》 中详细介绍了如何为 Swift & ObjC 的代码编写符合规范的注释,以及使用 Jazzy 来生成项目文档。 今天我们来尝试一下,如何一键生成多个私有库的文档,并将其部署到 Github page 或者 Gitlab page 上。

本文知识目录

利用 Jazzy + SourceKitten 生成多依赖库的在线文档.png

背景

随着公司项目的迭代,一般都会沉淀出多个私有库。如果这些私有库可以够提供统一的文档查询和预览服务,那将有助于团队中的新成员快速了解业务。

作者所在的公司就维护者 20 多个的私有库,同时这些项目的代码注释完整度不一,注释的内容也参差不齐。如果我们可以通过这个在线文档,不仅可以提供快速的 API 查阅能力,也可以更好的监督和规范项目。

如何一键生成多依赖库的文档

我们先来简单分析一下要实现这个想法 💡需要做哪些事情。

现有的文档生成工具都是基于单个项目,而我们想要的是多依赖库的集合文档。那么就需要有一个索引页将各个依赖库串联起来,能够通过索引来访问它。 由于公司的项目是包含了 Swift & ObjC 混编的庞大项目,所维护的私有仓库不仅包含了纯 ObjC 和纯 Swift 实现的,还包括了 Swift & ObjC 混编代码的依赖库。所以需要支持这个三种场景。 生成的文档都是静态页面,需要将这些页面托管在静态资源服务上,关于这点 Github Page 和 Gitlab Page 就能解决。 毕竟项目是不断的迭代演进的,那如何在一定时机的情况下自动触发或者手动触发更新文档,也是十分重要的一件事情。

明确了我们要解决的问题,剩下的事情就简单了。

生成工具

就直接使用 shell 将上面的步骤串联起来,如果大家熟悉其他语言也可以,文档生成工具就是 Jazzy + SourceKitten。Jazzy 之前介绍过了,一起看看 SourceKitten 吧:

SourceKitten

An adorable little framework and command line tool for interacting with SourceKit.

Sourcekitten 是基于 Apple 的 SourceKit 封装的命令行工具,SourceKitten 链接并与 sourcekitd.framework 通信以解析 Swift AST,提取 Swift 或 ObjC 项目的注释文档,获取 Swift 文件的语法数据等等。

SourceKit

SourceKit is a framework for supporting IDE features like indexing, syntax-coloring, code-completion, etc. In general it provides the infrastructure that an IDE needs for excellent language support.

文档索引页的生成

为了整体的样式统一,我们的索引页采用与 Jazzy 所生成的文档相同的 CSS 样式。由于 Jazzy 支持切换生成文档的主题,这里我们使用默认主题。

当我们访问静态网站时,入口一般都指向一个名为 index.html 的页面。 Jazzy 生成的入口也是 index.html 。

我们要做的就是往 index.html 内添加含对应的标签,并将标签链接指向各个依赖库的文档地址就可以了。

索引页

下面是我们需要修改的代码,完整的 index.html 模版可访问 Jazzy-template。

1 2 ... 3 4 5 6 业务库 7 8 9 10 11 12 13 token-business 14 15 16 17 18 19 基础库 20 21 22 23 24 25 token-base 26 27 28 29 30 31

要修改的就是上面的 token-* 元素,这里留的默认 token 是为了方便替换。

业务库

由于业务库逻辑一般会比较多,如果和基础库文档放一起,可能会导致生成文档的太大,Github Page 无法正常解析。因此,需要单独的文档仓库来存放文档。

基础库

基础库生成的文档会统一放到项目的 docs 目录下,同时 token-base 标签的地址最后会指向 docs/$lib_name/index.html 目录。

目前的结构是这样的:

文档结构

我们先来看一下以 Alamofire 项目生成的 docs 文档目录结构:

WechatIMG13.png

第一层包含了 Classes、Enums、Extensions、Protocols、Structs 等分类和对应的 index.html 索引文件。

第二层为具体到的每个 Class、Enum 或其他数据结构的 HTML 页面。如果该结构还存在嵌套的内部数据类型,会以递归的方式呈现。

整个 docs 的基础结构特别简单:

jazzy document.png

我们要做的就是复制上面的文件,以及修改的 index.html 就可以。

多依赖库的文档生成

对于 iOS 项目的依赖库管理标配为 CocoaPods (后面简称 Pod) ,它将所有的依赖库源码统一存放在项目的 /Pods 目录下。我们要做的就是遍历 /Pods 目录,逐一生成文档并将其输出到一个指定目录就可以了。

想法是美好的,现实是残酷的。在实际操作起来发现并没有那么简单。让我们开启踩坑之旅吧!

Swift 依赖库的文档生成

之前在 《Cocoa 代码注释与文档生成》 中介绍的 Swift 的文档生成都是基于该项目的 project 工程或者是 SwiftPM 配置来完成。好在 Pod 也为我们生成对应的 project,我们仅需通过 --build-tool-arguments 来指定 project 和 target 就可以了。

从零开始,我们先新建一个 Demo.xcodeproj 并配置如下 Podfile:

1target 'Demo' do 2 pod 'SnapKit' 3 pod 'AFNetworking' 4end

调用 Jazzy 生成 Swift 库 SnapKit 的文档:

1$ bundle exec jazzy -o docs/SnapKit \ 2 --build-tool-arguments -project,Pods/Pods.xcodeproj,-target,SnapKit

通过 -o 将结果输出到 docs/SnapKit 目录下,执行后输出结果如下:

1Running xcodebuild 2Parsing Constraint.swift (1/34) 3... 4Parsing UILayoutSupport+Extensions.swift (34/34) 5`ConstraintLayoutSupport` has no USR. ... 69% documentation coverage with 239 undocumented symbols 7included 264 public or open symbols 8skipped 81 private, fileprivate, or internal symbols (use `--min-acl` to specify a different minimum ACL) 9building site 10building search index 11jam out ♪♫ to your fresh new docs in `docs/SnapKit`

可以看到 Jazzy 会遍历项目下的每个 swift 文件,对于项目中未引用的代码也会有提示。最后会输出代码的注释覆盖率,SnapKit 的覆盖率为 9%,有 239 个未注释的符号或变量。

指定文档的范围

需要注意的是,Jazzy 可以通过 --min-acl 来控制输出文档的范围。

对于 Swift 项目,默认仅生成声明为 public 和 open 的类、属性和方法等,如果想要输出私有变量的注释,还可以设置为 internal、 fileprivate 或 private。

对于 ObjC 项目,Jazzy 仅会生成在 --umbrella-header 所指定的 header 文件中所引用的 .h 文件。

ObjC 依赖库的文档生成

相比 Swift,Objc 的依赖库需要多处理 umbrella header 的问题。先看 AFNetworking 的文档生成命令:

1$ lib_name=AFNetworking 2lib_path=$(pwd)/Pods/$lib_name 3umbrella_header="$lib_path/$lib_name/$lib_name-umbrella.h" 4sdk_path=`xcrun --show-sdk-path --sdk iphonesimulator` 5 6bundle exec jazzy -o docs/$lib_name \ 7--objc \ 8--sdk iphoneos \ 9--build-tool-arguments \ 10--objc,$umbrella_header,--,-x,objective-c,-isysroot,$sdk_path,-I,$lib_path

第一个是需要指定 --objc,因为 Jazzy 默认解析 Swift 项目。

再来看 --build-tool-arguments 后跟的几个参数:

–objc :这里的 --objc 是通知 SourceKitten 我要解析的是 Objc 的头文件,后面紧跟的为依赖库的 umbrella header –:作为分割符,表示之后的参数会转发到 xcodebuild 或 swift build -x objective:通知 xcodebuild 或 swift build 我要编译 ObjC 啦 -isysroot:指定所编译的 sdk,这里我们使用模拟器的 sdk -I $lib_path:指定 include 的搜索路径 获取 umbrella header

在 ObjC 中引用代码是需要通过 #import 来完成的,而对于 ObjC 的 framework 而言,我们可以通过引入 umbrella header 来引入该 framework 暴露出来的全部 public header 文件。因此,可以理解为 umbrella header 是 ObjC framework 的 master header。具体可以看:讨论。

这一点需要感谢 Pod,它为我们的依赖库统一生成了 A-umberlla.h 文件,存放在 Target Support Files/A/A-umberlla.h 。

在此之前很多依赖库的 umbrella header 并不是很规范。经常会有一些文件是 public 状态,却未添加到 umbrella header 中,导致无法直接通过 umbrella header 来完成引用。包括很多公司维护的私有库也会经常忘记更新 umbrella header 的情况,好在 Pod 帮我们自动生成了。

复制 umbrella header

细心的同学从 AFNetworking 的文档生成命令中能发现,AFNetworking-umbrella.h 的位置是在源码的文件夹下。如果直接指定为 Target Support Files 下的 umbrella header 文件是无法生成文档的。我们需要把它复制到源代码在同层目录下。

那么问题来了:如何正确的获取源码所在目录。

首先想到的是和通过 .podspec 文件就能准确拿到 Source 目录。不过比较难实现,我们只能拿到的是 Local Podspecs 下的 .podspec 文件,否则需要在 pod install 时才能获取到。但是这么做需要修改 Podfile 也比较麻烦。

选择简单粗暴的方式,直接列出可能出现的 Source 路径:

1# /A/Classes/... 2# /A/src/a/... 3# /A/A/Classe/... 4# /A/A/Classes/... 5# /A/A/Source/.. 6# /A/A/Sources/.. 7# /A/Source/A/... 8# /A/Sources/A/... 9# /A/Source/... 10# /A/A/.. 11# /A/... 12# libextobjc/extobjc

有用 Classes、 Source、Sources、src 等等,情况五花八门,逐一匹配就可以了。

这么做是可以覆盖大部分的情况,但是仍然发现部分私有库生成的文档缺失甚至是空的。最终发现的问题是:clang 没有递归处理多级目录的文件,这里应该是参数没有正确设置,查看了 Clang 手册 感觉就是 -I 参数,不过也没有生效,有了解的同学求指点。

咋办,先暴力解决:

1find $lib_path -type f ! -regex '*.\(h\|m\|swift\)' \ 2 ! -name '*.json' \ 3 ! -name '*.pdf' \ 4 -exec mv -i {} $lib_path \;

将子目录下文件全部移到 framework 源码目录下,再通过 Jazzy 来生成文档,算是暂时解决问题了。

然而 AFNetworking 的文档依旧不是完整的,不过属于另外一种情况。目录如下,大家可以 🤔 一下:

WechatIMG12.png

Swift & ObjC 混编依赖库的文档生成

对 Swift & ObjC 混编的依赖库本身是不提倡的,虽然在实际开发过程中无法避免。

为了测试混编库的文档生成,这里新建一个 Pod 库:Mixin,添加了 MixinSwift 和 MixinObjC 两个类:

MixinSwift 1/// Test Swfit Class import Objective-C's property 2public class MixinSwift: NSObject { 3 4 /// say hello from Swift 5 @objc public static let sayHi: String = "Hi, I'm from Swift" 6 7 /// call Objective-C say Hi 8 @objc public class func callObjC() { 9 print("hello from MixinObjc: \(MixinObjC.sayHi)") 10 } 11} MixinObjC 1#import "MixinObjC.h" 2#import 3 4@implementation MixinObjC 5 6 + (NSString *)sayHi 7{ 8 return @"Hi, I'm from Objective-C"; 9} 10 11+ (void)callSwift 12{ 13 NSLog(@"hello from MixinSwift: %@", MixinSwift.sayHi); 14} 15 16@end

由于 Jazzy 无法直接生成混编项目的文档,这里需要通过 SourceKitten 分别将 Swift 和 ObjC 的代码注释转成 json 的中间格式,才能生存完整的文档。生成命令如下:

1lib_name=Mixin 2output="public/docs/$lib_name" 3swift_doc="$output/$lib_name-swift-doc.json" 4objc_doc="$output/$lib_name-objc-doc.json" 5 6lib_path=$(pwd)/Pods/$lib_name/$lib_name/Classes 7umbrella_header="$lib_path/$lib_name-umbrella.h" 8sdk_path=`xcrun --show-sdk-path --sdk iphonesimulator` 9 10sourcekitten doc --objc $umbrella_header \ 11 -- -x objective-c -isysroot $sdk_path \ 12 -I $lib_path \ 13 -fmodules > $objc_doc 14 15sourcekitten doc -- -project Pods/Pods.xcodeproj -target Mixin > $swift_doc 16 17jazzy -o $output --sourcekitten-sourcefile $swift_doc,$objc_doc

文档如下:

WechatIMG13.png

依赖库类型判断

由于不同类型的依赖库,其生成文档的脚本有所不同,我们还需要判断每个依赖库类型,是纯 ObjC、纯 Swift 还是混编类型。解决方式就是对 Source 目录下的文件类型进行 count 以判断依赖库类型:

1swift_count=`find $lib_path -maxdepth 6 -type f -name '*.swift' | wc -l` 2objc_count=`find $lib_path -maxdepth 6 -type f -name '*.m' | wc -l` 3 4# file state, 0: only objc, 1: only swift, 2: swift & objc 5lib_state=0 6if [[ $swift_count -ge 1 && $objc_count -ge 1 ]]; then 7 lib_state=2 8elif [[ $swift_count -eq 0 && $objc_count -ge 1 ]]; then 9 lib_state=0 10elif [[ $swift_count -ge 1 && $objc_count -eq 0 ]]; then 11 lib_state=1 12fi 静态文档的部署

我们使用是 Github Page 来进行文档部署,特别简单仅需在 repo 的设置页指定文档类型就可以了。剩下的就是提交代码,Git 会自动触发编译。

WechatIMG14.png

更多介绍请查看 Github Page 说明。

最后,完整 Demo 的托管地址为:Cocoa-Documentation-Example。

Git page 文档地址:https://looseyi.github.io/Cocoa-Documentation-Example,这个地址是 Github 自动生成的。效果如下:

WechatIMG15.png

One More Thing …

尽管我们当前的方案可以正确的生成文档,但是其实还可以更进一步。

当前的文档生成是基于 project 的方式,而我们完全可以针对每一个文件生成一份 json 数据,最后在把它们全部粘一起。命令的话 SourceKitten 都准备好了:

Swift 文件解析

1$ sourcekitten doc --single-file $input_file -- -j4 $input_file >> $temp_outout

ObjC .h 文件解析

1$ sourcekitten doc --objc \ 2 --single-file $input_file \ 3 -- -x objective-c \ 4 -isysroot $sdk_path \ 5 -I $lib_path -fmodules >> $temp_outout

通过这种方式,既不不需要配置 project 判断依赖库类型,也省去了查找找 umbrella header 的麻烦。

完整脚本传送门:docs_deploy.sh

总结 多依赖库的文档生成还是比较简单的,感觉最难的还是读懂 Jazzy + SourceKitten 的文档和参数的配置。 思路是充分利用了 CocoaPods 为我们搭好的环境,在其之上就可以轻松生成文档,主题可定制哦。 倒腾过个人博客的同学,对于 Github Page 和文档的部署应该很熟悉,免费的 Github 资源还是要充分利用的。 知识点问题梳理

这里罗列了四个问题用来考察你是否已经掌握了这篇文章,如果没有建议你加入收藏再次阅读:

Jazzy 对 API 的控制范围有几种选择? 对于文中所采用的判断依赖库语言类型的方法是什么,还有更好的方式吗? ObjC 的 umbrella header 是从哪里获取的? 扩展: SourceKitten 所生成的 JSON 结构包括哪些字段? iOS' Document SourceKit Jazzy gitpage


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有