天天看點

将iOS項目進行子工程化(二)

二、建立依賴子產品工程的一個示例

    開發中還有一種場景,公司可能有一組App,這些App中可能有很多相似的子產品,例如某些應用程式分為使用者端和老闆端,他們都有相同的登入子產品,我們可以使用workspace來進行項目和子產品的管理。建立一個檔案夾命名為Projects,在其中建立一個workspace檔案,也命名為Projects。在workspace檔案中建立兩個項目工程和一個動态庫工程,在建立時,注意選擇加入workspace,如下圖:

将iOS項目進行子工程化(二)

建立的3個工程分别命名為UserProject,BossProject和LoginLib,結構如下:

将iOS項目進行子工程化(二)

類似我們的第一個示例,配置完頭檔案路徑後,将動态庫引入UserProject和BossProject工程,即實作了LoginLib子產品的複用。

三、如果子工程隻能夠有資源檔案

    如果子工程中有資源檔案,無論是plist檔案還是圖檔素材,在主工程調用動态庫時,這些檔案都是沒有被打包進來的。有兩種方式來處理這個問題:

1.将資源檔案打包成Bundle包,從包中取資源

    Xcode可以建立Bundle資源包,這種檔案建立後編譯時會自動打包成Bundle檔案。需要注意,Xcode隻能建立MacOS下的Bundle模闆,建立後需要将編譯選項設定為iOS。這種方式有很大的弊端,首先主工程必須引入編譯後的Bundle包,如果每次新增或修改資源,都要重新打包和導入。其次,在子工程中對素材進行使用時,都必須以Bundle為媒介,增加的複雜度。

2.使用shell拷貝資源腳本

    這種方式每次在編譯時都會将資源進行拷貝,類似CocoaPods的管理模式,推薦使用。例如,在主工程的編譯選項中建立一個腳本檔案,如圖:

将iOS項目進行子工程化(二)

編寫如下腳本代碼即可:

#!/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産品,但其中一定有某些基礎子產品是可以複用的,除了進行靜态庫封裝或動态庫封裝外,進行并列工程化也是一種很好的選擇,這樣可以同步開發,疊代更快。除了公用的子產品,還有一些子產品可能并不公用但是确可以獨立開發,例如資訊類項目中可能會有使用者子產品,社交子產品和内容子產品,将這些拆分為項目内的子工程可以使項目的結構更加清晰,子產品化測試也更容易進行。

    最後,僅僅項目結構上的子產品化遠遠達不到真正實作元件化項目的要求,遵守協定為标準,以函數式程式設計為方式,全局着眼的接口設計與路由規劃,良好的程式設計習慣與統一的代碼風格,這種代碼層面的項目開發管理才真正任重道遠。後面有時間我會陸續通過其他部落格來探讨這些問題。希望一起交流,共同學習!