二、创建依赖模块工程的一个示例
开发中还有一种场景,公司可能有一组App,这些App中可能有很多相似的模块,例如某些应用程序分为用户端和老板端,他们都有相同的登录模块,我们可以使用workspace来进行项目和模块的管理。新建一个文件夹命名为Projects,在其中创建一个workspace文件,也命名为Projects。在workspace文件中新建两个项目工程和一个动态库工程,在创建时,注意选择加入workspace,如下图:
创建的3个工程分别命名为UserProject,BossProject和LoginLib,结构如下:
类似我们的第一个示例,配置完头文件路径后,将动态库引入UserProject和BossProject工程,即实现了LoginLib模块的复用。
三、如果子工程只能够有资源文件
如果子工程中有资源文件,无论是plist文件还是图片素材,在主工程调用动态库时,这些文件都是没有被打包进来的。有两种方式来处理这个问题:
1.将资源文件打包成Bundle包,从包中取资源
Xcode可以创建Bundle资源包,这种文件创建后编译时会自动打包成Bundle文件。需要注意,Xcode只能创建MacOS下的Bundle模板,创建后需要将编译选项设置为iOS。这种方式有很大的弊端,首先主工程必须引入编译后的Bundle包,如果每次新增或修改资源,都要重新打包和导入。其次,在子工程中对素材进行使用时,都必须以Bundle为媒介,增加的复杂度。
2.使用shell拷贝资源脚本
这种方式每次在编译时都会将资源进行拷贝,类似CocoaPods的管理模式,推荐使用。例如,在主工程的编译选项中新建一个脚本文件,如图:
编写如下脚本代码即可:
#!/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产品,但其中一定有某些基础模块是可以复用的,除了进行静态库封装或动态库封装外,进行并列工程化也是一种很好的选择,这样可以同步开发,迭代更快。除了公用的模块,还有一些模块可能并不公用但是确可以独立开发,例如资讯类项目中可能会有用户模块,社交模块和内容模块,将这些拆分为项目内的子工程可以使项目的结构更加清晰,模块化测试也更容易进行。
最后,仅仅项目结构上的模块化远远达不到真正实现组件化项目的要求,遵守协议为标准,以函数式编程为方式,全局着眼的接口设计与路由规划,良好的编程习惯与统一的代码风格,这种代码层面的项目开发管理才真正任重道远。后面有时间我会陆续通过其他博客来探讨这些问题。希望一起交流,共同学习!