8

在Objective-C中将只读属性转换为读写属性的方法是什么?请记住,我无法访问源代码。

原因:出于模拟目的,我需要在单元测试中执行此操作。

4

3 回答 3

9

如果不访问类的主要实现块,或者至少是包含它的编译单元,则无法像这样更改属性的功能,因为您无法访问该单元之外的ivar。即使您要在类别中为类添加一个 setter,您也无法影响类的存储,就像您完全在类之外一样。

但是,您可以做的是使用 KVC。如果可以找到一个setValue:forKey:,将绕过 setter 并直接进入 ivar 。您可以使用它来设置您喜欢的任何值,即使是已声明的属性,只要有您知道其名称的后备存储即可。readonly

它是这样的:

//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;
}

就像我说的那样,这将失败——实际上引发了一个异常——如果既没有同名的 setter 也没有 ivar(或附加下划线_vanadium(KVC 非常聪明),例如如果属性的值是完全计算的:

//Passaquisset.m

#import "Passaquisset.h"

@implementation Passaquisset

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

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

@end

为了完整起见,让我提一下,如果属性由完全不同名称的 ivar 支持(例如,@synthesize vanadium = erythronium;),您需要知道ivar 的名称才能使用 KVC。

于 2013-01-27T19:48:12.133 回答
3

您不能简单地将属性变成readwrite并希望访问 setter,因为 setter 本身还没有被合成,因此它根本不存在。

您可能会想到做的是猜测 ivar 的名称并在运行时添加一个 setter。

假设您的属性被调用foo并且具有该copy属性。

  1. 猜猜那个ivar的名字。让我们尝试一下_foo

  2. 准备一个二传手

    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. resolveInstanceMethod:类方法中给类添加setter

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

此时您已setFoo:在运行时将该方法添加到您的类中,因此您可以通过以下方式访问它

YourClass yourObject = ...;
[yourObject setFoo:whatever];
于 2013-01-27T19:48:46.990 回答
2

我认为覆盖属性意味着再次重新声明它会起作用

在您的 .h 文件中:

@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

或者

我没有经过测试,但我认为,您必须尝试关注

[youReadOnlyrProperty retain]
于 2013-01-27T17:57:48.183 回答