將iOS項目進行子工程化
摘要: 使用靜態(tài)庫或動態(tài)庫來將開發(fā)中的獨立模塊抽離的方法列疗。
將iOS項目進行子工程化
在iOS項目開發(fā)中赘艳,隨著項目的越來越大丁稀,工程的結構化會變差应民,編譯的速度也會越來越慢话原。使用靜態(tài)庫或動態(tài)庫的方式來構建子工程不僅可以加快項目的編譯速度夕吻,從結構上,也優(yōu)化了項目的組織繁仁。有兩種方式來來對項目進行子工程化涉馅,可以在項目中創(chuàng)建子項目,也可以創(chuàng)建并列的項目黄虱,建立項目依賴稚矿。需要注意,無論哪種方式捻浦,你都應該盡量保證子工程不要用到主工程中的內容晤揣,如果必須這樣做,你可以采用代理或其他回調編程方式來轉交給主工程自己處理默勾。
一碉渡、創(chuàng)建子工程的一個示例
使用Xcode新建一個命名為ProjectDemo的工程聚谁,在ProjectDemo工程中再次新建一個framework庫工程母剥,點擊新建文件中的Project...選項,選擇其中的Cocoa Touch Framework工程(創(chuàng)建Cocoa Touch Static Library則會打包為靜態(tài)庫)形导。
將新創(chuàng)建的工程命名為LoginLib环疼,用來模擬項目中的登錄模塊。需要注意朵耕,新建工程時炫隶,需要將其加入ProjectDemo組,如下圖:
對于創(chuàng)建的LoginLib工程阎曹,你可以創(chuàng)建一個LoginLib.h頭文件用來公開外界需要使用到的類伪阶,便于演示,我在里面創(chuàng)建一個視圖控制器和一個類別工具類处嫌,結構如下:
配置LoginLib的頭文件選項栅贴,將外界需要用到的進行公開,如下:
現(xiàn)在熏迹,分別編譯LoginLib工程和ProjectDemo工程檐薯,都沒有問題,但是你依然無法在ProjectDemo工程中使用LoginLib庫中的內容注暗,你需要建立主子工程的關聯(lián)坛缕,在ProjectDemo工程中建立依賴工程并接入動態(tài)庫,如下所示:
配置Target Dependencies的作用是確保每次主工程編譯前都會先對所依賴的工程進行編譯捆昏。之后赚楚,在ProjectDemo工程中導入LoginLib相關頭文件即可使用其中功能。
注意骗卜,如果報錯找不到頭文件直晨,你需要設置一下頭文件的尋找路徑搀军,在ProjectDemo的Build Setting中搜索header,如下圖
設置Header Search Paths如下即可勇皇。
二罩句、創(chuàng)建依賴模塊工程的一個示例
開發(fā)中還有一種場景,公司可能有一組App敛摘,這些App中可能有很多相似的模塊门烂,例如某些應用程序分為用戶端和老板端,他們都有相同的登錄模塊兄淫,我們可以使用workspace來進行項目和模塊的管理屯远。新建一個文件夾命名為Projects,在其中創(chuàng)建一個workspace文件捕虽,也命名為Projects慨丐。在workspace文件中新建兩個項目工程和一個動態(tài)庫工程,在創(chuàng)建時泄私,注意選擇加入workspace房揭,如下圖:
創(chuàng)建的3個工程分別命名為UserProject,BossProject和LoginLib晌端,結構如下:
類似我們的第一個示例捅暴,配置完頭文件路徑后,將動態(tài)庫引入UserProject和BossProject工程咧纠,即實現(xiàn)了LoginLib模塊的復用蓬痒。
三、如果子工程只能夠有資源文件
如果子工程中有資源文件漆羔,無論是plist文件還是圖片素材梧奢,在主工程調用動態(tài)庫時,這些文件都是沒有被打包進來的演痒。有兩種方式來處理這個問題:
1.將資源文件打包成Bundle包亲轨,從包中取資源
Xcode可以創(chuàng)建Bundle資源包,這種文件創(chuàng)建后編譯時會自動打包成Bundle文件嫡霞。需要注意瓶埋,Xcode只能創(chuàng)建MacOS下的Bundle模板,創(chuàng)建后需要將編譯選項設置為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!"
四属瓣、一點小體悟
本文所討論的,只是從工程結構上實現(xiàn)模塊化與組件化的方式讯柔,一個公司可能會有很多個App產品抡蛙,但其中一定有某些基礎模塊是可以復用的,除了進行靜態(tài)庫封裝或動態(tài)庫封裝外魂迄,進行并列工程化也是一種很好的選擇粗截,這樣可以同步開發(fā),迭代更快捣炬。除了公用的模塊熊昌,還有一些模塊可能并不公用但是確可以獨立開發(fā),例如資訊類項目中可能會有用戶模塊遥金,社交模塊和內容模塊浴捆,將這些拆分為項目內的子工程可以使項目的結構更加清晰蒜田,模塊化測試也更容易進行稿械。
最后,僅僅項目結構上的模塊化遠遠達不到真正實現(xiàn)組件化項目的要求冲粤,遵守協(xié)議為標準美莫,以函數(shù)式編程為方式,全局著眼的接口設計與路由規(guī)劃梯捕,良好的編程習慣與統(tǒng)一的代碼風格厢呵,這種代碼層面的項目開發(fā)管理才真正任重道遠。后面有時間我會陸續(xù)通過其他博客來探討這些問題傀顾。希望一起交流襟铭,共同學習!