Search code examples
iosobjective-cuicollectionviewprotocolsobjective-c-runtime

If object from UIViewController how get only the names of custom properties that I created (in run time)?


I have implemented the following code in a view controller that is defined to conform to UICollectionViewDelegate and UICollectionViewDataSource:

objc_property_t *properties = class_copyPropertyList(self, &outCount);

for (i = 0; i < outCount; i++) {
    objc_property_t property = properties[i];
    const char *propName = property_getName(property);
    if(propName) {
        [properitiesList addObject:[NSString stringWithUTF8String:propName]];
    }
}

When I do that, I see not only my custom properties, but also a few introduced by those protocols, namely: hash, superclass, description and debugDescription.

How do I show just the properties from my subclass, and not those introduced by those protocols?


Solution

  • The class_copyPropertyList method will only return properties declared for the class, not those declared for superclass. As the header for class_copyPropertyList says, it returns

    An array of pointers of type objc_property_t describing the properties declared by the class. Any properties declared by superclasses are not included.

    But the issue is that you've defined your class to conform to UICollectionViewDataSource and UICollectionViewDelegate. Thus properties introduced by those protocols are being interpreted as properties for your subclass, not for some superclass.

    The simplest solution is to remove those protocol declarations and then the error goes away.

    If you don't want to remove those protocol declarations (e.g. you want to still enjoy code completion for those protocol methods), you can create an abstract class that conforms to these protocols, and then subclass that. For example:

    @interface AbstractViewControllerForCollectionView : UIViewController <UICollectionViewDelegate, UICollectionViewDataSource>
    @end
    
    @implementation AbstractViewControllerForCollectionView
    
    // we have to implement the required methods; let's just warn the developer if they accidentally fail to subclass
    
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
        NSAssert(FALSE, @"this must be subclassed");
        return 0;
    }
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
        NSAssert(FALSE, @"this must be subclassed");
        return nil;
    }
    
    @end
    

    And:

    @interface ViewController : AbstractViewControllerForCollectionView
    
    @property (nonatomic, strong) NSString *myPropertyName;
    @property (nonatomic, strong) UICollectionView *collectionView;
    @property (nonatomic, strong) NSArray *objects;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.collectionView.delegate = self;
        self.collectionView.dataSource = self;
    
        NSLog(@"%@", [[self class] properties]);
    }
    
    + (NSArray *)properties {
        NSMutableArray *propertyList = [NSMutableArray array];
    
        unsigned int outCount, i;
        objc_property_t *properties = class_copyPropertyList(self, &outCount);
        for(i = 0; i < outCount; i++) {
            objc_property_t property = properties[i];
            const char *propName = property_getName(property);
            if (propName) {
                [propertyList addObject:[NSString stringWithUTF8String:propName]];
            }
        }
        free(properties);
    
        return propertyList;
    }
    
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
        return self.objects.count;
    }
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
        UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
    
        // configure cell
    
        return cell;
    }
    @end
    

    The only downside here is that the compiler won't warn you if you fail to subclass the required methods. But at least you'll get a runtime failure/warning that describes the problem.