摘要: 使用靜態庫或動態庫來將開發中的獨立模塊抽離的方法。
將iOS項目進行子工程化
在iOS項目開發中,隨著項目的越來越大,工程的結構化會變差,編譯的速度也會越來越慢。使用靜態庫或動態庫的方式來構建子工程不僅可以加快項目的編譯速度,從結構上,也優化了項目的組織。有兩種方式來來對項目進行子工程化,可以在項目中創建子項目,也可以創建並列的項目,建立項目依賴。需要注意,無論哪種方式,你都應該盡量保證子工程不要用到主工程中的內容,如果必須這樣做,你可以采用代理或其他回調編程方式來轉交給主工程自己處理。
一、創建子工程的一個示例
使用Xcode新建一個命名為ProjectDemo的工程,在ProjectDemo工程中再次新建一個framework庫工程,點擊新建文件中的Project...選項,選擇其中的Cocoa Touch Framework工程(創建Cocoa Touch Static Library則會打包為靜態庫)。
將新創建的工程命名為LoginLib,用來模擬項目中的登錄模塊。需要注意,新建工程時,需要將其加入ProjectDemo組,如下圖:
對於創建的LoginLib工程,你可以創建一個LoginLib.h頭文件用來公開外界需要使用到的類,便於演示,我在裡面創建一個視圖控制器和一個類別工具類,結構如下:
配置LoginLib的頭文件選項,將外界需要用到的進行公開,如下:
現在,分別編譯LoginLib工程和ProjectDemo工程,都沒有問題,但是你依然無法在ProjectDemo工程中使用LoginLib庫中的內容,你需要建立主子工程的關聯,在ProjectDemo工程中建立依賴工程並接入動態庫,如下所示:
配置Target Dependencies的作用是確保每次主工程編譯前都會先對所依賴的工程進行編譯。之後,在ProjectDemo工程中導入LoginLib相關頭文件即可使用其中功能。
注意,如果報錯找不到頭文件,你需要設置一下頭文件的尋找路徑,在ProjectDemo的Build Setting中搜索header,如下圖
設置Header Search Paths如下即可。
二、創建依賴模塊工程的一個示例
開發中還有一種場景,公司可能有一組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 << "$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產品,但其中一定有某些基礎模塊是可以復用的,除了進行靜態庫封裝或動態庫封裝外,進行並列工程化也是一種很好的選擇,這樣可以同步開發,迭代更快。除了公用的模塊,還有一些模塊可能並不公用但是確可以獨立開發,例如資訊類項目中可能會有用戶模塊,社交模塊和內容模塊,將這些拆分為項目內的子工程可以使項目的結構更加清晰,模塊化測試也更容易進行。
最後,僅僅項目結構上的模塊化遠遠達不到真正實現組件化項目的要求,遵守協議為標准,以函數式編程為方式,全局著眼的接口設計與路由規劃,良好的編程習慣與統一的代碼風格,這種代碼層面的項目開發管理才真正任重道遠。後面有時間我會陸續通過其他博客來探討這些問題。希望一起交流,共同學習!