將iOS項目進行子工程化

將iOS項目進行子工程化

    在iOS項目開發中,隨着項目的愈來愈大,工程的結構化會變差,編譯的速度也會愈來愈慢。使用靜態庫或動態庫的方式來構建子工程不只能夠加快項目的編譯速度,從結構上,也優化了項目的組織。有兩種方式來來對項目進行子工程化,能夠在項目中建立子項目,也能夠建立並列的項目,創建項目依賴。須要注意,不管哪一種方式,你都應該儘可能保證子工程不要用到主工程中的內容,若是必須這樣作,你能夠採用代理或其餘回調編程方式來轉交給主工程本身處理。shell

1、建立子工程的一個示例

    使用Xcode新建一個命名爲ProjectDemo的工程,在ProjectDemo工程中再次新建一個framework庫工程,點擊新建文件中的Project...選項,選擇其中的Cocoa Touch Framework工程(建立Cocoa Touch Static Library則會打包爲靜態庫)。編程

將新建立的工程命名爲LoginLib,用來模擬項目中的登陸模塊。須要注意,新建工程時,須要將其加入ProjectDemo組,以下圖:bash

對於建立的LoginLib工程,你能夠建立一個LoginLib.h頭文件用來公開外界須要使用到的類,便於演示,我在裏面建立一個視圖控制器和一個類別工具類,結構以下:app

配置LoginLib的頭文件選項,將外界須要用到的進行公開,以下:iphone

如今,分別編譯LoginLib工程和ProjectDemo工程,都沒有問題,可是你依然沒法在ProjectDemo工程中使用LoginLib庫中的內容,你須要創建主子工程的關聯,在ProjectDemo工程中創建依賴工程並接入動態庫,以下所示:模塊化

配置Target Dependencies的做用是確保每次主工程編譯前都會先對所依賴的工程進行編譯。以後,在ProjectDemo工程中導入LoginLib相關頭文件便可使用其中功能。函數式編程

注意,若是報錯找不到頭文件,你須要設置一下頭文件的尋找路徑,在ProjectDemo的Build Setting中搜索header,以下圖svn

設置Header Search Paths以下便可。函數

2、建立依賴模塊工程的一個示例

    開發中還有一種場景,公司可能有一組App,這些App中可能有不少類似的模塊,例如某些應用程序分爲用戶端和老闆端,他們都有相同的登陸模塊,咱們可使用workspace來進行項目和模塊的管理。新建一個文件夾命名爲Projects,在其中建立一個workspace文件,也命名爲Projects。在workspace文件中新建兩個項目工程和一個動態庫工程,在建立時,注意選擇加入workspace,以下圖:工具

建立的3個工程分別命名爲UserProject,BossProject和LoginLib,結構以下:

相似咱們的第一個示例,配置完頭文件路徑後,將動態庫引入UserProject和BossProject工程,即實現了LoginLib模塊的複用。

3、若是子工程只可以有資源文件

    若是子工程中有資源文件,不管是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!"

4、一點小體悟

    本博客所討論的,只是從工程結構上實現模塊化與組件化的方式,一個公司可能會有不少個App產品,但其中必定有某些基礎模塊是能夠複用的,除了進行靜態庫封裝或動態庫封裝外,進行並列工程化也是一種很好的選擇,這樣能夠同步開發,迭代更快。除了公用的模塊,還有一些模塊可能並不公用可是確能夠獨立開發,例如資訊類項目中可能會有用戶模塊,社交模塊和內容模塊,將這些拆分爲項目內的子工程可使項目的結構更加清晰,模塊化測試也更容易進行。

    最後,僅僅項目結構上的模塊化遠遠達不到真正實現組件化項目的要求,遵照協議爲標準,以函數式編程爲方式,全局着眼的接口設計與路由規劃,良好的編程習慣與統一的代碼風格,這種代碼層面的項目開發管理才真正任重道遠。後面有時間我會陸續經過其餘博客來探討這些問題。但願一塊兒交流,共同窗習!

相關文章
相關標籤/搜索