Working on larger Xcode projects with many dependencies, sometimes a warning like this pops up in the output of xcodebuild:

Some object files have incompatible Objective-C category definitions. Some category metadata may be lost. All files containing Objective-C categories should be built using the same compiler.

This post explores what causes this message, and how to figure out where it’s coming from.


Objective-C class property support was added in Xcode 8. The message above actually means that we are linking against a library that was built using an older version of Xcode.

Greg Parker pointed me in the right direction at WWDC of how to figure out which libraries cause these warnings to print:

  • Get relevant flag value fom the objc-abi.h file in the Objective-C runtime
  • For each static library, dump the flags value using otool
  • Compare to the flag value above.

Step 1: ABI Flag

We can find the set of ABI flags in objc-abi.h in the runtime. The particular one we are interested in is:

HasCategoryClassProperties  = 1<<6,  // class properties in category_t

Step 2: otool

otool let’s us view particular parts of object/library files. So we can use this to see the combined flag value (that may include the HasCategoryClassProperties above).

Looking at a library like MMWormhole, we can see that for each object file within the static library that contains Objective-C code, we get a flags value:

$ otool -o libMMWormholeXcode7.a
/Users/dynamicdispatch/libMMWormhole.a(MMWormholeSessionMessageTransiting.o):
Contents of (__DATA,__objc_classlist) section
0000000000000d98 0x0
           isa 0x48535641e5894855
    superclass 0x8d4800000090ec81
         cache 0x8948e87d8948d845
        vtable 0xd845c748e075
          data 0xd68948c789480000 (struct class_ro_t *)
Contents of (__DATA,__objc_classrefs) section
0000000000000918 0x0
0000000000000920 0x0
0000000000000928 0x0
0000000000000930 0x0
0000000000000938 0x0
Contents of (__DATA,__objc_superrefs) section
00000000000006d8 0x0
Contents of (__DATA,__objc_imageinfo) section
  version 0
    flags 0x20
.... (repeat for other .o files in the library)

So comparing the two flag values: 0x20 & (1<<6) != (1<<6) we can see that this library was NOT built with the newest Objective-C ABI.

Step 3: Automate all the things

If you want to test this out, a repository is available here: https://github.com/dynamicdispatch/objc-abi-resources that contains static library versions of MMWormhole built with both Xcode 7 and Xcode 9.

Here’s a ruby script that given an input directory, will scan all static libraries:

#!/usr/bin/env ruby

require 'optparse'
require 'pathname'
require 'open3'

# This tool checks an input path for all static libraries *.a files 
# and makes sure they were built with the most modern ObjC ABI version.

# Parse command line options.
options = {}
OptionParser.new do |opts|
  opts.banner = 'Usage: staticlibchecker.rb [options]'
  opts.on('-i', '--input-path FOLDER', 'Input path') { |v| options[:input_path] = v }
end.parse!

input_path = ""
if options[:input_path].nil?
  raise "No input path specified"
else 
  input_path = options[:input_path]
end

all_static_libs = Dir["#{input_path}/**/*.a"]
puts all_static_libs
category_class_var_flag = 1<<6
all_static_libs.each { |path|

  # Run otool against the static lib
  command = "otool -o #{path}"
  stdout, stderr, status = Open3.capture3(command)
  if !status.success?
    puts "Failed with output: #{stderr}"
    exit status.exitstatus
  end

  # extract 'flags' value
  match = stdout.match /^\s+flags\s+(0x[\d]+)$/
  if match 
    flags = Integer(match[1])
    if (flags & category_class_var_flag) == category_class_var_flag
      puts "File: #{File.basename(path)} is ok"
    else 
      puts "File: #{File.basename(path)} was built with an old ABI version! #{flags} #{category_class_var_flag}"
    end
  end
}

Also available in gist form here: https://gist.github.com/dynamicdispatch/42d03fffbc64cfea75ed2e42b3adf519