二、建立依賴子產品工程的一個示例
開發中還有一種場景,公司可能有一組App,這些App中可能有很多相似的子產品,例如某些應用程式分為使用者端和老闆端,他們都有相同的登入子產品,我們可以使用workspace來進行項目和子產品的管理。建立一個檔案夾命名為Projects,在其中建立一個workspace檔案,也命名為Projects。在workspace檔案中建立兩個項目工程和一個動态庫工程,在建立時,注意選擇加入workspace,如下圖:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yMyEjZ4kjMmNTMjlDM0kTYmRGZ0QTZ1cjZwkjNzQ2Yw8CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
建立的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産品,但其中一定有某些基礎子產品是可以複用的,除了進行靜态庫封裝或動态庫封裝外,進行并列工程化也是一種很好的選擇,這樣可以同步開發,疊代更快。除了公用的子產品,還有一些子產品可能并不公用但是确可以獨立開發,例如資訊類項目中可能會有使用者子產品,社交子產品和内容子產品,将這些拆分為項目内的子工程可以使項目的結構更加清晰,子產品化測試也更容易進行。
最後,僅僅項目結構上的子產品化遠遠達不到真正實作元件化項目的要求,遵守協定為标準,以函數式程式設計為方式,全局着眼的接口設計與路由規劃,良好的程式設計習慣與統一的代碼風格,這種代碼層面的項目開發管理才真正任重道遠。後面有時間我會陸續通過其他部落格來探讨這些問題。希望一起交流,共同學習!