class Xcodeproj::Project::Object::AbstractObject

@abstract

This is the base class of all object types that can exist in a Xcode project. As such it provides common behavior, but you can only use instances of subclasses of AbstractObject, because this class does not exist in actual Xcode projects.

Almost all the methods implemented by this class are not expected to be used by {Xcodeproj} clients.

Subclasses should clearly identify which methods reflect the xcodeproj document model and which methods are offered as convenience. Object lists always represent a relationship to many of the model while simple arrays represent dynamically generated values offered a convenience for clients.

Attributes

isa[R]

@return [String] the object’s class name.

project[R]

@return [Project] the project that owns the object.

referrers[R]

@return [Array<ObjectList>] The list of the objects that have a

reference to this object.

@visibility private

simple_attributes_hash[R]

@return [Hash] the simple attributes hash.

uuid[R]

@return [String] the object universally unique identifier.

Public Class Methods

attributes() click to toggle source

@return [Array<AbstractObjectAttribute>] the attributes associated

with the class.

@note It includes the attributes defined in the superclass and the

list is cleaned for duplicates. Subclasses should not duplicate
an attribute of the superclass but for the method implementation
they will duplicate them.

@visibility private

# File lib/xcodeproj/project/object_attributes.rb, line 216
def attributes
  unless @full_attributes
    attributes = @attributes || []
    if superclass.respond_to?(:attributes)
      super_attributes = superclass.attributes
    else
      super_attributes = []
    end
    # The uniqueness of the attributes is very important because the
    # initialization from plist deletes the values from the
    # dictionary.
    @full_attributes = attributes.concat(super_attributes).uniq
  end
  @full_attributes
end
isa() click to toggle source

@return [String] the ISA of the class.

# File lib/xcodeproj/project/object.rb, line 39
def self.isa
  @isa ||= name.split('::').last
end
new(project, uuid) click to toggle source

It is not recommended to instantiate objects through this constructor. To create objects manually is easier to use the {Project#new}. Otherwise, it is possible to use the convenience methods offered by {Xcodeproj} which take care of configuring the objects for common usage cases.

@param [Project] project

the project that will host the object.

@param [String] uuid

the UUID of the new object.

@visibility private

# File lib/xcodeproj/project/object.rb, line 61
def initialize(project, uuid)
  @project = project
  @uuid = uuid
  @isa = self.class.isa
  @referrers = []
  unless @isa =~ /^(PBX|XC)/
    raise "[Xcodeproj] Attempt to initialize an abstract class (#{self.class})."
  end
end
references_by_keys_attributes() click to toggle source

@visibility private

# File lib/xcodeproj/project/object_attributes.rb, line 263
def references_by_keys_attributes
  @references_by_keys_attributes ||= attributes.select { |a| a.type == :references_by_keys }
end
simple_attributes() click to toggle source

@return [Array<AbstractObjectAttribute>] the simple attributes

associated with with the class.

@visibility private

# File lib/xcodeproj/project/object_attributes.rb, line 237
def simple_attributes
  @simple_attributes ||= attributes.select { |a| a.type == :simple }
end
to_many_attributes() click to toggle source

@return [Array<AbstractObjectAttribute>] the attributes

representing a to many relationship associated with with the
class.

@visibility private

# File lib/xcodeproj/project/object_attributes.rb, line 257
def to_many_attributes
  @to_many_attributes ||= attributes.select { |a| a.type == :to_many }
end
to_one_attributes() click to toggle source

@return [Array<AbstractObjectAttribute>] the attributes

representing a to one relationship associated with with the
class.

@visibility private

# File lib/xcodeproj/project/object_attributes.rb, line 247
def to_one_attributes
  @to_one_attributes ||= attributes.select { |a| a.type == :to_one }
end

Protected Class Methods

add_attribute(attribute) click to toggle source

Adds an attribute to the list of attributes of the class.

@note This method is intended to be invoked only by the

{AbstractObject} meta programming methods

@return [void]

# File lib/xcodeproj/project/object_attributes.rb, line 456
def add_attribute(attribute)
  unless attribute.classes
    raise "[Xcodeproj] BUG - missing classes for #{attribute.inspect}"
  end

  unless attribute.classes.all? { |klass| klass.is_a?(Class) }
    raise "[Xcodeproj] BUG - classes:#{attribute.classes} for #{attribute.inspect}"
  end

  @attributes ||= []
  @attributes << attribute
end

Private Class Methods

attribute(name, klass, default_value = nil) click to toggle source

Defines a new simple attribute and synthesises the corresponding methods.

@note Simple attributes are directly stored in a hash. They can

contain only a string, array of strings or a hash containing
strings and thus they are not affected by reference counting.
Clients can access the hash directly through the
{AbstractObject#simple_attributes_hash} method.

@param [Symbol] name

the name of the attribute.

@param [Class] klass

the accepted {Class} for the values of the attribute.

@param [String, Array<String>, Hash{String=>String}] default_value

the default value for new objects.

@example

attribute :project_root
#=> leads to the creation of the following methods

def project_root
  @simple_attributes_hash[projectRoot]
end

def project_root=(value)
  attribute.validate_value(value)
  @simple_attributes_hash[projectRoot] = value
end

@macro [attach] attribute

@!attribute [rw] $1
# File lib/xcodeproj/project/object_attributes.rb, line 303
def attribute(name, klass, default_value = nil)
  attrb = AbstractObjectAttribute.new(:simple, name, self)
  attrb.classes = [klass]
  attrb.default_value = default_value
  add_attribute(attrb)

  define_method(attrb.name) do
    @simple_attributes_hash ||= {}
    @simple_attributes_hash[attrb.plist_name]
  end

  define_method("#{attrb.name}=") do |value|
    @simple_attributes_hash ||= {}
    attrb.validate_value(value)

    existing = @simple_attributes_hash[attrb.plist_name]
    if existing.is_a?(Hash) && value.is_a?(Hash)
      return value if existing.keys == value.keys && existing == value
    elsif existing == value
      return value
    end
    mark_project_as_dirty!
    @simple_attributes_hash[attrb.plist_name] = value
  end
end
has_many(plural_name, isas) click to toggle source

Defines a new ordered relationship to many.

@note This attribute only generates the reader method. Clients are

not supposed to create {ObjectList} objects which are created
by the methods synthesised by this attribute on demand.
Clients, however can mutate the list according to its
interface. The list is responsible to manage the reference
counting for its values.

@param [String] plural_name

the name of the relationship.

@param [Class, Array<Class>] isas

the list of the classes corresponding to the accepted isas for
this relationship.

@macro [attach] has_many

@!attribute [r] $1
# File lib/xcodeproj/project/object_attributes.rb, line 389
def has_many(plural_name, isas)
  isas = [isas] unless isas.is_a?(Array)

  attrb = AbstractObjectAttribute.new(:to_many, plural_name, self)
  attrb.classes = isas
  add_attribute(attrb)

  variable_name = :"@#{attrb.name}"
  define_method(attrb.name) do
    # Here we are in the context of the instance
    list = instance_variable_get(variable_name)
    unless list
      list = ObjectList.new(attrb, self)
      instance_variable_set(variable_name, list)
    end
    list
  end
end
has_many_references_by_keys(plural_name, classes_by_key) click to toggle source

Defines a new ordered relationship to many.

@note This attribute only generates the reader method. Clients are

not supposed to create {ObjectList} objects which are created
by the methods synthesised by this attribute on demand.
Clients, however can mutate the list according to its
interface. The list is responsible to manage the reference
counting for its values.

@param [String] plural_name

the name of the relationship.

@param [{Symbol, Array<Class>}] classes_by_key

the list of the classes corresponding to the accepted isas for
this relationship.

@macro [attach] has_many

@!attribute [r] $1
# File lib/xcodeproj/project/object_attributes.rb, line 427
def has_many_references_by_keys(plural_name, classes_by_key)
  attrb = AbstractObjectAttribute.new(:references_by_keys, plural_name, self)
  attrb.classes = classes_by_key.values
  attrb.classes_by_key = classes_by_key
  add_attribute(attrb)

  variable_name = :"@#{attrb.name}"
  define_method(attrb.name) do
    # Here we are in the context of the instance
    list = instance_variable_get(variable_name)
    unless list
      list = ObjectList.new(attrb, self)
      instance_variable_set(variable_name, list)
    end
    list
  end
end
has_one(singular_name, isas) click to toggle source

Defines a new relationship to a single and synthesises the corresponding methods.

@note The synthesised setter takes care of handling reference

counting directly.

@param [String] singular_name

the name of the relationship.

@param [Class, Array<Class>] isas

the list of the classes corresponding to the accepted isas for
this relationship.

@macro [attach] has_one

@!attribute [rw] $1
# File lib/xcodeproj/project/object_attributes.rb, line 347
def has_one(singular_name, isas)
  isas = [isas] unless isas.is_a?(Array)
  attrb = AbstractObjectAttribute.new(:to_one, singular_name, self)
  attrb.classes = isas
  add_attribute(attrb)

  attr_reader(attrb.name)
  # 1.9.2 fix, see https://github.com/CocoaPods/Xcodeproj/issues/40.
  public(attrb.name)

  variable_name = :"@#{attrb.name}"
  define_method("#{attrb.name}=") do |value|
    attrb.validate_value(value)

    previous_value = send(attrb.name)
    return value if previous_value == value
    mark_project_as_dirty!
    previous_value.remove_referrer(self) if previous_value
    instance_variable_set(variable_name, value)
    value.add_referrer(self) if value
  end
end

Public Instance Methods

<=>(other) click to toggle source
# File lib/xcodeproj/project/object.rb, line 487
def <=>(other)
  uuid <=> other.uuid
end
==(other) click to toggle source

@!group Object methods

# File lib/xcodeproj/project/object.rb, line 483
def ==(other)
  other.is_a?(AbstractObject) && to_hash == other.to_hash
end
add_referrer(referrer) click to toggle source

Informs the object that another object is referencing it. If the object had no previous references it is added to the project UUIDs hash.

@return [void]

@visibility private

# File lib/xcodeproj/project/object.rb, line 203
def add_referrer(referrer)
  @referrers << referrer
  @project.objects_by_uuid[uuid] = self
end
ascii_plist_annotation() click to toggle source
# File lib/xcodeproj/project/object.rb, line 416
def ascii_plist_annotation
  " #{display_name} "
end
attributes() click to toggle source

@return (see AbstractObject.attributes)

@visibility private

# File lib/xcodeproj/project/object_attributes.rb, line 484
def attributes
  self.class.attributes
end
configure_with_plist(objects_by_uuid_plist) click to toggle source

Configures the object with the objects hash from a plist.

**Implementation detail**: it is important that the attributes for a given concrete class are unique because the value is removed from the array at each iteration and duplicate would result in nil values.

@return [void]

@visibility private

# File lib/xcodeproj/project/object.rb, line 273
def configure_with_plist(objects_by_uuid_plist)
  object_plist = objects_by_uuid_plist[uuid].dup

  unless object_plist['isa'] == isa
    raise "[Xcodeproj] Attempt to initialize `#{isa}` from plist with " \
      "different isa `#{object_plist}`"
  end
  object_plist.delete('isa')

  simple_attributes.each do |attrb|
    attrb.set_value(self, object_plist[attrb.plist_name])
    object_plist.delete(attrb.plist_name)
  end

  to_one_attributes.each do |attrb|
    ref_uuid = object_plist[attrb.plist_name]
    if ref_uuid
      ref = object_with_uuid(ref_uuid, objects_by_uuid_plist, attrb)
      attrb.set_value(self, ref) if ref
    end
    object_plist.delete(attrb.plist_name)
  end

  to_many_attributes.each do |attrb|
    ref_uuids = object_plist[attrb.plist_name] || []
    list = attrb.get_value(self)
    ref_uuids.each do |uuid|
      ref = object_with_uuid(uuid, objects_by_uuid_plist, attrb)
      list << ref if ref
    end
    object_plist.delete(attrb.plist_name)
  end

  references_by_keys_attributes.each do |attrb|
    hashes = object_plist[attrb.plist_name] || {}
    list = attrb.get_value(self)
    hashes.each do |hash|
      dictionary = ObjectDictionary.new(attrb, self)
      hash.each do |key, uuid|
        ref = object_with_uuid(uuid, objects_by_uuid_plist, attrb)
        dictionary[key] = ref if ref
      end
      list << dictionary
    end
    object_plist.delete(attrb.plist_name)
  end

  unless object_plist.empty?
    UI.warn "[!] Xcodeproj doesn't know about the following " \
            "attributes #{object_plist.inspect} for the '#{isa}' isa." \
            "\nIf this attribute was generated by Xcode please file " \
            'an issue: https://github.com/CocoaPods/Xcodeproj/issues/new'
  end
end
display_name() click to toggle source

Returns the value of the name attribute or returns a generic name for the object.

@note Not all concrete classes implement the name attribute and this

method prevents from overriding it in plist.

@return [String] a name for the object.

# File lib/xcodeproj/project/object.rb, line 133
def display_name
  declared_name = name if self.respond_to?(:name)
  if declared_name && !declared_name.empty?
    declared_name
  else
    isa.gsub(/^(PBX|XC)/, '')
  end
end
Also aliased as: to_s
initialize_defaults() click to toggle source

Initializes the object with the default values of simple attributes.

This method is called by the {Project#new} and is not performed on initialization to prevent adding defaults to objects generated by a plist.

@return [void]

@visibility private

# File lib/xcodeproj/project/object.rb, line 81
def initialize_defaults
  simple_attributes.each { |a| a.set_default(self) }
end
inspect() click to toggle source
# File lib/xcodeproj/project/object.rb, line 491
def inspect
  optional = ''
  optional << " name=`#{name}`" if respond_to?(:name) && name
  optional << " path=`#{path}`" if respond_to?(:path) && path
  "<#{isa}#{optional} UUID=`#{uuid}`>"
end
mark_project_as_dirty!() click to toggle source

Marks the project that this object belongs to as having been modified.

@return [void]

@visibility private

# File lib/xcodeproj/project/object.rb, line 253
def mark_project_as_dirty!
  project.mark_dirty!
end
nested_object_for_hash(object, method) click to toggle source
# File lib/xcodeproj/project/object.rb, line 407
def nested_object_for_hash(object, method)
  case method
  when :to_ascii_plist
    Nanaimo::String.new(object.uuid, object.ascii_plist_annotation)
  else
    object.uuid
  end
end
object_with_uuid(uuid, objects_by_uuid_plist, attribute) click to toggle source

Initializes and returns the object with the given UUID.

@param [String] uuid

The UUID of the object that should be initialized.

@param [Hash{String=>String}] objects_by_uuid_plist

The hash contained by `objects` key of the plist containing
the information about the object that should be initialized.

@param [AbstractObjectAttribute] attribute

The attribute that requested the object. It is used only for
exceptions.

@raise If the hash for the given UUID contains an unknown ISA.

@return [AbstractObject] the initialized object. @return [Nil] if the UUID could not be found in the objects hash. In

this case a warning is printed to STDERR.

@visibility private

# File lib/xcodeproj/project/object.rb, line 349
def object_with_uuid(uuid, objects_by_uuid_plist, attribute)
  unless object = project.objects_by_uuid[uuid] || project.new_from_plist(uuid, objects_by_uuid_plist)
    UI.warn "`#{inspect}` attempted to initialize an object with " \
      "an unknown UUID. `#{uuid}` for attribute: " \
      "`#{attribute.name}`. This can be the result of a merge and " \
      'the unknown UUID is being discarded.'
  end
  object
rescue NameError
  attributes = objects_by_uuid_plist[uuid]
  raise "`#{isa}` attempted to initialize an object with unknown ISA "\
        "`#{attributes['isa']}` from attributes: `#{attributes}`\n" \
        'If this ISA was generated by Xcode please file an issue: ' \
        'https://github.com/CocoaPods/Xcodeproj/issues/new'
end
pretty_print() click to toggle source

@return [Hash{String => Hash}] A hash suitable to display the object

to the user.
# File lib/xcodeproj/project/object.rb, line 468
def pretty_print
  if to_many_attributes.count == 1
    children = to_many_attributes.first.get_value(self)
    { display_name => children.map(&:pretty_print) }
  else
    display_name
  end
end
references_by_keys_attributes() click to toggle source

@return (see AbstractObject.to_many_attributes)

@visibility private

# File lib/xcodeproj/project/object_attributes.rb, line 516
def references_by_keys_attributes
  self.class.references_by_keys_attributes
end
remove_from_project() click to toggle source

Removes the object from the project by asking to its referrers to remove the reference to it.

@note The root object is owned by the project and should not be

manipulated with this method.

@return [void]

# File lib/xcodeproj/project/object.rb, line 101
def remove_from_project
  mark_project_as_dirty!
  project.objects_by_uuid.delete(uuid)

  referrers.dup.each do |referrer|
    referrer.remove_reference(self)
  end

  to_one_attributes.each do |attrb|
    object = attrb.get_value(self)
    object.remove_referrer(self) if object
  end

  to_many_attributes.each do |attrb|
    list = attrb.get_value(self)
    list.clear
  end

  unless referrers.count == 0
    raise "[Xcodeproj] BUG: #{self} should have no referrers instead" \
      "the following objects are still referencing it #{referrers}"
  end
end
remove_reference(object) click to toggle source

Removes all the references to a given object.

@return [void]

@visibility private

# File lib/xcodeproj/project/object.rb, line 230
def remove_reference(object)
  to_one_attributes.each do |attrb|
    value = attrb.get_value(self)
    attrb.set_value(self, nil) if value.equal?(object)
  end

  to_many_attributes.each do |attrb|
    list = attrb.get_value(self)
    list.delete(object)
  end

  references_by_keys_attributes.each do |attrb|
    list = attrb.get_value(self)
    list.each { |dictionary| dictionary.remove_reference(object) }
  end
end
remove_referrer(referrer) click to toggle source

Informs the object that another object stopped referencing it. If the object has no other references it is removed from the project UUIDs hash because it is unreachable.

@return [void]

@visibility private

# File lib/xcodeproj/project/object.rb, line 216
def remove_referrer(referrer)
  @referrers.delete(referrer)
  if @referrers.count == 0
    mark_project_as_dirty!
    @project.objects_by_uuid.delete(uuid)
  end
end
simple_attributes() click to toggle source

@return (see AbstractObject.simple_attributes)

@visibility private

# File lib/xcodeproj/project/object_attributes.rb, line 492
def simple_attributes
  self.class.simple_attributes
end
sort(_options = nil) click to toggle source

Sorts the to many attributes of the object according to the display name.

# File lib/xcodeproj/project/object.rb, line 146
def sort(_options = nil)
  to_many_attributes.each do |attrb|
    list = attrb.get_value(self)
    list.sort! do |x, y|
      x.display_name.downcase <=> y.display_name.downcase
    end
  end
end
sort_recursively(options = nil) click to toggle source

Sorts the object and the objects that it references.

@param [Hash] options

the sorting options.

@option options [Symbol] :groups_position

the position of the groups can be either `:above` or
`:below`.

@note Some objects may in turn refer back to objects higher in the

object tree, which will lead to stack level deep errors.
These objects should **not** try to perform a recursive sort,
also because these objects would get sorted through other
paths in the tree anyways.

At the time of writing the only known case is
`PBXTargetDependency`.
# File lib/xcodeproj/project/object.rb, line 172
def sort_recursively(options = nil)
  to_one_attributes.each do |attrb|
    value = attrb.get_value(self)
    value.sort_recursively(options) if value
  end

  to_many_attributes.each do |attrb|
    list = attrb.get_value(self)
    list.each { |entry| entry.sort_recursively(options) }
  end

  sort(options)
end
to_ascii_plist() click to toggle source
# File lib/xcodeproj/project/object.rb, line 420
def to_ascii_plist
  Nanaimo::Dictionary.new(to_hash_as(:to_ascii_plist), ascii_plist_annotation)
end
to_hash() click to toggle source

Returns a cascade representation of the object with UUIDs.

@return [Hash] a hash representation of the project.

@visibility public

@note the key for simple and to_one attributes usually appears only

if there is a value. To-many keys always appear with an empty
array.
# File lib/xcodeproj/project/object.rb, line 375
def to_hash
  to_hash_as
end
to_many_attributes() click to toggle source

@return (see AbstractObject.to_many_attributes)

@visibility private

# File lib/xcodeproj/project/object_attributes.rb, line 508
def to_many_attributes
  self.class.to_many_attributes
end
to_one_attributes() click to toggle source

@return (see AbstractObject.to_one_attributes)

@visibility private

# File lib/xcodeproj/project/object_attributes.rb, line 500
def to_one_attributes
  self.class.to_one_attributes
end
to_s()
Alias for: display_name
to_tree_hash() click to toggle source

Returns a cascade representation of the object without UUIDs.

This method is designed to work in conjunction with {Hash#recursive_diff} to provide a complete, yet readable, diff of two projects not affected by ISA differences.

@todo The current implementation might lead to infinite loops.

@return [Hash] a hash representation of the project different from

the plist one.

@visibility private

# File lib/xcodeproj/project/object.rb, line 437
def to_tree_hash
  hash = {}
  hash['displayName'] = display_name
  hash['isa'] = isa

  simple_attributes.each do |attrb|
    value = attrb.get_value(self)
    hash[attrb.plist_name] = value if value
  end

  to_one_attributes.each do |attrb|
    obj = attrb.get_value(self)
    hash[attrb.plist_name] = obj.to_tree_hash if obj
  end

  to_many_attributes.each do |attrb|
    list = attrb.get_value(self)
    hash[attrb.plist_name] = list.map(&:to_tree_hash)
  end

  references_by_keys_attributes.each do |attrb|
    list = attrb.get_value(self)
    hash[attrb.plist_name] = list.map(&:to_tree_hash)
  end

  hash
end

Private Instance Methods

to_hash_as(method = :to_hash) click to toggle source
# File lib/xcodeproj/project/object.rb, line 379
def to_hash_as(method = :to_hash)
  plist = {}
  plist['isa'] = isa

  simple_attributes.each do |attrb|
    value = attrb.get_value(self)
    plist[attrb.plist_name] = value if value
  end

  to_one_attributes.each do |attrb|
    obj = attrb.get_value(self)
    plist[attrb.plist_name] = nested_object_for_hash(obj, method) if obj
  end

  to_many_attributes.each do |attrb|
    list = attrb.get_value(self)
    plist[attrb.plist_name] = list.map { |o| nested_object_for_hash(o, method) }
  end

  references_by_keys_attributes.each do |attrb|
    list = attrb.get_value(self)
    plist[attrb.plist_name] = list.map(&method)
  end

  plist
end