19

I am using a category NSAttributedString (Additions) and I really need a way to add a property that will be a BOOL (indicating whether the string is an HTML tag or not). I know that categories should not have properties but that's the the way I need to do it. I tired writing my own getters and setters but it didn't work. How can this work?

jscs
  • 63,694
  • 13
  • 151
  • 195
Mace
  • 201
  • 2
  • 5

6 Answers6

45

For the sake of completeness, this is how I got this to work:

@interface

@interface ClassName (CategoryName)
@property (readwrite) BOOL boolProperty;
@end

@implementation

#import <objc/runtime.h>

static char const * const ObjectTagKey = "ObjectTag";
@implementation ClassName (CategoryName)
- (void) setBoolProperty:(BOOL) property
{
    NSNumber *number = [NSNumber numberWithBool: property];
    objc_setAssociatedObject(self, ObjectTagKey, number , OBJC_ASSOCIATION_RETAIN);
}

- (BOOL) boolProperty
{
    NSNumber *number = objc_getAssociatedObject(self, ObjectTagKey);
    return [number boolValue]; 
}
@end
Joshua Goossen
  • 1,714
  • 1
  • 17
  • 36
  • What about memory management? The NSNumber instance is retained by self within the -setBoolProperty: method. Do i need to release it somehow? From runtime.h: "@param value The value to associate with the key key for object. Pass nil to clear an existing association." But i can't override dealloc in a category. – igrek Aug 13 '14 at 14:21
  • Found an answer here: http://stackoverflow.com/questions/10842829/will-an-associated-object-be-released-automatically/10843510#10843510 It is released properly – igrek Aug 13 '14 at 14:28
  • By the way, you can omit using extra constants for Keys; instead, just use @selector(boolProperty) instead of the ObjectTagKey, which will provide exactly what you need – igrek Aug 13 '14 at 14:29
  • @igrek that's not working with categories. Categories don't allow @selector() – d.ennis Dec 22 '14 at 12:24
  • Instead of a `static char const*` you can use `static int ObjectTagKey` and use `&ObjectTagKey` as parameter for `objc_...` – Eun Jan 22 '15 at 14:28
  • I believe that @iHS has the best up-to-date answer. https://stackoverflow.com/a/46920788/5494489 – CJ Dev Oct 23 '19 at 19:18
4

Categories can have read-only properties, you just can't add instance variables with them (well, you can, sort of - see associative references).

You can add a category method (presented by a read only property) isHTMLTag which would return a BOOL, you would just have to calculate if it was an HTML tag each time within that method.

If you are asking for a way to set the BOOL value then you'll need to use associated references (objc_setAssociatedObject) which I've never used so don't feel qualified to answer on in any more detail.

jrturton
  • 118,105
  • 32
  • 252
  • 268
2

My solution without the need of an object key and a little bit easier to read syntax

NSString+Helper.h

#import <Foundation/Foundation.h>

@interface NSString (Helper)

@property (nonatomic, assign) BOOL isHTML;

@end

NSString+Helper.h

#import "NSString+Helper.h"
#import "objc/runtime.h"

@implementation NSString (Helper)

- (void)setIsHTML:(BOOL)isHTML
{
    NSNumber *isHTMLBool = [NSNumber numberWithBool:isHTML];
    objc_setAssociatedObject(self, @selector(isHTML), isHTMLBool, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)isHTML
{
    NSNumber *isHTMLBool = objc_getAssociatedObject(self, @selector(isHTML));
    return isHTMLBool.boolValue;
}

@end
Rikco
  • 376
  • 5
  • 9
2

Probably very late in this world, when swift is already sweeping objective c. Anyhow, with Xcode 8, you can also use class properties, instead of using associative references.

@interface ClassName (CategoryName)
    @property (class) id myProperty;
@end

@implementation
static id *__myProperty = nil;

+ (id)myProperty {
    return __myProperty;
}

+ (void)setMyProperty:(id)myProperty {
    __myProperty = myProperty;
}

@end

From the Xcode 8 release notes:

Objective-C now supports class properties, which interoperate with Swift type properties. They are declared as: @property (class) NSString *someStringProperty;. They are never synthesized. (23891898)

iHS
  • 5,372
  • 4
  • 31
  • 49
0

Swift 3:

Using this method, you can add any new property (here add Bool value) to the exciting class.

import ObjectiveC

private var xoAssociationKey: UInt8 = 0

extension <ClassName> {
    var <propertyName>: Bool! {
        get {
            return objc_getAssociatedObject(self, &xoAssociationKey) as? Bool ?? false
        }
        set(newValue) {
            objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }
}
Onato
  • 9,916
  • 5
  • 46
  • 54
Isuru Jayathissa
  • 478
  • 4
  • 15
0

If you really need to add a property, then you should create a subclass instead of adding a category.

lnafziger
  • 25,760
  • 8
  • 60
  • 101
  • 1
    Sometimes that's not possible. For example, if the code that creates the instance of said class is not accessible (e.g. in a static library). – Ben Clayton Oct 04 '12 at 10:30