Lord Zsolt
Lord Zsolt

Reputation: 6557

Objective-C array of structs

I want to have a struct like the following:

struct foo {
NSString Title;
NSInteger numberOfBooks;
vector of NSStrings Books;
};

foo bar[50];

Now first, I'd like to know how to create an array of strings (Each row containing a book title).

Then I'd like to know is the code I've written correct.

And finally, can I access said element of the struct with bar[n].Title?


I've followed what you commented, and here's how it looks:

@interface Foo : NSObject {
NSString *name;
NSArray *books;
}

@end

A->name = @"Clancy, Thomas";
A->books = [[NSArray alloc] initWithObjects:@"Book 1"
            @"Book2",nil];
self.authors = [[NSArray alloc] initWithObjects:A, nil];

Now it gives me that instance variable 'name' is protected. I tried writing public: when defining Foo, but it doesn't accept that. (Yes, I'm new to Obj-C).

Upvotes: 5

Views: 15107

Answers (3)

foundry
foundry

Reputation: 31745

In Objective-C you will be better off working with NSDictionaries or custom objects rather than structs. Expanding on H2CO3's comments - you could declare a BookCollection class

BookCollection.h

#import <Foundation/Foundation.h>


@interface BookCollection: NSObject

@property (nonatomic, strong) NSString* title
@property (nonatomic, strong) NSArray* books
   //use NSArray* for a static array or NSMutableArray* for a dynamic array

@end

BookCollection.m

#import "BookCollection/BookCollection.h"

@implementation BookCollection

@end

Your implementation is empty as you object only has public properties and no custom methods. We use @property syntax to declare our instance variables as this brings many advantages such as built-in getters and setters. (See my answer here for details)

Unlike C++ you can mix object types inside collection classes, so your books array won't complain if you add titles, books and other book collections: your code should ensure consistency. We do not declare array size. For a static NSArray the size is implied on creation, for a dynamic NSMutableArray there is no fixed limit. For a collection of fifty book collections, create an NSArray of fifty pre-existing collections. Alternatively create an NSMutableArray which you can add to incrementally.

BookCollection* joyce = [[BookCollection alloc] init];
joyce.title = @"Books by James Joyce";
joyce.books = @["Ulysses",@"Finnegan's Wake"]; //using static NSArray


BookCollection* tolkien = [[BookCollection alloc] init];
tolkien.title = @"Books by J.R.R. Tolkien";
[tolkien.books addObject:@"The Hobbit"];  //using dynamic NSMutableArray
[tolkien.books addObject:@"Lord of the Rings"];

For a dynamic array of collections:

NSMutableArray* collections = [[NSMutableArray alloc] init];
[collections addObject:joyce];
[collections addObject:tolkien];

For a fixed array you can use literal syntax:

NSArray* collections = @[joyce, tolkien];

For numberOfBooks, just use the count property of arrays

int numberOfBooks = joyce.books.count;

If you want to ensure that collections are created with titles and books, use a custom initialiser, as per adrusi's answer. This is not required to make the object work, but it is useful if you want to guarantee that your objects have content.

To access instance variables, use [message syntax] (the original obj-C way) or dot.syntax (which works with @properties)

[tolkien books]
joyce.books

Don't use -> arrow syntax. See here for a detailed explanation.

Upvotes: 0

adrusi
adrusi

Reputation: 845

Unfortunately, Objective C with Automatic Reference Counting (ARC) enabled does not allow for objects inside C structs. Objc also only allows objects inside NSArrays (the loose equivalent of vectors).

The first step of the solution is to create a class instead of a struct:

// Foo.h
#import <Foundation/Foundation.h>

@interface Foo : NSObject
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSArray *books;
// no need to keep the number of items in an NSArray as a separate variable,
// NSArray does that for you.

- (id)initWithTitle:(NSString *)title books:(NSArray *)books;

@end

// Foo.m
#import "Foo.h"

@implementation Foo
@synthesize title;
@synthesize books;

- (id)initWithTitle:(NSString *)title books:(NSArray *)books
{
  if (self = [super init]) {
    self.title = title;
    self.books = books;
  }
  return self;
}

@end

Note that all object types are pointers. This is because all object in objc are heap allocated, so you can't object types that aren't pointers. Also note that the type NSArray isn't parameterized. That's because objc doesn't have a concept of templates of generics, so the contents of the array could be any object. This usually isn't a problem, but occasionally a type error won't be caught at compile time.

To keep an array of these Foo objects is simple. You could just make a C array of them like Foo *bar[50];, but I don't think that works with ARC, and it definitely isn't the right way to do it in objective c. The right way is just to use another NSArray: NSArray *bar = [[NSArray alloc] init];, or with objc2.0 syntax: NSArray *bar = @[];

Some notes that might help you

@property and @synthesize are shortcuts for making an instance variable and accessor methods for if. You need accessor methods if you want to access an instance variable from outside the class (or rather, the implementation file). The strong flag tells the reference counter that this is a strong reference, like shared_ptr from the c++ boost libraries. I have no idea what nonatomic does, but I'm told you need it.

The method initWithTitle:books: is like a constructor, except that objc doesn't have a notion of constructors. The implementation calls the superclass's constructor with self = [super init] (pretend that self is a c++ reference to the object). Constructors can return nil, so that's why you need the if block.

The line self.title = title; is technically shorthand for [self setTitle:title];, which uses a method autogenerated by @property and @synthesize.

Upvotes: 9

StatusReport
StatusReport

Reputation: 3427

It is possible to box C-structs in Objective-C into the generic NSValue container. You can use [NSValue value:withObjCType:] to encode the value and [NSValue getValue:] to get that value back. Then, you can add the new NSValue * object to any Objective-C container.

See this for an example and read this for more information about type encoding.

Note that the boxing/unboxing is not free, therefore if you're writing a performance-critical application I recommend you to use std::vector or other C++ containers to avoid this process each time you store/get an item.

Upvotes: 4

Related Questions