将iOS项目进行子工程化

在iOS项目开发中,随着项目的越来越大,工程的结构化会变差,编译的速度也会越来越慢。使用静态库或动态库的方式来构建子工程不仅可以加快项目的编译速度,从结构上,也优化了项目的组织。有两种方式来来对项目进行子工程化,可以在项目中创建子项目,也可以创建并列的项目,建立项目依赖。需要注意,无论哪种方式,你都应该尽量保证子工程不要用到主工程中的内容,如果必须这样做,你可以采用代理或其他回调编程方式来转交给主工程自己处理。

一、创建子工程的一个示例

使用Xcode新建一个命名为ProjectDemo的工程,在ProjectDemo工程中再次新建一个framework库工程,点击新建文件中的Project...选项,选择其中的Cocoa Touch Framework工程(创建Cocoa Touch Static Library则会打包为静态库)。

image

将新创建的工程命名为LoginLib,用来模拟项目中的登录模块。需要注意,新建工程时,需要将其加入ProjectDemo组,如下图:

image

对于创建的LoginLib工程,你可以创建一个LoginLib.h头文件用来公开外界需要使用到的类,便于演示,我在里面创建一个视图控制器和一个类别工具类,结构如下:

image

配置LoginLib的头文件选项,将外界需要用到的进行公开,如下:

image

现在,分别编译LoginLib工程和ProjectDemo工程,都没有问题,但是你依然无法在ProjectDemo工程中使用LoginLib库中的内容,你需要建立主子工程的关联,在ProjectDemo工程中建立依赖工程并接入动态库,如下所示:

image

配置Target Dependencies的作用是确保每次主工程编译前都会先对所依赖的工程进行编译。之后,在ProjectDemo工程中导入LoginLib相关头文件即可使用其中功能。

注意,如果报错找不到头文件,你需要设置一下头文件的寻找路径,在ProjectDemo的Build Setting中搜索header,如下图

image

设置Header Search Paths如下即可。

image

二、创建依赖模块工程的一个示例

开发中还有一种场景,公司可能有一组App,这些App中可能有很多相似的模块,例如某些应用程序分为用户端和老板端,他们都有相同的登录模块,我们可以使用workspace来进行项目和模块的管理。新建一个文件夹命名为Projects,在其中创建一个workspace文件,也命名为Projects。在workspace文件中新建两个项目工程和一个动态库工程,在创建时,注意选择加入workspace,如下图:

image

创建的3个工程分别命名为UserProject,BossProject和LoginLib,结构如下:

image

类似我们的第一个示例,配置完头文件路径后,将动态库引入UserProject和BossProject工程,即实现了LoginLib模块的复用。

三、如果子工程只能够有资源文件

如果子工程中有资源文件,无论是plist文件还是图片素材,在主工程调用动态库时,这些文件都是没有被打包进来的。有两种方式来处理这个问题:

1.将资源文件打包成Bundle包,从包中取资源

Xcode可以创建Bundle资源包,这种文件创建后编译时会自动打包成Bundle文件。需要注意,Xcode只能创建MacOS下的Bundle模板,创建后需要将编译选项设置为iOS。这种方式有很大的弊端,首先主工程必须引入编译后的Bundle包,如果每次新增或修改资源,都要重新打包和导入。其次,在子工程中对素材进行使用时,都必须以Bundle为媒介,增加的复杂度。

2.使用shell拷贝资源脚本

这种方式每次在编译时都会将资源进行拷贝,类似CocoaPods的管理模式,推荐使用。例如,在主工程的编译选项中新建一个脚本文件,如图:

image

编写如下脚本代码即可:

#!/bin/sh
# set -e

mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"

install_resource()
{
  if [[ "$1" = /* ]] ; then
    RESOURCE_PATH="$1"
  fi
  if [[ ! -e "$RESOURCE_PATH" ]] ; then
    cat << EOM
error: Resource "$RESOURCE_PATH" not found.
EOM
    exit 1
  fi
  case $RESOURCE_PATH in
    *.storyboard)
      echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
      ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
      ;;
    *.xib)
      echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
      ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
      ;;
    *.framework)
      echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
      mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
      echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
      rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
      ;;
    *.xcdatamodel)
      echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
      xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
      ;;
    *.xcdatamodeld)
      echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
      xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
      ;;
    *.xcmappingmodel)
      echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
      xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
      ;;
    *.xcassets)
      echo "all xcassets will compile later!"
      ;;
    *)
      echo "$RESOURCE_PATH"
      echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
      ;;
  esac
}

install_project_resouces()
{
  PROJECT_RESOURCE_DIR="${PROJECT_DIR}/../$1"
  
  if [[ ! -e "${PROJECT_RESOURCE_DIR}" ]]; then
    cat << EOM
error: PROJECT_RESOURCE_DIR "${PROJECT_RESOURCE_DIR}" not found
EOM
    exit 1
  fi

  echo "copy resources in ${PROJECT_RESOURCE_DIR} to ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"

  ALL_RESOURCES=()

  FIND_ALL_RESOURCES=$(find "$PROJECT_RESOURCE_DIR" -iname "*.xcassets" -o -iname "*.xib" -o -iname "*.storyboard" -o -iname "*.plist" ! -iname "Info.plist")
  while read line; do
      ALL_RESOURCES+=("$line")
  done <<<"$FIND_ALL_RESOURCES"

  RESOURCES_TO_COPY="${PROJECT_RESOURCE_DIR}/resources-to-copy.txt"
  > "$RESOURCES_TO_COPY"

  case "${TARGETED_DEVICE_FAMILY}" in
    1,2)
      TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
    ;;
    1)
      TARGET_DEVICE_ARGS="--target-device iphone"
    ;;
    2)
      TARGET_DEVICE_ARGS="--target-device ipad"
    ;;
    3)
      TARGET_DEVICE_ARGS="--target-device tv"
    ;;
    *)
      TARGET_DEVICE_ARGS="--target-device mac"
    ;;
  esac

  for i in ${ALL_RESOURCES[@]}; do
    install_resource "${i}"
  done

  mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
  rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
  if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
    mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
    rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
  fi

  rm -f "$RESOURCES_TO_COPY"
}

for module in ${MODULES}; do
  install_project_resouces "${module}"
done

XCASSETS_SEARCH_DIR="${PROJECT_DIR}/.."
XCASSET_FILES=()

if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ]
then
  # Find all other xcassets (this unfortunately includes those of path pods and other targets).
  ALL_XCASSETS=$(find "$XCASSETS_SEARCH_DIR" -iname "*.xcassets" -type d)
  while read line; do
    if [[ $line != "${PODS_ROOT}*" ]]; then
      XCASSET_FILES+=("$line")
    fi
  done <<<"$ALL_XCASSETS"

  echo "compile all xcassets: ${XCASSET_FILES[@]}"
  printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
fi

echo "all done!"

四、一点小体悟

本博客所讨论的,只是从工程结构上实现模块化与组件化的方式,一个公司可能会有很多个App产品,但其中一定有某些基础模块是可以复用的,除了进行静态库封装或动态库封装外,进行并列工程化也是一种很好的选择,这样可以同步开发,迭代更快。除了公用的模块,还有一些模块可能并不公用但是确可以独立开发,例如资讯类项目中可能会有用户模块,社交模块和内容模块,将这些拆分为项目内的子工程可以使项目的结构更加清晰,模块化测试也更容易进行。

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

推荐阅读更多精彩内容