简体   繁体   中英

How to turn a readonly property into readwrite property in Objective-C?

What are the ways to turn a readonly property into readwrite property in Objective-C? Keep in mind I have no access to the source.

Reason for this: I need to do this in unit test for mock purposes.

You can't change the property's functionality like this without access to the main implementation block of the class, or at least the compilation unit that contains it, because you don't have any access to the ivar outside that unit. Even if you were to add a setter to the class in a category, you wouldn't be able to affect the class's storage any more than you can from entirely outside the class.

What you can do is use KVC, however. setValue:forKey: will bypass setters and go straight to the ivar if it can find one . You can use this to set any value you like even for a property that was declared readonly , provided there's backing storage whose name you know.

It goes like this:

//Passaquisset.h
#import <Foundation/Foundation.h>

@interface Passaquisset : NSObject

@property (copy, readonly, nonatomic) NSString * vanadium;

@end

//Passaquisset.m
#import "Passaquisset.h"

@implementation Passaquisset

@synthesize vanadium;

- (id) init {

    self = [super init];
    if( !self ) return nil;

    vanadium = @"Number 23";

    return self;
}

@end

//Elsewhere...
#import <Foundation/Foundation.h>

#import "Passaquisset.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        Passaquisset * pq = [Passaquisset new];
        NSLog(@"%@", [pq vanadium]);

        [pq setValue:@"Number 24" forKey:@"vanadium"];

        NSLog(@"%@", [pq vanadium]);

    }
    return 0;
}

Like I said, this will fail -- actually raise an exception -- if there's neither a setter nor an ivar by the same name (or with an underscore appended _vanadium (KVC is pretty smart), such as if the property's value is entirely calculated:

//Passaquisset.m

#import "Passaquisset.h"

@implementation Passaquisset

/** KVC will fail with this version! **/

- (NSString *)vanadium
{
    return @"Number 23";
}

@end

For completeness, let me mention that if the property is backed by an ivar of an entirely different name (eg, @synthesize vanadium = erythronium; ), you'll need to know the ivar's name in order to use KVC.

I think overriding property that means redeclaring it again will work

In your .h file:

@property (readonly, copy) NSString *yourProperty;
In your .m file:

@interface MyClass ()

// Redeclare property as readwrite
@property (readwrite, copy) NSString *yourProperty;

@end


@implementation MyClass

@synthesize yourProperty;
@end

or

i am not tested but i think ,you have to try following

[youReadOnlyrProperty retain]

You cannot simply turn the property into readwrite and hope to access the setter, since the setter itself has not been synthesized, therefore it doesn't exist at all.

What you may think of doing is to guess the name of the ivar and add a setter at runtime.

Suppose your property is called foo and that is has the copy property.

  1. Guess that the name of the ivar. Let's try with _foo .

  2. Prepare a setter

     void fooSetter(id self, SEL _cmd, id newFoo) { Ivar ivar = class_getInstanceVariable(self, "_foo"); id oldFoo = object_getIvar(self, ivar); if (oldFoo != newFoo) object_setIvar(self, ivar, [newFoo copy]); } 
  3. Add the setter to the class in the resolveInstanceMethod: class method

     + (BOOL) resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(setFoo:)) { class_addMethod(self, @selector(setFoo:), (IMP)fooSetter, "v@:@"); return YES; } return [super resolveInstanceMethod:aSel]; } 

At this point you have added the setFoo: method to your class at runtime, therefore you can access it by doing

YourClass yourObject = ...;
[yourObject setFoo:whatever];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM