Swift制作framework
公司的需要需要制作sdk給其他團隊用,其實就是framework
簡直炸裂!踩了一個又一個的坑!
遍體鱗傷之后,決定一定要記錄下來,方便以后自己和有需要的人查閱,能有一點點幫助也是好的
進入正題
官方提供的是.framework
與.a
兩種方式制作SDK的方式。
分別對應創建工程時下方的Cocoa Touch Framework
和Cocoa Touch Static Library
。
.framework
與.a
兩者區別自行百度吧,簡書上制作Framework相關的文章基本都有,就懶得copy了。
.framework
其實是分動態和靜態的,所以.framework
即可滿足我的要求,想要制作動態或者靜態創建framework后可以修改,往后面看。
ps:
在 iOS 8 之前,
iOS 平臺不支持開發者使用用戶自己的動態 Framework,這種限制可能是出于安全的考慮。
換一個角度講,因為 iOS 應用都是運行在沙盒當中,不同的程序之間不能共享代碼,
同時動態下載代碼又是被蘋果明令禁止的,沒辦法發揮出動態庫的優勢,
實際上動態庫也就沒有存在的必要了。
但是,iOS 8/Xcode 6 推出之后,
iOS添加了對動態庫的支持,為什么 iOS 8 要添加動態庫的支持?唯一的理由大概就是Extension 的出現。
Extension 和 App 是兩個分開的可執行文件,同時需要共享代碼,這種情況下動態庫的支持就是必不可少的了。
但是這種動態 Framework 和系統的UIKit.Framework 還是有很大區別。
系統的 Framework 不需要拷貝到目標程序中,我們自己做出來的 Framework 哪怕是動態的,
最后也還是要拷貝到 App 中(App 和Extension 的 Bundle 是共享的),
因此蘋果又把這種 Framework 稱為 Embedded Framework
手洗干凈,挽起袖子干 0_0
第一步:創建Framework工程
運行 XCode
-> Cocoa Touch Framework
-> 取個名, 語言選擇 Swift
-> 創建成功
第二步:基本設置
創建完不急著編寫代碼,先做一些設置:
-
修改最低的系統要求,建議當然低一些好
-
mach -0 type
,即選擇動態庫or靜態庫(甚至Object File)
想知道這幾種type的區別可以移步
參考淺談 SDK 開發(一)五種 Mach-O 類型的凜冬之戰
這里我選擇默認的Dynamic Library
即動態庫
-
Architectures 該編譯選項指定了工程將被編譯成支持哪些指令集,支持指令集是通過編譯生成對應的二進制數據包實現的,如果支持的指令集數目有多個,就會編譯出包含多個指令集代碼的數據包,造成最終編譯的包很大。
那么指令集是什么呢:**iPhone指令集**,蘋果處理器支持兩個不同的指令集: 32位ARM指令集(armv6|armv7|armv7s)和 64位ARM指令集(arm64),i386|x86_64 是Mac處理器的指令集, i386是針對intel通用微處理器32架構的。 x86_64是針對x86架構的64位處理器。 當使用iOS模擬器的時候會遇到i386|x86_64,iOS模擬器沒有arm指令集。
所以我們看看我們需要什么指令集
1、debug環境下 設備:arm64(測試機型有限: 6P、5、7 ) 模擬器:iPhone7-Plus:x86_64、iPhone4s:i386 2、release環境下 設備:armv7、arm64 模擬器:i386、x86_64
以上所生成的framework均不包含armv7s, 在 Building Setting 中設置一下 Architectures,在原有基礎上添加一行 armv7s ,如下:
在原有基礎上增加 armv7s
-
Build Active Architecture Only 意思是: 該編譯項用于設置是否只編譯當前使用的設備對應的arm指令集
當該選項設置成YES時,你連上一個armv7指令集的設備,就算你的Valid Architectures和Architectures都設置成armv7/armv7s/arm64,還是依然只會生成一個armv7指令集的二進制包
Release模式為發布模式,需要支持各種設備指令集,所以設置為NO
-
Valid Architectures 設置的支持arm指令集。指令集的版本有:armv7/armv7s/arm64。
假設Architectures設置的支持arm指令集版本只有:arm64時,這時Xcode只會生成一個arm64指令集的二進制包
所以這里我們都不用改,都包含進來就好了
-
Dead Code Stripping, 設置為 NO 關閉對代碼中“dead”,“unreachable”代碼過濾
-
Link With Standard Libraries 設置為 NO 避免重復鏈接
-
Build 環境 設置build環境為
release
環境下
第三步:編寫代碼
在此之前,我們先command+B
看看是否成功
build success
并看到Products
下的 文件 XPKit.framework
由紅變黑,說明制作成功,右鍵show in finder
這就是我們的framework,只是里面啥都還沒寫- -。
好了,咱們抓緊寫幾句,饑渴難耐了吧,但是還是先跟著我來吧,弄明白了再去寫自己的代碼,少踩好多坑
創建一個
Manager
類繼承自 NSObject
寫上這么個
func
:
@objc public class XPManager: NSObject {
@objc public func sayHello(){
print("XPKit-->: hello")
}
public func sayWorld() {
print("XPKit-->: world")
}
@objc func saySwift() {
print("XPKit-->: Swift")
}
}
完畢, command + B
,報錯了!來看看為啥:
ld: symbol dyld_stub_binder not found (normally in libSystem.dylib). Needed to perform lazy binding to function __T0s23_ContiguousArrayStorageCMa for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)
這里提示我們少了一個系統庫libSystem.dylib
,我也不知道為啥,那我們就給加上唄,來到PROJECT
->Build Phases
->Link Binary With Libraries
->+
加上libSystem.tbd
這時候再回來編譯,發現
Build Success
。目的達到了 0-0!
第四步:測試寫好的Framework
1. 先來測試Swift項目調用swift的framework
先到Products
下的 XPKit.framework
右鍵 show in finder
找到 XPKit.framework
緊接著 create
一個 swift
工程app single view app
,并將??的XPKit.framework
拖到工程中,記得勾選 copy if needed
import XPKit
然后 command
+ 左鍵
進入,可以看到暴露出來能用的方法
這里我們能發現我們寫了
public
修飾的 都暴露出來供app
調用
import UIKit
import XPKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let manager = XPManager()
manager.sayHello()
manager.sayWorld()
}
}
command + R
Run一下發現報錯了
dyld: Library not loaded: @rpath/XPKit.framework/XPKit
Referenced from: /Users/midoo/Library/Developer/CoreSimulator/Devices/8F8ADF9B-D5DD-45A1-B925-03BCBB11D9FF/data/Containers/Bundle/Application/C7D5F85A-DDC8-4BC7-964A-FDC4B5154C5A/SwiftInvokDemo.app/SwiftInvokDemo
Reason: image not found
(lldb)
這里因為我們制作的framework
是 dynamic library
動態的,所以我們到Project
->General
->滑到最下方
選中
linked frameworks and libraries
中的 XPKit.framework
點 -號
刪除,再在上方 Embedded Binaries
點 +號
將 XPKit.framework
加回來,發現上下都有了,如:這時候再
command + R
Run,就不報錯了,并且成功打印:2. 再來測試Swift項目調用swift的framework
同理 create
一個 OC
的 app,拖進我們的 XPKit.framework
并在 Project
->General
-> Embedded Binaries
下添加進去
這個時候就要導入#import <XPKit/XPKit-Swift.h>
而不是 #import <XPKit/XPKit.h>
#import <XPKit/XPKit-Swift.h>
是 OC
項目導入 Swift Framework
時自動產生的文件, 給我們展示可以用哪些接口
command
+ 左鍵
點到這個文件里去可以看到
這時候我們就會發現,
XPManager
我們的類暴露出來了,但是方法只有一個sayHello()
所以敲黑板,劃重點:
我們制作 swift framework 的時候,一定要注意可用性,因為難免會遇到讓OC調用的時候。
所以要在暴露在外給人家用的話,一定要寫上修飾詞 `@objc` 與 `public` 缺一不可
而我們的類,即 Class
,繼承了 NSObject
那么即使不寫 @objc
, 也是OK的,但是屬性
與 func
一定要寫
可以做個測試,在framework工程中寫:
public class XPTest: NSObject{
public func sayWorld() {
print("XPKit-->: world")
}
@objc public func sayHello(){
print("XPKit-->: hello")
}
}
重新 build
, 注: (每次重新build才會更新framework)
并刪除 OC-App
下的 framework
。重新拖到項目中并添加到Embedded Binaries
下
發現,XPKit-Swift.h下暴露的是這樣的:
@interface XPTest : NSObject
- (void)sayHello;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
所以如果是 Class
繼承了 NSObject
那么即使不寫 @objc
, 也是OK的,但是屬性
與 func
一定要寫
這里就是簡單介紹,還有些細節你們可以慢慢測試
第五步:創建可以直接調試的工程
如果你都跟著步驟寫了以后,會發現一直 Build
, 一直拖,一直添加,太繁瑣。所以咱們可以這樣創建一個 workspace
將兩個項目都包含進去
-
新建項目或者我們把原先的添加了的framework先丟掉,左上角
File
->save as workspace
-
關閉這個
.xcodeproj
文件重新打開這個.xcworkspace
-
將我們的
XPKit
項目 拖到workspace
中,與Demo-App
并行
這個時候就會發現,我們就有兩個項目了可以分別build
了
-
到
Demo
中工程文件
->General
->embedded binaries
中將XPKit
下的.framework
加進來
試一試,
scheme
選擇Demo
,build
一下是success
,
我們再來到我們的XPManager.h
,添加幾行代碼(當測試)
public class XPHomeViewController: UIViewController{
@objc public let size = CGSize(width: 40, height: 40)
@objc public var point: CGPoint = CGPoint(x: 20, y: 20)
public var content: String?
@objc public var textColor: UIColor?
public override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.red
}
@objc public func sayHello(){
print("XPKit-->: hello")
}
public func sayWorld() {
print("XPKit-->: world")
}
}
當然還是先build一下我們的XPKit
,否則Demo
里的framework
不更新吶,再到OC項目中import
并點進去看看。
注:我這里試了幾次總是沒有自動補全,那就自己手寫 import
地址吧
#import <XPKit/XPKit-Swift.h>
點進去發現
@interface XPHomeViewController : UIViewController
@property (nonatomic, readonly) CGSize size;
@property (nonatomic) CGPoint point;
@property (nonatomic, strong) UIColor * _Nullable textColor;
- (void)viewDidLoad;
- (void)sayHello;
- (nonnull instancetype)initWithNibName:(NSString * _Nullable)nibNameOrNil bundle:(NSBundle * _Nullable)nibBundleOrNil OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
@end
SWIFT_CLASS("_TtC5XPKit9XPManager")
@interface XPManager : NSObject
- (void)sayHello;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
SWIFT_CLASS("_TtC5XPKit6XPTest")
@interface XPTest : NSObject
- (void)sayHello;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
哇,完美,并且,可以直接在項目里用。那么邊寫邊測,還能設斷點,完美~跟平時寫app
沒兩樣,其實還差最后一步 >_<.
當你調用并且運行,發現又報錯了
dyld: Library not loaded: @rpath/libswiftCore.dylib
Referenced from: /Users/midoo/Library/Developer/Xcode/DerivedData/OCInvokDemo-fvmdsbbzuqqnjnbswwgpcidptord/Build/Products/Debug-iphonesimulator/XPKit.framework/XPKit
Reason: image not found
并且這次我們已經加到 Embedded Binaries
中了,原因是,如果我們在OC項目
中引用swift framework
,還需要到 build setting
中設置如下:
到此為止才是真正完美 ~
第五步:創建通用包
之前說過iphone
指令集, 真機與模擬器是不同的,所以編譯出來的包也是不通用的,現在我們要做通用的包提供別人使用。
- 切換到
XPKit
, 選擇任意一個模擬器build
一下, - 再選擇
Generic iOS Device
,build
一下 -
show in finder
,我們只看release
包的,不要關閉finder
- 我們需要用到終端來創建通用包,打開我們的終端
- 先
cd
到一個文件目錄下,為了存放我們制作的包
cd /Users/midoo/Desktop/測試文件
- 輸入
lipo -create
空格以后,分別到release-iphoneos
和release-iphonesimulator
下的XPKit.framework
-> 選擇XPKit
二進制文件,拖動到 終端下
- 緊接在后面寫上
-output
+你的包名
,回車
注:我選擇的文件夾下面有XPKit
文件名,重復了 - -。創建失敗...所以換了個文件夾
- OK,這個時候還沒完,只是創建了一個通用的二進制文件,還得把其他東西給他加上,回到
release-iphoneos
和release-iphonesimulator
,選擇任意一個文件夾,整個復制出來 -
并將制作好的二進制文件拖出來 替換掉
image.png -
還沒完!!!再到另一個文件夾,將指令集文件copy到這個,我們這個新的文件夾下
保證這些都包含在內,那么這個framework包才算制作完成
- 到此為止我們得到了我們想要的通用包,按照相同的方法,拖到工程中,引入到
Embedded Binaries
,可以調試了
第六步:用Shell腳本創建通用包
創建通用包用到的次數不多,上面的方法夠用了,但是如果你還是覺得不方便、很繁瑣。那你可以跟我這樣做
- 選擇
XPKit
工程點擊左下角+
- 創建一個
Aggregate
。去個名字,類似CommonBuilder
image.png - 選中
CommonBuilder
->Build Phases
-> 添加New Run Script Phase
- 在編輯器內輸入我們的腳本代碼,請全部復制,黏貼,記得修改第二步引號內的內容為你的
framework name
# Merge Script
# 1
# Set bash script to exit immediately if any commands fail.
set -e
# 2
# Setup some constants for use later on.
FRAMEWORK_NAME="Your framework name"
# 3
# If remnants from a previous build exist, delete them.
if [ -d "${SRCROOT}/build" ]; then
rm -rf "${SRCROOT}/build"
fi
# 4
# Build the framework for device and for simulator (using
# all needed architectures).
xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch arm64 -arch armv7 -arch armv7s only_active_arch=no defines_module=yes -sdk "iphoneos"
xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch x86_64 -arch i386 only_active_arch=no defines_module=yes -sdk "iphonesimulator"
# 5
# Remove .framework file if exists on Desktop from previous run.
if [ -d "${HOME}/Desktop/${FRAMEWORK_NAME}.framework" ]; then
rm -rf "${HOME}/Desktop/${FRAMEWORK_NAME}.framework"
fi
# 6
# Copy the device version of framework to Desktop.
cp -r "${SRCROOT}/build/Release-iphoneos/${FRAMEWORK_NAME}.framework" "${HOME}/Desktop/${FRAMEWORK_NAME}.framework"
# 7
# Replace the framework executable within the framework with
# a new version created by merging the device and simulator
# frameworks' executables with lipo.
lipo -create -output "${HOME}/Desktop/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${SRCROOT}/build/Release-iphoneos/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${SRCROOT}/build/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}"
# 8
# Copy the Swift module mappings for the simulator into the
# framework. The device mappings already exist from step 6.
cp -r "${SRCROOT}/build/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule/" "${HOME}/Desktop/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule"
# 9
# Delete the most recent build.
if [ -d "${SRCROOT}/build" ]; then
rm -rf "${SRCROOT}/build"
fi
如圖:
-
scheme
選擇CommonBuilder
,任意模擬器,編譯,報錯了看看報了什么錯
Command /bin/sh failed with exit code 65
你們以后看到這些不用慌,網上看,信息都在上面
=== BUILD TARGET XPKit OF PROJECT XPKit WITH CONFIGURATION Release ===
Check dependencies
No architectures to compile for (ARCHS=x86_64 i386, VALID_ARCHS=arm64 armv7 armv7s).
** BUILD FAILED **
分析一下,這里都是我們提到過的指令集。 VALID_ARCHS=arm64 armv7 armv7s
這就是我們開始在 Build Setting
->Valid Architectures
中設置的內容,很明顯,意思是腳本里,要制作包含 x86_64
和 i386
的包,但是我們的Valid Architectures
中沒有。
那么解決問題就方便了,分別添加x86_64
和 i386
編譯成功~
來到桌面我們發現
XPKit.framework
,已經靜悄悄的在桌面上了,這就是我們的通用包
總結一下
好了,我們總結一下,本篇簡單的介紹了一下
- 如何用
Swift
編寫,OC項目
與Swift項目
都能用的dynamic framework
- 如何正確的調試我們的
framework
- 如何制作通用的
framework 包
為了能讓OC項目也能調用,你還記得 @objc 和 public 嗎 ^_^
我們的 framework
如果需要導入其他第三方庫,該怎么做
本來也想寫篇文章,有點懶,大家先可以看看
Swift + framework 的制作(基于pod管理的workspace)