Skip to content

Instantly share code, notes, and snippets.

@storoj
Last active December 28, 2019 17:24
Show Gist options
  • Save storoj/ed86a2434b711b4986e6f03c2afadcad to your computer and use it in GitHub Desktop.
Save storoj/ed86a2434b711b4986e6f03c2afadcad to your computer and use it in GitHub Desktop.
A demonstration of how UITableView caches the results of `[self.dataSource respondsToSelector:]` and `self.delegate respondsToSelector:]`
@import UIKit;
@import ObjectiveC.runtime;
@interface MyDataSource: NSObject <UITableViewDataSource>
@end
@implementation MyDataSource
/**
Do not implement `numberOfSectionsInTableView:` to add it dynamically in runtime
*/
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return nil;
}
@end
// IMP to be added in runtime as `-[UITableViewDataSource numberOfSectionsInTableView:]`
static NSInteger NumberOfSectionsInTableView(MyDataSource *self, SEL sel, UITableView *tableView) {
return 10;
}
static void Test() {
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
MyDataSource *dataSource = [[MyDataSource alloc] init];
tableView.dataSource = dataSource;
[tableView reloadData];
NSLog(@"%d", (int)[tableView numberOfSections]);
// Output: 1
// Dynamically add an implementation for `-[MyDataSource numberOfSectionsInTableView:]`
char sig[16];
snprintf(sig, 16, "%s%s%s%s", @encode(NSInteger), @encode(id), @encode(SEL), @encode(id));
class_addMethod([MyDataSource class],
@selector(numberOfSectionsInTableView:),
(IMP)NumberOfSectionsInTableView,
sig);
[tableView reloadData];
NSLog(@"%d", (int)[tableView numberOfSections]);
// Output: 1, because UITableView cached the result of
// [tableView.dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]
/**
See https://github.com/nst/iOS-Runtime-Headers/blob/fbb634c78269b0169efdead80955ba64eaaa2f21/PrivateFrameworks/UIKitCore.framework/UITableView.h
struct {
unsigned int dataSourceNumberOfRowsInSection : 1;
unsigned int dataSourceCellForRow : 1;
unsigned int dataSourceNumberOfSectionsInTableView : 1;
unsigned int dataSourceTitleForHeaderInSection : 1;
unsigned int dataSourceTitleForFooterInSection : 1;
....
} _tableFlags;
*/
Ivar ivar = class_getInstanceVariable([UITableView class], "_tableFlags");
char* _tableFlags = (char*)(__bridge void*)tableView + ivar_getOffset(ivar);
char firstFlags = *_tableFlags;
firstFlags = firstFlags | 0b100; // set dataSourceNumberOfSectionsInTableView bit
*_tableFlags = firstFlags;
[tableView reloadData];
NSLog(@"%d", (int)[tableView numberOfSections]);
// Output: 10
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment