本文是《Sunvey the Major Framworks》一文的翻译
框架是一个目录,这个目录包含了共享库,访问共享库里代码的头文件,和其它的图片和声音的资源文件。一个共享库定义的方法或函数可以被应用程序调用。
IOS提供了很多你可以在应用程序里调用的框架。要使用一个框架,需要将它添加到你的项目中,你的项目才可以使用它。许多应用程序都使用了如 Foundation、UIKit、和Core Graphics这些框架。根据你为应用程序选择的模版,相关的框架就已经被自动引入了。如果默认加入的框架不能满足你的应用程序的需求,你也可以加入需 要的框架。
看看HelloWorld.xcodeproj项目里都包含了哪些框架(注:HelloWorld.xcodeproj是《Your First iOS App tutorial 》这篇教程里的一个项目)
1.在XCode里打开HelloWorld.xcodeproj项目(如果还没有打开)
2. 在project navigator窗口里点击,点击Frameworks目录前面的三角形图标。你会看到:
UIKit.framework,Foundation.framework, 和CoreGraphics.framework
3. 点击任意一个framework前面的三角,然后再点击Headers前面的三角,可以看到框架里的头文件。
每个框架对应IOS系统里的一层,每层建立在它下面层的上面。应该尽量使用上层的框架来代替下面的框架。更高层次的框架是对底层框架基于对象的抽象。
iOS应用程序基于Foundation和UIKit框架
在你开发程序时,主要使用框架就是Foundation和UIKit,因为它们包含了你需要的大部分东西。
Foundation框架为所有的应用程序提供基本系统服务
你的应用程序,UIKit和其它的框架都是建立在Foundation框架上面的。Foundation框架是用Object-C对Core Foundation框架里许多特性的封装。
使用Foundation可以:
- 创建和管理集合,比如数组和字典
- 访问存储在应用程序里的图片和其它资源
- 创建和管理字符串
- 提交和接收通知
- 创建日期和时间对象
- 自动发现IP网络上的设备
- 操作URL流
- 执行异步代码
你已经在《Your First iOS App》里使用到了Foundation框架。比如,你使用一个NSString类的实例存储用户输入的userName。你还使用了Foundation框架的initWithFormat方法创建了一个字符串。
UIKit框架提供创建基于触摸用户界面的类
所有的iOS应用程序都基于UIKit,你不能是应用程序脱离这个框架。UIKit提供了在屏幕上绘制的机制,捕获事件,和创建通用用户界面元素。UIKit也通过管理显示在屏幕上的组件来组织复杂的项目。
使用UIKit可以:
- 构建和管理你的用户界面
- 捕获触摸和基于移动的事件
- 呈现文字和web内容
- 优化你的多任务程序
- 创建定制的用户界面元素
你需要知道的其它重要的框架
Core Data框架管着理应用程序数据模型
Core Data提供对象的管理,使用Core Data,你可以创建模型对象,并管理这些对象。你管理这这些对象间的联系并修改数据。Core Data提供的内建SQLlite技术可以高效的管理数据。
使用Core Data可以:
- 在库里存储和接收对象
- 提供基本的undo/redo
- 自动验证属性值
- 过滤、分组和优化内存中的数据
- 用[NSFetchedResultsController]管理表视图中的结果
- 支持基于文档的应用程序
Core Graphics框架帮助你创建图形
高质量的图形对于所有的iOS应用程序都是很重要的。在iOS中最简单且最快捷的创建图形的方式是使用UIKit框架提供的基于预渲染图形的视图和控 件,然后让UIKit和iOS完成绘制。但是当你需要创建复杂的图形时,Core Graphics则提供了更底层的库来帮助你。
使用Core Graphics可以:
- 创建基于路径的绘图
- 抗锯齿渲染
- 添加梯度、图片和颜色
- Use coordinate-space transformations.
- 创建、显示和分析PDF文档
Core Animation允许你创建高级的动画和虚拟效果
UIKit提供建立在Core Animation之上的动画。如果你需要比UIKit能力更高级的功能,可以直接使用Core Animation。Core Animation接口包含在Quartz Core框架里。使用Core Animation可以创建嵌套的对象,并且可以对它们操作、旋转、缩放和转换。使用Core animation,你可以创建动态的用户界面而不用使用更底层的图形API,如OpenGL ES。
使用Core Animation可以:
- 创建定制动画
- 添加定时函数和图形
- 支持帧动画
- Specify graphical layout constraints.
- Group multiple-layer changes into anatomic update.
OpenGL ES 框架提供2D和3D绘图工具
OpenGL ES支持2D和3D绘图,Apple的OpenGL ES实现通过硬件提供了高速的全屏游戏式的应用程序。
使用OpenGL ES可以:
- 创建2D和3D图形
- 创建更复杂的图形,比如数据虚拟化、模拟飞行,或者视频游戏
- 访问底层图形设备
根据需要向项目中添加其它框架
还有许多框架可以添加到你的程序里。当你决定使用一个框架但项目里却没有引入这个框架时,你就需要将它加入到你的项目里。
将别的框架添加到工程里
1 打开工程
2 点击项目名,显示project editor
3 在TARGETS列表中选中要添加框架的工程
4 点击project editor顶部的Build Phases
5 点击Link Binary With Libraries前面的三角形打开这个分组
6 通过点击添加(+)来添加一个框架
7 在列表中选择一个框架,然后点击Add按钮
创建一个静态库工程
打开Xcode,点击File\New\Project,选择iOS\Framework and Library\Cocoa Touch Static Library新建一个静态库工程.
将工程命名为RWUIControls(你的框架名, 下同),然后将工程保存到一个空目录下。
一个静态库工程由头文件和实现文件组成,这些文件将被编译为库本身。
为了方便其他开发者使用你的库和framework,你将进行一些操作,让他们仅需要导入一个头文件便可以访问所有你想公开的类。
当创建静态库工程时,Xcode会自动添加RWUIControls.h和RWUIControls.m。你不需要实现文件,因此右键单击RWUIControls.m选择delete,将它删除到废纸篓中。
打开RWUIControls.h,将所有内容替换为:
1
|
#import < UIKit/UIKit.h>
|
导入UIKit的头文件,这是创建一个库所需要的。当你在创建不同的组成类时,你将会将它们添加到这个文件中,确保它们能够被库的使用者获取到。
你所构建的项目依赖于UIKit,然而Xcode的静态库工程不会自动连接到UIKit。要解决这个问题,就要将UIKit作为依赖库添加到工程中。在工程导航栏中选择工程名,然后在中央面板中选择RWUIControls目标。
点击BuildPhases,展开Link Binary with Libraries这一部分,点击+添加一个新的framework,找到UIKit.framework,点击add添加进来。
如果不结合头文件,静态库是没有用的,静态库编译一组文件,在这些文件中类和方法都以二进制数据的形式存在。在你创建的库中,有些类将能够被公开访问到,有些类只能由库内部访问并使用。
接下来,你需要在build栏中添加新的phase,来包含所有头文件,并将它们放到编译器可以获取到的某个地方。然后,你将会拷贝这些到你的framework中。
依然是在Xcode的Build Phases界面,选择Editor\Add Build Phase\Add Copy Headers Build Phase。
Note:如果你发现按上面找到的菜单项是灰色的(不可点击的),点击下方Build Phases界面的白色区域来获取Xcode的应用焦点,然后重新试一下。
把RWUIControls.h从项目导航栏中拖到中央面板的Copy Headers下的Public部分。这一步确保任何使用你的库的用户均可以获取该头文件。
Note:显然,所有包含在你的公共头文件中的头文件必须是对外公开的,这一点非常重要。否则,开发者在使用你的库时会得到编译错误。如果Xcode在读取公共头文件时不能读到你忘记设为public的头文件,这实在是太令人沮丧了。
把项目中要用到的API接口文件,从Finder中拖到Xcode下RWUIControls目录下。
选择Copy items into destination group’s folder,点击下方的选择框,确保RWUIControls静态库目标被选中。
这一步默认把实现文件添加到编译列表,把头文件添加到Project组。这意味着它们目前是私有的。
Note:在你弄清楚之前,这三个组的名称可能会让你迷惑,Public是你期望的,Private下的头文件依然是可以暴露出来的,因此名字可能有些误导。讽刺的是,在Project下的头文件对你的工程来说才是“私有”的,因此,你将会更多地希望你的头文件或者在Public下,或者在Project下。
现在,你需要将控件的头文件RWKnobControl.h分享出来,有几种方式可以实现这一点,首先是在Copy Headers面板中将这个头文件从Project栏拖到Public栏。
或者,你可能会发现,更简单的方法是,编辑文件,改变Target Membership面板下的membership。这个选项更方便一些,可以让你不断添加文件,扩充你的库。
Note:如果你不断往库中添加新的类,记得及时更新这些类的关系(membership),使尽可能少的类成为public,并确保其他非public的头文件都在Project下。
对你的控件的头文件需要做的另一件事是将其添加到库的主头文件RWControls.h中。在这个主头文件的帮助下,开发者使用你的库仅仅需要导入一个头文件,如下面的代码一样,而不是自己去选择自己需要的一块导入。
1
|
#import < RWUIControls/RWUIControls.h>
|
因此,在RWUIControls.h中添加下面的代码:
1
2
|
// Knob Control
#import
|
配置Build Settings
现在距离构建这个项目、创建静态库已经非常接近了。不过,这里要先进行一些配置,让我们的库对于用户来说更友好。
首先,你需要提供一个目录名,表示你将把拷贝的公共头文件存放到哪里。这样确保当你使用静态库的时候可以定位到相关头文件的位置。
在项目导航栏中点击项目名,然后选择RWUIControls静态库目标,选择Build Setting栏,然后搜索public header,双击Public Headers Folder Path,在弹出视图中键入如图所示内容:
一会你就会看到这个目录了。
现在你需要改变一些其他的设置,尤其是那些在二进制库中遗留下的设置,编译器提供给你一个选项,来消除无效代码:永远不会被执行的代码。当然你也可以移除掉一些debug用符号,例如某些函数名称或者其他跟debug相关的细节。
因为你正在创建framework供他人使用,最好禁掉这些功能(无效代码和debug用符号),让用户自己选择对自己的项目有利的部分使用。和之前一样,使用搜索框,改变下述设置:
-
Dead Code Stripping设置为NO
-
Strip Debug Symbol During Copy 全部设置为NO
-
Strip Style设置为Non-Global Symbols
编译然后运行,到目前为止没什么可看的,不过确保项目可以成功构建,没有错误和警报是非常好的。
选择目标为iOS Device,按下command + B进行编译,一旦成功,工程导航栏中Product目录下libRWUIControls.a文件将从红色变为黑色,表明现在该文件已经存在了。右键单击libRWUIControls.a,选择Show in Finder。
再此目录下,你将看到静态库,libRWUIControls.a,以及其他你为头文件指定的目录。注意到,正如你所期望的,那些定为public的头文件可以在此看到。
创建一个Framework
到现在,你可能迫不及待地点着脚趾头,想着什么时候framework可以出来。可以理解,因为到现在为止你已经做了许多工作,然而却没有看到过framework的身影。
现在该有所改变了,你之所以到现在都没有创建一个framework,是因为framework本身就是静态库加上一组头文件——实际上正是你已经创建好的东西。
当然,framework也有几点不同之处:
-
目录结构。Framework有一个能被Xcode识别的特殊的目录结构,你将会创建一个build task,由它来为你创建这种结构。
-
片段(Slice)。目前为止,当你构建库时,仅仅考虑到当前需要的结构(architecture)。例如,i386、arm7等,为了让一个framework更有用,对于每一个运行framework的结构,该framework都需要构建这种结构。一会你就会创建一个新的工程,构建所有需要的结构,并将它们包含到framework中。
这一部分非常神奇,不过我们会慢慢地来。实际上它并不像看起来那样复杂。
Framework结构
选择Build Phases栏,然后选择Editor/Add Build Phase/Add Run Script Build Phase来添加一个新的脚本。
这一步在build phases部分添加了一个新的面板,这允许你在构建时运行一个Bash脚本。你希望让脚本在build的过程中何时执行,就把这个面板拖动到列表中相对应的那一位置。对于该framework工程来说,脚本最后执行,因此你可以让它保留在默认的位置即可。
双击面板标题栏Run Script,重命名为Build Framework。
在脚本文本框中粘贴下面的Bash脚本代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
set -e
export FRAMEWORK_LOCN=
"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework"
# Create the path to the real Headers die
mkdir -p
"${FRAMEWORK_LOCN}/Versions/A/Headers"
# Create the required symlinks
/bin/ln -sfh A
"${FRAMEWORK_LOCN}/Versions/Current"
/bin/ln -sfh Versions/Current/Headers
"${FRAMEWORK_LOCN}/Headers"
/bin/ln -sfh
"Versions/Current/${PRODUCT_NAME}"
\
"${FRAMEWORK_LOCN}/${PRODUCT_NAME}"
# Copy the public headers into the framework
/bin/cp -a
"${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/"
\
"${FRAMEWORK_LOCN}/Versions/A/Headers"
|
现在,选择RWUIControls静态库scheme,然后选择iOS Device构建目标,然后使用cmd+B构建。
在RWUIControls工程里Products目录下右键单击libRWUIControls.a静态库,然后再一次选择Show in Finder。
在这次构建目录中你可以看到RWUIControls.framework,可以确定一下这里展示了正确的目录结构:
这算是在完成你的framework的过程中迈出了一大步。不过你会注意到这里并没有一个静态lib文件。这就是我们下一步将要解决的问题。
多架构(Multi-Architecture)编译
iOS app需要在许多不同的CPU架构下运行:
-
arm7: 在最老的支持iOS7的设备上使用
-
arm7s: 在iPhone5和5C上使用
-
arm64: 运行于iPhone5S的64位 ARM 处理器 上
-
i386: 32位模拟器上使用
-
x86_64: 64为模拟器上使用
每个CPU架构都需要不同的二进制数据,当你编译一个应用时,无论你目前正在使用那种架构,Xcode都会正确地依照对应的架构编译。例如,如果你想跑在虚拟机上,Xcode只会编译i386版本(或者是64位机的x86_64版本)。
这意味着编译会尽可能快地进行,当你归档一款app或者构建app的发布版本(release mode)时,Xcode会构建上述三个用于真机的ARM架构。因此这样app就可以跑在所有设备上了。不过,其他的编译架构又如何呢?
当你创建你的framework时,你自然会想让所有开发者都能在所有可能的架构上运行它,不是吗?你当然想,因为这样可以从同行那儿得到尊敬与赞美。
因此你需要让Xcode在所有架构下都进行编译。这一过程实际上是创建了二进制FAT(File Allocation Table,文件配置表),它包含了所有架构的片段(slice)。
Note:这里实际上强调了创建依赖静态库的示例项目的另一个原因:库仅仅在示例项目运行所需要的架构下编译,只有当有变化的时候才重新编译,为什么这一点会让人激动?因为开发周期会尽可能地缩短。
这里将使用在RWUIControls工程中的一个新的目标来构建framework,在项目导航栏中选择RWUIControls,然后点击已经存在的目标下面的Add Target按钮(或editor下面Add Target)。
找到iOS/Other/Aggregate,点击Next,将目标命名为Framework。
Note:为什么使用集合(Aggregate)目标来创建一个framework呢?为什么这么不直接?因为OS X对库的支持更好一些,事实上,Xcode直接为每一个OS X工程提供一个Cocoa Framework编译目标。基于此,你将使用集合编译目标,作为Bash脚本的连接串来创建神奇的framework目录结构。你是不是开始觉得这里的方法有些愚蠢了?
为了确保每当这个新的framework目标被创建时,静态链接库都会被编译,你需要往静态库目标中添加依赖(Dependency)。在库工程中选择Framework目标,在Build Phases中添加一个依赖。展开Target Dependencies面板,点击 + 按钮选择RWUIControls静态库。
这个目标的主要编译部分是多平台编译,你将使用一个脚本来做到这一点。和你之前做的一样,在Framework目标下,选择Build Phases栏,点击Editor/Add Build Phase/Add Run Script Build Phase,创建一个新的Run Script Build Phase。
双击Run Script,重命名脚本的名字。这次命名为MultiPlatform Build。
在脚本文本框中粘贴下面的Bash脚本代码:
1
2
3
4
5
6
7
8
9
10
11
|
set -e
# If we're already inside this script then die
if
[ -n
"$RW_MULTIPLATFORM_BUILD_IN_PROGRESS"
]; then
exit 0
fi
export RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1
RW_FRAMEWORK_NAME=${PROJECT_NAME}
RW_INPUT_STATIC_LIB=
"lib${PROJECT_NAME}.a"
RW_FRAMEWORK_LOCATION=
"${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"
|
把下面的代码加到脚本的底部。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
function
build_static_library {
# Will rebuild the static library as specified
# build_static_library sdk
xcrun xcodebuild -project
"${PROJECT_FILE_PATH}"
\
-target
"${TARGET_NAME}"
\
-configuration
"${CONFIGURATION}"
\
-sdk
"${1}"
\
ONLY_ACTIVE_ARCH=NO \
BUILD_DIR=
"${BUILD_DIR}"
\
OBJROOT=
"${OBJROOT}"
\
BUILD_ROOT=
"${BUILD_ROOT}"
\
SYMROOT=
"${SYMROOT}"
$ACTION
}
function
make_fat_library {
# Will smash 2 static libs together
# make_fat_library in1 in2 out
xcrun lipo -create
"${1}"
"${2}"
-output
"${3}"
}
|
把下面的代码添加到脚本的底部。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK name
if
[[
"$SDK_NAME"
=~ ([A-Za-z]+) ]]; then
RW_SDK_PLATFORM=${BASH_REMATCH[1]}
else
echo
"Could not find platform name from SDK_NAME: $SDK_NAME"
exit 1
fi
# 2 - Extract the version from the SDK
if
[[
"$SDK_NAME"
=~ ([0-9]+.*$) ]]; then
RW_SDK_VERSION=${BASH_REMATCH[1]}
else
echo
"Could not find sdk version from SDK_NAME: $SDK_NAME"
exit 1
fi
# 3 - Determine the other platform
if
[
"$RW_SDK_PLATFORM"
==
"iphoneos"
]; then
RW_OTHER_PLATFORM=iphonesimulator
else
RW_OTHER_PLATFORM=iphoneos
fi
# 4 - Find the build directory
if
[[
"$BUILT_PRODUCTS_DIR"
=~ (.*)$RW_SDK_PLATFORM$ ]]; then
RW_OTHER_BUILT_PRODUCTS_DIR=
"${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}"
else
echo
"Could not find other platform build directory."
exit 1
fi
|
在脚本最后添加下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# Build the other platform.
build_static_library
"${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"
# If we're currently building for iphonesimulator, then need to rebuild
# to ensure that we get both i386 and x86_64
if
[
"$RW_SDK_PLATFORM"
==
"iphonesimulator"
]; then
build_static_library
"${SDK_NAME}"
fi
# Join the 2 static libs into 1 and push into the .framework
make_fat_library
"${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}"
\
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}"
\
"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"
|
脚本的最后是简单的拷贝命令,将下面代码添加到脚本最后:
1
2
3
4
5
6
|
# Ensure that the framework is present in both platform's build directories
cp -a
"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"
\
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"
# Copy the framework to the user's desktop
ditto
"${RW_FRAMEWORK_LOCATION}"
"${HOME}/Desktop/${RW_FRAMEWORK_NAME}.framework"
|
选择Framework集合方案(aggregate scheme),按下cmd+B编译该framework。
这一步将构建并在你的桌面上存放一个RWUIControls.framework。
为了检查一下我们的多平台编译真的成功了,启动终端,导航到桌面上的framework,像下面一样:
1
2
|
$ cd ~/Desktop/RWUIControls.framework
$ RWUIControls.framework xcrun lipo -info RWUIControls
|
第一条指令导航到framework中,第二行使用lipo指令从RWUIControls静态库中得到需要的信息,这将列出存在于该库中的所有片段。
这里你可以看到,一共有五种片段:i386, x86_64, arm7, arm7s 和 arm64,正如你在编译时设定的那样。如果你之前使用lipo –info指令,你可以看到这些片段的一个分组。