0

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.

Arainty
  • 91
  • 9
  • Then how can I update the "caller" cell `MyCell` from the `MyController` it pushes? – Arainty Feb 27 '21 at 22:56
  • Your link is very comprehensive thanks. I will probably do a delegate. Also can you explain me shortly how I should refactor my code? – Arainty Feb 27 '21 at 23:42
  • 1. Don’t use the cell to store model data. If you want to provide it model data so it can configure itself, fine, but it’s not for storage, but rather just for display and capture. 2. If you have “processing” code, that should be pulled out of the cell, too. 3. Delegate-protocol pattern for passing data back from presented view controller back to presenting view controller. – Rob Feb 28 '21 at 05:54
  • Thank you very much, will do! Thanks for the time you gave me. – Arainty Feb 28 '21 at 09:48

1 Answers1

1

A few points:

  1. 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).

  2. 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 * *.

  3. 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;

  4. 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

  5. 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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thank you! I understand your point of view and objc conventions. I did my cell like that because I didn't want to create a model for each custom view, as I do a lot of them. Also your solution would not work because I want to call a method from `MyCell` and I can't without a pointer reference... I have also other properties in `MyCell`, and that's why I passed the cell itself, to use its properties plus being able to call a method from it. – Arainty Feb 27 '21 at 21:49
  • The method of my cell I want to call does some process and set the text of a label of the cell, that's why I need to call it – Arainty Feb 27 '21 at 22:01
  • Views are neither for the storage of model data nor for the processing of that data. But I get it, that you don’t want to tackle the broader architectural design at this point. Understood and good luck – Rob Feb 27 '21 at 22:27