创建同时适用于 iOS 和 OSX 的 Framework
之前分享过一篇文章 Universal iOS / OS X Frameworks,使用文中描述的方法,就是可以使用同一个 code base 来产生同时适用于 iOS 和 OSX 的框架,否则你就需要在项目中添加针对各个平台的 targets 然后在其中维护不同的版本,当然你也可以对各个 targets 中的文件使用 soft link,但是还是没有文中的方法来得方便。不过文中的方式还有有一些坑的,后面会讲到。
现在,让我们一步一步的创建一个 framework,然后按照文中的方法来配置(我的 Xcode 版本为 7.3)。
1. 创建项目
这里我们以创建一个 OSX 下的 Framework 为起点,点击 Next 来输入一些项目的基本信息
2. 输入基本信息
在这一步中,你需要输入一些关于即将创建的项目的基本信息,下面是我的项目的基本信息的截图:
这里我给项目起名 UFD,就是 Universal Framework Demo 的缩写。并且语言选择了 swift。
再次点击 Next 你将需要选择合适的磁盘位置去用于存放项目文件。
3. 修改 Support Platforms
按照上图中序号标注的步骤,点击图中椭圆标记的部分,会看到下拉菜单,选择下拉菜单中的 Other,像下面这样:
点击了 Other 之后,会出现下图:
点击图中标记的 +
号,然后分别输入 iphoneos
和 iphonesimulator
,像下面这样:
这一步完成之后,你可以发现我们项目的可选的 build devices 变得不再是只有 Mac 了:
4. 修改 Valid Architectures
现在我们开始修改 Valid Architectures 选项的内容。
双击图中标记的部分,将会出现编辑区域,我们需要添加 arm64
armv7
armv7s
,像下面这样:
现在按下 CMD+R
,如果你的设备选择的是 My Mac,那么编译并没有什么问题,现在试试将设备改为 iphone 模拟器后再次 CMD+R
。不出意外的话,你会看到下面的错误:
这是因为在 iOS 的 SDK 中并没有 <Cocoa/Cocoa.h>
这个头文件,于是我们需要加上条件编译语句,其实就是简单的预处理,根据当前不同的编译平台来选择合适的代码分支。
现在你将你的项目中的 项目名称.h
文件中的 #import
语句改为下面的样子:
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#elif TARGET_OS_MAC
#import <Cocoa/Cocoa.h>
#endif
然后选择设备为 My Mac,运行下 CMD+R
发现没有什么问题,现在讲设备切换为 iphone 模拟器,再次 CMD+R
,发现了什么?居然还是报错!:
发生这个错误的原因我觉得是 Xcode 的 bug,这个 bug 原因与框架所采用的语言有关(你在创建框架项目时选择的语言),如果你的项目语言和我一样选择的是 swift,那么你就会遇到和我一样的问题。不信的话你可以另外创建一个使用 Objc 语言的框架项目,按照与上面相同的步骤,你会发现它们会工作的很好。
为什么我会想到这个问题可能和项目所采用的语言有关呢?因为根据上面的过程,我们的预处理语句在设备为 My Mac 的时候工作的很好,只是当我将设备选择为了 iphone 模拟器之后,相应的宏似乎没有被自动的设置,而恰好在我的项目使用的是 swift 语言,并且我们知道在 swift 中不再使用预处理(Preprocessor)了,取而代之的是功能相对单一的构建配置 (Build Configuration)有可能就是这里出了问题,果然新建一个新的 Objc 框架之后同样的代码是可以的。
那么怎么解决这个问题?
如果你的项目是纯 Swift 的,那么很简单,将 项目名称.h
文件内容改成下面的样子就可以了:
#import <Foundation/NSObjCRuntime.h>
//! Project version number for UFD.
FOUNDATION_EXPORT double UFDVersionNumber;
//! Project version string for UFD.
FOUNDATION_EXPORT const unsigned char UFDVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <UFD/PublicHeader.h>
FOUNDATION_EXPORT
的定义在头文件 <Foundation/NSObjCRuntime.h>
中,它的作用就是根据文件是 C/C++
而替换成 extern
或者 extern "C"
,所以这里你直接将文件内容换成这样也行:
extern double UFDVersionNumber;
//! Project version string for UFD.
extern const unsigned char UFDVersionString[];
如果你的项目不是纯 swift 的,那么有时预处理就显得很重要,而现在的问题是当选择了项目语言为 swift 语言时,并不是所有的设备都不能通过编译。折中的方法就是,我们可以在可以通过编译的设备下先写着,而需要为那些在 Xcode 选了之后无法通过编译的设备采用手动的编译 - 因为 TARGET_OS_IPHONE
只是一个宏,它的定义在 TargetConditionals.h ,如果它没有被自动设置的话我们可以手动的设置它,通过使用 xcodebuild 的 GCC_PREPROCESSOR_DEFINITIONS
参数就可以了。
Carthage
不得不说的是,采用上面方式的项目代码并不能和 Carthage 一起工作,这也是我刚开始分享那个文章没有注意到的,当我动手试了之后,发现了这个问题。
不过这似乎并也不是什么大的问题,我们依然可是使用 xcodebuild 来手动的编译,要知道 Carthage 其实也是使用的是 xcodebuild。
那么现在我们就开始手动的编译一下,(更多的 xcodebuild 参数见 Xcode Build Setting Reference )首先是 iOS 版本,使用下面的命令行即可:
/usr/bin/xcrun xcodebuild clean build -project \
/path/to/your/workspace/YourProject/YourProject.xcodeproj -scheme YourProject \
-configuration Release \
-sdk iphoneos \
ARCHS='armv7 armv7s arm64' \
VALID_ARCHS='armv7 armv7s arm64' \
BITCODE_GENERATION_MODE=bitcode \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY= \
GCC_PREPROCESSOR_DEFINITIONS='TARGET_OS_IPHONE=1' \
CONFIGURATION_BUILD_DIR='/path/to/place/your/framework.ios'
然后是 iphone 模拟器的:
/usr/bin/xcrun xcodebuild clean build -project \
/path/to/your/workspace/YourProject/YourProject.xcodeproj -scheme YourProject \
-configuration Release \
-sdk iphonesimulator \
ARCHS='x86_64' \
VALID_ARCHS='x86_64' \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY= \
GCC_PREPROCESSOR_DEFINITIONS='TARGET_OS_IPHONE=1' \
CONFIGURATION_BUILD_DIR='/path/to/place/your/framework.ios.simulator'
最后是 OSX 的:
/usr/bin/xcrun xcodebuild clean build -project \
/path/to/your/workspace/YourProject/YourProject.xcodeproj -scheme YourProject \
-configuration Release \
-sdk macosx \
ARCHS='x86_64' \
VALID_ARCHS='x86_64' \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY= \
GCC_PREPROCESSOR_DEFINITIONS='TARGET_OS_IPHONE=0' \
CONFIGURATION_BUILD_DIR='/path/to/place/your/framework.osx'
暂时就这么多吧,算是给看之前分享的文章填个坑。