Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending data between classes in Cocoa Objective C

I've started programming for iPhones about a month ago now, and have found the site very useful. So I though someone out there could help me.

I think I understand the basics of how @property and @synthesise work and using setters and getters, but I have a problem.

Say I have 3 classes: Functions, Class1 and Class2.

Functions is a class that holds all the data and Class1 and Class2 reads that data from it and writes new data (amends the data) to it. However both Class1 and Class2 will make there own instance of Functions as it is basically just a blueprint for the data.

So if Class1 writes or 'sets' some data (lets say) 5 to Functions and Class2 'gets' it, Class2 will get back 0 as Class1 only wrote 5 to its instance of Functions, that Class2 can't see.

I suppose first off, is that right? Second, how do I do it so Class1 and Class2 can see the same data to get and set.

Thanks for the help. I realise this might include a topic I haven't learned yet, but if so I'd love to know what it is, so I can lern it.

like image 445
Baza207 Avatar asked Nov 18 '10 00:11

Baza207


2 Answers

You have a number of options that all stem back to ensuring Class1 and Class2 use the same instance of the "shared object".

  1. Expressly pass the same instance to Class1 and Class2 from the outside, rather than allowing Class1 and Class2 to create an instance themselves; or
  2. Provide a singleton initializer to the Functions class, then make sure Class1 and Class2 use this; or
  3. Use a design pattern known as the registry pattern, and ensure Class1 and Class2 get their instance of the functions class from the registry; or
  4. Use dependency injection (complex).

Scenario (1) is the simplest to comprehend since it goes exactly like this:

Functions *funcs = [[Functions alloc] init];

Class1 *obj1 = [[Class1 alloc] initWithFunctions:funcs];
Class2 *obj2 = [[Class2 alloc] initWithFunctions:funcs];
/* or alternatively use setters after initialization */

Scenario (2) is the next simplest and is very common. Cocoa provides singleton initializers to many of it's classes. Whenever you see +shared... as a prefix for an initializer it's probably returning a singleton.

@implementation Functions

+(id)sharedFunctions {
  static Functions *sharedFunctions = nil;

  @synchronized(self) {
    if (sharedFunctions == nil) {
      sharedFunctions = [[self alloc] init];
    }

    return sharedFunctions;
  }
}

@end

The static variable is initialized only once, which means you can lazily load an instance of the object into it using a check for it's initial value (which only occurs once). Every subsequent call to the method returns the same instance.

Functions *f1 = [Functions sharedFunctions];
Functions *f2 = [Functions sharedFunctions];

/* f1 == f2; */ // always

Option (3) isn't seen very much in objective-C compared to in other languages and it achieves much the same goals as the singleton. The idea is that you have a dictionary of objects that can be looked up by key. Everything that needs access to items in the registry just asks the registry for its own instance. Typically the registry itself would be a singleton.

Option (4), dependency injection, is actually a very elegant solution with a number of benefits at the cost of additional complexity. The benefits come from the fact that you've ensured the dependencies are always loosely coupled (which makes swapping out implementations, and unit testing the dependencies independently much simpler). The complexity comes from the non-standard mechanisms for retrieving instances of what you need.

Dependency injection revolves around the idea that no object instantiates its own dependencies. Instead of relies on something else to provide those dependencies. That "something else" being known as a dependency-injection container. A dependency injection container is effectively a layer on top of the registry pattern, since the container will either hold pre-built instances of the dependencies, or it will know how to instantiate new instances.

In the simplest case, dependency injection is exactly what I demonstrated in option (1). You don't even need a dependency-injection container to achieve this.

Shifting up a level of complexity, dependency injection brings in the concept of a DI container, which encapsulates a number of existing design patterns (the registry, as seen in option (3), the singleton (likely, but not strictly) and the factory (to know how to make new instances of objects it manages).

Demonstrating a full-implementation of a DI container would probably be beyond the scope of this question, but from a public interface point of view, one implementation might look like this:

DIContainer *container = [DIContainer sharedContainer];

[container registerClass:[ClassA class]];
[container registerClass:[ClassB class]];
[container registerDependentProperty:@selector(setClassA:)
                      withInstanceOf:[ClassA class]
                            forClass:[ClassB class]];

ClassB *obj = [container makeInstanceOfClass:[ClassB class]];
NSLog(@"ClassB's -classA type = %@", [[obj classA] class]);

I just typed that off the top of my head in the middle of this post, so don't assume it is 100% accurate, but you get the concept. The container has been instructed that when it initializes instances of ClassB, it must then invoke -setClassA:, using an instance of ClassA, which it also initializes according to the rules defined in the container (in this case there are no dependencies of ClassA so it just returns a plain instance.

If you take nothing more away from this answer, just remember options (1) and (2) ;)

like image 185
d11wtq Avatar answered Sep 27 '22 20:09

d11wtq


You need to read the Cocoa Fundamentals Guide. The section relevant to your question is called Communicating with Objects.

like image 24
Joshua Nozzi Avatar answered Sep 27 '22 21:09

Joshua Nozzi