CocoaPodsThe Adventures project is
Edmondand
Wax gourdCocoaPods is a version management tool for iOS/MacOS projects. It is designed to help you understand CocoaPods better, not just limited to
pod installand
pod update.

Contents of knowledge of this article

primers

In the version management tools and Ruby toolchain environment above, we talked about how to unify the CocoaPods production environment and the Ruby toolchain used by our team members. Today, let’s turn our attention to CocoaPods and talk about its main components and how each component relates to the entire Pods workflow.

For the sake of the whole
CocoaPodsFor this project, I suggest you get started with Ruby, the scripting language. In addition, this article is based on CocoaPods 1.9.2.

Core components of CocoaPods

As a package management tool, CocoaPods is constantly iterating and evolving along with the booming development of the Apple ecosystem, and each part of the core function has evolved into a relatively independent component. These functional independent components, are split into an independent Gem package, and CocoaPods is the “integration” of these components.

CocoaPods dependency overview

We know that in a project managed by POD, the Podfile file describes the dependencies that it depends on. Similar dependencies for gems can be found in Gemfile. What are CocoaPods Gemfile dependencies?

SKIP_UNRELEASED_VERSIONS = false # ... source 'https://rubygems.org' gemspec gem 'json', :git => 'https://github.com/segiddins/json.git', :branch => 'seg-1.7.7-ruby-2.2' group :development do cp_gem 'claide', 'claide' cp_gem 'cocoapods-core', 'core', '1-9-stable' cp_gem 'cocoapods-deintegrate', 'cocoapods-deintegrate' cp_gem 'cocoapods-downloader', 'cocoapods-downloader' cp_gem 'cocoapods-plugins', 'cocoapods-plugins' cp_gem 'cocoapods-search', 'cocoapods-search' cp_gem 'cocoapods-stats', 'cocoapods-stats' cp_gem 'cocoapods-trunk', 'cocoapods-trunk' cp_gem 'cocoapods-try', 'cocoapods-try' cp_gem 'molinillo', 'Molinillo' cp_gem 'nanaimo', 'Nanaimo' cp_gem 'xcodeproj', 'xcodeproj' gem 'cocoapods-dependencies', '~> 1.0.beta.1' #... # Integration tests gem 'diffy' gem 'clintegracon' # Code Quality gem 'inch_by_inch' gem 'rubocop' gem 'danger' end group :debugging do gem 'cocoapods_debug' gem 'rb-fsevent' gem 'kicker' gem 'awesome_print' gem 'ruby-prof', :platforms => [:ruby] end

Cp_gems = cp_gems = cp_gems;

def cp_gem(name, repo_name, branch = 'master', path: false) return gem name if SKIP_UNRELEASED_VERSIONS opts = if path { :path => ".. /#{repo_name}" } else url = "https://github.com/CocoaPods/#{repo_name}.git" { :git => url, :branch => branch } end gem name, opts end

When **SKIP_UNRELEASED_VERSIONS** is false && path is true, you will use the Git repository in the same directory as your local CocoaPods project. Otherwise it will be loaded directly through the Gem using the corresponding project.

Through simple directory segmentation and Gemfile management, the most basic and intuitive hot plug is realized, which is very friendly to component development. So you just need to arrange multiple repositories as shown in the figure below to achieve cross-warehouse component development:

$ ls -l
lrwxr-xr-x  1 gua  staff    31 Jul 30 21:34 CocoaPods
lrwxr-xr-x  1 gua  staff    26 Jul 31 13:27 Core 
lrwxr-xr-x  1 gua  staff    31 Jul 31 10:14 Molinillo 
lrwxr-xr-x  1 gua  staff    31 Aug 15 11:32 Xcodeproj 
lrwxr-xr-x  1 gua  staff    42 Jul 31 10:14 cocoapods-downloader

Component composition and corresponding responsibilities

Through the above simple analysis of Gemfile, it can be seen that CocoaPods is not just a warehouse as simple, as a tripartite library version management tool, it is also very particular about its own component management and componentization. Let’s move on to the core development components of this Gemfile:

CLAide

The CLAide gem is a simple command line parser, which provides an API that allows you to quickly create a full featured command-line interface.

Claide is a simple command-line interpreter, but it provides a full-featured command-line interface and API. It is not only responsible for parsing the PODS commands we use (pod install, pod update, etc.), but also for packaging common scripts into simple command-line widgets.

PS: A command line interpreter is a program that reads commands from standard input or a file and executes them. As shown in the
Wiki.

cocoapods-core

The CocoaPods-Core gem provides support to work with the models of CocoaPods, for example the Podspecs or the Podfile.

CocoaPods-core is used to parse template files in CocoaPods, including Podfile,.podspec, and all special YAML files in.lock files.

cocoapods-downloader

The Cocoapods-downloader gem is a small library that provides downloaders for various source control types (HTTP/SVN/Git/Mercurial). It can deal with tags, commits, revisions, branches, extracting files from zips and almost anything these source control system would use.

CocoaODS-Downloader is a small tool for downloading source code. It supports various types of versioning tools, including HTTP/SVN/Git/Mercurial. It allows you to download and decompress tags, commites, revisions, branches and ZIPS files.

Molinillo

The Molinillo gem is a generic dependency resolution algorithm, used in CocoaPods, Bundler and RubyGems.

Molinillo is the encapsulation of CocoaPods for Dependent Arbitration. It is a backtracking algorithm with forward checking. Not only in PODS, Bundler and RubyGems also use this quorum algorithm.

Xcodeproj

The Xcodeproj gem lets you create and modify Xcode projects from Ruby. Script boring management tasks or build Xcode-friendly libraries. Also includes support for Xcode workspaces (.xcworkspace) and configuration files (.xcconfig).

XcodeProj can be used by Ruby to handle Xcode project creation, editing, etc. Friendly support for script management and libraries building of Xcode projects, as well as Xcode workspace (.xcworkspace) and configuration file.xcconfig.

cocoapods-plugins

CocoaPods plugin which shows info about available CocoaPods plugins or helps you get started developing a new plugin. Yeah, it’s very meta.

CocoaPods – Plugins plugin management function, which has a full set of POD Plugin command, support for CocoaPods plugin list (list), search (search), create (create) functions.

There are, of course, many other components that I won’t cover here. By looking at Gemfile, we can see that POD’s granularity for component splitting is relatively subtle, and the current complete version is achieved through the combination of various components. My understanding of each of these components is limited, but we will cover each in a series of articles.

CocoaPods que

Next, combine the POD Install process to show the upstream and downstream relationships of each component in the PODS workflow.

Order entry

Each time we enter the pod XXX command, the system will first call the pod command. All commands are scripts in the /bin directory, and Ruby is no exception. We can use Which Pod to see where the command is:

$which pod/Users/edmond /. RVM/gems/ruby - 2.6.1 / bin/pod

The display path here is not
/usr/local/bin/podThe reason is because of use
RVMVersion controlled.

Let’s use the cat command to see what the entry script does

$/ cat/Users/edmond. RVM/gems/ruby - 2.6.1 / bin/pod

The output is as follows:

#! /usr/bin/env ruby_executable_hooks require 'rubygems' version = ">= 0.a" str = ARGV.first if str str = str.b[/\A_(.*)_\z/, 1] if str and Gem::Version.correct? (str) version = str ARGV.shift end end if Gem.respond_to? (:activate_bin_path) load Gem.activate_bin_path('cocoapods', 'pod', version) else gem "cocoapods", version load Gem.bin_path("cocoapods", "pod", version) end

The program CocoaPods is installed as a Gem. This script is used to evoke CocoaPods. The logic is relatively simple, is a simple command forward. GEM.ACTIVATE_BIN_PATH and GEM.BIN_PATH are used to find the installation directory cocoaPods /bin, and finally load the /pod file in the directory:

#! /usr/bin/env ruby # ... If the environment configuration specifies a Ruby-prof configuration file, this puts caller #, when require 'cocoaPods' # manually output the call stack, If profile_filename = ENV['COCOAPODS_PROFILE'] require 'ruby-prof' File.open(profile_filename, 'w') do |io| reporter.new(RubyProf.profile { Pod::Command.run(ARGV) }).print(io) end else Pod::Command.run(ARGV) end

Let’s take a look at the output of pod:

$pod/Users/edmond /. RVM/gems/ruby - 2.6.1 / bin/pod: 24: in ` load '/ Users/edmond /. RVM/gems/ruby - 2.6.1 / bin/pod: 24: in ` < main >' / Users/edmond. RVM/gems/ruby - 2.6.1 / bin/ruby_executable_hooks: 24: in ` eval ' / Users/edmond. RVM/gems/ruby - 2.6.1 / bin/ruby_executable_hooks: 24: in ` < main > '

RUBY_EXECUTABLE_HOOKS wakes up via the pod entry in the bin directory and uses eval to set up the CocoaPods project we need. This is the behavior of RVM itself, which makes use of Execute-Hook to inject Gems plugins to customize extensions.

PS: Most dynamic languages support it
evalThis is the magic function. This is supported from Lisp, which takes a string type as an argument, parses it into a statement and runs it in the current scope. Please refer to this article for details
The article.

At the end of the entry, we begin our Claide Command parsing phase by instantiating a Claide ::Command object by calling Pod::Command.run(ARGV). I won’t go into too much analysis of the command parsing tool CLAide here, which is the subject of a later series of articles. Here we just need to know:

The execution of each Claide Command ultimately corresponds to a specific Command Class
runMethods.

The run method corresponding to POD command is implemented as follows:

module Pod class Command class Install < Command # ... Def run # verifies if the Podfile file exists, and if so, verify_Podfile_exists initializes! Instalaller installer_for_config Instalaller instaler_for_config Instalaller instaler_for_config Instalaller instaler_for_config Instalaller instaler_for_config Instalaller instaler_for_config Instalaller instaler_for_config Instalaller instaler_for_config (:default => false) installer.update = false installer.deployment = @deployment # install The real process installer.install! end end end end

The Command::Install class seen above corresponds to pod Install. The pod install process is dependent on the Podfile file, so it checks at the entry point. If No Podfile exists, it will simply throw an exception warning with No ‘Podfile’ found in the project directory and terminate the command.

Executive functional subject

After the installer instance is assembled, call its install! Method. At this time, the main part of our pod install command is entered. The process is as follows:

The corresponding implementation is as follows:

def install!
    prepare
    resolve_dependencies
    download_dependencies
    validate_targets
    if installation_options.skip_pods_project_generation?
        show_skip_pods_project_generation_message
    else
        integrate
    end
    write_lockfiles
    perform_post_install_actions
end

def integrate
    generate_pods_project
    if installation_options.integrate_targets?
        integrate_user_project
    else
        UI.section 'Skipping User Project Integration'
    end
end

0x1 Install environment prepare

Def prepare # raise to terminate if dir.pwd. START_WITH? (sandbox.root.to_path) message = 'Command should be run from a directory outside Pods directory.' message << "\n\n\tCurrent directory is #{UI.path(Pathname.pwd)}\n" raise Informative, Message End Ui. message 'Preparing' do # If you don't have the same version of your Lock file as your current one, The xcodeproj project file will be updated with the new version configuration. Deintegrate_if_different_major_version # create a subdirectory structure for the sandbox(Pods) directory PluginManager has a pre-install plugin ensure_plugins_are_installed! # Run_plugins_pre_install_hooks end for all hooks used in the pre-install plugin

In the prepare stage, the environment of pod install will be prepared, including version consistency, directory structure, all loading plug-in scripts of pre-install will be taken out, and the corresponding pre_install hook will be executed.

Resolve dependencies (resolve_dependencies)

Def resolve_dependencies # get Sources plugin_sources = run_source_provider_hooks # create an Analyzer = Create_Analyzer (plugin_sources) # If with repo_update ** flag ** Updating local specs repositories' do # Analyzer. Update_Repositories end if repo_update? Ui.section 'Analyzing dependencies' do # fetch the latest analysis results from the Analyzer, @analysis_result, @aggregate_targets, @pod_targets analyze(analyzer) # validate_build_configurations end # If deployment? 'section 'Verifying no changes' do verify_no_podfile_changes! verify_no_lockfile_changes! end if deployment? analyzer end

Dependency resolution is the process of generating Analyzer objects through the manifest in Podfile, Podfile.lock, and sandbox. _Analyzer_ internal use Molinillo (specific is Molinillo: : DependencyGraph graph algorithm) parsing a dependency list.

PS:
AnalyzerYou can get a lot of dependency information, for example
PodfileThe file dependency analysis results can also be viewed from specs_by_target
targetRelated specs.

Also, note that there is a pre_download phase in the analyze process, which is the Fetching external sources process seen under –verbose. This pre_download phase is not part of the dependency download process, but is in the current dependency analysis phase.

PS: This process is mainly to solve the problem that when we introduce the POD repository through Git address, the system cannot get the corresponding Spec from the default Source, so we need to directly visit our Git address to download the zip package of the repository and take out the corresponding one
podspecFile for comparative analysis.

0x3 Download dependencies (download_dependencies)

Def Download_Dependencies u. section 'M.E. Dependencies' do # init the file accessors of the sandbox create_file_accessors # Source Installer install_pod_sources: * * * * * * * * * * * * * * * * * * * * * * * * Clean_pod_sources end end clean_pod_sources end end

In create_file_accessors, the file accessors for the sandbox directory are created, and the various files in the sandbox are resolved by constructing the FileAccessor instance. Next comes the all-important install_pod_sources process, which calls the install! Of the corresponding Pod. Method to download the resource.

Let’s start with the install_pod_sources method implementation:

Def install_pod_sources @installed_specs = [] # install Pod only needs these two states, Added and changed state and sets pods_to_install = sandbox_state. Added | sandbox_state. Changed title_options = {: verbose_prefix = > '- >'. The green} puts "root_specs" root_specs. Each do | item | puts the item end # will be ordered after Podfile analytical processing Root_specs. Sort_by (& : name). Each do | spec | # if it is added or changed status of the Pod if pods_to_install. Include? If sandbox_state.change.include?(spec.name) &&sandbox. Manifest # version is updated if it is changed and manifest is already recorded Previous_version = sandbox.manifest. Version (spec.name) # Variation Record has_changed_version = current_version ! = previous_version # Find the first Pod containing spec.name, get the corresponding Repo, Is actually find methods current_repo = analysis_result. Specs_by_source. Detect {| key, Values | break key if values. The map (& : name). Include? (spec. Name)} # for current warehouse current_repo && = current_repo. Url | | Previous_spec_repo = sandbox.manifest.spec_repo(spec.name) # has_changed_repo =! previous_spec_repo.nil? && current_repo && (current_repo ! = previous_spec_repo) Else # not changed state The log title = "package #{spec}" end ui.titled_section (tit.green, Title_options) do # get the installer by name, @pod_installers install_source_of_pod(spec.name) end else # If not changed, use Using. Log ui.titled_section ("Using #{spec}", title_options) do # # Generate Installer instances from the platform information of the sandbox, specs, and specs. Create_pod_installer (spec.name) end end end # return the PodsourceInstaller instance def by caching Create_pod_installer (pod_name) specs_by_platform = specs_for_pod(pod_name) # When pod_target or platform cannot be found by pod_name The configuration, Take the initiative to throw an error message if specs_by_platform. Empty? Requiring_targets = pod_targets. Select {| | pt pt. Recursive_dependent_targets. Any?"  |dt| dt.pod_name == pod_name } } # message = "..." Raise StandardError, message end # Pod_installer = podsourceinstaller. new(sandbox, podfile, specs_by_platform, :can_cache => installation_options.clean?) Pod_installers << pod_installer pod_installer end # If a resolver declares that a POD is installed or already exists, it will be removed and reinstalled. If not, install it directly. def install_source_of_pod(pod_name) pod_installer = create_pod_installer(pod_name) pod_installer.install! @installed_specs.concat(pod_installer.specs_by_platform.values.flatten.uniq) end

At the beginning of the method, the root_specs method pulls out all root specs via analysis_result

def root_specs
  analysis_result.specifications.map(&:root).uniq
end

Now look again at install! In pod_installer. Method, mainly by calling CocoaPods-Downloader component, to download the POD corresponding Source to the local. The implementation is as follows:

def install!
    download_source unless predownloaded? || local?
    PodSourcePreparer.new(root_spec, root).prepare! if local?
    sandbox.remove_local_podspec(name) unless predownloaded? || local? || external?
end

0x4 Verify targets (validate_targets)

Used to validate the artifacts in the previous process (Targets generated by POD). The main function is to construct the targetValidator and execute Validate! Methods:

def validate_targets
    validator = Xcode::TargetValidator.new(aggregate_targets, pod_targets, installation_options)
    validator.validate!
end

def validate!
    verify_no_duplicate_framework_and_library_names
    verify_no_static_framework_transitive_dependencies
    verify_swift_pods_swift_version
    verify_swift_pods_have_module_dependencies
    verify_no_multiple_project_names if installation_options.generate_multiple_pod_projects?
end

Validation is only a small part of the overall Install process. Because it’s just the validation part, it’s completely decoupled.

  1. verify_no_duplicate_framework_and_library_names

Verify that there are frameworks with duplicate names, and throw Frameworks with Conflicting Names exceptions.

  1. verify_no_static_framework_transitive_dependencies

Verifies that there is a statically linked library (. A or. Framework) dependency in the dynamic library. If there is, it will trigger Transitive Dependencies That Include Static Binaries… Error. Suppose the following scenario exists:

  1. Component A and component B both depend on component C, which is A static library, such asWeibo_SDK
  2. Component A is determined to have A static library dependency on Component B when the following Settings exist in Component B’s. PodSpec file:

    1. Podspec set ups.static_framework = true
    2. Podspec tos.dependency 'xxx_SDKRelies on static librariesxxx_SDK
    3. Podspec tos.vendored_libraries = 'libxxx.a'Mode has a static library embeddedlibxxx

At this point, if the project’s Podfile has use_framework set! This error is emitted when packaged as a dynamic link. No use_frameworks used in Podfile! Each pod will generate a corresponding. A (statically Linked library) file, and then manage the pod code through static libraries. Linked will include other. A files that are referenced by the pod. Use use_frameworks in your Podfile! The POD code is managed through dynamic frameworks. Linked will include the POD references to other POD. Framework files. In the above scenario, although the B component is referenced in framework form, the B component is actually a static library that needs to be copied and linked to the POD. However, the dynamic frameworks mode does not do this, so an error is reported. The solution

  1. Modify the POD librarypodspec, an increase ofpod_target_xcconfig, definedFRAMEWORK_SEARCH_PATHSOTHER_LDFLAGSTwo environment variables;
  2. hook verify_no_static_framework_transitive_dependenciesTo kill it!Corresponding to the issue
  3. Modify the POD librarypodspec, which opens the static_framework configurations.static_framework = true
  1. verify_swift_pods_swift_version

Ensure that the SWIFT versions of SWIFT POD are properly configured and compatible with each other.

  1. verify_swift_pods_have_module_dependencies

Check whether the dependencies of the Swift library support modules, which are mainly for Objective-C libraries. First, Swift naturally supports the module system for managing code, which is a module system built on top of the LLVM module. After parsing the Swift library, it generates the corresponding ModuleMap and Umbrella. H files, which are standard with LLVM modules, as well as Objective-C support for LLVM modules. When we introduced the Objective-C library in the Dynamic Framework, Xcode supported configuring and generating headers, while the static library. A had to write its own corresponding umbrella.h and ModuleMap. Second, if your Swift Pod relies on the Objective-C library and you want to package the Swift Pod statically, make sure that the Objective-C library has modular_headers enabled. CocoaPods will generate the corresponding ModuleMap and Umbrella. H for us to support the LLVM Module. You can from this address – http://blog.cocoapods.org/CocoaPods-1.5.0/ for more details.

  1. verify_no_pods_used_with_multiple_swift_versions

Check for version consistency issues in all POD targets.

Summarize the verification process with a flowchart:

0x5 Generation Project (Integrate)

The generation of Project files is the last step of pod install, which will organize all components after the arbitration of previous versions in the form of Project files, and do some user-specified configuration in the Project.

Integrate generate_PODS_project if installation_options.integrate_targets? Use xcconfig to configure Integrate_User_Project Else UI.section 'Skipping User Project Integration' End End Def Stage_sandbox (sandbox, pod_targets) # check if incremental compilation is supported Cache result cache_analysis_result = analyze_project_cache # Target pod_targets_to_generate = that needs to be regenerated Cache_analysis_result.pod_targets_to_generate # The aggregate target aggregate_targets_to_generate = needs to be regenerated Cache_analysis_result.aggregate_targets_to_generate # Clean up the need to regenerate the header and pod folders of the target Clean_sandbox (pod_targets_to_generate) # generate Pod Project, Create_and_save_projects (pod_targets_to_generate, pod_targets_to_generate, etc.) aggregate_targets_to_generate, cache_analysis_result.build_configurations, Cache_analysis_result.project_object_version) # SandboxDirCleaner to clean up useless headers, target support files directories in an incremental POD installation SandboxDirCleaner.new(sandbox, pod_targets, aggregate_targets).clean! # Update_project_cache (cache_analysis_result); # Update_project_cache (cache_analysis_result); target_installation_results) end

In the process of install, apart from the time consumption of the dependent arbitration part and the download part, there will be relatively large time cost in the project file generation. This is often the core location of speed optimization.

0x6 Write dependencies (write_lockFiles)

Write the dependency updates to podfile.lock and manifest.lock

End callback 0x7 (perform_post_install_action)

The final step wraps up the work by providing post-Installation operations and hooks for all plug-ins.

Def perform_post_install_actions # calls HooksManager to execute each plug-in's post_install method run_plugins_post_install_hooks # to print out expired PODS Target warning WARN_FOR_DEPRECATIONS # if POD is configured with script phases POD installation complete! POD installation complete! POD installation complete! POD installation complete! ` print_post_install_message end

The functions of core components in each stage of pod install are as follows:

conclusion

Once we know the general process of CocoaPods in Install, we can make some modifications and controls to it. Knowing the exact timing of the plugin’s pre_install and post_install, for example, allows us to execute the corresponding Ruby scripts in our Podfile to meet our expectations. At the same time, understanding the process of install also helps us to conduct performance analysis at each stage to optimize and improve the efficiency of install.

Later, you’ll learn the implementation of each component in CocoaPods and find answers to all your questions in the code.

Knowledge point problem carding

Here are four questions to check if you have mastered this article, and if not, to bookmark it again:

  1. A brief overview of CocoaPods core modules?
  2. podHow does the command find and start the CocoaPods program?
  3. Describe the POD install process?
  4. resolve_dependenciesIn the stage ofpre_downloadWhat problem is it trying to solve?
  5. validate_targetsWhat checks were done?