0

Another newbie question. I am trying to understand how to use scope effectively to organise my projects using classes to hold the data instead of having everything on the view controller. So, I am working on a couple of versions of a simple project to understand how scope works. I have a view controller hooked to a view. In that view there are buttons that when clicked show images. I want to add another button that randomizes the images. I also have a class called "Cards" to hold the cards and the methods for creating and shuffling the cards. I have duplicated the project, so I have one that works and one that doesn't.

First project. These are the files:

view controller h file:

#import <UIKit/UIKit.h>
#import "Cards.h"

@interface ViewController : UIViewController

- (IBAction)buttonPressed:(id)sender;

@end

view contoller m file:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    Cards *instance = [[Cards alloc] init];

    instance.images = [instance createImages];

    NSLog(@"I've got %lu Images", (unsigned long)instance.images.count);

    instance.shuffled = [instance shuffleImages];

    NSLog(@"Image numbers shuffled: %@", instance.shuffled);
  }

- (IBAction)buttonPressed:(id)sender {

   //Nothing hooked to this yet

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

Cards h file:

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

@interface Cards : NSObject

// Creating Images

@property NSMutableArray *images;

- (NSMutableArray*) createImages;

//Shuffling Images

@property NSMutableArray *shuffled;

- (NSMutableArray*) shuffleImages;

@end

Cards m file:

#import "Cards.h"

@implementation Cards

- (NSMutableArray*) createImages{
    self.images = [[NSMutableArray alloc] initWithObjects:

                   [UIImage imageNamed:@"Image1.png"],
                   [UIImage imageNamed:@"Image2.png"],
                   [UIImage imageNamed:@"Image3.png"],
                   [UIImage imageNamed:@"Image4.png"], nil];

    return self.images;
}

- (NSMutableArray*) shuffleImages{

    NSUInteger imageCount = [self.images count];

    NSMutableArray *localvar = [[NSMutableArray alloc]init];

    for (int tileID = 0; tileID < imageCount; tileID++){
        [localvar addObject:[NSNumber
                             numberWithInt:tileID]];
    }

    for (NSUInteger i = 0; i < imageCount; ++i) {
            NSInteger nElements = imageCount - i;
            NSInteger n = (arc4random() % nElements) + i;
            [localvar exchangeObjectAtIndex:i
                                    withObjectAtIndex:n];
    }

    return localvar;
}

@end

This works and I get the expected output on the console:

2015-12-31 23:43:44.885 VCScope[2138:533369] I've got 4 Images
2015-12-31 23:43:44.886 VCScope[2138:533369] Image numbers shuffled: (
    0,
    2,
    3,
    1
)

Second project:

What I want to do, is put a button to randomize the images only when the button is pressed and not as part of viewDidLoad. So, in my second project, I have the same files for the view controller.h and for both the Cards.h and Cards.m, but on the view controller.m I move the calling of the method for the shuffling of the cards to a UIButton method, like so:

new View controller m file:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    Cards *instance = [[Cards alloc] init];

    instance.images = [instance createImages];

    NSLog(@"I've got %lu Images", (unsigned long)instance.images.count);

  }

- (IBAction)buttonPressed:(id)sender {

    Cards *instance = [[Cards alloc] init];

    instance.shuffled = [instance shuffleImages];

    NSLog(@"Image numbers shuffled: %@", instance.shuffled);

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

This outputs to the console the following:

2015-12-31 23:32:07.495 4StackVCScope[2029:486608] I've got 4 Images
2015-12-31 23:32:11.924 4StackVCScope[2029:486608] Image numbers: (
)

So it's not working and I am guessing it's to do with scope. Can someone throw some light into this? thanks

Paul
  • 1,277
  • 5
  • 28
  • 56

1 Answers1

1

Welcome to Stack Overflow. You mention you're a "newbie", but it would be helpful to know what background you have so I know how much detail is needed here.

Cards *instance = [[Cards alloc] init];

creates a fresh Cards instance in a local variable. You are doing this separately inside -viewDidLoad and in -buttonPressed:.

If you want one Cards object per ViewController, then the view controller needs to have per-instance storage for it. There are several possible ways to do this. Which one you pick is a question of code style and API design.

If the Cards instance is for internal use only, you can declare an ivar in your @implementation block:

@implementation ViewController {
    Cards *_cards;
}

- (void)viewDidLoad { _cards = ... }

- (IBAction)buttonPressed:(id)sender { access _cards }

@end

(Ivars can be declared in the public @interface as well, but I wouldn't recommend that as it leaks implementation details.)

Or you can use a property in the public interface:

// in your .h file:
@interface ViewController
@property (nonatomic) Cards *cards;
@end

// in your @implementation:
- (void)viewDidLoad { self.cards = ... }

- (IBAction)buttonPressed:(id)sender { access self.cards }

A property can also be privately declared in a class extension:

// in your .m file:
@interface ViewController ()
@property (nonatomic) Cards *cards;
@end
jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • This is very helpful. Thanks and happy new year! – Paul Jan 01 '16 at 00:31
  • 1
    Glad to hear it; you too! More info (though perhaps the document is getting a bit old): https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html – jtbandes Jan 01 '16 at 00:32
  • 1
    The second pattern is not only equally valid, it is the recommended practice. Declaring instance variables really isn't done much anymore outside of a handful of rare situations. – bbum Jan 01 '16 at 09:58
  • 1
    @bbum Certainly instance variables don't belong in public API, but for internal implementation details, in the age of ARC, there's not much of a compelling reason to prefer properties. I think it's worth knowing how both of these work, but which to use is more of a style choice. – jtbandes Jan 01 '16 at 13:14
  • 1
    Fair enough, @jtbandes. For the vast majority of the code I've worked with since the move to ARC, internal only state is generally declared as ivars and then the values are set and accessed directly. I'm not sure why that has been the settled state, but it is what it is. I'd clarify that iVars should never show up in your .h files. – bbum Jan 01 '16 at 19:04
  • 1
    Thanks for the feedback @bbum; I've updated the answer to hopefully lend some more clarity. If you still see it as confusing or misleading feel free to edit :) – jtbandes Jan 02 '16 at 02:42