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
@return [String] the object’s class name.
@return [Project] the project that owns the object.
@return [Array<ObjectList>] The list of the objects that have a
reference to this object.
@visibility private
@return [Hash] the simple attributes hash.
@return [String] the object universally unique identifier.
Public Class Methods
@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
@return [String] the ISA of the class.
# File lib/xcodeproj/project/object.rb, line 39 def self.isa @isa ||= name.split('::').last end
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
@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
@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
@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
@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
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
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
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
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
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
# File lib/xcodeproj/project/object.rb, line 487 def <=>(other) uuid <=> other.uuid end
@!group Object
methods
# File lib/xcodeproj/project/object.rb, line 483 def ==(other) other.is_a?(AbstractObject) && to_hash == other.to_hash end
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
# File lib/xcodeproj/project/object.rb, line 416 def ascii_plist_annotation " #{display_name} " end
@return (see AbstractObject.attributes
)
@visibility private
# File lib/xcodeproj/project/object_attributes.rb, line 484 def attributes self.class.attributes end
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
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
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
# 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
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
# 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
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
@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
@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
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
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
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
@return (see AbstractObject.simple_attributes
)
@visibility private
# File lib/xcodeproj/project/object_attributes.rb, line 492 def simple_attributes self.class.simple_attributes end
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
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
# 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
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
@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
@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
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
# 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