3

In objective-C i made 11 classes which subclass RLMObject to represent the model of my database. And as of now I have a problem building my App with this, because as stated in the title, they seem to not see each other. Additionally: they are in the same folder, the #import does not make any problems itself.

As a sample I want to provide two classes The following is the class for Books:

#import <Realm/Realm.h>
#import "Chapter.h"

@interface Book : RLMObject

@property NSInteger id;
@property NSString *name;
@property RLMArray<Chapter> *chapters;

@end

// This protocol enables typed collections. i.e.:
// RLMArray<Book>
RLM_ARRAY_TYPE(Book)

The following would be my class for chapters:

#import <Realm/Realm.h>

@class Book;

@interface Chapter : RLMObject

@property NSInteger id;
@property NSString *name;
@property Book *book;

@end

// This protocol enables typed collections. i.e.:
// RLMArray<Chapter>
RLM_ARRAY_TYPE(Chapter)

In Book.h I get:

Cannot find protocol declaration for 'Chapter'

Does anybody have an idea? It is definitely some kind of import circle. But how can I resolve it? If not necessary I would like to avoid putting all of the model-classes in a prefix header.

EDIT: a @class import helped in the chapter-file, but it doesn't in the Book-file

dfinki
  • 324
  • 4
  • 13

2 Answers2

4

You can also make a protocol forward declaration for the objects, you want to use in your list property, like below:

#import <Realm/Realm.h>

@protocol Chapter;

@interface Book : RLMObject

@property NSInteger id;
@property NSString *name;
@property RLMArray<Chapter> *chapters;

@end

// This protocol enables typed collections. i.e.:
// RLMArray<Book>
RLM_ARRAY_TYPE(Book)

Note: You have to use @class for one-to-one relations, and @protocol for one-to-many relations.


Beside that I would recommend for your scenario to use a backlink from Chapter to Book instead of a property. What you have in your question are two independent properties, which are not automatically synchronized.

If you don't maintain it correctly, this could led to something like that:

Books:

  • id: 0, name: "Moby Dick", chapters: [Chapter#0, Chapter#1]
  • id: 1, name: "The Swift Programming Language", chapters: [Chapter#2, Chapter#3]

Chapters:

  • id: 0, name: "Loomings.", book: Book#0
  • id: 1, name: "The Carpet-Bag.", book: Book#0
  • id: 2, name: "About Swift", book: Book#1
  • id: 3, name: "A Swift Tour", book: Book#0

See how "A Swift Tour" is referencing back to "Moby Dick", while it's clearly the second chapter of the Swift book. Your model allows that, currently, but you can define it in a way to prevent that such scenarios can exist at all.

The solution with backlinks would look like that:

@interface Chapter : RLMObject

@property NSInteger id;
@property NSString *name;
@property (readonly) Book *book;

@end

@implementation Chapter
// Define "book" as the inverse relationship to Book.chapters
- (Book *)book {
    return [self linkingObjectsOfClass:@"Book" forProperty:@"chapters"].firstObject;
}
@end
marius
  • 7,766
  • 18
  • 30
  • Thanks for writing this! I didn't know about the @protocol forward declaration. I love the backlink approach, I'm totally going to implement it later! Does the protocol and include also work on many-to-many relationships? – dfinki Jul 29 '15 at 14:21
  • Sure, it does. In fact due to Realm's nature as an object store, there is no real difference between a one-to-many and a many-to-many relation. The only difference, you can make yourself is in which multiplicity you define the backlink accessor. – marius Jul 30 '15 at 09:55
1

Ok i figured out a way:

I made a header file ModelProtocols.h and added all the protocols for typed collections to that file:

#ifndef Your_Project_ModelProtocols_h
#define Your_Project_ModelProtocols_h

@class Book;
@class Chapter;

// This protocol enables typed collections. i.e.:
// RLMArray<Book>
RLM_ARRAY_TYPE(Book)

// This protocol enables typed collections. i.e.:
// RLMArray<Chapter>
RLM_ARRAY_TYPE(Chapter)

#endif

Once i had this file setup, i needed to import it in my modelclasses:

#import "ModelProtocols.h"

and could use @class for the rest of my model-classes. This builds just fine, I still have to test it, but it should work.

dfinki
  • 324
  • 4
  • 13
  • 2
    You might not even need #ifndef stuff on top of ModelProtocols.h. As far as I know, the difference between #import and #include is that using #import avoids the include cycles and lets you get rid of the extra #ifndef and #define lines on top of every header file. I don't know the implementation of RLM_ARRAY_TYPE but I assume it uses #include insted of #import and that's why you're getting an error of this sort. I might be wrong though. There are a few decent explanations here: http://stackoverflow.com/questions/439662/what-is-the-difference-between-import-and-include-in-objective-c – halileohalilei Jul 29 '15 at 09:05
  • I tried to use #include just now, and it seemed to work too. Excellent, thanks. Probably going to use that next time I encounter this kind of problem. On the other hand my solution brings some kind of organization to my models, which I like too. – dfinki Jul 29 '15 at 09:36
  • 1
    `RLM_ARRAY_TYPE` doesn't import or include any files. Declaring the array types in a separate header file, which you import from all other model object class headers is still a legitimate solution, but @halileohalilei is right in the aspect to prefer `#import` over `#include` for that to avoid the extra ifndefs. – marius Jul 29 '15 at 13:43