若是你想将你开发的控件与别人分享,一种方法是直接提供源代码文件。然而,这种方法并非很优雅。它会暴露全部的实现细节,而这些实现你可能并不想开源出来。此外,开发者也可能并不想看到你的全部代码,由于他们可能仅仅但愿将你的这份漂亮代码的一部分植入本身的应用中。html
另外一种方法是将你的代码编译成静态库(library),让其余开发者添加到本身的项目中。然而,这须要你一并公布全部的公开的头文件,实在是很是不方便。ios
你须要一种简单的方法来编译你的代码,这种方法应该使得你的代码易分享,而且在多个工程中易复用。你须要的是一种方法来打包你的静态库,将全部的头文件放到一个单元中,这样你就能够马上将其加入到你的项目中并使用。正则表达式
很是幸运,这正是本篇教程所要解决的问题。你将会学到制做并使用Framework,帮助你解决这个头疼的问题。OS X完美地支持这一点,由于Xcode就提供了一个项目模板,包含着默认构建目标(target)和能够容纳相似于图片、声音、字体等资源的文件。你能够为iOS建立Framework,不过这是一个比较复杂的手工活,若是你跟着教程走,你将学到怎么样跨过路障,顺利地完成Framework的建立。macos
Framework是资源的集合,将静态库和其头文件包含到一个结构中,让Xcode能够方便地把它归入到你的项目中。xcode
在OS X上,可能会建立一个动态链接(Dynamically Linked)的framework。经过动态链接,framework能够更新,不须要应用从新链接。在运行时,库中代码的一份拷贝被分享出来,整个工程均可以使用它,所以,这样减小了内存消耗,提升了系统的性能。正如你看到的,这是一个功能强大的特性。bash
在iOS上,你不能用这种方式添加为系统添加自定义的framework,所以仅有的动态连接的framework只能是Apple提供的那些。架构
打开Xcode,点击File - New - Project,选择iOS - Framework and Library - Cocoa Touch Static Library新建一个静态库工程.app
将工程命名为你要建立的静态库名(这里我用ZYResource命名),而后将工程保存到一个空目录下iphone
一个静态库工程由头文件和实现文件组成,这些文件将被编译为库自己。ide
为了方便其余开发者使用你的库和framework,你将进行一些操做,让他们仅须要导入一个头文件即可以访问全部你想公开的类。
当建立静态库工程时,Xcode会自动添加ZYResource.h和ZYResource.m。你不须要实现文件,所以右键单击ZYResource.m选择delete,将它删除到废纸篓中。
导入UIKit的头文件,这是建立一个库所须要的。当你在建立不一样的组成类时,你将会将它们添加到这个文件中,确保它们可以被库的使用者获取到。
你所构建的项目依赖于UIKit,然而Xcode的静态库工程不会自动链接到UIKit。要解决这个问题,就要将UIKit做为依赖库添加到工程中。
在工程导航栏中选择工程名,而后在中央面板中选择ZYResource目标。
点击BuildPhases,展开Link Binary with Libraries这一部分,点击+添加一个新的framework,找到UIKit.framework,点击add添加进来。
打开ZYResource.h,将全部内容替换为:
接下来,你须要在build栏中添加新的phase,来包含全部头文件,并将它们放到编译器能够获取到的某个地方。而后,你将会拷贝这些到你的framework中。
依然是在Xcode的Build Phases界面,选择Editor - Add Build Phase - Add Headers Build Phase。
Note:若是你发现按上面找到的菜单项是灰色的(不可点击的),点击下方Build Phases界面的白色区域来获取Xcode的应用焦点,而后从新试一下。
把ZYResource.h从项目导航栏中拖到中央面板的Headers下的Public部分。这一步确保任何使用你的库的用户都可以获取该头文件
Note:显然,全部包含在你的公共头文件中的头文件必须是对外公开的,这一点很是重要。不然,开发者在使用你的库时会获得编译错误。
若是Xcode在读取公共头文件时不能读到你忘记设为public的头文件,这实在是太使人沮丧了。
既然你已经设置好你的工程了,是时候为你的库添加一些功能了。因为本篇教程的关键在于教你怎么样建立一个framework,
而不是怎么样构建一个UI控件,这里你将要导入Framework的文件从Finder中拖到Xcode下工程目录下。
选择Copy items info needed,点击下方的选择框,确保ZYResource静态库目标被选中。
这一步默认把实现文件添加到编译列表,把头文件添加到Project组。这意味着它们目前是私有的
Note:在你弄清楚以前,这三个组的名称可能会让你迷惑,Public是你指望的,Private下的头文件依然是能够暴露出来的,
所以名字可能有些误导。讽刺的是,在Project下的头文件对你的工程来讲才是“私有”的,所以,你将会更多地但愿你的头文件或者在Public下,或者在Project下。
如今,你须要将控件的头文件分享出来(这里我用UICombox.h),有几种方式能够实现这一点,首先是在Headers面板中将这个头文件从Project栏拖到Public栏。
或者,你可能会发现,更简单的方法是,编辑文件,改变Target Membership面板下的membership。这个选项更方便一些,可让你不断添加文件,扩充你的库。
Note:若是你不断往库中添加新的类,记得及时更新这些类的关系(membership),使尽量少的类成为public,并确保其余非public的头文件都在Project下。
另外,注意拖进来的文件或文件夹不要有图片,静态库里面是不包含图片的,图片的后面须要另作处理
对你的控件的头文件须要作的另外一件事是将其添加到库的主头文件ZYResource.h中。
在这个主头文件的帮助下,开发者使用你的库仅仅须要导入一个头文件,就像你使用UIKit同样,只用导入<UIKit/UIKit.h>,而不是本身去选择本身须要的一块导入。
如今距离构建这个项目、建立静态库已经很是接近了。不过,这里要先进行一些配置,让咱们的库对于用户来讲更友好。
首先,你须要提供一个目录名,表示你将把拷贝的公共头文件存放到哪里。这样确保当你使用静态库的时候能够定位到相关头文件的位置。
在项目导航栏中点击项目名,而后选择ZYResource静态库目标,选择Build Setting栏,而后搜索public header,
双击Public Headers Folder Path,在弹出视图中键入内容:include/$(PROJECT_NAME)
一会你就会看到这个目录了。
如今你须要改变一些其余的设置,尤为是那些在二进制库中遗留下的设置,编译器提供给你一个选项,
来消除无效代码:永远不会被执行的代码。固然你也能够移除掉一些debug用符号,例如某些函数名称或者其余跟debug相关的细节。
由于你正在建立framework供他人使用,最好禁掉这些功能(无效代码和debug用符号),
让用户本身选择对本身的项目有利的部分使用。和以前同样,使用搜索框,改变下述设置:
Dead Code Stripping设置为NO
Strip Debug Symbol During Copy 所有设置为NO(在我这个版本下,默认为NO,只用确认一遍便可)
编译而后运行,到目前为止没什么可看的,不过确保项目能够成功构建,没有错误和警报是很是好的。
选择目标为iOS Device,按下command + B进行编译,一旦成功,工程导航栏中Product目录下libZYResource.a文件将从红色变为黑色,
代表如今该文件已经存在了。右键单击libZYResource.a,选择Show in Finder。
在此目录下,你将看到静态库,libZYResource.a,以及其余你为头文件指定的目录。注意到,正如你所指望的,那些定为public的头文件能够在此看到。
在没法看到真实效果的状况下为iOS开发一个UI控件库是极其困难的,而这是咱们如今面临的问题。
没有人指望你闭着眼睛开发出一个UI控件,所以在这一部分你将建立一个新的Xcode工程,该工程依赖于你刚刚建立好的库。
这意味着容许你使用示例app建立一个framework。固然,这部分代码将和库自己彻底分离,结构会很是清晰。
选择File - Close Project关闭以前的静态库工程,使用File - New - Project建立一个新的工程,
选择iOS - Application - Single View Application,将新工程命名为ZTResource,将项目保存到和以前的ZYResource相同的目录下。
添加ZYResource依赖库,将ZYResource.xcodeproj从Finder中拖到Xcode中ZTResource组下
如今你能够在你的工程中导航到库工程了,这样作很是好,由于这样意味着你能够在库中编辑代码,而且运行示例工程来测试你作的改变。
Note:你没法将同一工程在两个Xcode窗口中同时打开,若是你发现你没法在你的工程中导航到库工程的话,检查一下是否库工程在其余Xcode窗口中打开了。
如今,你将添加静态库做为实例项目的依赖库:
在项目导航栏中选择ZTResource。
导航到ZTResource目标下Build Phases面板下。
打开Target Dependencies面板,点击+按钮调出选择器。
找到ZYResource静态库,选择并点击Add。这一步代表当构建应用时,Xcode会检查是否静态库须要从新构建。
为了链接到静态库自己,展开Link Binary With Libraries面板,再次点击+按钮,
从Workspace组中选择libZYResource.a而后点击Add。
这一步确保Xcode能够链接到静态库,就像链接到系统framework(例如UIKit)同样。
像这样使用嵌套工程的好处是你能够对库自己作出修改,而不用离开示例工程,即便你同时改变两个地方的代码也同样。
每次你编译工程,你都要检查是否将头文件的public/project关系设置正确。若是实例工程中缺失了任何须要的头文件,它都不能被编译。
到如今,你可能火烧眉毛地点着脚趾头,想着何时framework能够出来。能够理解,
由于到如今为止你已经作了许多工做,然而却没有看到过framework的身影。
如今该有所改变了,你之因此到如今都没有建立一个framework,
是由于framework自己就是静态库加上一组头文件——实际上正是你已经建立好的东西。
固然,framework也有几点不一样之处:
目录结构。Framework有一个能被Xcode识别的特殊的目录结构,你将会建立一个build task,由它来为你建立这种结构。
片断(Slice)。目前为止,当你构建库时,仅仅考虑到当前须要的结构(architecture)。例如,i38六、arm7等,为了让一个framework更有用,对于每个运行framework的结构,该framework都须要构建这种结构。一会你就会建立一个新的工程,构建全部须要的结构,并将它们包含到framework中。
这一部分很是神奇,不过咱们会慢慢地来。实际上它并不像看起来那样复杂。
一个framework有一个特殊的目录结构,看起来像是这样的:
如今你须要在静态库构建过程当中添加脚原本建立这种结构,在项目导航栏中选择ZYResource,而后选择ZYResource静态库目标,
选择Build Phases栏,而后选择Editor - Add Build Phase - Add Run Script Build Phase来添加一个新的脚本。
这一步在build phases部分添加了一个新的面板,这容许你在构建时运行一个Bash脚本。你但愿让脚本在build的过程当中什么时候执行,
就把这个面板拖动到列表中相对应的那一位置。对于该framework工程来讲,脚本最后执行,所以你可让它保留在默认的位置便可。
双击面板标题栏Run Script,重命名为Build Framework。
在脚本文本框中粘贴下面的Bash脚本代码
set -e export FRAMEWORK_LOCN="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework" # Create the path to the real Headers diemkdir -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"复制代码
这个脚本首先建立了ZYResource.framework - Versions - A - Headers目录,而后建立了一个framework所须要的三个链接符号(symbolic links)。
Versions - Current => A
Headers => Versions - Current - Headers
ZYResource => Versions - Current - ZYResource
最后,将公共头文件从你以前定义的公共头文件路径拷贝到Versions - A - Headers目录下,
-a参数确保修饰次数做为拷贝的一部分不会改变,防止没必要要的从新编译。
如今,选择ZYResource静态库scheme,而后选择iOS Device构建目标,而后使用cmd+B构建。
在ZYResource工程里Products目录下右键单击libZYResource.a静态库,而后再一次选择Show in Finder。
在此次构建目录中你能够看到ZYResource.framework,能够肯定一下这里展现了正确的目录结构:
这算是在完成你的framework的过程当中迈出了一大步。不过你会注意到这里并无一个静态lib文件。这就是咱们下一步将要解决的问题。
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:这里实际上强调了建立依赖静态库的示例项目的另外一个缘由:库仅仅在示例项目运行所须要的架构下编译,
只有当有变化的时候才从新编译,为何这一点会让人激动?由于开发周期会尽量地缩短。
这里将使用在ZYResource工程中的一个新的目标来构建framework,在项目导航栏中选择ZYResource
找到Other - Aggregate,点击Next,将目标命名为Framework。
Note:为何使用集合(Aggregate)目标来建立一个framework呢?为何这么不直接?由于OS X对库的支持更好一些,事实上,
Xcode直接为每个OS X工程提供一个Cocoa Framework编译目标。基于此,你将使用集合编译目标,做为Bash脚本的链接串来建立神奇的framework目录结构。
为了确保每当这个新的framework目标被建立时,静态连接库都会被编译,你须要往静态库目标中添加依赖(Dependency)。
在库工程中选择Framework目标,在Build Phases中添加一个依赖。展开Target Dependencies面板,点击 + 按钮选择ZYResource静态库。
这个目标的主要编译部分是多平台编译,你将使用一个脚原本作到这一点。和你以前作的同样,在Framework目标下,
选择Build Phases栏,点击Editor - Add Build Phase - Add Run Script Build Phase,建立一个新的Run Script Build Phase。
双击Run Script,重命名脚本的名字。此次命名为MultiPlatform Build。
在脚本文本框中粘贴下面的Bash脚本代码:
set -e # If we're already inside this script then dieif [ -n "$RW_MULTIPLATFORM_BUILD_IN_PROGRESS" ]; then exit 0fiexport 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"复制代码
set –e确保脚本的任何地方执行失败,则整个脚本都执行失败。这样能够避免让你建立一个部分有效的framework。
接着,用RW_MULTIPLATFORM_BUILD_IN_PROGRESS变量决定是否循环调用脚本,若是有该变量,则退出。
而后设定一些变量。该framework的名字与项目的名字同样。也就是RWUIControls,另外,静态lib的名字是libRWUIControls.a。
接下来,用脚本设置一些函数,这些函数一会项目就会用到,把下面的代码加到脚本的底部。
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}"复制代码
build_static_library把SDK做为参数,例如iPhone7.0,而后建立静态lib,大多数参数直接传到当前的构建工做中来,
不一样的是设置ONLY_ACTIVE_ARCH来确保为当前SDK构建全部的结构。
make_fat_library使用lipo将两个静态库合并为一个,其参数为两个静态库和结果的输出位置。从这里了解更多关于lipo的知识。
为了使用这两个方法,接下来脚本将定义更多你要用到的变量,你须要知道其余SDK是什么,
例如,iphoneos7.0应该对应iphonesimulator7.0,反过来也同样。你也须要找到该SDK对应的编译目录。
把下面的代码添加到脚本的底部。
# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK nameif [[ "$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 1fi # 2 - Extract the version from the SDKif [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]; then RW_SDK_VERSION=${BASH_REMATCH[1]}else echo "Could not find sdk version from SDK_NAME: $SDK_NAME" exit 1fi # 3 - Determine the other platformif [ "$RW_SDK_PLATFORM" == "iphoneos" ]; then RW_OTHER_PLATFORM=iphonesimulatorelse RW_OTHER_PLATFORM=iphoneosfi # 4 - Find the build directoryif [[ "$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 1fi复制代码
上面四句声明都很是类似,都是使用字符串比较和正则表达式来肯定RW_OTHER_PLATFORM和RW_OTHER_BUILT_PRODUCTS_DIR。
详细解释一下上面四句声明:
SDK_NAME将指代iphoneos7.0和iphonesimulator6.1,这个正则表达式取出字符串开头不包含数字的那些字符,所以,其结果是iphoneos 或 iphonesimulator。
这个正则表达式取出SDK_NAME中表示版本用的数字,7.0或6.1等。
这里用简单的字符串比较来将iphonesimulator 转换为iphoneos,反过来也同样。
从构建好的工程的目录路径的末尾找出平台名称,将其替换为其余平台。这样能够确保为其余平台构建的目录能够被找到。这是将两个静态库合并的关键部分。
如今你能够启动脚本为其余平台编译,而后获得合并两静态库的结果。
在脚本最后添加下面的代码:
# 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_64if [ "$RW_SDK_PLATFORM" == "iphonesimulator" ]; then build_static_library "${SDK_NAME}"fi # Join the 2 static libs into 1 and push into the .frameworkmake_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}"复制代码
首先,调用你以前定义好的函数为其余平台编译
若是你如今正在为模拟器编译,那么Xcode会默认只在该系统对应的结构下编译,例如i386 或 x86_64。为了在这两个结构下都进行编译,这里调用了build_static_library,基于iphonesimulator SDK从新编译,确保这两个结构都进行了编译。
最后调用make_fat_library将在当前编译目录下的静态lib同在其余目录下地lib合并,依次实现支持多结构的FAT静态库。这个被放到了framework中。
脚本的最后是简单的拷贝命令,将下面代码添加到脚本最后:
# Ensure that the framework is present in both platform's build directoriescp -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 desktopditto "${RW_FRAMEWORK_LOCATION}" "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.framework"复制代码
第一条命令确保framework在全部平台的编译目录中都存在。
第二条将完成的framework拷贝到用户的桌面上,这一步是可选的,但我发现这样作能够很方便的存取framework。
选择Framework集合方案(aggregate scheme),按下cmd+B编译该framework。
这一步将构建并在你的桌面上存放一个ZYResource.framework
为了检查一下咱们的多平台编译真的成功了,启动终端,导航到桌面上的framework,像下面同样:
$ cd ~/Desktop/ZYResource.framework$ ZYResource.framework xcrun lipo -info ZYResource复制代码
第一条指令导航到framework中,第二行使用lipo指令从ZYResource静态库中获得须要的信息,这将列出存在于该库中的全部片断。
只须要在拖进工程,包含你的头文件就能使用了!
至于图片的处理,本人研究了挺久并无研究明白,这里给出原文地址,你们能够去看看,若是有研究明白的,还望告诉本人!
原文地址:www.cocoachina.com/ios/2015012…
注:本文只是本人作一个备忘,不喜勿喷!