为什么要做CLI

指令行东西CLI(command-line interface) 是开发者必不行少的东西之一,编写指令行东西来处理一些作业上的事情也是开发者必备的技术。关于一些重复性的作业,运用指令行脚本能够将使命自动化,这将极大的提升咱们的作业功率。一起也能防止由于人为的要素导致过错。比方批量处理一些文件、表格等,在APP开发中继续集成的概念也是建立在一些列的自动化指令行脚本的根底之上。

How to create Swift CLI

为什么运用Swift

指令行的开发言语有很多,比方最根本的shell脚本,还有常见的PythonGoRuby等。而iOS开发者作业中最常用也是最了解的便是Objective-c和Swift言语,所以这会带来几个个问题。

  • App开发者编写脚本言语需求必定的学习本钱。
  • App开发者编写脚本时分需求频繁的进行言语切换。
  • Swift作为苹果主推的言语,iOS相关的脚本都现已运用Swift开发,比方一些重签动态库注入脚本等。Swift开发的指令行程序不但能像正常的脚本言语相同完结各种批处理使命,并且对iOS的项目有天然的优势。

虽然Objective-c编写的代码也能够经过clang XXXXXX.m -framework Foundation -o XXXXX 来编译成可履行文件来运用,可是究竟Objective-c一开端规划的初衷并不包括指令行程序,所以一些功能上还在存在不少缺点,比方参数的传入与处理。而Swift在官方一推出的时分就宣传了Swift能够开发App 、脚本、后台服务、前端等,运用广泛。

Swift CLI根本流程

1:工程创立

运用Swift Package Manager(SPM)来创立工程,SPM是苹果官方供给的一个用于管理源代码分发的东西,相似于 Cocoapods或许Carthage,可是更轻量化,并且Xcode原生支撑,无需装备各种环境,能够直接运用。

$ cd CLIDemo  // 进入到你的文件夹
$ swift package init --type executable

履行完指令后会生成所需求的文件

How to create Swift CLI

其间

  • Package.swift :相似Cocoapods中的 Podfile文件,里边描绘了一些库的引证依靠关系,和工程装备。

  • Source/CLIDemo文件夹:咱们的工程目录,后续咱们新加源代码或许文件都放到该目录下。

  • CLIDemo.swift:指令行程序进口,不行更改文件姓名,里边包括main函数。

    @main
    public struct CLIDemo {
        public private(set) var text = "Hello, World!"
        public static func main() {
            print(CLIDemo().text)
        }
    }
    
  • Tests文件夹:测验工程,与正常的Xcode工程相似。

2:运用Xcode开发

工程文件结构创立好之后目前还短少XXX.xcodeproj文件,没办法用Xcode直接打开,运用如下指令创立Xcode进口

$ swift package generate-xcodeproj

然后打开生成的CLIDemo.xcodeproj文件,将运转设备挑选为Mac,然后编译运转后就能够在Xcode的控制台看到输出的Hello World案牍,截止到此,咱们的整个指令行开发工程就现已建立完结。

How to create Swift CLI

同样除了运用Xcode GUI的当时编译运转之外也能够运用指令行方法进行

$ swift run CLIDemo

然后得到相同的输出

How to create Swift CLI

3:参数传递与处理

方法1:系统API解析参数

上面讲述工程创立和指令行编写,一般咱们在调用指令行的时分会带有参数

$ command 参数1 参数2 参数3 ....

同样在代码层面也有解析参数的API

/// Command-line arguments for the current process.
@frozen public enum CommandLine {
    /// Access to the raw argc value from C.
    public static var argc: Int32 { get }
    /// Access to the raw argv value from C. Accessing the argument vector
    /// through this pointer is unsafe.
    public static var unsafeArgv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> { get }
    public static var arguments: [String]
}

运用方法比较简略

// 解析外部传进来的参数
let arguments = CommandLine.arguments
// 第一个参数
let firstArg = arguments[1]
// 第二个参数
let secondtArg = arguments[2]
print("My args = \(arguments)  first = \(firstArg)  second = \(secondtArg)");

需求留意这儿返回的参数数组中第一个元素是可履行文件自身途径,然后用户真实的输入的第一个参数是从第二个元素开端,相似与iOS中objcMsgSend函数,其间第一个参数是self。然后能够经过解析这些参数来到达不同的意图

How to create Swift CLI

方法2:运用SwiftArgumentParser

在实际运用中,一个完善的指令行参数必定不会这么简略,并且咱们在解析参数的时分也不知道运用方传入参数的顺序,一些简略的指令,或许只要一个参数的情况下能够运用CommandLine 的API,更复杂的情况下需求时用SwiftArgumentParser来进行处理。

SwiftArgumentParser是苹果开源的一个用Swift编写的参数解析器,用于解析指令行参数(command-line arguments),具有直观、易用、简洁、安全的特色。虽然是苹果自己开发的,可是究竟仍是外部库需求运用Swift package打包进来,对Package文件进行编写

import PackageDescription
let package = Package(
    name: "CLIDemo",
    dependencies: [
        //引入swift-argument-parser解析器
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"),
    ],
    targets: [
        .executableTarget(
            name: "CLIDemo",
            dependencies: [
                //将解析器依靠到target
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
            ]),
        .testTarget(
            name: "CLIDemoTests",
            dependencies: ["CLIDemo"]),
    ]
)

一起CLIDemo.swift代码文件也要相应的进行修正

  • 1:引入ArgumentParser
  • 2:将struct改为Class(方便后续的开发),并遵从ParsableCommand协议
  • 3:修正main函数为run:由于遵从协议后,原来的main被ParsableCommand接管进口,内部会调用函数名为run的函数作为进口。
import ArgumentParser
@main
class CLIDemo: ParsableCommand {
    required init() {
    }
    func run() {
        // 解析外部传进来的参数
        let arguments = CommandLine.arguments
        // 第一个参数
        let firstArg = arguments[1]
        // 第二个参数
        let secondtArg = arguments[2]
        print("My args = \(arguments)  first = \(firstArg)  second = \(secondtArg)");
    }
}

工程修正完后现已具有了ArgumentParser的开发环境,ArgumentParser的参数分为三类

  • @Argument:无符号位参数,与上面介绍的直接运用CommandLine的API解析方法相似,该类型的参数没有别号符号位,并且必须依照用户传入的顺序做解析。
  • @Option:带有符号位参数,这个类型的参数便是经过别号或许符号为来标识的,也是咱们常见的参数用法比方 -n myName或许--name myName。其间-n--name便是该参数的长别号和短别号,同样由于有了别号,所以解析时分不必关系用户输入参数的顺序。
  • @Flag:符号位,是一个bool变量,比方常用 --verbose-h
static var configuration = CommandConfiguration(abstract: "这是一个测验Demo")
@Argument(help: "这是一个Argument 参数")
var argumentArg: String = "Argument"
@Option(name: [.short, .long], help: "这是一个option参数")
var optionArg: String = "option"
@Flag(name: [.short, .long], help: "这是一个Flag参数")
var flagArg: Bool = false

关于参数的描绘系统供给以下界说,一般运用shortlong

internal enum Representation: Hashable {
  case long // 参数原符号位,便是变量名
  case customLong(_ name: String, withSingleDash: Bool)  // 自界说符号位
  case short // 参数短符号位  为-加上变量名第一个字母
  case customShort(_ char: Character, allowingJoined: Bool) // 自界说短符号位
}

ArgumentParser默许集成了-h参数,完结以上参数界说后,经过-h输出咱们指令行Demo帮助文档

How to create Swift CLI

:::warning

留意:参数命名时分如果运用驼峰结构,终究的参数会被增加-比方上面的我界说的flagArg,终究指令行的Flag参数为--flag-arg。所以这儿尽量不必驼峰结构。

:::

4:调试运转

由于咱们是在Xcode中编程开发,所以不必每次都跑到指令行中取履行Swift run CLIDemo XXX来编译运转咱们的东西,这样否则切来切去影响作业功率,并且无法运用断点调试,正确的方法是像正常的iOS开发相同直接在Xcode中编译运转。而参数传递能够在Xcode上方的Edit Scheme中处理

How to create Swift CLI

How to create Swift CLI

然后编译运转即可,以上行为等价于在终端中输入swift run CLIDemo arg1 -f -o option

5:编译成可履行文件

咱们假定现已完结了指令行程序的编写,终究要到达的目是履行咱们的指令行程序然后输出Hello World!,那么首先咱们需求把代码编译成可履行文件,经过如下指令

& swift build -c release

编译之后咱们能够在工程目录下找到咱们产物

How to create Swift CLI

这样一来咱们就能够把该文件进行分发,让其他人或许服务器端运用咱们的的指令行东西了,如果有需求能够把该文件放到/usr/local/bin/ 目录下,这样能够在任意途径下运用

How to create Swift CLI

Swift CLI实战(iPa下载器)

上面讲了一个Swift CLI 东西从开发到运用的完好流程,可是一个真实的指令行东西必定不仅仅是输出一个Hello World,需求有子指令公共参数二次输入敏感输入终端输出款式, 进度回调等功能。本节内容会经过实现一个ipa下载器,来介绍下Swift CLI的一些进阶用法,这些用法简直能覆盖之后百分之九十的作业场景。

一个iPa下载器能够从Appstore下载App,一起集成了Appstore相关才能,如登录,查找,下载等。咱们能够将这些才能封装成不同的子指令来进行调用,像如下这样

How to create Swift CLI

1:子指令

子指令也是ArgumentParser的才能项之一,能够在这儿检查官方文档,具体代码

static var configuration = CommandConfiguration(abstract: "一个iPa下载东西", subcommands: [Search.self, Login.self, Download.self])

其间要创立子指令对应的.swift文件。且每个文件中都应像之前的CLIDemo.swift的结构相同,界说自己的类,且遵从ParsableCommand协议,以Search举例,其他同级子指令同理,子指令嵌套子指令,结构相似,以此类推。

class Search: ParsableCommand {
   required init() {
   }
   static var configuration = CommandConfiguration(abstract: "查找appstore上的App")
   func run() {
   }
}

2:公共参数

当有多个子指令的时分咱们必定会有一些参数是共用的,比方上面展现的--verbose,如果每个子指令文件都写一遍显然不现实,所以ArgumentParser供给了OptionGroup选项组的才能。

咱们能够在一个公共的类或结构体中界说一系列共用参数,然后在需求运用公共参数的子指令文件中界说@OptionGroup如下图。在解析的时分能够用GlobalOptions.verbose来取值

struct GlobalOptions: ParsableArguments {
    @Flag(name: .shortAndLong)
    var verbose: Bool
    @Argument var values: [Int]
}
class Search: ParsableArguments {
    @Option var name: String
    @OptionGroup var globals: GlobalOptions
}

3:Appstore登录

首先登录需求输入用户名暗码,所以Login文件的参数必定是包括username,password,运用上面提到的方法很简单将这两个参数传入,可是输入暗码的时分如果是明文的话就太不安全了,终端输入暗码的方法都是隐式输入,同样咱们的东西也要具有这个才能,运用getpass函数能够到达隐式输入图的意图,这样打字就不会显现到终端中,也不必为暗码独自分配一个参数。

CommonMethod().showCommonMessage(text: "请输入暗码:")
  guard let psd = getpass("") else {
 	 CommonMethod().showErrorMessage(text: "需求输入暗码")
   Login.exit()
}

How to create Swift CLI

  • 登录Api "https://p25-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate?guid=MAC地址"

4:二次输入

拿到用户名和暗码能够进行Appstore API恳求进行登录了,可是Appstore 是有二次认证的,所以咱们还需求输入一个授权码。此刻咱们能够经过Appstore服务端返回的信息来提示用户输入授权码,需求授权码的过错信息为MZFinance.BadLogin.Configurator_message,此刻咱们的进程还未完毕,需求用户二次输入,对应的api为readLine

CommonMethod().showWarningMessage(text: "请输入双重认证的Code:")
let authCode = readLine();
self.authCode = authCode ?? ""

收到用户的授权码后,带着授权码从头恳求Appstore API接口即可,

5:本地持久化

登录成功后会获得DSID Token以及相关Cookie信息,需求把这些信息持久化到本地,防止每次运用该东西都要走登录流程,持久化的方法能够运用数据库、UserDefault、写文件等方法进行,这些关于iOS开发人员来说并不陌生。

6:文件查找

文件查找比较简略,咱们能够经过APP的姓名进行查找,入参为:

  • appname :APP名称
  • appid:APP在applestore上的ID(非必要)
  • limit:成果条数约束(非必要)
  • Country:APP所在国家(非必要)

整个查找流程为:

How to create Swift CLI

App 查找的API为https://itunes.apple.com/search

7:文件下载

经过上一步拿到的bundleid调用AppStore的下载接口能够实现ipa包的下载,所以这儿的入参为:

  • bundleid:App的bundleid
  • path:下载途径

How to create Swift CLI

拼接好恳求后很简单就进入下载流程开端下载了。在Swift中能够运用系统原生的NSUrlsession或许运用一些开源三方空相似Alamofire、moya等。

8:指令行输出款式

在履行下载使命或许一些耗时使命,咱们需求供给进度条来给运用者必定的提示,终端中的进度条其实也是经过各式各样的字符编码组成的图画,一起经过不同的色彩来区分不同的状态

  • 下载中

  • 下载完结

    How to create Swift CLI

  • 下载失败

    How to create Swift CLI

进度条图画是由两部分组成

  • 表明完结字符:█
  • 表明剩余字符:░

进度

默许生成50个░,然后每次下载进度回调回来咱们会依据百分比把已完结的部分用█替换,这样就展现了相似一个进度条在前进的款式。如果追求精细化,能够依据指令行窗口的宽度来动态调整进度条的长度,防止窗口过小,导致进度条会折行显现。

一起为了保证进度条保持在一行,所以每次展现都要把光标移到开端方位然后在该行从头展现,这儿运用\r打头,一起去掉print函数结尾自带的\n 操作。这儿封装一个显现进度的函数

func showProcess(process:Float, customEnd:String) -> Void {
		//  宽度50
    let barW = 50
    let com = Int(Float(barW)*Float(process))
    let rem = barW - com
		//  自界说结尾案牍和色彩
    var endStr = ""
    var color = ""
    if customEnd.count > 0 {
      endStr = customEnd
    }
  	//  下载完结款式
    if com == 50 {
      endStr = "下载完结:【100%】"
      color = "\u{001B}[0;32m"
    }
	  // 进度条
    let bar = String(repeating: "█", count: com) + String(repeating: "░", count: rem)
    // 打印进度条
    print("\r\(color)\(bar) \(endStr)", terminator: "")
  	// 改写输出缓冲区
    fflush(stdout)
}

色彩

在文本前增加相应的编码能够更改文本的款式,比方

  • 文字色彩 \u{001b}[?m 其间 ? ∈ [30, 37]

    黑(black):\u{001b}[30m
    红(red):\u{001b}[31m
    绿(green):\u{001b}[32m
    黄(yellow):\u{001b}[33m
    蓝(blue):\u{001b}[34m
    品红(magenta):\u{001b}[35m
    蓝绿(cyan):\u{001b}[36m
    白(white):\u{001b}[37m
    复原初始(reset) :\u{001b}[0m
    
  • 文字布景色彩\u{001b}[?m,其间 ? ∈ [40, 47]

    黑(black):\u{001b}[40m
    红(red):\u{001b}[41m
    绿(green):\u{001b}[42m
    黄(yellow):\u{001b}[43m
    蓝(blue):\u{001b}[44m
    品红(magenta):\u{001b}[45m
    蓝绿(cyan):\u{001b}[46m
    白(white):\u{001b}[47m
    
  • 字体款式

    加粗加亮:\u{001b}[1m
    下降亮度:\u{001b}[2m
    斜体:\u{001b}[3m
    下划线:\u{001b}[4m
    反色:\u{001b}[7m
    

以上指令能够独自运用,也能够组合运用,如将以下条件组合在一起

  • \u{001b}[1m :加粗加亮
  • \u{001b}[4m:下划线
  • \u{001b}[42m:绿色布景
  • \u{001b}[31m:赤色字体
print("\n\u{001b}[1m\u{001b}[4m\u{001b}[42m\u{001b}[31m 这是一段绿色布景赤色字体加粗带有下划线的文字")

How to create Swift CLI

总结

做完以上操作后一个ipa下载器就完结了,具体的源码能够检查此处。运用Swift编写CLI能够极大的提高iOS开发者的开发功率,下降脚本言语的学习本钱,一起随时Apple对Swift的不断更新迭代,未来或许能用swift做更多的事情。