Why does my mutable array become NULL in the prepareForSegue method? - ios

I want to pass an array onto another class using storyboards, and I've prepared the following code, however, the log shows that the mutable array is null, where as this is clearly not the case (in another method, the log shows it is not null, it only becomes null when prepareForSegue gets called). Why is this?
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"confirmSegue"]) {
SBBookingConfirmation *bookingConfirmed=(SBBookingConfirmation*)segue.destinationViewController;
NSLog(#"dates chosen - %#", self.dateChosen);
bookingConfirmed.confirmedTimings=self.dateChosen;
}
}
Output:
dates chosen - (null)

Clearly, you are changing self.dateChosen somewhere else. This has nothing to do with prepareForSegue:.
Go back and check what you do before the segue is invoked (through IB or by method).

Make sure, your dateChosen property is of type strong:
#property (nonatomic, strong) NSMutableArray *dateChosen;
//if you have an NSArray instead of NSMutableArray, use copy instead of strong
also in your "method" method (worst name ever!) , you should use the setter to set dateChosen:
-(void)method:(NSMutableArray *)array {
self.dateChosen=array; //not dateChosen = array;
NSLog(#"The following has been copied %#", self.dateChosen);
}
Finally, you need to initialize your array somewhere. If you never did something like
self.dateChosen = [NSMutableArray array];
or
self.dateChosen = <NON-NIL array pointer>
it is no wonder it would be nil.
On a side note: choose your method / variable names better. Don't call a method just "method".
If you use an array, it is usually better to name it in its plural form: date*s*Chosen, instead of dateChosen.

Related

ARC and pointer storing

My situation is a bit more complex than what I've seen here before posting, and I'm not really good with memory management.
I have a custom UITableViewCell (that we will call MyCell here) and I pass its pointer to an UITableViewController (MyController here) when clicking on it. I pass the pointer because I want to call a method of this cell and the reference is only made by copy in Objective-C so it doesn't call the method on the right cell. I have made this:
MyController.h
#interface MyController : UITableViewController {
MyCell * __autoreleasing *_cell;
}
-(instancetype)initWithCell:(MyCell * __autoreleasing *)cell;
#end
MyController.m
- (instancetype)initWithCell:(MyCell **)cell {
if (self = [super init]) {
_cell = cell;
// Checkpoint 1
}
}
Then I want to use this variable later in my code, for example to define the number of sections:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Checkpoint 2
return (*_cell).contents.count; // contents being an NSArray property of the custom cell
}
The issue: At the "checkpoints" marked here, I have an NSLog(#"%ld", (unsigned long)(*_cell).contents.count);, however it shows 2 (the right number) in the first checkpoint, but 0 in the second checkpoint, so basically when I click on the cell an empty table view is shown.
I used to pass the cell by copy by storing it in a nonatomic, strong property and everything worked well, but by changing the calls from self.cell to _cell because of the pointer reference, the view is now empty as I said. It is likely a memory management issue, but I have no clue on how to solve it (fairly new to Objective-C, first app).
N.B.: I tried to change the __autoreleasing by a __strong, but this lead to a crash at every access of a property of the _cell. I have also tried to use a nonatomic, assign property to store it instead of using a ivar but it didn't solve my problem.
Thanks!
Edit: Forgot to mention that I call the view controller by using
[self.navigationController pushViewController:[[MyController alloc] initWithCell:(MyCell **)&cell] animated:YES];
in my previous view controller, in the tableView:didSelectRowAtIndexPath: method.
A few points:
The * __autoreleasing * pattern serves a very specific purpose, namely where a called method creates an object and needs to update the caller’s pointer to reference this new object. For example, consider an example from the regular expression documentation:
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\\b(a|b)(c|d)\\b"
options:NSRegularExpressionCaseInsensitive
error:&error];
So, error is a NSError pointer, and the caller is
supplying the address to that pointer so that if
regularExpressionWithPattern instantiates a
NSError object, it can update the caller’s reference to point to it.
The only time you need to employ this “pointer to a pointer” pattern
is when the called routine is instantiating an object and wants to
update a pointer of the calling routine. We generally only have to
use that pattern when you want to return pointers to more than one
object (e.g. in the case of regularExpressionWithPattern, it will
return a NSRegularExpression * pointer, but optionally may also
want to update the NSError * pointer, too).
You said:
I pass the pointer because I want to call a method of this cell and the reference is only made by copy in Objective-C so it doesn't call the method on the right cell.
That logic is not quite right. MyCell * is a pointer to that
original object, not a copy of it. You can access it without
resorting to the “pointer to a pointer” pattern. In your case, if
you would just use MyCell *, not MyCell * *.
You should not pass a cell reference to this view controller at all ... one view controller should not be reaching into the view hierarchy of another view controller;
Table views have all sorts of optimizations associated with cell reuse ... one shouldn’t use a cell beyond interaction in its own table view data source and delegate methods; and
One should not use a cell (a “view” object) to store “model” data (what is currently stored in the contents property). Do not conflate the “model” (the data) with the “view” (the UI).
So, I would suggest a simple pointer (not a pointer to a pointer) to a model object:
#interface MyController : UITableViewController
#property (nonatomic, strong) NSArray <ModelObject *> *contents; // or whatever you previously stored in cell `contents`
#end
And then:
MyController *controller = [[MyController alloc] init]; // usually we'd instantiate it using a storyboard reference, but I'm gather you're building your view hierarchy manually
controller.contents = self.contents[indexPath.row]; // note, not `cell.contents`, but rather refer to this current view controller’s model, not the cell (the “view”)
[self.navigationController pushViewController:controller animated:YES];
And for passing data to and from view controllers, see Passing data between view controllers. But, as a general rule, one view controller shouldn’t be accessing the view objects of another.

Confusion when using the self keyword

I have a few questions about this code:
#import "ViewController.h"
#interface ViewController ()
#property (copy, nonatomic) NSArray *myArray;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.myArray= #[#"one", #"two", #"three", #"four"];
}
I know the self keyword means that's method will be sent to the receiver of that message, and I know when and how to use it if i was creating a simple command utility code, but when learning about creating an iPhone app and using the storyboard every time I see the self keyword it confuses me, I don't know which object is the receiver
2nd question, I know that #[#"object", #"object"];is quick way to create an NSArray object, but "myArray" is a property not an NSArray object. please explain, thank you.
myArray is. The name of a property, as you say. By default, the compiler will create an instance variable called _myArray that's used by that property. In any case, because you've declared the property to be of type NSArray * (and not readonly) you can assign an array to it.
The self keyword is simply a pointer to the "current" object, some instance of the class that you're writing code for when you type "self". If you assign something to self.myArray, the class in which you make that assignment needs to have a myArray property. In your code example, self is a pointer to the particular instance of ViewController that just loaded its view. None of that changes when you're writing an iPhone apportion using a storyboard. self is always the object receiving the method in which self is used. (Note: when used in a class method, i.e. one that starts with +, the object receiving the method is the class itself, not an instance of the class. Don't worry about this if it sounds confusing -- it doesn't come up all that often.)
If you find an expression like self.myArray confusing, it may help to know that property syntax is just shorthand for a method call. Also, self in an expression like that isn't special at all. The syntax is foo.bar where foo can any object pointer (including self) and bar can be any property of the object pointed to by foo. The expression translates directly to either [foo bar] or [foo setBar:...], depending on whether you're reading or assigning a value to the property. So,
self.myArray = #[a, b, c];
means exactly the same as:
[self setMyArray:#[a, b, c]];
self always corresponds to the self class. So every instance of a class will have its own self.
In your case myArray is a property of Class ViewController. Here you can refer to the property by using self.myArray or _myArray.
In the following statement you are creating an array with four strings and adding it to myArray.
self.myArray= #[#"one", #"two", #"three", #"four"];
Your wordings in the question :
I know the self keyword means thats method will be sent to the
receiver of that message
is valid for a method call where you use it as :
[self aMethod];
Even in this you are calling a method which is a part of the class. If you call any method which is not of the current(self) class then you call it by using that class' object name as:
SomeClass *someClassObject = ...
[someClassObject itsMethod];
"self" is a pointer to object to which the method belongs. In your case you may have many instances of ViewController and each of them can call viewDidLoad and in that method "self" is the pointer to the instance itself. To look at it from the C perspective you could create a method using pointer to function where you would also send own pointer to the function called "self", for instance void foo(MyClass *self); to call it then myClassInstance->foo(myClassInstance); this procedure kind of simulates methods but you always need to send instance pointer as well (self). So there should be no confusion as to what "self" is except it is more commonly known by "this" keyword. It is just a reference to the object on which the method is being called.
The property is just a convenience of usually 2 methods called getter and setter. When creating a property #property NSArray *myArray you will actually generate a pointer usually NSArray *_myArray; then a getter:
- (NSArray *)myArray {
return _myArray;
}
And a setter
- (void)setMyArray:(NSArray *)myArray {
_myArray = myArray;
}
How getters and setters are created depends on property attributes (strong, weak, readonly...). You may even override any of these methods or both. So as you stated "but "myArray" is a property not an NSArray object" a property can have truly any type.

Trying to store values received in a ViewController and store them in a single instance of an NSMutableArray in another View Controller in iOS

I have an application where A View Controller (A)is called twice in close succession. Now each time it is called, an NSString object is created, and I need this value to be stored in an NSMutableArray that is a public property of ANOTHER View Controller (B).
In A, I create an instance of the second View Controller (B), and using that instance, add the NSString objects into the NSMutableArray which I've created as a public property. Later, when I am inside View Controller B and print the contents of the NSMutableArray property, the array is empty. Why? Here is the code that is inside View Controller A:
-(void)viewDidLoad {
ViewControllerA *aVC = [[ViewControllerA alloc] init];
if (aVC.stringArray == nil) {
aVC.stringArray = [[NSMutableArray alloc] init];
}
[aVC.stringArray addObject:#"hello"];
[aVC.stringArray addObject:#"world"];
for (NSString *wow in aVC.stringArray) {
NSLog(#"The output is: %#", wow);
}
}
Inside my View Controller B class, I have the following code:
- (IBAction)buttonAction:(UIButton *)sender {
NSLog(#"Button selected");
for (NSString *test in self.stringArray) {
NSLog(#"Here are the contents of the array %#", test);
}
}
Now the buttonAction method gets called, as I do see the line Button selected in the system output, but nothing else is printed. Why? One thing I want to ensure is that View Controller A is called twice, which means I would like to see in the output, "Hello World", "Hello World" (i.e. printed twice), and not "Hello World" printed just once.
The other thing I wish to point out is that View Controller B may not be called at all, or it may be called at a later point in time. In any case, whenever View Controller B is called, I would like to have the values inside the array available, and waiting for the user to access. How do I do this?
Your approach is not ideal, potentially leading to a memory cycle, with two objects holding strong pointers to each other.
You can instead achieve your goal in two ways;
Delegate Protocol
This method allows you to set delegates and delegate methods to pass data back and forth between view controllers
in viewControllerA.h
#protocol viewControllerADelegate <NSObject>
- (void)addStringToNSMutableArray:(NSString *)text;
#end
#interface viewControllerA : UIViewController
#property (nonatomic, weak) id <viewControllerADelegate> delegate;
in viewControllerB.m
// create viewControllerA class object
[self.viewControllerA.delegate = self];
- (void)addStringToNSMutableArray:(NSString *)text
{
[self.mutableArray addObject:text];
}
in viewControllerA.m
[self.delegate addStringToNSMutableArray:#"some text"];
Utility Classes
Alternatively you can use a utility class with publicly accessible methods (and temporary data storage). This allows both viewController classes to access a shared data store, also if you use class methods, you don't even need to instantiate the utility class.
in XYZUtilities.h
#import <Foundation/Foundation.h>
#interface XYZUtilities : NSObject
+ (void)addStringToNSMutableArray;
#property (strong, nonatomic) NSMutableArray *array;
#end
in XYZUtilities.m
+ (void)addStringToNSMutableArray
{
NSString *result = #"some text";
[self.array addObject:result];
}
+ (NSArray)getArrayContents
{
return self.array;
}
in viewControllerA.m
NSString *stringFromObject = [XYZUtilities addStringToNSMutableArray];
in viewControllerB.m
self.mutableArray = [[NSMutableArray alloc] initWithArray:[XYZUtilities getArrayContents]];
I'm not sure what kind of a design pattern you are trying to follow but from the looks of it IMHO that's not a very safe one. However, there are many, many ways this could be accomplished.
One thing though, you said that View Controller B may never get allocated and if it is alloc-ed, it will be down the road. So you can't set a value/property on an object that's never been created.
Since you already aren't really following traditional patterns, you could make a static NSMutableArray variable that is declared in the .m of your View Controller B Class and then expose it via class methods.
So it would look like this:
viewControllerB.h
+(void)addStringToPublicArray:(NSString *)string;
viewContrllerB.m
static NSMutableArray *publicStrings = nil;
+(void)addStringToPublicArray:(NSString *)string{
if (publicStrings == nil){
publicStrings = [[NSMutableArray alloc]init];
}
if (string != nil){
[publicStrings addObject:string];
}
}
Then it would be truly public. All instances of view controller B will have access to it. This, of course is not a traditional or recommended way of doing it—I'm sure that you will have many replies pointing that out ;).
Another idea would be to use a singleton class and store the values in there. Then, when or if view controller B is ever created, you can access them from there.

Passing NSArray makes array empty

i'm trying to pass an array from a view to another using the PrepareForSegue method.
In the first view i got a button called "Submit" that, if pressed, reads a textView and store the text in a NSArray, and then should pass this array to another view (push segue), but when the array arrives is empty.
Here is the code
//.h
#property (strong, nonatomic) NSArray *words;
//.m
- (IBAction)Submit:(id)sender{
//read textView
_words = [self.myTextView.text componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//if Submit is pressed go to SecondViewController
if ([segue.identifier isEqualToString:#"secondSegue"]) {
SecondViewController *vc = [segue destinationViewController];
vc.array = _words;
}
}
So here is the code of the SecondViewController that receives the array
- (void)viewDidLoad
{
[self Calculate];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(void)Calculate
{
int size = [array count];
NSLog(#"size is %d",size);
}
the log always says "size is 0".
Did i make some mistake?
Could it be that the Segue happens before i can read the TextView and fill the array so it's always empty?
thanks in advance!
EDIT:
I tried to NSLog the Submit action and i discovered that the program never accesses to it, so it never reads! (p.s. yes i connected the button)
So the segue happens before the Submit action
How can i solve? can i copy the PrepareForSegue code in the Submit action?
Try copying the array when you send it, Could be getting freed also are you sure -(IBAction)submit.. is being called?
Also the submit button I assume has the action that performs the segue . I'm not sure on the order of execution. Try calling the method Submit from prepare for segue and not from the button. That way you can guarantee it is being called first.
Remove the #synthesize, the compiler will automatically synthesize
the ivar to be _words
Use self.words when assigning and accessing the 'words' array. This ensures the ivar gets set properly as well, if you plan to refer to it directly (which you won't in most cases).
Your problem is that #synthesize creates an ivar called 'words', not '_words'.

Accessing properties of an object inside array of objects

I have a list of items showing up on a table view.
Every item has its properties such as name, pic, rank etc'.
My goal is, every time the user selects a row the item with its properties will be added to a new list.
I've created a new list called listOfBugs and because i want it to be global i've allocated and initialized it inside viewDidLoad. (Is that a proper thing to do?)
Here is my code:
MasterViewController.h
#interface MasterViewController : UITableViewController
{
NSMutableArray *listOfBugs;
}
#property (strong) NSMutableArray *bugs;
MasterViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
listOfBugs = [[NSMutableArray alloc]init];
self.title = #"Scary Bugs";
}
...
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ScaryBugDoc *bug = [self.bugs objectAtIndex:indexPath.row];
UIAlertView *messageAlert = [[UIAlertView alloc]
initWithTitle:#"Row Selected" message:bug.data.title delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[messageAlert show];
[listOfBugs addObject:bug];
NSLog(#"this is %#",listOfBugs);
}
Using NSLog I can see that the objects are added:
ScaryBugs[1195:11303] this is <ScaryBugDoc: 0x75546e0>
2012-12-05 17:45:13.100
ScaryBugs[1195:11303] this is <ScaryBugDoc: 0x75546e0>
I have a few questions.
1.How can I access the properties of the objects inside of the array listOfBugs ?
Update: This worked for me:
NSLog(#"this is %#",((ScaryBugDoc *)[listOfBugs objectAtIndex:0]).data.title);
But I can't access the listOfBugs from another class.
I turned it into a property as suggested to make my life easier but still can't access it from another class.
For example in listOfBugsViewController.m return [_listOfBugs count]; will give me the error Use of undeclared identifier '_listOfBugs'
2.I want to be abale to populate a table view with the customized list, how can i do that?
After accomplishing that I would like to save the list as a plist and also add and remove objects from it at ease so I need to take that under consideration.
This is the code that I'm based on, I only made a few adjustments to create the new list
This is really two questions:
1) How do I make my property a public property which can be accessed by other classes?
You do this just like you did with your bugs property. Add this to your .h file:
#property (strong) NSMutableArray *newList;
Note that if you aren't using different threads, you can make it a little more efficient by using the nonatomic property as well (#property (nonatomic, strong)).
Once you do that, you don't need your iVar declaration because it will automatically be generated for you. (i.e. you can remove NSMutableArray *newList;.)
2) How do I access an object in an array?
Objects in an array are stored as an id object, meaning that it is a "generic" object. If you know what type of object is stored, then you need to tell the compiler what it is so that it knows what properties and methods are appropriate for that class. You do this by casting the variable to the proper type:
ScaryBugDoc *bug = (ScaryBugDoc *)[self.newList objectAtIndex:0];
Then, you can access the properties of the object, assuming that they are public (as covered in point 1 above) like this:
NSLog(#"this is %s", bug.data.tile);
Okay, so based from the comments, this should work:
Album* tempAlbum = [albumList objectAtIndex:i];
//now you can access album's properties
Song* tempSong = [album.songs objectAtIndex:j];
//now you can access song's properties
This can be simplified down to:
Song* someSong = [((Album)[albumList objectAtIndex:i]).songs objectAtIndex:j];
When returning an object from an NSArray, or a collection object like that it will return a generic id object. This will need to be typecasted to the expected object so you can access the right properties.

Resources