Search code examples
objective-cobjective-c-category

Any way to apply Objective-C category only to current class (or equivalent effect)?


Let's say I have a custom subclass of UIView called MyCustomView. Let's also say that I have a category on UIView called UIView+Dictionary that adds an NSDictionary property called dictionary to every UIView.

If I were to import UIView+Dictionary.h into MyCustomView.m then every view referenced within MyCustomView.m would have this added dictionary property, which in many situations is exactly the desired behavior.

However, if I wanted UIView+Dictionary applied only to MyCustomView itself and not to every UIView referenced within MyCustomView.m, is there a way to do so (or achieve a similar effect)?

I'd like to avoid making MyCustomView a subclass of another custom subclass (e.g., MyViewWithDictionary), as I'd ideally like to be able to import multiple categories for something akin to multiple inheritance (e.g., UIView+Dictionary, UIView+Border, UIView+CustomAnimations).

In my actual own scenario, I've written a category to automatically implement a custom UINavigationBar in a view controller, but I'd like that category to apply only to the view controller into which I am importing the category and not any other view controllers that may be referenced in that file.

Any and all insights are appreciated! And I apologize in advance as I am fairly certain there are more correct terminologies for the effect described above.


Solution

  • As Josh pointed out, any methods added in categories are basically inert unless you call them. The issue that I was having was for generated properties and swizzled methods in categories (since, as Josh also pointed out, there are no mixins in Objective-C).

    I was able to solve this by adding in a custom BOOL in my category that defaults to NO and acts as a "switch" for whatever category methods and properties I want to specify.

    E.g., if I wanted my dictionary property to be lazily instantiated but only within MyCustomView, I could do the following:

    // UIView+Dictionary.h
    
    @interface UIView (Dictionary)
    @property (nonatomic) BOOL enableDictionary;
    @property (nonatomic, strong) NSDictionary *dictionary;
    @end
    
    // UIView+Dictionary.m
    
    #import "UIViewController+CustomNavigationBar.h"
    #import <objc/runtime.h>
    
    @implementation UIView (Dictionary)
    
    - (void)setEnableDictionary:(BOOL)enableDictionary {
        objc_setAssociatedObject(self, @selector(enableDictionary), @(enableDictionary), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (BOOL)enableDictionary {
        NSNumber *enableDictionaryValue = objc_getAssociatedObject(self, @selector(enableDictionary));
        if (enableDictionaryValue) {
            return enableDictionaryValue.boolValue;
        }
    
        objc_setAssociatedObject(self, @selector(enableDictionary), @NO, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        return self.enableDictionary;
    }
    
    - (void)setDictionary:(NSDictionary *)dictionary {
        objc_setAssociatedObject(self, @selector(dictionary), dictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (NSDictionary *)dictionary {
        if (!self.enableDictionary) {
            return nil;
        }
    
        NSDictionary *dictionary = objc_getAssociatedObject(self, @selector(dictionary));
        if (dictionary) {
            return dictionary;
        }
    
        objc_setAssociatedObject(self, @selector(dictionary), @{}, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        return self.dictionary;
    }
    
    @end
    

    And then within -[MyCustomView viewDidLoad] I could simply call self.enableDictionary = YES. That way, only instances of MyCustomView will have a non-nil lazily instantiated NSDictionary. (Note that, in this example, all instances of UIViews will still respond to the selector @selector(dictionary), but our behavior will differ based on whether enableDictionary is YES or NO.)

    While that is a trivial example, the same strategy can be used for methods that are swizzled within categories. (Again, swizzling methods within categories is probably bad form but a necessary evil in certain scenarios.)