Last active
December 28, 2019 17:24
-
-
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:]`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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