Objective-C-iVar范围的方法变量?
早些时候,我在Objective-C中胡闹,遇到了一个非常常见的情况: 我有一个类,它不是单例,需要在方法调用之间共享一个变量,比如Objective-C-iVar范围的方法变量?,objective-c,methods,scope,objective-c++,ivar,Objective C,Methods,Scope,Objective C++,Ivar,早些时候,我在Objective-C中胡闹,遇到了一个非常常见的情况: 我有一个类,它不是单例,需要在方法调用之间共享一个变量,比如static,但每个实例都需要它自己的变量。然而,这个变量只需要在一个特定的方法中使用,我们将其称为-foo 我想做的是,有一个宏,我们称之为ivar,它让我可以执行以下操作: @implementation MyClass -(foo) { ivar int someVal = 10; // default value, ivar scoped var
static
,但每个实例都需要它自己的变量。然而,这个变量只需要在一个特定的方法中使用,我们将其称为-foo
我想做的是,有一个宏,我们称之为ivar
,它让我可以执行以下操作:
@implementation MyClass
-(foo)
{
ivar int someVal = 10; // default value, ivar scoped variable.
}
-(bar)
{
someVal = 5; // error, outside of `foo`'s scope.
}
@end
变量的定义方式对我来说并不重要(像OBJC_IVAR(Type,Name,Default)
或IVAR someType someName=value这样的宏),只要它满足以下要求:
- 具有线程安全性
- 在另一个方法中可以有相同名称(但不同值)的变量
- 类型较少(与变量的类型无关)
- 默认值支持
- 变量可以在一行中声明(我不需要写15行代码就可以在代码中放入变量)
我目前正在自己开发一个Objective-C++实现,我只是想知道是否有其他人对如何实现这一点有任何想法(或现有的工具)
显然,这不一定要用真正的iVar来完成。更有可能的是,这应该在运行时与关联对象一起完成,这也为我们管理释放
我有一个类,它不是一个单例,需要一个变量
在方法调用之间共享,如静态调用,但每个实例都需要
它是自己的变量
在这种情况下,变量是对象状态的一部分,因此最适合使用实例变量(或属性)这正是IVAR的用途,无论它们用于十几种方法还是一种方法
我目前正在自己开发一个Objective-C++实现
我只是想知道是否还有其他人有任何想法(或现有的工具)
关于如何做到这一点
我的建议是根本不要这样做。如果您的目标是避免混乱,那么不要不必要地尝试向该语言添加新的存储类
但是,如果您决定继续这条线,我会考虑使用块而不是关联对象。块获取其自身的变量副本,这些变量的作用域为块的生存期。例如,您可以执行以下操作:
- (void)func
{
__block int i = 0;
void (^foo)() = ^{
i++;
NSLog(@"i = %d", i);
};
foo();
foo();
foo();
}
您得到的输出是:
i = 1
i = 2
i = 3
也许你能找到一个聪明的方法将其封装在宏中,但在我看来,仅仅为了避免声明实例变量就有很多麻烦。花了很多时间后,我相信我在Objective-C++中找到了一个完全可行的解决方案。其中一些特点:
- 变量是唯一的。只要它们有不同的范围,它们的值是独立的
- 每个实例都有自己的值
- 线程安全(通过相关对象实现)
- 简单变量声明:
- 宏重载:仅指定所需的信息
- 定义OBJC_IVAR的可能方法:
OBJC_IVAR(); // creates a warning, does nothing
OBJC_IVAR(Name); // creates an ivar named 'Name' of type 'id'
OBJC_IVAR(Type, Name); // creates an ivar named 'Name' of type 'Type'
OBJC_IVAR(Type, Name, Default); // creates an ivar named 'Name', of type 'Type', and a default value of 'Default' (which is only executed once);
<> L> >P>用C++模板实现完整的支持(<代码> <弱>代码>,<代码>强> ,<代码>自动还原< /代码>,<代码>易失性< /代码>等)均支持
- 子类不与其超类共享变量(因此不存在冲突,变量实际上仅限于其作用域)
- 可以在单例中使用而不会出现问题
- 速度很快,查找变量需要大约15-30个CPU周期,而一旦查找,则需要与任何其他变量一样长的时间来设置它
- 大部分繁重的工作都是由预处理器完成的,这样可以实现更快的代码
- 只需拖放到现有的Xcode项目中,不依赖于自定义处理器
实施中的一些小缺点:
< L> > P>对象必须具有所有权说明符(限制为C++引用:<代码>引用为非const类型ID,没有明确的所有权< /代码>)。通过向变量类型添加
\uuuuu strong
、\uuuu弱
或\uuu自动删除
可以轻松修复此问题
实现很难理解。因为它依赖于C++模板和Objy-C和谐地工作,很难改变一件事,并希望它能起作用。我在实现中添加了大量的评论,希望这样可以减轻一些负担
方法swizzling主要会混淆这一点。这不是最大的问题,但是如果您开始玩弄方法swizzling,如果您得到意外的结果,不要感到惊讶
不能用于C++对象内。不幸的是,C++不支持运行时属性,比如Objto-C,所以我们不能依赖于我们的变量最终被清理。因此,在C++对象内部不能使用ObjcIvar。不过,我有兴趣看到它的实现
#line
会把事情搞得一团糟,所以不要使用它
版本历史记录
- 1.0:首次发布
- 1.1:更新了
OBJC_IVAR_NAME
以仅依赖预处理器。因此,我们不能使用\uuuuu func\uuuu
因此,不用多说,下面是代码:
OBJC_IVAR.hpp
//
// OBJC_IVAR.h
// TestProj
//
// Created by Richard Ross on 8/17/12.
// Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved.
//
#ifndef OBJC_IVAR_HPP
#define OBJC_IVAR_HPP
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "NSValue+CppObject.h"
// Argument counting algorithm. Not too complex
#define __NARG(_1, _2, _3, _4, _5, VAL, ...) VAL
#define NARG(...) __NARG(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
// Different implementations based on number of parameters passed in
#define __OBJC_IVAR(N, ...) _OBJC_IVAR_ ## N (__VA_ARGS__)
#define _OBJC_IVAR(N, ...) __OBJC_IVAR(N, __VA_ARGS__)
// Usage: OBJC_IVAR(Type (optional), Name (required), Default (optional))
#define OBJC_IVAR(...) _OBJC_IVAR(NARG(__VA_ARGS__), __VA_ARGS__)
// create a unique name. we use '__COUNTER__' here to support scoping on the same line, for compressed source code
#define __OBJC_IVAR_STRINGIFY_NAME(file, line, name, counter) @file ":" #line " " #name ":" #counter
#define _OBJC_IVAR_NAME(file, line, name, counter) __OBJC_IVAR_STRINGIFY_NAME(file, line, name, counter)
#define OBJC_IVAR_NAME(name) _OBJC_IVAR_NAME(__FILE__, __LINE__, name, __COUNTER__)
// old style creation. advantage: uses __func__ to determine calling function
// #define OBJC_IVAR_NAME(Name) [NSString stringWithFormat:@"%s:%i %s:%s:%i", __FILE__, __LINE__, __func__, #Name, __COUNTER__]
// implemenations for each of the overloads
#define _OBJC_IVAR_0(...) _Pragma("message \"Cannot call OBJC_IVAR with 0 params!\"")
#define _OBJC_IVAR_1(Name) _OBJC_IVAR_2(__strong id, Name)
// first major implemenation. because we do no assignment here, we don't have to check for is_set
#define _OBJC_IVAR_2(Type, Name) Type& Name = (_OBJC_IVAR::IMPL<Type>(self, OBJC_IVAR_NAME(Name)))
// this is where things get fun. we have 'OBJC_IVAR_CUR_NAME', instead of calling OBJC_IVAR_NAME
// multiple times, because we must ensure that COUNTER does not change during the course of the macro
// this is the 'inner bowels' of C, and it's quite hacky. Returns a reference to an associated object
// which is wrapped in a NSValue. Note that we only evaluate 'default' once throught the course of the
// application's cycle, so you can feel free to put intensive loading code there.
static NSString *_OBJC_IVAR_CUR_NAME;
#define _OBJC_IVAR_3(Type, Name, Default) Type& Name = (_OBJC_IVAR::IS_SET(self, (_OBJC_IVAR_CUR_NAME = OBJC_IVAR_NAME(Name))) ? _OBJC_IVAR::IMPL<Type>(self, _OBJC_IVAR_CUR_NAME) : _OBJC_IVAR::IMPL<Type>(self, _OBJC_IVAR_CUR_NAME, Default))
// namespace to wrap al lof our functions
namespace _OBJC_IVAR
{
// internal dictionary of all associated object names, so that we don't run
// into memory management issues. we use a set here, because we should never
// have duplicate associated object names.
static NSMutableSet *_names = [NSMutableSet set];
// wraps a value and a reference to a value. used over std::reference_wrapper,
// as that doesn't actually copy in the value passed. That is required for what
// we are doing, as we cannot be assigning to constants.
template<typename T>
class Wrapper {
private:
// private value wrapped by this object.
T _value;
// private reference wrapped by this object. should always point to _value.
T& _ref;
public:
// default constructor. assumes 'T' has a valid 0-argument constructor
Wrapper() : _value(), _ref(_value) { }
// argument constructor. makes sure that value is initialized properly
Wrapper(T val) : _value(val), _ref(_value) { }
// returns the reference wrapped by this object
operator T& () {
return _ref;
}
T& get() {
return _ref;
}
};
// interns a name. because objc_getAssociatedObject works only by comparing
// pointers (and +stringWithFormat: isn't guaranteed to return the same pointer),
// we have to make sure that we maintain a list of all valid associated object
// names. these are NOT linked to specific objects, which allows us to reuse some
// memory
inline NSString *name_intern(NSString *name)
{
// intern the value. first check if the object has been interned already,
// and if it is, return that interned value
if (id tmpName = [_names member:name])
{
name = tmpName;
}
// if we haven't interned this value before, then add it to the list and return it.
else
{
[_names addObject:name];
}
return name;
}
// check and see if the requested iVar has been set yet. used for default value setting
BOOL IS_SET(id target, NSString *name)
{
// first intern the name
name = name_intern(name);
// check if the object has this property. objc_getAssociatedObject will ALWAYS
// return NULL if the object doesn't exist. Note the bridged cast. This is because
// objc_getAssociatedObject doesn't care what you throw into the second parameter,
// as long as it is a pointer. That gives us the flexibility at a later date, to,
// for example, just pass a pointer to a single byte, and pull out the value that
// way. However, we pass in a NSString pointer, because it makes it easy for us to
// use and to re-use later.
id val = objc_getAssociatedObject(target, (__bridge const void *) name);
return val != nil;
}
// the actual implementation for setting the iVar. luckily this code isn't too hacky,
// but it is a bit confusing.
template<typename T>
Wrapper<T>& IMPL(id target, NSString *name)
{
// first intern the name
name = name_intern(name);
// define a reference. we use pointers & new here, because C++ memory managment is
// weird at best. Most of the time, you should be using RAII, but when dealing with
// templates & objective-c interpolation, it is almost required that you use pointers
// with new.
Wrapper<T> *reference = nullptr;
// check and see if the object already contains this property, if so, return that value
NSValue *result = objc_getAssociatedObject(target, (__bridge const void *) name);
if (result == nil)
{
// at this point, we need to create a new iVar, with the default constructor for the type.
// for objective-c objects this is 'nil', for integers and floating point values this is 0,
// for C++ structs and classes, this calls the default constructor. If one doesn't exist,
// you WILL get a compile error.
reference = new Wrapper<T>();
// we now set up the object that will hold this wrapper. This is an extension on NSValue
// which allows us to store a generic pointer (in this case a C++ object), and run desired
// code on -dealloc (which will be called at the time the parent object is destroyed), in
// this case, free the memory used by our wrapper.
result = [NSValue valueWithCppObject:reference onDealloc:^(void *) {
delete reference;
}];
// finally, set the associated object to the target, and now we are good to go.
// We use OBJC_ASSOCIATION_RETAIN, so that our NSValue is properly freed when done.
objc_setAssociatedObject(target, (__bridge const void *) name, result, OBJC_ASSOCIATION_RETAIN);
}
// from result, we cast it's -cppObjectValue to a Wrapper, to pull out the value.
reference = static_cast<Wrapper<T> *>([result cppObjectValue]);
// finally, return the pointer as a reference, not a pointer
return *reference;
}
// this is pretty much the same as the other IMPL, but it has specific code for default values.
// I will ignore everything that is the same about the two functions, and only focus on the
// differences, which are few, but mandatory.
template<typename T>
Wrapper<T>& IMPL(id target, NSString *name, const T& defVal)
{
name = name_intern(name);
Wrapper<T> *reference = nullptr; // asign to be the default constructor for 'T'
NSValue *result = objc_getAssociatedObject(target, (__bridge const void *) name);
if (result == nil)
{
// this is the only difference. Instead of constructing with the default constructor,
// simply pass in our new default value as a copy.
reference = new Wrapper<T>(defVal);
result = [NSValue valueWithCppObject:reference onDealloc:^(void *) {
delete reference;
}];
objc_setAssociatedObject(target, (__bridge const void *) name, result, OBJC_ASSOCIATION_RETAIN);
}
reference = static_cast<Wrapper<T> *>([result cppObjectValue]);
return *reference;
}
}
#endif // OBJC_IVAR_HPP
用法示例:
#import "OBJC_IVAR.hpp"
@interface SomeObject : NSObject
-(void) doSomething;
@end
@implementation SomeObject
-(void) doSomething
{
OBJC_IVAR(__strong id, test, @"Hello World!");
OBJC_IVAR(int, test2, 15);
NSLog(@"%@", test);
NSLog(@"%i", test2 += 7);
// new scope
{
OBJC_IVAR(int, test, 100);
NSLog(@"%i", ++test);
}
[self somethingElse];
}
-(void) somethingElse
{
OBJC_IVAR(int, newVar, 7);
NSLog(@"%i", newVar++);
}
@end
int main()
{
SomeObject *obj = [SomeObject new];
[obj doSomething];
[obj doSomething];
[obj doSomething];
}
我很困惑——为什么它不能是一个常规的ivar,你只能在一种方法中使用?然后使用代码检查来确保它只在预期的地方使用?如果您觉得有必要,您可以用方法命名它——例如,“foo_someVal”。@HotLicks,因为这会使对象的iVar声明变得混乱。另外,为一个可能被调用或不被调用的方法添加iVar有点过分了,你不觉得吗?这对于类别也很有用,因为类别中不能添加iVar。有很多东西会把东西弄得乱七八糟。Objective-C比代码更混乱。如何为大都会增加ivar
//
// NSValue+CppObject.m
// TestProj
//
// Created by Richard Ross on 8/17/12.
// Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved.
//
#import "NSValue+CppObject.h"
// the concrete NSValue subclass for supporting C++ objects. Pretty straight-forward interface.
@interface ConcreteCppObject : NSValue
{
// the underlying object that is being pointed to
void *_object;
// the block that is called on -dealloc
void (^_deallocBlock)(void *);
}
@end
@implementation ConcreteCppObject
// object initialization
+(id) valueWithCppObject:(void *)ptr onDealloc:(void (^)(void *))deallocBlock
{
return [[self alloc] initWithCppObject:ptr onDealloc:deallocBlock];
}
-(id) initWithCppObject:(void *)ptr onDealloc:(void (^)(void *))deallocBlock
{
if (self = [super init])
{
_object = ptr;
_deallocBlock = deallocBlock;
}
return self;
}
// required methods for subclassing NSValue
-(const char *) objCType
{
return @encode(void *);
}
-(void) getValue:(void *)value
{
*((void **) value) = _object;
}
// comparison
-(BOOL) isEqual:(id)compare
{
if (![compare isKindOfClass:[self class]])
return NO;
return [compare cppObjectValue] == [self cppObjectValue];
}
// cleanup
-(void) dealloc
{
// this should manage cleanup for us
_deallocBlock(_object);
}
// value access
-(void *) cppObjectValue
{
return _object;
}
@end
// NSValue additions for creating the concrete instances
@implementation NSValue (CppObject)
// object initialization
+(id) valueWithCppObject:(void *)ptr onDealloc:(void (^)(void *))deallocBlock
{
return [[ConcreteCppObject alloc] initWithCppObject:ptr onDealloc:deallocBlock];
}
-(id) initWithCppObject:(void *)ptr onDealloc:(void (^)(void *))deallocBlock
{
return [[self class] valueWithCppObject:ptr onDealloc:deallocBlock];
}
// unless the NSValue IS a ConcreteCppObject, then we shouldn't do anything here
-(void *) cppObjectValue
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
@end
#import "OBJC_IVAR.hpp"
@interface SomeObject : NSObject
-(void) doSomething;
@end
@implementation SomeObject
-(void) doSomething
{
OBJC_IVAR(__strong id, test, @"Hello World!");
OBJC_IVAR(int, test2, 15);
NSLog(@"%@", test);
NSLog(@"%i", test2 += 7);
// new scope
{
OBJC_IVAR(int, test, 100);
NSLog(@"%i", ++test);
}
[self somethingElse];
}
-(void) somethingElse
{
OBJC_IVAR(int, newVar, 7);
NSLog(@"%i", newVar++);
}
@end
int main()
{
SomeObject *obj = [SomeObject new];
[obj doSomething];
[obj doSomething];
[obj doSomething];
}