From Empty Project to Running App
Building even a simple iOS app in Objective-C touches the same core pieces every iOS app needs: an app delegate that handles lifecycle events, a storyboard or programmatic view hierarchy, at least one view controller, and a way to display dynamic data such as a UITableView. Walking through this end to end, starting from a new Xcode project with the Objective-C language option selected, is the fastest way to internalize how the pieces fit together before layering on networking or persistence.
Cricket analogy: Wiring up an app delegate before anything else is like a team appointing a captain first, before naming the batting order, someone needs to own the overall flow of the innings before individual roles make sense.
The App Delegate and Root View Controller
AppDelegate.m implements UIApplicationDelegate methods like application:didFinishLaunchingWithOptions:, where you typically create a UIWindow, set its rootViewController, and call makeKeyAndVisible. For a programmatic, storyboard-free app you instantiate your first view controller directly here, giving you full control over the initial navigation stack, which is especially useful when the launch flow needs conditional logic, such as showing an onboarding screen only on first launch.
Cricket analogy: Setting the rootViewController in the app delegate is like the toss deciding which team bats first, it determines the starting point every subsequent decision in the match builds on.
// AppDelegate.m
#import "AppDelegate.h"
#import "TaskListViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
TaskListViewController *taskListVC = [[TaskListViewController alloc] init];
UINavigationController *navController =
[[UINavigationController alloc] initWithRootViewController:taskListVC];
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
return YES;
}
@endDisplaying Data with UITableView
A view controller becomes a working table view by conforming to UITableViewDataSource and UITableViewDelegate, implementing tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath:, and registering a reusable cell class with registerClass:forCellReuseIdentifier:. Cell reuse via dequeueReusableCellWithIdentifier:forIndexPath: is essential for performance: instead of allocating a new cell for every row, the table view recycles cells that have scrolled off screen, which keeps memory and CPU usage flat even for lists with thousands of rows.
Cricket analogy: Cell reuse is like a team reusing the same practice nets for every batter in the lineup instead of building a brand-new net for each individual player, far more efficient with the same resources.
Forgetting to dequeue cells properly, or configuring cell state without resetting it for reused cells, is a common source of bugs where table rows appear to 'remember' stale content from a previously scrolled-away row. Always fully configure every visible property of a reused cell in tableView:cellForRowAtIndexPath:, don't assume a fresh default state.
// TaskListViewController.m
@interface TaskListViewController () <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, copy) NSArray<NSString *> *tasks;
@end
@implementation TaskListViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tasks = @[@"Buy groceries", @"Write report", @"Walk the dog"];
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds
style:UITableViewStylePlain];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.tableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@"TaskCell"];
[self.view addSubview:self.tableView];
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return self.tasks.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TaskCell"
forIndexPath:indexPath];
cell.textLabel.text = self.tasks[indexPath.row];
return cell;
}
@endRunning and Debugging on the Simulator
With the app delegate and view controller wired up, selecting a simulator target and pressing Run compiles the project and launches it, giving you a live table view you can scroll and tap. From here, breakpoints set directly in Objective-C methods, combined with the LLDB console's po command for printing object descriptions, are the fastest way to inspect state, such as confirming the tasks array actually contains the expected strings before the table view renders them.
Cricket analogy: Setting a breakpoint to inspect the tasks array is like a coach pausing match footage at a specific ball to check exactly what field placement was in effect before the shot was played.
Once your table view works with a static array, the natural next step is wiring in NSURLSession to fetch remote JSON and reloading the table via [self.tableView reloadData] on the main thread after parsing, the same pattern scales from a hardcoded array to a fully dynamic, network-backed list.
- Every iOS app needs an app delegate to handle lifecycle events and set up the initial UIWindow and root view controller.
- A programmatic (storyboard-free) setup gives full control over the launch flow, useful for conditional onboarding logic.
- UITableViewDataSource and UITableViewDelegate are the two protocols required to power a working table view.
- Cell reuse via dequeueReusableCellWithIdentifier:forIndexPath: keeps memory and CPU flat even for very long lists.
- Always fully configure every visible property of a reused cell to avoid stale content bugs.
- Breakpoints plus the LLDB po command are the fastest way to inspect object state while debugging.
- A static-array-backed table view is the natural stepping stone to a network-backed, dynamic list.
Practice what you learned
1. Where does a programmatic (non-storyboard) Objective-C app typically set its rootViewController?
2. Why is cell reuse important in UITableView?
3. What two protocols must a view controller typically conform to for a basic UITableView to work?
4. What is a common bug caused by improper cell reuse handling?
5. What LLDB console command is commonly used to print an object's description while debugging?
Was this page helpful?
You May Also Like
Objective-C Best Practices
Practical, battle-tested conventions for writing safe, readable, and maintainable Objective-C code, from memory management to naming and API design.
Objective-C Quick Reference
A condensed reference for Objective-C syntax, common Foundation types, memory attributes, and idioms you'll reach for constantly while coding.
Objective-C vs Swift
A practical comparison of Objective-C and Swift covering syntax, memory management, interoperability, and when each language is the right choice on Apple platforms.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics