Optimizing iOS Development: Essential Build Phase Scripts
Yassine Lafryhi , 19 Feb 2024Optimizing 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 :
- With CocoaPods (Add the following to your
Podfile
then runpod install
) :
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 :
- With CocoaPods (Add the following to your
Podfile
then runpod install
) :
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 :
- With CocoaPods (Add the following to your
Podfile
then runpod install
) :
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 :
- With CocoaPods (Add the following to your
Podfile
then runpod install
) :
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