###### tags: `第14屆IT邦鐵人賽文章`
# 【在 iOS 開發路上的大小事2-Day19】Swift Framework 封裝 (基本款)
Xcode 開新專案,選 Framework

輸入 Framework 的名稱

如果是 Xcode 13 以上的話
要先將隱藏的 Products 資料夾開啟,因為待會產生的 Framework 會出現在這裡
```
Terminal 先切換到專案底下
=> cd 專案路徑
=> open 專案名稱.xcodeproj/pbxproj
找到 productRefGroup (請善用 Command+F,除非你眼力很好)
將 mainGroup 的值複製到 productRefGroup,存檔
神奇的 Products 資料夾就出現了
```
然後新增 Swift 檔案

輸入檔案名稱

接著在 class 前面加上 **public** 修飾字,像是下面這樣
```swift
// 原本長這樣
class Manager {
}
// 加完 public 後長這樣
public class Manager {
}
```
這是因為要封裝成 Framework,所以為了要讓外層檔案可以使用
(也就是要裝這套件的專案)
所以必須要設為 **public**,如果是要可以 override 或是繼承的話,則是要設為 **open**
```swift
// 原本長這樣
class Manager {
}
// 加完 open 後長這樣
open class Manager {
}
```
[Swift 的檔案存取層級參考](https://medium.com/@wuufone/swift-%E7%9A%84%E5%AD%98%E5%8F%96%E6%8E%A7%E5%88%B6%E6%A9%9F%E5%88%B6-access-control-e9f89bb73f2b)
接著就可以寫我們要的東西了
這邊用換背景色、輸入輸出的 func 做示範
```swift
import Foundation
import UIKit
public class Manager {
/// 更換 ViewController 的背景色
/// - Parameters:
/// - vc: UIViewController,放你想要改變的 UIViewController
/// - color: UIColor,放你想要更換的顏色
public func changeVCBackgroundColor(vc: UIViewController, color: UIColor) {
vc.view.backgroundColor = color
}
/// 你輸入什麼,我輸出什麼
/// - Parameters:
/// - text: String?,輸入字串
public func inputAndOutPut(text: String?) {
print("You Input: \(text), and I Output: \(text)")
}
}
```
然後切到 專案名稱.xcodeproj 檔案
* 修改 SDK 最低支援的版本 (紅框處)
```
TARGETS → Framework 名稱 → General → Deployment Info → Deployment Target
```

* 修改 SDK 支援的裝置 (紅框處裡面的藍色那條)
```
TARGETS → Framework 名稱 → Build Settings → Architectures → Build Active Architecture Only
將 Debug 改成 Yes,是為了讓編譯當下,只編譯所選的裝置,而不是編譯所有的裝置,可以加快編譯效率
將 Release 改成 No,是為了讓每個裝置都可以用,而不會受限於只有編譯的那個裝置可以用
```

* 修改 SDK 配置 (紅框處裡面的藍色那兩條)
```
TARGETS → Framework 名稱 → Build Settings → Linking
將 Dead Code Stripping 改成 No,是將對程式不會造成影響的 Code 移除,讓編譯最佳化
將 Mach-O Type 改成 Static Library,因為非官方的 Framework 貌似不能用動態庫
```

* 添加要暴露出的標頭檔 (Header)
```
TARGETS → Framework 名稱 → Build Phases → Headers
Swift 不用做但 Objective-C 要做的事
因為 Swift 在產生 Project 的時候已經預設產生並添加好了
Objective-C 的話,需要將要暴露出來的標頭檔放在 Public 裡面
```

* 修改 Scheme 裡面的 Build Configuration (紅框處)
```
裝置旁邊的公事包 (target) → Edit Scheme → Build Configuration
將 Build Configuration 改為 Release
```

如果要讓 Objective-C 的專案可以用的話
要將 class 繼承 NSObject,並且要是 public 跟加上 @objc
如果用前面的範例來改寫的話,像下面這樣
```swift
import Foundation
import UIKit
@objc public class Manager: NSObject {
/// 更換 ViewController 的背景色
/// - Parameters:
/// - vc: UIViewController,放你想要改變的 UIViewController
/// - color: UIColor,放你想要更換的顏色
@objc public func changeVCBackgroundColor(vc: UIViewController, color: UIColor) {
vc.view.backgroundColor = color
}
/// 你輸入什麼,我輸出什麼
/// - Parameters:
/// - text: String?,輸入字串
@objc public func inputAndOutPut(text: String?) {
print("You Input: \(text), and I Output: \(text)")
}
}
```
## 要注意的事情
**!!模擬器產生的 Framework 跟 實機產生的 Framework 是不能混用的!!**
所以我們要將模擬器跟實機各自產生的 Framework 進行合併,這樣兩者才能通用
```
先 Command+B 模擬器的 SDK
再 Command+B 實機的 SDK
打開 Terminal,切到 Framework 所在的資料夾路徑
Tips:點擊 Products 裡面的 Framework,選 Show in Finder
(這邊的 Products 指的是 Xcode 專案裡面那個,也就是上面我們叫出來的隱藏資料夾)
這樣就可以開啟 Framework 所在的資料夾
下圖紅框處,就是剛才 Command+B 後的 模擬器的 SDK 和 實機的 SDK
Release-iphoneos:實機的 SDK
Release-iphonesimulator:模擬器的 SDK
```

```
在 Terminal 輸入指令
=> cd Framework 所在的資料夾路徑 (也就是 Products)
```

```
Framework名稱 要自行替換成實際自己設的名稱喔!!!
以下圖來說,Framework名稱 就是 SDK_Demo
=> lipo -create Release-iphoneos/Framework名稱.framework/Framework名稱 Release-iphonesimulator/Framework名稱.framework/Framework名稱 -output Framework名稱
這邊我不使用腳本的原因是因為每次用腳本合併,總是無法讓模擬器跟實機都可以用
```

```
輸入完上面的指令後,這樣就將 模擬器的 SDK 和 實機的 SDK 的二進制檔案合併完成了
下圖的紅框處就是合併後的二進制檔案
```

```
要如何驗證有沒有合併成功的話,可以在 Terminal 輸入下面的指令
Framework名稱 要自行替換成實際自己設的名稱喔!!!
以下圖來說,Framework名稱 就是 SDK_Demo
=> lipo -info Framework名稱
看到下面這行的話就算合併成功了
「Architectures in the fat file: Framework名稱 are: x86_64 arm64」
```

```
接著複製一份實機的資料夾,並將他改名,名稱的話隨意~
但為了要方便找的話,取名為「Release-iphoneUniversal」會比較好一點
像是下圖紅框處這樣
```

```
然後將模擬器 SDK 裡面的 Modules/Framework名稱.swiftmodule 的
四個檔案跟 Project 資料夾裡面的兩個檔案
個別複製到
「Release-iphoneUniversal/Framework名稱.framework/Modules/Framework名稱.swiftmodule」
裡面對應的資料夾內
複製完成後,會長的像下圖一樣
Framework名稱.swiftmodule 資料夾裡面:
副檔名為 .swiftdoc 會有四個 (arm64、x86_64 各兩個)
副檔名為 .swiftmodule 會有四個 (arm64、x86_64 各兩個)
Framework名稱.swiftmodule/Project 資料夾裡面:
副檔名為 .swiftsourceinfo 會有四個 (arm64、x86_64 各兩個)
```

這樣就算完成 Swift Framework 封裝了
## 補充
如果是 Apple Silicon 的 Mac 要用的話
要在使用 SDK 的專案改用 My Mac (Design with iPad)
或是接手機來燒,**不能用模擬器燒**!!
因為 Xcode 的模擬器似乎是 x86_64 架構的
## 參考資料 (來自各路大神,讚嘆大神~)
> 1. [iOS Swift Framework靜態庫製作與發佈](https://www.twblogs.net/a/5db39dbfbd9eee310da05b46)
> 2. [iOS-SDK开发经验分享](https://www.jianshu.com/p/cbb1f54b89d2)
> 3. [Framework 嵌套与依赖](https://www.jianshu.com/p/b03d617917d6)
> 4. [swift framework中引入第三方的framework或者.a](https://www.jianshu.com/p/1eaa63388594)
> 5. [iOS静态库SDK制作(包含第三方静态库)](https://juejin.cn/post/6844903521465139214)
> 6. [iOS開發~製作同時支持armv7,armv7s,arm64,arm64e,i386,x86_64的靜態庫.a](https://www.twblogs.net/a/5c67a9f8bd9eee01cc9e1133)
> 7. [xcode 13新建工程找不到product文件夾](https://juejin.cn/post/7036384854782509063)
> 8. [swift封装Framework静态库SDK](https://michaellynx.github.io/swift-with-static-library-framework/)
> 9. [关于Xcode12静态库打包的一些心得](https://www.cnblogs.com/wgb1234/p/14258036.html)
> 10. [【iOS】SDK靜態庫的封裝](https://juejin.cn/post/6844904178762907661)
> 11. [iOS封装SDK](https://juejin.cn/post/6844903878094225422)
> 12. [Creating and Publishing Custom iOS Framework using Cocoapods | Swift 5, XCode 11](https://youtu.be/vSMmZxmKIA4)
> 13. [How to Create a Swift Framework using Xcode 11](https://www.youtube.com/watch?v=iFi1QnKHFnM&t=372s)
> 14. [Swift Framework - Create a Custom Framework in iOS](https://youtu.be/oZloIsF1H4g)