TinySolution: Fix CocoaPods duplicate implement warning

Galvin Li
6 min readFeb 10, 2019

--

此文章同时提供中文版本:TinySolution:CocoaPods类重复实现警告处理

CocoaPods is a dependency management tool used by many iOS developers. Normally CocoaPods is very convenient because it automatically does lots of project configuration and even create the xcworkspace file for us.

But when the project structure is getting more complicated, the automatic configuration of CocoaPods will cause some problems, which is why Carthage will appear and become a part of the developer’s choice.

However the flexibility of Carthage is accompanied by the cumbersome configuration. So my choice is to still use CocoaPods, and with CocoaPods’ powerful script support to solve the problems.

Some “harmless” runtime warning

I directly use the code snippets and screenshot in the TinySolution project as an illustration to make the problem clearer.

The problem happen in company’s project. The project is refactored to a stage. I extracted the data layer of the project into an internal framework. In order to make the decoupling more thorough. As I often emphasize, although the agreement is flexible, it will always be broken in some specific scenarios, only the restrictions are long-term stable. However, after extracting the Framework, a bunch of “harmless” runtime warnings appear in the Xcode console, just like the console output of Target CocoapodsDuplicateWarning.

It is harmless, because the operation of the program is not affected, and the output information of the console is generally a lot and refreshed quickly, so some developers may not notice.

But let’s take a closer look at the specific warning message. Some Classes are implemented repeatedly, so although there is no problem with the operation for now, a class is implemented multiple times, and we don’t know the difference between all these implementations and which one would be use by the runtime. So there is a certain risk here.

The fundamental problem and solution

To solve the problem, we must first locate the problem. Let’s sort out the specific information of the warning:

Class AFXXX is implemented in both .../CocoapodsDuplicateWarningFramework and .../CocoapodsDuplicateWarning. One of the two will be used. Which one is undefined.

After simplifying the content, we can find that the same Class is implemented in both the Framework and the App. And AFXXX we know that it is AFNetworking class, look at Podfile we can see that only CocoapodsDuplicateWarningFramework has linked AFNetworking, and CocoapodsDuplicateWarning only linked CocoapodsDuplicateWarningFramework in the project configuration, and does not direct linked AFNetworking. So the warning shouldn't happen. It should be a bug of CocoaPods.

After some searching, other peoples also reflected the same problem:

https://github.com/CocoaPods/CocoaPods/issues/7155

https://stackoverflow.com/questions/46932341/class-is-implemented-in-both-one-of-the-two-will-be-used-which-one-is-undefine/52773670

Some people suggest adding use_frameworks! to solve this problem. This could really removes the warning, but it just bypasses the problem, not solves the problem. Static and dynamic libraries have different usage scenarios, so this is not a general solution.

Some people have found the fundamental of the problem, which is that the project xcconfig file generated by CocoaPods repeatedly introduces the same dependency library into the OTHER_LDFLAGS:

The problem is clear and the solution is very simple. We remove the -l"AFNetworking" of OTHER_LDFLAGS of Pods-CocoapodsDuplicateWarning.xxx.xcconfig, then there will be no warning. Because the internal Framework has been linked the necessary library.

Practical automated processing

But we can’t manually remove duplicate dependencies every time after Pod install or Pod update. Manually modifying the automatically generated content is always a stupid choice, which is error-prone and a lot of work. So we need an automated solution.

CocoaPods is written in Ruby, and the Podfile is actually a specific set of Ruby code. In addition to the common configuration, we can add logic code. I added a Target CocoapodsDuplicateWarningFix to the project for comparison. All configurations are the same as CocoapodsDuplicateWarning. The same runtime warning will also appear when running directly.

Then we start adding our logic code under the Podfile:

post_install do |installer|

auto_process_target(['CocoapodsDuplicateWarningFix'], 'CocoapodsDuplicateWarningFramework', installer)

end

The code that was added first is an entry, post_install is an event hook defined by CocoaPods, which calls the auto_process_target(...) method is the actual processing method. The three parameters are:

  1. The group of the application target, we use array here because it’s easier for multi target, for example, we can also fix the warning under target CocoapodsDuplicateWarning by adding CocoapodsDuplicateWarning directly to the array.
  2. Internal framework name, only supports a single framework, because usually it is many-to-one, if there are multiple frameworks have this problem is also simple, just call auto_process_target(...) multi times.
  3. Just pass the installer into the method,no need to custom configuration.

Then we need to add the implementation of auto_process_target(...) method:

def auto_process_target(app_target_names, embedded_target_name, installer)
words = find_words_at_embedded_target('Pods-' + embedded_target_name,
installer)
handle_app_targets(app_target_names.map{ |str| 'Pods-' + str },
words,
installer)
end

Here you can see that the two methods are also quite simple. The first is words = find_words_at_embedded_target(...), we find out all dependencies from the framework, and then use the handle_app_targets(...) method to remove duplicate dependencies. The specific implementation is as follows:

def find_line_with_start(str, start)
str.each_line do |line|
if line.start_with?(start)
return line
end
end
return nil
end

def remove_words(str, words)
new_str = str
words.each do |word|
new_str = new_str.sub(word, '')
end
return new_str
end

def find_words_at_embedded_target(target_name, installer)
target = installer.pods_project.targets.find { |target| target.name == target_name }
target.build_configurations.each do |config|
xcconfig_path = config.base_configuration_reference.real_path
xcconfig = File.read(xcconfig_path)
old_line = find_line_with_start(xcconfig, "OTHER_LDFLAGS")

if old_line == nil
next
end
words = old_line.split(' ').select{ |str| str.start_with?("-l") }.map{ |str| ' ' + str }
return words
end
end

def handle_app_targets(names, words, installer)
installer.pods_project.targets.each do |target|
if names.index(target.name) == nil
next
end
puts "Updating #{target.name} OTHER_LDFLAGS"
target.build_configurations.each do |config|
xcconfig_path = config.base_configuration_reference.real_path
xcconfig = File.read(xcconfig_path)
old_line = find_line_with_start(xcconfig, "OTHER_LDFLAGS")

if old_line == nil
next
end
new_line = remove_words(old_line, words)

new_xcconfig = xcconfig.sub(old_line, new_line)
File.open(xcconfig_path, "w") { |file| file << new_xcconfig }
end
end
end

The specific implementation logic will not be explained one by one. If you use it directly, you can directly configure and call auto_process_target(...), and the underlying implementation does not need to be modify. If you are interested in the detail implementation, it's quite easy to understand, just read it.

If the code is added correctly, each time you execute Pod install or Pod update, the extra dependencies are automatically cleared, and there is a corresponding prompt output at the terminal.

Finally, we can look at the xcconfig contents of CocoapodsDuplicateWarning and CocoapodsDuplicateWarningFix. The extra -l"AFNetworking" is indeed removed:

Then we run CocoapodsDuplicateWarningFix and won’t see any runtime warning. The problem is solved forever. Adding any new dependencies also does not require any manual maintenance.

  • All code in this article can be found in the GitHub project.
  • If you have questions or suggestions, welcome to leave comment for discuss.
  • If you feel this article is valuable, please forward it so more people can see it.
  • If you like this type of content, welcome to follow my Medium and Twitter, I will keep posting useful content for everyone.

--

--

Galvin Li

A Tiny iOS developer who love to solve problems and make things better.