Optimizing iOS Development: Essential Build Phase Scripts

In today's post, we will see some essential build phase scripts that can significantly enhance your iOS project's efficiency and maintainability.

Optimizing your development process is key to delivering high-quality software efficiently. In iOS development, build phase scripts play a crucial role in automating repetitive tasks and enforcing best practices. Let’s explore some of these scripts that are essential for any iOS project.

Handling Assets

Managing assets efficiently is crucial for any iOS project. Automating tasks like image optimization can significantly improve your app’s performance.

Script for Image Optimization :

1- Install pngcrush :

brew install pngcrush

2- Create the build phase script :

find $SRCROOT/ProjectName/Resources/Assets.xcassets -type f -name "*.png" | xargs -I {} pngcrush -brute -ow {}

This script finds all PNG files in your Assets.xcassets folder and optimizes them in place using pngcrush.

Generating Code for Assets :

Automatically generating code for assets like images and fonts can save time and reduce human errors.

R.swift Integration for Assets :

1- Install R.swift :

 pod 'R.swift'

2- Create the build phase script :

"$PODS_ROOT/R.swift/rswift" generate "$SRCROOT/ProjectName/Resources/R.generated.swift"

Code Quality Checks

Maintaining code quality is essential. You can integrate linting tools into your build phase to enforce coding standards.

SwiftLint Integration :

1- Install SwiftLint :

pod 'SwiftLint'

2- Create the .swiftlint.yml file in the root of your project :

# swiftlint rules
included: # paths to include during linting. `--path` is ignored if present.
  - ./ProjectName
  - ./ProjectNameTests
  - ./ProjectNameUITests

excluded: # paths to ignore during linting. Takes precedence over `included`.
  - ./ProjectName/Resources/R.generated.swift
  - ./Pods

disabled_rules:
  - explicit_enum_raw_value
  - array_init
  - explicit_type_interface
  - nimble_operator
  - no_grouping_extension
  - attributes
  - private_over_fileprivate
  - strict_fileprivate
  - explicit_top_level_acl
  - trailing_whitespace
  - file_header
  - trailing_closure
  - switch_case_alignment
  - let_var_whitespace
  - opening_brace

opt_in_rules:
  - void_return
  - first_where
  - number_separator
  - block_based_kvo
  - class_delegate_protocol
  - closing_brace
  - closure_end_indentation
  - closure_parameter_position
  - closure_spacing
  - colon
  - comma
  - compiler_protocol_init
  - conditional_returns_on_newline
  - contains_over_first_not_nil
  - control_statement
  - discarded_notification_center_observer
  - discouraged_direct_init
  - dynamic_inline
  - empty_count
  - empty_enum_arguments
  - empty_parameters
  - empty_parentheses_with_trailing_closure
  - explicit_init
  - fallthrough
  - fatal_error_message
  - file_length
  - for_where
  - force_cast
  - force_try
  - force_unwrapping
  - function_body_length
  - function_parameter_count
  - generic_type_name
  - identifier_name
  - implicit_getter
  - implicit_return
  - implicitly_unwrapped_optional
  - is_disjoint
  - joined_default_parameter
  - large_tuple
  - leading_whitespace
  - legacy_cggeometry_functions
  - legacy_constant
  - legacy_constructor
  - legacy_nsgeometry_functions
  - literal_expression_end_indentation
  - mark
  - multiline_arguments
  - multiline_parameters
  - multiple_closures_with_trailing_closure
  - nesting
  - object_literal
  - operator_usage_whitespace
  - operator_whitespace
  - overridden_super_call
  - override_in_extension
  - pattern_matching_keywords
  - private_outlet
  - protocol_property_accessors_order
  - prohibited_super_call
  - quick_discouraged_call
  - quick_discouraged_focused_test
  - quick_discouraged_pending_test
  - redundant_discardable_let
  - redundant_nil_coalescing
  - redundant_optional_initialization
  - redundant_string_enum_value
  - redundant_void_return
  - return_arrow_whitespace
  - shorthand_operator
  - single_test_class
  - statement_position
  - superfluous_disable_command
  - switch_case_on_newline
  - syntactic_sugar
  - todo
  - trailing_newline
  - trailing_semicolon
  - type_body_length
  - type_name
  - unneeded_break_in_switch
  - unneeded_parentheses_in_closure_argument
  - unused_closure_parameter
  - unused_enumerated
  - valid_ibinspectable
  - vertical_parameter_alignment
  - vertical_parameter_alignment_on_call
  - vertical_whitespace
  - weak_delegate
  - xctfail_message



custom_rules:
  avoid_print:
    included: ".*.swift"
    name: "Avoid Print"
    regex: "print\\("
    message: "Consider using a logging method instead of print"
    severity: warning

# Rules configuration

closure_spacing:
  severity: error

colon:
  severity: error

force_unwrapping:
  severity: warning

force_cast:
  severity: warning

force_try:
  severity: warning

control_statement:
  severity: warning

cyclomatic_complexity:
  warning: 25
  error: 30

explicit_init:
  severity: error

file_length:
  warning: 500
  error: 800

first_where:
  severity: error

function_body_length:
  warning: 70
  error: 100

function_parameter_count:
  warning: 8
  error: 10

generic_type_name:
  min_length:
    warning: 0
    error: 1
  max_length:
    warning: 80
    error: 100

identifier_name:
  allowed_symbols: "_"
  min_length: 1
  max_length:
    warning: 40
    error: 50
  validates_start_with_lowercase: false

implicitly_unwrapped_optional:
  severity: error

large_tuple:
  warning: 4
  error: 6

leading_whitespace:
  severity: error

legacy_cggeometry_functions:
  severity: error

legacy_constant:
  severity: error

legacy_constructor:
  severity: error

legacy_nsgeometry_functions:
  severity: error

line_length:
  warning: 250
  error: 500
  ignores_function_declarations: true
  ignores_comments: true
  ignores_urls: true

mark:
  severity: warning

nesting:
  severity: warning

notification_center_detachment:
  severity: error

number_separator:
  severity: error

object_literal:
  severity: error

operator_whitespace:
  severity: error

operator_usage_whitespace:
  severity: error

overridden_super_call:
  severity: error

private_outlet:
  severity: error

private_unit_test:
  severity: error

prohibited_super_call:
  severity: error

redundant_nil_coalescing:
  severity: error

redundant_void_return:
  severity: error

return_arrow_whitespace:
  severity: error

shorthand_operator:
  severity: error

statement_position:
  statement_mode: default
  severity: error

switch_case_on_newline:
  severity: error

syntactic_sugar:
  severity: error

trailing_comma:
  severity: warning

trailing_newline: error

trailing_semicolon:
  severity: error

type_body_length:
  warning: 800
  error: 1000

type_name:
  min_length:
    warning: 0
    error: 3
  max_length:
    warning: 80
    error: 100

unused_closure_parameter:
  severity: error

unused_enumerated:
  severity: error

unused_optional_binding:
  severity: error

valid_ibinspectable:
  severity: error

vertical_parameter_alignment:
  severity: error

vertical_whitespace:
  severity: error

weak_delegate:
  severity: error

reporter: "xcode"

3- Create the build phase script :

"${PODS_ROOT}/SwiftLint/swiftlint" --fix && "${PODS_ROOT}/SwiftLint/swiftlint"

SwiftFormat Integration :

1- Install SwiftFormat :

pod 'SwiftFormat/CLI'

2- Create the .swiftformat file in the root of your project :

--exclude Pods, ProjectName/Resources/R.generated.swift

--swiftversion 5.6
--self remove
--importgrouping testable-bottom
--commas inline
--trimwhitespace always
--indent 4
--ifdef no-indent
--indentstrings true
--wraparguments before-first
--wrapparameters before-first
--wrapcollections before-first
--wrapconditions before-first
--wrapreturntype if-multiline
--closingparen same-line
--wraptypealiases before-first
--funcattributes prev-line
--typeattributes prev-line
--wrapternary before-operators
--extensionacl on-declarations
--patternlet hoist
--redundanttype inferred
--emptybraces no-space
--operatorfunc spaced
--maxwidth 130

# file options
#--exclude Package.swift

# rules
--rules anyObjectProtocol
--rules blankLinesBetweenScopes
--rules consecutiveSpaces
--rules consecutiveBlankLines
--rules duplicateImports
--rules extensionAccessControl
--rules hoistPatternLet
--rules indent
--rules redundantParens
--rules redundantReturn
--rules redundantSelf
--rules redundantType
--rules redundantPattern
--rules redundantGet
--rules redundantFileprivate
--rules redundantRawValues
--rules sortImports
--rules sortDeclarations
--rules strongifiedSelf
--rules trailingCommas
--rules trailingSpace
--rules typeSugar
--rules wrap
--rules wrapMultilineStatementBraces
--rules wrapArguments
--rules wrapAttributes
--rules braces
--rules redundantClosure
--rules redundantInit
--rules redundantVoidReturnType
--rules unusedArguments
--rules spaceInsideBrackets
--rules spaceInsideBraces
--rules spaceAroundBraces
--rules spaceInsideParens
--rules spaceAroundParens
--rules enumNamespaces
--rules spaceAroundComments
--rules spaceInsideComments
--rules spaceAroundOperators
--rules blankLinesAtStartOfScope
--rules blankLinesAtEndOfScope
--rules emptyBraces
--rules andOperator

3- Create the build phase script :

"${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat" "${SRCROOT}"

Check for unused code with Periphery

1- Install Periphery :

pod 'Periphery'

2- Create the .periphery.yml file in the root of your project :

retain_objc_accessible: true
retain_public: true
schemes:
  - ProjectName
targets:
  - ProjectName
workspace: ProjectName.xcworkspace

3- Create the build phase script :

Since Periphery needs to build the project to analyze the code, It’s crucial to create a temporary file to skip the analysis after building the project for the first time (to avoid recursive builds) :

if [ -f "/tmp/skip_periphery.txt" ]; then
    rm -f "/tmp/skip_periphery.txt"  # Clean up after skipping
    exit 0
fi
touch "/tmp/skip_periphery.txt"  # Create the file as a flag
"${PODS_ROOT}/Periphery/periphery" scan