Take the tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I plan on including this work in a portfolio.

Question: Will this code get me hired or laughed at?

More Specifically:

How would you rate the general complexity of the code?

How bad does the code smell?

Is the VC too fat?

Glaring best practices mistakes?

Really absolutely any feedback would be appreciated. Thanks

Quick follow-up to Watson's reply:

1) Can you expand on what you said about magic numbers? (Totally missed the bool/BOOL usually I'm better than that. The names w/2 are just temp renames for SO)

2)I have a lot of .h files because I tried to refactor somethings, like animation and URL methods, into helpers, presumably to increase readability. Did I go overboard?

3) In theory, the way the program is setup, the MasterVC should never be deallocated. VC's are just pushed on top, not more than one layer at a time. Is it still necessary to unsubscribe from notifications?

4) Lastly, on a scale of 1-10 what is your impression of the code overall? (from what you've seen and discounting the name and bool problem)

Details: iOS: 6 (updating to 7 currently) | xcode: 4.6 | tested: iphone 4 device | ARC Enabled

MasterViewController.h

#import <UIKit/UIKit.h>
#import "SRAPI.h"
#import "SRChoiceBox.h"
#import "SRPostTopic.h"
#import "SRDetailViewController.h"
#import "SRCollapsibleCell.h"
#import "SRAnimationHelper.h"
#import "SROpenTokVideoHandler.h"
#import "SRObserveViewController.h"


@interface SRMasterViewController2 : UIViewController <UITableViewDelegate, UITableViewDataSource, SRChoiceBoxDelegate, SRPostTopicDelegate>

@property (weak, nonatomic) IBOutlet UITableView *topicsTableView;
@property (weak, nonatomic) IBOutlet UIView *postTopicContainer;
@property (weak, nonatomic) IBOutlet UILabel *statusLabel;

@property (strong, nonatomic) NSIndexPath *openCellIndex;
@property (strong, nonatomic) RKPaginator *paginator;
@property (strong, nonatomic) SROpenTokVideoHandler *openTokHandler;

@end

MasterViewController.m

#import "SRMasterViewController2.h"
#import "UIScrollView+SVPullToRefresh.h"
#import "UIScrollView+SVInfiniteScrolling.h"
#import "SRUrlHelper.h"
#import "SRNavBarHelper.h"

@interface SRMasterViewController2 ()
@property NSInteger offset;
@property NSInteger totalPages;
@property NSMutableArray *topicsArray;
@property bool isPaginatorLoading;

@end

@implementation SRMasterViewController2

- (void)viewDidLoad {
    [super viewDidLoad];

    [self configureTableView];
    [self configureNavBar];
    [self configurePostTopicContainer];
    [SRAPI sharedInstance];
    [self paginate];
    self.openTokHandler = [SROpenTokVideoHandler new];
    [self configureNotifications];
}

- (void)configureNotifications {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotifications:) name:kFetchNewTopicsAndReloadTableData object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotifications:) name:kFetchRoomFromUrl object:nil];
}

- (void)receiveNotifications:(NSNotification *)notificaiton {
    if ([notificaiton.name isEqualToString:kFetchRoomFromUrl]) {
        NSURL *url = notificaiton.userInfo[@"url"];
        [self fetchRoomWithUrl:url];
    }
    else if ([notificaiton.name isEqualToString:kFetchNewTopicsAndReloadTableData]) {
        self.offset = 1;
        [self.topicsTableView.infiniteScrollingView startAnimating];
        [self paginate];
    }
}

- (void)fetchRoomWithUrl:(NSURL *)url {
    NSDictionary *dict = [SRUrlHelper parseQueryString:[url query]];

    SRRoom *room = [[SRRoom alloc] init];
    room.position  = (NSString *)[url pathComponents][3];
    room.topicId = [url pathComponents][2];
    room.sessionId = dict[@"sessionID"];

    [self performSegueWithIdentifier:@"showDetail2" sender:room];
}

- (void)configureNavBar {
    //Button displays container for posting topics
    UIBarButtonItem *rightPostTopicButton =
    [SRNavBarHelper buttonForNavBarWithImage:[UIImage imageNamed:@"logo"]
                            highlightedImage:nil
                                    selector:@selector(showPostTopicContainer)
                                      target:self];
    self.navigationItem.rightBarButtonItem = rightPostTopicButton;

    //suffle button - Joins random room
    UIBarButtonItem *leftShuffleButton =
    [SRNavBarHelper buttonForNavBarWithImage:[UIImage imageNamed:@"shuffle.png"]
                            highlightedImage:[UIImage imageNamed:@"shufflePressed.png"]
                                    selector:@selector(joinRandomRoom)
                                      target:self];
    self.navigationItem.leftBarButtonItem = leftShuffleButton;
}

- (void)joinRandomRoom {
    NSMutableArray *activeTopics = [NSMutableArray new];
    SRTopic *randomTopic = [SRTopic new];
    SRRoom *randomRoom = [SRRoom new];

    //find topics with people in them
    for (SRTopic *topic in self.topicsArray) {
        if ([topic.agreeDebaters integerValue] > 0 || [topic.disagreeDebaters integerValue] > 0) {
            [activeTopics addObject:topic];
        }
    }
    int numberOfActiveTopics = activeTopics.count;

    if (numberOfActiveTopics > 0) {
        //Put user into a random Active Room
        int random = arc4random() % numberOfActiveTopics;
        randomTopic = (SRTopic *)activeTopics[random];
        if (randomTopic.agreeDebaters.intValue > randomTopic.disagreeDebaters.intValue) {
            randomRoom.position  = @"disagree";
        }
        else if (randomTopic.agreeDebaters.intValue < randomTopic.disagreeDebaters.intValue) {
            randomRoom.position  = @"agree";
        }
        else {
            randomRoom.position = [self randomlyChooseAgreeDisagree];
        }
    }
    else {
        //No Active Rooms, put user in a random room
        int random = arc4random() % self.topicsArray.count;
        randomTopic = (SRTopic *)self.topicsArray[random];
        randomRoom.position = [self randomlyChooseAgreeDisagree];
    }
    randomRoom.topicId = randomTopic.topicId;
    [self performSegueWithIdentifier:@"showDetail2" sender:randomRoom];
}

- (NSString *)randomlyChooseAgreeDisagree {
    int r = arc4random() % 2;
    return (r == 0) ? @"agree" : @"disagree";
}

- (void)configureTableView {
    //set offset for loading tabledata
    self.offset = 1;

    //add pull to refresh controls
    UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
    [refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];
    [self.topicsTableView addSubview:refreshControl];

    //add infinite scrolling
    [self addInfiniteScrolling:self.topicsTableView];

    //close all cells
    self.openCellIndex = nil;

    //Smooth scrolling
    self.topicsTableView.layer.shouldRasterize = YES;
    self.topicsTableView.layer.rasterizationScale = [[UIScreen mainScreen] scale];
}

- (void)configurePostTopicContainer {
    //configure container for posting topics
    SRPostTopic *postTopic = [[SRPostTopic alloc]initWithFrame:CGRectMake(0, 0, 320, 133)];
    [self.postTopicContainer addSubview:postTopic];
    postTopic.delegate = self;
}

- (void)addInfiniteScrolling:(UITableView *)tableView {
    [tableView addInfiniteScrollingWithActionHandler: ^(void) {
        self.offset += 1;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self paginate];
            double delayInSeconds = 0.8;
            dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
            dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
                [self.topicsTableView.infiniteScrollingView stopAnimating];
            });
        });
    }];

    //configure infinite scrolling style
    self.topicsTableView.infiniteScrollingView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
}

- (void)refresh:(UIRefreshControl *)refreshControl {
    self.offset = 1;
    self.openCellIndex = nil;

    //stop refresh after successful AJAX call for topics
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self paginate];
        double delayInSeconds = 1;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
            [refreshControl endRefreshing];
        });
    });
}

- (void)paginate {
    // Create weak reference to self to use within the paginators completion block
    __weak typeof(self) weakSelf = self;

    // Setup paginator
    if (!self.paginator) {
        self.paginator.perPage = 20;
        NSString *requestString = [NSString stringWithFormat:@"?page=:currentPage&per_page=:perPage"];
        self.paginator = [[RKObjectManager sharedManager] paginatorWithPathPattern:requestString];

        [self.paginator setCompletionBlockWithSuccess: ^(RKPaginator *paginator, NSArray *objects, NSUInteger page) {
            NSMutableArray *topicsArrayTemp = [objects mutableCopy];
            weakSelf.isPaginatorLoading = NO;

            if (weakSelf.offset == 1) {
                [weakSelf replaceRowsInTableView:topicsArrayTemp];
            }
            else {
                [weakSelf insertRowsInTableView:topicsArrayTemp];
            }
            [weakSelf.topicsTableView.infiniteScrollingView stopAnimating];
        } failure: ^(RKPaginator *paginator, NSError *error) {
            weakSelf.isPaginatorLoading = NO;
            [weakSelf.topicsTableView.infiniteScrollingView stopAnimating];
            [weakSelf.self noResults];
        }];
    }

    if (!weakSelf.isPaginatorLoading) {
        weakSelf.isPaginatorLoading = YES;
        [self.paginator loadPage:self.offset];
    }
}

- (void)noResults {
    double delayInSeconds = 0.6;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
        [self performSegueWithIdentifier:@"noResults" sender:nil];
    });
}

#pragma mark - Posting a new topic
//open/close container for posting topics
- (void)showPostTopicContainer {
    [self.view endEditing:YES];
    CGRect newTableViewFrame = self.topicsTableView.frame;
    CGRect newPostTopicFrame = self.postTopicContainer.frame;
    float duration, alpha;

    if ([self isPostTopicContainerOpen]) {
        newTableViewFrame.origin.y -= 133;
        newPostTopicFrame.origin.y -= 133;
        duration = .3;
        alpha = 0;
    }
    else {
        newTableViewFrame.origin.y += 133;
        newPostTopicFrame.origin.y += 133;
        duration = .4;
        alpha = 1;
    }

    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations: ^{
        self.postTopicContainer.alpha = alpha;
        self.topicsTableView.frame = newTableViewFrame;
        self.postTopicContainer.frame = newPostTopicFrame;
    } completion:nil];
}

- (BOOL)isPostTopicContainerOpen {
    return (self.postTopicContainer.frame.origin.y < 0) ? NO : YES;
}

//update fading status UILabel at the bottom of the screen
- (void)statusUpdate:(NSString *)message {
    self.statusLabel.text = message;
    [self.statusLabel.layer addAnimation:[SRAnimationHelper fadeOfSRMasterViewStatusLabel] forKey:nil];
}

//Post a new Topic to the Server
- (void)postTopicButtonPressed:(NSString *)contents {
    //set up params
    NSDictionary *newTopic = @{ @"topic":contents };

    //send new topic posting
    [[RKObjectManager sharedManager] postObject:nil path:@"topics/new" parameters:newTopic success: ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        if ([self isPostTopicContainerOpen]) {
            //close post box if it's open
            [self showPostTopicContainer];
        }
        [self statusUpdate:@"Topic Posted!"];
    } failure: ^(RKObjectRequestOperation *operation, NSError *error) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Oops"
                                                        message:@"We weren't able to post your shout. Try again soon!"
                                                       delegate:nil
                                              cancelButtonTitle:@"Sure"
                                              otherButtonTitles:nil, nil];
        [alert show];
    }];
}

//Delegate for SRChoiceBox - user chooses Agree/Disagree/Observe
- (void)positionWasChoosen:(NSString *)choice topicId:(NSNumber *)topicId {
    SRRoom *room = [[SRRoom alloc] init];
    room.position  = choice;
    room.topicId = topicId;

    if ([choice isEqualToString:@"observe"]) {
        [self performSegueWithIdentifier:@"showObserve" sender:room];
    }
    else {
        [self performSegueWithIdentifier:@"showDetail2" sender:room];
    }
}

- (void)segueToRoomWithTopicID:(NSNumber *)topicId andPosition:(NSString *)choice {
    SRRoom *room = [[SRRoom alloc] init];
    room.position  = choice;
    room.topicId = topicId;

    if ([choice isEqualToString:@"observe"]) {
        [self performSegueWithIdentifier:@"showObserve" sender:room];
    }
    else {
        [self performSegueWithIdentifier:@"delete" sender:nil];
    }
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    //close Post Topic Container
    if ([self isPostTopicContainerOpen]) {
        [self showPostTopicContainer];
    }

    if ([[segue identifier] isEqualToString:@"showDetail2"] || [[segue identifier] isEqualToString:@"showObserve"]) {
        if (self.openTokHandler) {
            [self.openTokHandler safetlyCloseSession];
        }
        [[segue destinationViewController] setOpenTokHandler:self.openTokHandler];
        [[segue destinationViewController] setRoom:sender];
    }
    sender = nil;
}

#pragma mark - UITABLEVIEW
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.topicsArray count];
}

- (void)insertRowsInTableView:(NSMutableArray *)topics {
    if (topics.count < 1) {
        [self noNewResults];
        return;
    }

    NSMutableArray *temp = [NSMutableArray new];
    int lastRowNumber = [self.topicsTableView numberOfRowsInSection:0] - 1;

    for (SRTopic *topic in topics) {
        if (![self.topicsArray containsObject:topic]) {
            [self.topicsArray addObject:topic];
            NSIndexPath *ip = [NSIndexPath indexPathForRow:lastRowNumber inSection:0];
            [temp addObject:ip];
            ++lastRowNumber;
        }
    }

    [self.topicsTableView beginUpdates];
    [self.topicsTableView insertRowsAtIndexPaths:temp
                                withRowAnimation:UITableViewRowAnimationTop];
    [self.topicsTableView endUpdates];

    if (temp.count == 0) {
        [self noNewResults];
    }
}

- (void)noNewResults {
    int lastRowNumber = [self.topicsTableView numberOfRowsInSection:0] - 1;
    [self statusUpdate:@"No New Topics. Check Back Soon!"];
    [self.topicsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:lastRowNumber - 6 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
    self.offset--;
}

- (void)replaceRowsInTableView:(NSMutableArray *)topics {
    self.topicsArray = topics;

    [UIView animateWithDuration:.3 delay:.5 options:UIViewAnimationOptionCurveEaseInOut animations: ^{
        self.topicsTableView.layer.opacity = 0;
    } completion: ^(BOOL finished) {
        self.topicsTableView.layer.opacity = 1;
        [[self.topicsTableView layer] addAnimation:[SRAnimationHelper tableViewReloadDataAnimation] forKey:@"UITableViewReloadDataAnimationKey"];
        [self.topicsTableView reloadData];
    }];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *CellIdentifier2 = @"SRCollapsibleCellClosed";
    SRCollapsibleCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier2];
    if (cell == nil) {
        cell = [[SRCollapsibleCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier2];
    }

    SRTopic *topic = [self.topicsArray objectAtIndex:indexPath.row];
    [cell updateWithTopic:topic];

    if ([self isCellOpen:indexPath]) {
        CGAffineTransform transformation = CGAffineTransformMakeRotation(M_PI / 2);
        cell.arrow.transform = transformation;
        if (![self hasChoiceBox:cell]) {
            [self insertChoiceBox:cell atIndex:indexPath];
        }
    }
    else {
        CGAffineTransform transformation = CGAffineTransformMakeRotation(0);
        cell.arrow.transform = transformation;
    }

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    if ([self isCellOpen:indexPath]) {
        [self closeCellAtIndexPath:indexPath];
    }
    else {
        NSIndexPath *openCell = self.openCellIndex;
        NSIndexPath *newOpenCell = indexPath;
        [self closeCellAtIndexPath:openCell];
        [self openCellAtIndexPath:newOpenCell];
    }
    [tableView beginUpdates];
    [tableView endUpdates];
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if ([indexPath isEqual:self.openCellIndex]) {
        return 217.0;
    }
    else {
        return 63.0;
    }
}

- (void)rotateCellArrowAtIndexPath:(NSIndexPath *)indexPath willOpen:(bool)willOpen animated:(bool)animated {
    // Change Arrow orientation
    SRCollapsibleCell *cell = (SRCollapsibleCell *)[self.topicsTableView cellForRowAtIndexPath:indexPath];

    CGAffineTransform transformation;

    if (willOpen) {
        transformation = CGAffineTransformMakeRotation(M_PI / 2);
    }
    else {
        transformation = CGAffineTransformMakeRotation(0);
    }

    if (animated) {
        [UIView animateWithDuration:.2 delay:0 options:UIViewAnimationOptionCurveLinear animations: ^{
            cell.arrow.transform = transformation;
        }  completion:nil];
    }
    else {
        cell.arrow.transform = transformation;
    }
}

- (BOOL)isCellOpen:(NSIndexPath *)indexPath {
    return [indexPath isEqual:self.openCellIndex];
}

- (void)closeCellAtIndexPath:(NSIndexPath *)indexPath {
    [self rotateCellArrowAtIndexPath:indexPath willOpen:NO animated:YES];
    [self removeSRChoiceBoxFromCellAtIndexPath:indexPath];
    self.openCellIndex = nil;
}

- (void)openCellAtIndexPath:(NSIndexPath *)indexPath {
    [self rotateCellArrowAtIndexPath:indexPath willOpen:YES animated:YES];
    SRCollapsibleCell *cell = (SRCollapsibleCell *)[self.topicsTableView cellForRowAtIndexPath:indexPath];
    [self insertChoiceBox:cell atIndex:indexPath];
    self.openCellIndex = indexPath;
}

- (void)removeSRChoiceBoxFromCellAtIndexPath:(NSIndexPath *)indexPath {
    SRCollapsibleCell *cell = (SRCollapsibleCell *)[self.topicsTableView cellForRowAtIndexPath:indexPath];
    for (id subview in cell.SRCollapsibleCellContent.subviews) {
        if ([subview isKindOfClass:[SRChoiceBox class]]) {
            [subview removeFromSuperview];
        }
    }
}

- (void)insertChoiceBox:(SRCollapsibleCell *)cell atIndex:(NSIndexPath *)indexPath {
    SRChoiceBox *newBox = [[SRChoiceBox alloc] initWithFrame:CGRectMake(0, 0, 310, 141)];
    SRTopic *topic = [self.topicsArray objectAtIndex:indexPath.row];
    [newBox updateWithSRTopic:topic];
    newBox.delegate = self;

    [cell.SRCollapsibleCellContent addSubview:newBox];
}

- (bool)hasChoiceBox:(SRCollapsibleCell *)cell {
    for (UIView *subview in cell.SRCollapsibleCellContent.subviews) {
        if ([subview isKindOfClass:[SRChoiceBox class]]) {
            return true;
        }
    }
    return false;
}

@end
share|improve this question
add comment

2 Answers

up vote 3 down vote accepted

A few things I would ask if I saw this as an interviewer (from a very quick read, and obviously not having seen it run):

  • why use a UIViewController and not a UITableViewController (refresh control property is free then)
  • the rasterisation on table view shouldn't be needed for smooth scrolling, you have other problems if that is required
  • there are a few things which I wouldn't like to see and would ask about: magic numbers, names with a 2 at the end, inconsistencies (bool vs BOOL etc)
  • why all the imports in the .h?
  • why aren't the notification observers removed?

responses: 1) makes sense 2) was just more about practice of importing .h files in a .h (meaning any class that needs to import your class knows about all those imported classes) 3) Its just good practice I guess, but I didn't realise this was a permanent view controller 4) Overall impressions are good (would depend a bit on the level of developer the job is for): the code is reasonably complex, uses 3rd party libraries, has some fairly advanced functions like dealing with layers / transforms.

The real test though would be how well you can explain it during the interview, e.g. if you sound like its just a cut and paste job then the impression is lessened, but if you can explain your choices, pitfalls of the approach, anything you tried which you discard and why, and are able to talk about what you have learnt and where you would make improvements next time then you'd be looking pretty good I think.

share|improve this answer
 
Thanks for this, if you have a moment I have a brief follow up. cheers –  Emin Israfil iOS Oct 28 at 19:03
 
no worries. go for it –  wattson12 Oct 28 at 19:13
 
Thanks, the question was updated. –  Emin Israfil iOS Oct 28 at 19:19
 
@bluths_bananas added my responses in the answer –  wattson12 Oct 28 at 19:34
add comment

First let me say up front that this code looks quite good compared to a lot of code I've been asked to maintain, so you have nothing to be ashamed of here. I'll make a few more suggestions beyond the ones wattson12 offered, but you should view these as ways to make your good code incrementally better.

viewDidLoad

First, a technical point. You should be aware that this code may not work the way you expect, especially if it needs to run on iOS 5. It's a common mistake to assume that viewDidLoad will only get called once. In fact, viewDidLoad can get called many times in the life of a view controller. If the view controller receives a memory warning when its view isn't visible, the default behavior up until iOS 6 was to unload its view hierarchy and set self.view to nil. The next time self.view gets accessed after that, it gets lazy-loaded from the NIB again, and viewDidLoad gets called again. This can lead to some tricky bugs if your code isn't designed to handle it. For example, your implementation of viewDidLoad re-adds your notification handlers. This will cause them to get called twice when their notifications are posted.

Since iOS 6 the risk is greatly reduced because self.view isn't set to nil by default in response to memory warnings, but you may decide to do so manually at some point as an optimization, so I'd still consider designing your viewDidLoad to leave your object in a valid state if it gets called multiple times. If nothing else, you should be prepared to explain to an old-school iOS developer in an interview why your code should be safe in iOS 6. Review Resource Management in View Controllers in the View Controller Programming Guide.

Now then, on to code smells.

Comments

The main code smell that jumps out at me here is, believe it or not, the comments. Whenever you feel the urge to add a comment, you should stop and ask yourself a couple of questions:

  1. Could I make this comment unnecessary by extracting a block of code out into a method?
  2. Could I make this comment unnecessary by renaming my methods to something more descriptive?

For example, applying just this one principle to the comments in -joinRandomRoom would turn this:

- (void)joinRandomRoom {
    NSMutableArray *activeTopics = [NSMutableArray new];
    SRTopic *randomTopic = [SRTopic new];
    SRRoom *randomRoom = [SRRoom new];

    //find topics with people in them
    for (SRTopic *topic in self.topicsArray) {
        if ([topic.agreeDebaters integerValue] > 0 || [topic.disagreeDebaters integerValue] > 0) {
            [activeTopics addObject:topic];
        }
    }
    int numberOfActiveTopics = activeTopics.count;

    if (numberOfActiveTopics > 0) {
        //Put user into a random Active Room
        int random = arc4random() % numberOfActiveTopics;
        randomTopic = (SRTopic *)activeTopics[random];
        if (randomTopic.agreeDebaters.intValue > randomTopic.disagreeDebaters.intValue) {
            randomRoom.position  = @"disagree";
        }
        else if (randomTopic.agreeDebaters.intValue < randomTopic.disagreeDebaters.intValue) {
            randomRoom.position  = @"agree";
        }
        else {
            randomRoom.position = [self randomlyChooseAgreeDisagree];
        }
    }
    else {
        //No Active Rooms, put user in a random room
        int random = arc4random() % self.topicsArray.count;
        randomTopic = (SRTopic *)self.topicsArray[random];
        randomRoom.position = [self randomlyChooseAgreeDisagree];
    }
    randomRoom.topicId = randomTopic.topicId;
    [self performSegueWithIdentifier:@"showDetail2" sender:randomRoom];
}

... into this:

- (void)joinRandomRoom {
    SRRoom *randomRoom;

    //find topics with people in them
    NSMutableArray *activeTopics = [self activeTopicsWithPeopleInThem];
    int numberOfActiveTopics = activeTopics.count

    if (numberOfActiveTopics > 0) {
        //Put user into a random Active Room
        randomRoom = [self activeRandomRoom];
    }
    else {
        //No Active Rooms, put user in a random room
        randomRoom = [self anyRandomRoom];
    }

    [self performSegueWithIdentifier:@"showDetail2" sender:randomRoom];
}

Note that the method names are expressive enough that the comments are now redundant, so I can remove them:

- (void)joinRandomRoom {
    SRRoom *randomRoom;

    NSMutableArray *activeTopics = [self activeTopicsWithPeopleInThem];
    int numberOfActiveTopics = activeTopics.count

    if (numberOfActiveTopics > 0) {
        randomRoom = [self activeRandomRoom];
    }
    else {
        randomRoom = [self anyRandomRoom];
    }

    [self performSegueWithIdentifier:@"showDetail2" sender:randomRoom];
}

It's already much easier to tell at a glance what this method is doing, but I'd personally continue refactoring to this:

- (void)joinRandomRoom {
    SRRoom *randomRoom;

    if ([self numberOfActiveTopics] > 0) {
        randomRoom = [self activeRandomRoom];
    }
    else {
        randomRoom = [self anyRandomRoom];
    }
    [self performSegueWithIdentifier:@"showDetail2" sender:randomRoom];
}

and ultimately to this:

- (void)joinRandomRoom {
    [self performSegueWithIdentifier:kSegueIdentifierShowDetail2
                              sender:[self randomRoom]];
}

It's almost always possible to refactor your code into a form that documents itself expressively enough that your comments add no value and can therefore be removed. When you're done, your methods will almost read like pseudocode.

BTW, you'll notice your classes filling up with lots of little private methods. Don't fret. That's generally preferable to a handful of large methods, but if all the little private methods start to feel overwhelming, that's a code smell of its own. It suggests that some of your methods may belong on other classes. Move each method its most appropriate class. Don't be afraid to invent new classes, but also consider using Objective-C categories as convenient home for your stateless methods.

#pragma mark

My next suggestion is to come up with some convention for your #pragma mark statements, and then apply it with consistency and discipline. It really helps to know exactly where to put each method, and where you can go to find it later.

My personal convention is to put "Constructors" at the top and "Private" methods at the bottom for quick access. Above my private methods I have special sections for "Notification handlers", "Actions", "Dynamic properties", and "Public". Then above those I put all my protocol implementations and overridden methods in sections named after the place they were originally declared. So I would organize your file like this:

@implementation SRMasterViewController2

#pragma mark UIViewController
- (void)viewDidLoad {}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {}

#pragma mark <UITableViewDataSource>
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {}

#pragma mark <UITableViewDelegate>
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {}

#pragma mark <SRPostTopicDelegate>
- (void)postTopicButtonPressed:(NSString *)contents {}
- (void)positionWasChoosen:(NSString *)choice topicId:(NSNumber *)topicId {}
- (void)segueToRoomWithTopicID:(NSNumber *)topicId andPosition:(NSString *)choice {}

#pragma mark Notification handlers
- (void)receiveNotifications:(NSNotification *)notificaiton {}

#pragma mark Actions
- (void)showPostTopicContainer {}
- (void)joinRandomRoom {}
- (void)refresh:(UIRefreshControl *)refreshControl {}

#pragma mark Private
- (void)configureNotifications {}
- (void)fetchRoomWithUrl:(NSURL *)url {}
- (void)configureNavBar {}
- (NSString *)randomlyChooseAgreeDisagree {}
- (void)configureTableView {}
- (void)configurePostTopicContainer {}
- (void)addInfiniteScrolling:(UITableView *)tableView {}
- (void)paginate {}
- (void)noResults {}
- (BOOL)isPostTopicContainerOpen {}
- (void)statusUpdate:(NSString *)message {}
- (void)insertRowsInTableView:(NSMutableArray *)topics {}
- (void)noNewResults {}
- (void)replaceRowsInTableView:(NSMutableArray *)topics {}
- (void)rotateCellArrowAtIndexPath:(NSIndexPath *)indexPath willOpen:(bool)willOpen animated:(bool)animated {}
- (BOOL)isCellOpen:(NSIndexPath *)indexPath {}
- (void)closeCellAtIndexPath:(NSIndexPath *)indexPath {}
- (void)openCellAtIndexPath:(NSIndexPath *)indexPath {}
- (void)removeSRChoiceBoxFromCellAtIndexPath:(NSIndexPath *)indexPath {}
- (void)insertChoiceBox:(SRCollapsibleCell *)cell atIndex:(NSIndexPath *)indexPath {}
- (bool)hasChoiceBox:(SRCollapsibleCell *)cell {}

@end

It doesn't matter if you adopt my convention, but choose some convention and apply it with discipline.

A couple more random suggestions:

  • Don't use a single receiveNotifications: handler with a big if-then inside it. Instead handle each notification using its own well-named method like didFetchNewTopicsAndReloadTableDataNotification: and didFetchRoomFromUrlNotification:. Much simpler.

  • Rather than registering joinRandomRoom as the selector for the shuffle button press, register a method named something like shuffleButtonPressed:. That way when you need to change that behavior later a quick search for "shuffleButton" or "Pressed" will you bring you quickly to the right method. Much easier than tracing your way back to joinRandomRoom through the @selector(joinRandomRoom) parameter passed to the SRNavBarHelper class method.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.