プライベートライブラリを作る ~iOS編~

# はじめに

こんにちは、[どすこい](https://twitter.com/dosukoi_android)です。

本記事はSwiftWednesday Advent Calendar 2023 13日目の記事です。

https://qiita.com/advent-calendar/2023/swift-wednesday

昨日は[matsueiさん](https://qiita.com/matsuei)の[[Swift] aysncな関数内でThreadSafeでないインスタンスを扱う時は要注意](https://zenn.dev/matsuei/articles/d17d589fdb2d23)でした。

# 概要

社内などのプライベートなスペースでライブラリを公開してみようという記事です。

ライブラリは使用者が不明なことが多いので、Swift Package, CocoaPodsでの公開としました。

# 環境

OS: macOS sonoma 14.1.1

Swift: 5.9

# Swift Package

## プロジェクトを作る

まずはSwift Packageに対応するプロジェクトを作りましょう。

今回のライブラリ名は `PrivateLibrary`とします。

ディレクトリとして `private-ios-library` の配下に作っていきます。

```bash
$ mkdir private-ios-library
$ cd private-ios-library
```

`private-ios-library`に移動したらSwift Packageの雛形を作ります。

```bash
$ swift package init --name PrivateLibrary --type library

Creating library package: PrivateLibrary
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/PrivateLibrary/PrivateLibrary.swift
Creating Tests/
Creating Tests/PrivateLibraryTests/
Creating Tests/PrivateLibraryTests/PrivateLibraryTests.swift
```

雛形の作成が終わったらプロジェクトを開きます

```bash
$ open Package.swift
```

## 実装する

プロジェクトを開くとこんな感じ

![スクリーンショット 2023-12-12 13.11.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411927/084b62b0-a6e3-73a7-6fdb-be98ec59e9d9.png)

Sources/PrivateLibrary/PrivateLibrary.swiftがライブラリの本体です。

ここに任意のコードを書いていきます。

## 設定する

Package.swiftにライブラリ名や対応プラットフォームなどを記載していきます。

今回はiOS15以上に対応するライブラリとします。

`platforms`を追加します。

```swift
import PackageDescription

let package = Package(
    name: "PrivateLibrary",
		platforms: [.iOS(.v15)],
    products: [
        // Products define the executables and libraries a package produces, making them visible to other packages.
        .library(
            name: "PrivateLibrary",
            targets: ["PrivateLibrary"]),
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .target(
            name: "PrivateLibrary"),
        .testTarget(
            name: "PrivateLibraryTests",
            dependencies: ["PrivateLibrary"]),
    ]
)
```

## 公開する

まずはGitHubにpushしましょう。

```bash
$ git init
$ git add .
$ git commit -m "Initial commit"
$ git remote add origin git@github.com:<username>/PrivateLibrary.git
$ git push origin main
```

**今回はプライベートなライブラリなため、リポジトリはプライベートにしておきましょう**

pushできたらGitHub上からタグを切ります。

今回は `0.0.1`としましょう。

![スクリーンショット 2023-12-13 12.30.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411927/14031a53-f3fe-13e7-e018-036f84af701e.png)

これで公開は完了です。

## 使ってみる

上で公開したプライベートライブラリを別のプロジェクトにて使ってみましょう。

### 準備

GitHubのPersonal Access Tokenを発行する必要があります。

Personal Access Tokenの発行方法は別記事を参照してください。

Personal Access Tokenのscopeは`repo`だけでOKです。

忘れないようにどこかにメモをしておきましょう。

### 準備できたら

Xcode上からプロジェクト設定を開きます。

![スクリーンショット 2023-12-13 12.34.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411927/4042e174-84fa-bf67-ef99-97ca6abac616.png)

Project > Package Dependenciesから「＋」を押すとウィンドウが表示されます。

まずはプライベートリポジトリから取得するために、XcodeとGitHubでアカウント連携します。

開かれたウィンドウの左下の「+」を押下し、「Add Source Controll Account」を選択します。

新たに表示されたウィンドウから「GitHub」を選択し、自身のアカウント名、準備で発行したPersonal Access Tokenを入力します。

入力を終えたら「Sign In」をクリックし完了。

そうすると以下の状態になります。 

プライベートライブラリのhttpsのURLをコピーしてきます。(例: `https://github.com/Naoki-Hidaka/private-ios-library.git`)

コピーしたURLを「Search or Enter Package URL」にペーストします。

検索欄に「private-ios-library」が表示されるので、それを選択し「Add Package」ボタンを押下したら終了です。

![スクリーンショット 2023-12-13 12.42.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411927/40d90694-8608-1060-d562-f6121e84364f.png)


これでプライベートで公開されるSwift Packageを使用できるようになりました。

# CocoaPods

こっちはちょっと大変です。

まずパブリックなCocoaPodsライブラリはhttps://cocoapods.org/ によって管理されています。

ここにプライベートなライブラリを公開することはできません。

ではどうするのか

[Private Pods](https://guides.cocoapods.org/making/private-cocoapods.html) の仕組みを使います。

プライベートなスペースでのSpecリポジトリを用意し、そこからライブラリを取得するという方法です。Mavenに似てますね。

## ライブラリをCocoaPodsに対応させる

まずは上記のSwift Packagesで作ったライブラリをCocoaPodsに対応させます。

プロジェクトのルートに `.podspec`ファイルを作成します。

今回は `PrivateLibrary.podspec`という名前でファイルを作りました。

ファイルの中身は以下です。自身のライブラリに合うように名前等を設定してください

```bash
Pod::Spec.new do |s|
  s.name             = 'PrivateLibrary'
  s.version          = '0.0.1'
  s.summary          = 'Private Library for CocoaPods'
  s.description      = 'write description'
  s.homepage         = 'ホームページのURL'
  s.author           = { 'Naoki Hidaka' => 'メールアドレス' }
  s.source           = { :git => 'git@github.com:Naoki-Hidaka/private-ios-library.git', :tag => s.version.to_s }

  s.ios.deployment_target = '15.0'

  s.source_files = 'Sources/**/*.swift'
  
  # s.public_header_files = 'Pod/Classes/**/*.h'
  # s.frameworks = 'UIKit', 'MapKit'
  # s.dependency 'AFNetworking', '~> 2.3'
end
```

CocoaPodsには設定が正しく行われているのかチェックできるlintが存在しています。

プロジェクトのルートでlintを実行します。

```bash
$ pod lib lint --allow-warnings

-> PrivateLibrary
-> PrivateLibrary (0.0.1)
    - WARN  | attributes: Missing required attribute `license`.
    - WARN  | license: Missing license type.
    - WARN  | source: Git SSH URLs will NOT work for people behind firewalls configured to only allow HTTP, therefore HTTPS is preferred.
    - WARN  | description: The description is shorter than the summary.
    - WARN  | url: There was a problem validating the URL ホームページのURL.
    - WARN  | [iOS] license: Unable to find a license file
    - WARN  | [iOS] swift: The validator used Swift `4.0` by default because no Swift version was specified. To specify a Swift version during validation, add the `swift_versions` attribute in your podspec. Note that usage of a `.swift-version` file is now deprecated.
    - NOTE  | xcodebuild:  note: Using codesigning identity override: -
    - NOTE  | [iOS] xcodebuild:  note: Building targets in dependency order
    - NOTE  | [iOS] xcodebuild:  note: Target dependency graph (3 targets)
    - NOTE  | [iOS] xcodebuild:  note: Metadata extraction skipped. No AppIntents.framework dependency found. (in target 'PrivateLibrary' from project 'Pods')
    - NOTE  | [iOS] xcodebuild:  note: Signing static framework with --generate-pre-encrypt-hashes (in target 'Pods-App' from project 'Pods')
    - NOTE  | [iOS] xcodebuild:  note: Metadata extraction skipped. No AppIntents.framework dependency found. (in target 'App' from project 'App')

PrivateLibrary passed validation.
```

Warnは最悪無視して大丈夫です。Errorがなければデプロイできる状態、ということです。

**ライブラリ側の設定は以上で終わりです。ひと休みしましょう。**

## Specリポジトリを作成

プライベートなライブラリを公開するためのプライベートSpecリポジトリを作成しましょう。

ここで気をつけるべき点はCocoaPodsのデフォルトブランチは `main`ではなく `master`であること

```bash
$ mkdir my-spec
$ cd my-spec
$ git init
$ git remote add origin git@github.com:<username>/my-spec.git
$ touch README.md <- 後で好きに編集してください
$ git add .
$ git commit -m "initial commit"
$ git push origin master
```

## ローカルのCocoaPodsにプライベートSpecを追加する

```bash
$ pod repo add my-spec(任意の名前) git@github.com:<username>/my-spec.git
```

これで先ほど作ったプライベートなSpecリポジトリをローカルのCocoaPodsに追加することができます。

## 公開する

それではプライベートSpecリポジトリに上記で作ったライブラリを公開していきましょう。

まずはライブラリのプロジェクトルートに移動します。

```bash
$ cd private-ios-library
```

ここで以下を実行します。

`my-spec`の部分は上記で(任意の名前)としたところです。Specリポジトリの名前です。

```bash
$ pod repo push my-spec PrivateLibrary.podspec --allow-warnings

Validating spec
 -> PrivateLibrary (0.0.1)
    - WARN  | attributes: Missing required attribute `license`.
    - WARN  | license: Missing license type.
    - WARN  | source: Git SSH URLs will NOT work for people behind firewalls configured to only allow HTTP, therefore HTTPS is preferred.
    - WARN  | description: The description is shorter than the summary.
    - WARN  | url: There was a problem validating the URL ホームページのURL.
    - WARN  | [iOS] license: Unable to find a license file
    - WARN  | [iOS] swift: The validator used Swift `4.0` by default because no Swift version was specified. To specify a Swift version during validation, add the `swift_versions` attribute in your podspec. Note that usage of a `.swift-version` file is now deprecated.
    - NOTE  | xcodebuild:  note: Using codesigning identity override: -
    - NOTE  | [iOS] xcodebuild:  note: Building targets in dependency order
    - NOTE  | [iOS] xcodebuild:  note: Target dependency graph (3 targets)
    - NOTE  | [iOS] xcodebuild:  note: Metadata extraction skipped. No AppIntents.framework dependency found. (in target 'PrivateLibrary' from project 'Pods')
    - NOTE  | [iOS] xcodebuild:  note: Signing static framework with --generate-pre-encrypt-hashes (in target 'Pods-App' from project 'Pods')
    - NOTE  | [iOS] xcodebuild:  note: Metadata extraction skipped. No AppIntents.framework dependency found. (in target 'App' from project 'App')

Updating the `my-spec' repo

Adding the spec to the `my-spec' repo

 - [Add] PrivateLibrary (0.0.1)

Pushing the `my-spec' repo
```

これでmy-specにPrivateLibraryという名のライブラリが公開されました。

## 使ってみる

実際にこれを別プロジェクトにて使ってみましょう。

### CocoaPodsに対応していないプロジェクト

```bash
$ pod init
$ pod install
```

これでXcode上にPodディレクトリが表示され、Podfileを操作できるようになります。

**CocoaPodsに対応しているプロジェクトもここから先は一緒です。**

### プライベートSpecリポジトリを使えるようにする

Podfileの先頭に以下を追加します。

```bash
source 'git@github.com:Naoki-Hidaka/my-spec.git'
```

### ライブラリを導入する

先ほど作った `PrivateLibrary`を追加します。

```bash
target 'private-library-consumer-sample' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  pod 'PrivateLibrary' <- この部分

end
```

Podfileに追加したら以下を実行

```bash
$ pod install
```

これで無事に使えるようになりました。

# 最後に

Swift Packageに対応、CocoaPodsに対応のどちらかはインターネット上で見られますが、どちらにも対応しているという記事があまりなく困ったこともあったので書きました。

困ってる人の役に立てれば幸いです。