Короткий ответ: accessing a static variable from another file невозможно. Это точно такая же проблема, как и попытка ссылаться на локальную переменную-функции из другого места; имя просто недоступно. В C существует три этапа «видимости» для объектов *, которые называются «привязкой»: внешние (глобальные), внутренние (ограниченные одной «единицей перевода» - свободно, один файл) и " no "(функция-локальная). Когда вы объявляете переменную как static
, она получает внутреннюю привязку; ни один другой файл не может получить к нему доступ по имени. Вы должны сделать какую-либо функцию доступа, чтобы разоблачить ее.
Расширенный ответ заключается в том, что, поскольку существует некоторая обтекаемость библиотеки ObjC, которую мы можем делать в любом случае для имитации переменных уровня класса, мы можем сделать несколько обобщенный тестовый код, который вы можете условно скомпилировать. Однако это не так просто.
Прежде чем мы начнем, отметим, что для этого все еще требуется индивидуальная реализация одного метода; нет никакого способа обойти это из-за ограничений связи.
Шаг один, объявить методы, один для настройки, а затем набор для valueForKey:
-как доступа:
// ClassVariablesExposer.h
#if UNIT_TESTING
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#define ASSOC_OBJ_BY_NAME(v) objc_setAssociatedObject(self, #v, v, OBJC_ASSOCIATION_ASSIGN)
// Store POD types by wrapping their address; then the getter can access the
// up-to-date value.
#define ASSOC_BOOL_BY_NAME(b) NSValue * val = [NSValue valueWithPointer:&b];\
objc_setAssociatedObject(self, #b, val, OBJC_ASSOCIATION_RETAIN)
@interface NSObject (ClassVariablesExposer)
+ (void)associateClassVariablesByName;
+ (id)classValueForName:(char *)name;
+ (BOOL)classBOOLForName:(char *)name;
@end
#endif /* UNIT_TESTING */
Эти методы семантически больше похожи на протокол, чем категория. Первый метод должен быть переопределен в каждом подклассе, потому что переменные, которые вы хотите связать, конечно, будут разными, и из-за проблемы с привязкой. Фактический вызов objc_setAssociatedObject()
, где вы ссылаетесь на переменную , должен быть в файле, где объявлена переменная.
Включение этого метода в протокол потребует дополнительного заголовка для вашего класса, поскольку, хотя реализация метода протокола должна идти в основном файле реализации, ARC и ваши модульные тесты должны видеть объявление, которое ваш класс соответствует протоколу. Громоздкие.Разумеется, вы можете сделать эту категорию NSObject
соответствующей протоколу, но тогда вам понадобится заглушка в любом случае, чтобы избежать предупреждения «неполной реализации». Я делал каждое из этих действий при разработке этого решения и решил, что они не нужны.
Второй набор, Аксессоры, очень хорошо работают как методы категории, потому что они просто выглядеть следующим образом:
// ClassVariablesExposer.m
#import "ClassVariablesExposer.h"
#if UNIT_TESTING
@implementation NSObject (ClassVariablesExposer)
+ (void)associateClassVariablesByName
{
// Stub to prevent warning about incomplete implementation.
}
+ (id)classValueForName:(char *)name
{
return objc_getAssociatedObject(self, name);
}
+ (BOOL)classBOOLForName:(char *)name
{
NSValue * v = [self classValueForName:name];
BOOL * vp = [v pointerValue];
return *vp;
}
@end
#endif /* UNIT_TESTING */
Полностью целом, однако их успешное использование действительно зависит от вашей занятости макросов из выше.
Далее определите класс, переопределение, что создать метод, чтобы захватить ваши переменные класса:
// Milliner.h
#import <Foundation/Foundation.h>
@interface Milliner : NSObject
// Just for demonstration that the BOOL storage works.
+ (void)flipWaterproof;
@end
// Milliner.m
#import "Milliner.h"
#if UNIT_TESTING
#import "ClassVariablesExposer.h"
#endif /* UNIT_TESTING */
@implementation Milliner
static NSString * featherType;
static BOOL waterproof;
+(void)initialize
{
featherType = @"chicken hawk";
waterproof = YES;
}
// Just for demonstration that the BOOL storage works.
+ (void)flipWaterproof
{
waterproof = !waterproof;
}
#if UNIT_TESTING
+ (void)associateClassVariablesByName
{
ASSOC_OBJ_BY_NAME(featherType);
ASSOC_BOOL_BY_NAME(waterproof);
}
#endif /* UNIT_TESTING */
@end
Убедитесь, что ваш блок тестовый файл импортирует заголовок для данной категории. Простая демонстрация этой функциональности:
#import <Foundation/Foundation.h>
#import "Milliner.h"
#import "ClassVariablesExposer.h"
#define BOOLToNSString(b) (b) ? @"YES" : @"NO"
int main(int argc, const char * argv[])
{
@autoreleasepool {
[Milliner associateClassVariablesByName];
NSString * actualFeatherType = [Milliner classValueForName:"featherType"];
NSLog(@"Assert [[Milliner featherType] isEqualToString:@\"chicken hawk\"]: %@", BOOLToNSString([actualFeatherType isEqualToString:@"chicken hawk"]));
// Since we got a pointer to the BOOL, this does track its value.
NSLog(@"%@", BOOLToNSString([Milliner classBOOLForName:"waterproof"]));
[Milliner flipWaterproof];
NSLog(@"%@", BOOLToNSString([Milliner classBOOLForName:"waterproof"]));
}
return 0;
}
Я поставил проект на GitHub: https://github.com/woolsweater/ExposingClassVariablesForTesting
Еще одно предостережение в том, что каждый тип POD вы хотите, чтобы иметь возможность доступа потребуется свой собственный метод: classIntForName:
,,
Хотя это работает, и я всегда наслаждаюсь обезьяной вокруг с ObjC, я думаю, что это может быть слишком умно пополам; если у вас есть только одна или две из этих переменных класса, самое простое предложение состоит в том, чтобы условно скомпилировать для них аксессоры (сделать фрагмент кода Xcode). Мой код здесь, вероятно, сэкономит вам время и силы, если у вас есть лотов переменных в одном классе.
По-прежнему, возможно, вы можете использовать его. Надеюсь, это было интересно читать, по крайней мере.
* Значение просто «вещь, которая, как известно линкера» - функция, переменная, структура и т.д. - не в ObjC или чувств C++.
check 'objc/runtime.h' –
Эти переменные берутся из C: теги не должны редактироваться. –
Я проверил заголовок runtime, и я часто использую некоторые из этих функций. –