2018 Xcode8 创建自己的Framework(一)

新年定个小目标,比如先学习组件化开发o( ̄▽ ̄)d
最近研究组件化开发,涉及自己生成静态库,创建Framework。查询资料的过程中,发现一个超级实用的译文教程,然而这个超实用的译文教程因其发表于2015年,部分内容已有出入(尽管不大😂😂😂),特在这里边学习边记录,因理解能力有限,部分内容仍有引用自原文。

尊重原创作者,原译文地址在此:iOS开发——创建你自己的Framework

原文地址在此:How to Create a Framework for iOS

这里是学习记录过程,如果有大佬能不吝赐教,荣幸之至😍😍😍

文中所涉及工程资源:

1.一个可重用的旋钮(即我们学习过程中需要封装的控件),下载地址:点此 <很重要>

2.对此控件开发过程感兴趣的朋友可以移步原译文:iOS自定义控件教程:制作一个可重用的旋钮

概念补充:
库,分为静态库和动态库,本质上来说是可执行代码的集合。非独立程序,为其他程序提供服务。

  1. 静态库,存在两种形式.a 和.framework。在实际使用当中,两者的关系可以简单的用一个公式标明
    .framework = .a + .h
  2. 动态库,存在.framework和.tbd两种形式。这个两种形式在系统库中很常见,苹果在 iOS8 以后为我们提供了Cocoa Touch Framework 来有条件的创建和使用动态库

创建一个静态库工程

打开 Xcode,新建一个静态库工程,如下图示

Static Library

创建完工程之后,会发现自动创建了一个.h和.m 文件。为了方便Framework使用,集中在该.h 文件中导入想要公开的类,在这里无需实现.m,所以删除.m 文件。到这里,我们可在工程放入我们想要封装的工具或者控件了。

因为这个学习教程是一个封装控件作为主要工作的,所以我们要添加一些的必要的依赖,我这里添加的依赖如下如所示,实际开发中可能还需要添加别的依赖,视情况酌情添加。这里记得我们要把导入工程的文件,替换#import<Foundation/Foundation.h> 为#import<UIKit/UIKit.h>哦

依赖添加

在当前的 Build Phases ,还需要添加一个Header Phase,依次选择导航栏Editor—>Add Build Phase —> Add Headers Build Phase ,

Headers Phases

添加完成后,展开 Header Phase,将.h 文件拖入 public。

在这里引用原译文对 headers 里面的几个组头名进行解释,应该会帮助了解到这里的作用:

Note:在你弄清楚之前,这三个组的名称可能会让你迷惑,Public是你期望的,Private下的头文件依然是可以暴露出来的,因此名字可能有些误导。讽刺的是,在Project下的头文件对你的工程来说才是“私有”的,因此,你将会更多地希望你的头文件或者在Public下,或者在Project下。

敲黑板在这里要注意到的是,所有导入到该.h 中的文件必须也是对外公开的,也就是导入到.h的文件也必须出现在 public这个地方。否则,导入到工程里面编译报错

在这里我们还需要把要暴露的控件头文件拖到 public 当中哦。

创建 UI 控件

这里我们暂时只学习创建静态库,控件直接从原作的 Git Hub 上下载并将所需文件拖进来的,这里我选择了原作主要的控件类。实际开发中,可以自行添加(内心 OS:在这个静态库里开发,又看不到控件的样子!!!别着急,这么影响开发的问题原作已为我们考虑到,后面会介绍😎😎😎)

当你把这几个文件拖进来后,你就会发现,它们的头文件已经被放入 Headers 的 project 中,

光是把要暴露的头文件放在 public 中,还无法是使用者找到这个控件的,所以我们要在工程创建时我们留下的那个.h 文件中,导入头文件哦。这样,在实际使用当中,它的导入会是这个样子的

#import <ToolsFoundation/ToolsFoundation.h>

就像我们平时导入 UIKit 一样,

#import <UIKit/UIKit.h>

配置 Build Settings

这里我们需要在工程名静态库里的 Build settings 做一些事情,

这里我们搜索 public,在 Public Headers Folder Path 这个地方双击,修改内容为如下:

include/$(PROJECT_NAME)

这里在原译文中所给出的理由是这样的:

你需要提供一个目录名,表示你将把拷贝的公共头文件存放到哪里。这样确保当你使用静态库的时候可以定位到相关头文件的位置

我们在开发的过程中,可能存在一些实际使用时永远不会被执行的代码和debug相关内容。这里对使用者用处不大,我们可以禁用掉这些功能,仍然在 Build Setings 中,搜索以下内容,做出对应的设置

• Dead Code Stripping 设置为NO 延伸: 《Dead Code Stripping》

• Strip Debug Symbol During Copy 全部设置为NO

• Strip Style 设置为Non-Global Symbols

选择 target 设备,到这里编译运行,如果没错那是好的,如果有错误看看提示,视具体情况解决。编译成功后,可以发现之前 product 文件夹下的.a 文件名由红变黑了。点击右键在访达中显示.a文件所在位置

在这里可以看到我们之前想要公开的头文件和 .a 文件都已经生成了,怎么样是不是和我们之前用的其他的 Framework 越来越像了呢,有暴露的头文件和.a 静态库。But~这里的.framework文件呢?哈哈哈,欲知 Framework 何在且看下文。。。

创建一个联调工程

联调工程,什么鬼?就是我们又创建一个新的工程,工程里面我们会用到我们的刚才静态库文件,在这里,我们便于在静态库工程里开发,联调工程里测试。怎么样?哈哈,调试不要太方便哦\( ̄︶ ̄)/。

这里的操作步骤,我们要在刚刚静态库工程所在的文件家里同样创建一个新的开发工程哦,single view application 哦。关闭之前的静态库工程,把静态库文件夹中.xcodeproj工程文件拖到开发工程。

note:这里需要注意的是这个静态库工程拖入进来后就算是打开了,这个工程无法在别的地方开启了。

这里面,先删除了默认创建的 viewcontroller的.h、.m 和 main.storyboard 文件。从下载的控件文件夹中,直接把原来RWViewController.h、RWViewController.m 和 mian.storyboard 拖进来。(当然我这里是偷懒步骤,实际开发中可以结合自己要开发的工程进行测试)。拖进来之后,在下图位置中添加控件的静态库文件。

在你所需要测试的文件中import 静态库暴露的头文件,现在可以这样导入了

惊不惊喜,意不意外,看着有点样子了,哈哈哈哈哈

创建 Framework

Framework 本身的结构就是静态库加一组头文件。你可能觉得已经创建好,毕竟在 .a 文件所在文件中你看到了 include 的里面已经有了一组头文件。

这里还需要记录 Framework 的两个特点:

1. 目录结构。Framework有一个能被Xcode识别的特殊的目录结构,你将会创建一个build task,由它来为你创建这种结构。

2. 片段(Slice)。目前为止,当你构建库时,仅仅考虑到当前需要的结构(architecture)。例如,i386、arm7等,为了让一个framework更有用,对于每一个运行framework的结构,该framework都需要构建这种结构。一会你就会创建一个新的工程,构建所有需要的结构,并将它们包含到framework中。

一下是一个来自原文的 Framework 结构表

我们可以通过添加一段脚本,来创建这种结构。添加方法,看图👈👈👈

image

看清楚了嘛,红色框选位置1,没错就是我们要开发的静态库工程文件,点它喵的,然后target控件静态库Build Phases,点它喵的。按照图示步骤来,都 👌的,我这里之所以出现不可选中,是因为我已经点了创建了,嘻嘻~创建好之后我们把它的名字Run Script改为 Build Framework。然后把如下代码贴进去:


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"

现在选择静态库工程,选择iOS device,Build 一下。在静态库工程的 product 里面找到生成的.a文件。右键在 finder 里面显示看看

image

在这里是不是看着离目标一步一步接近了?哈哈哈,这里我们已经完成大部分的工作了。然鹅,这里还没有静态 lib 文件。这就是下一步我们将要做的

多架构支持

简单的说 app 可以运行在不同架构的设备上,那我们的 Framework 也要做到支持多架构哦。

arm7: 在最老的支持iOS7的设备上使用

arm7s: 在iPhone5和5C上使用

arm64: 运行于iPhone5S的64位 ARM 处理器 上

i386: 32位模拟器上使用

x86_64: 64为模拟器上使用

下面的一段 js 代码将帮助我们实现多架构的支持, 并将模拟器和真机的framework合并到一起了,详细原理,我还没吃透(看不懂。。。)所以先上步骤。为了方便向 run script统一添加将每个步骤的代码放到一起了,不过没关系,代码中有各个步骤英文注解

1. 你可以用我们上文用到的开发工程(联调工程),也可以用静态库工程。去在 target 里面新建一个 iOS/Framework。在这里我直接在开发工程里面创建了,以下是在开发工程(联调工程)里操作的一些步骤:

如上图所示,点击 target 下的小加号,选择创建一个 Framework 工程,在新建的库工程的 Build phases 里面添加依赖。这个依赖就是我们之前生成.a静态库。

接下来,仍是在 Build phases 中添加一段脚本语言,进行到这一步,真是完全懵逼过来的,那些脚本看不懂啊(╥╯^╰╥),只能复制粘贴了。添加脚本的方法仍与前文提到的相同,不再赘述。仍旧是双击 run script ,改名字为MultiPlatform Build。

添加如下脚本代码:


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"

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 - 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

# 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}"

# 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,编译


编译

编译成功后,你将发现在桌面上生成了一个Framework



接下来我们需要验证下我们生成的这个 Framework 是否真的支持多架构。
打开终端,cd 这个 .framework 键入一下命令
lipo -info xxx.framework
lipo.png

得到如上信息,表明成功了!(づ。◕‿‿◕。)づ

实践是检验真理的唯一标准,接下来你可以自己创建一个新的工程,去把这个生成的.framework导入工程中,然后测试一下吧!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容