3

私は以前に Objective-C をいじっていましたが、非常に一般的な状況に遭遇しました。

のようなメソッド呼び出し間で共有される変数を必要とするシングルトンではないクラスがありましたがstatic、各インスタンスには独自の変数が必要でした。ただし、この変数は 1 つの特定のメソッドでのみ使用する必要があり、それを と呼びます-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

  • スレッドセーフあり
  • 別のメソッドで同じ名前 (ただし異なる値) の変数を持つことができます
  • 型なし (変数の型は関係ありません)
  • デフォルト値のサポート
  • 変数は 1 行で宣言できます (変数をコードに入れるためだけに 15 行のコードを書く必要はありません)。

私は現在、自分自身で Objective-C++ の実装に取り​​組んでいます。他の誰かがこれを行う方法について何か考え (または既存のツール) を持っているかどうか疑問に思っていました。

明らかに、これは真の iVar で行う必要はありません。多くの場合、これは実行時に関連付けられたオブジェクトで行う必要があります。これにより、割り当て解除も管理されます。

4

2 に答える 2

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);
      
  • C++ テンプレートによる完全なタイプのサポート ( __weak__strong__autoreleasingvolatileなどはすべてサポートされています)

  • サブクラスはスーパークラスと変数を共有しません (そのため、競合の可能性はなく、変数は実際にはそのスコープに制限されています)。
  • 問題なくシングルトンで使用できます
  • 高速で、変数を検索するのに約 15 ~ 30 の CPU サイクルを要し、一度検索すると、他の変数を設定するのと同じくらい時間がかかります。
  • ハードワークのほとんどはプリプロセッサによって行われるため、コードの高速化が可能になります。
  • 既存の Xcode プロジェクトにドラッグ アンド ドロップするだけで、カスタム プロセッサに依存しません

実装に関するいくつかのマイナーな短所:

  • オブジェクトには所有権指定子が必要です (C++ 参照の制限: Reference to non-const type 'id' with no explicit ownership)。変数の型に__strong__weak、またはを追加することで簡単に修正できます__autoreleasing

  • 実装が読みにくい。C++ テンプレートと Objective-C が調和して動作することに大きく依存しているため、「1 つのこと」を変更してそれが機能することを期待することは困難です。実装に広範なコメントを追加したので、これで負担が軽減されることを願っています。

  • メソッドの入れ替わりは、これを大きく混乱させる可能性があります。最大の問題ではありませんが、メソッドの入れ替わりをいじり始めると、予期しない結果が得られても驚かないでください。

  • C++ オブジェクト内では使用できません。残念ながら、C++ は Objective-C のようにランタイム属性をサポートしていないため、変数が最終的にクリーンアップされることに依存することはできません。このため、C++ オブジェクト内では OBJC_IVAR を使用できません。ただし、そのための実装を見ることに興味があります。

  • #lineこれを大幅に台無しにする可能性があるため、使用しないでください。

バージョン履歴

  • 1.0: 初期リリース
  • 1.1:OBJC_IVAR_NAMEプリプロセッサのみに依存するように更新。その結果、使用できません__func__

したがって、これ以上苦労することなく、コードは次のとおりです。

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

NSValue+CppObject.h

//
//  NSValue+CppObject.h
//  TestProj
//
//  Created by Richard Ross on 8/17/12.
//  Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved.
//

#import <Foundation/Foundation.h>

// Extension on NSValue to add C++ object support. Because of the difficulty
// involved in templates, I took the easy way out and simply passed in a block
// of code to be run at dealloc.
@interface NSValue (CppObject)

// create a new NSValue instance that holds ptr, and calls 'deallocBlock' on destruction.
+(id) valueWithCppObject:(void *) ptr onDealloc:(void (^)(void *)) deallocBlock;
-(id) initWithCppObject:(void *)  ptr onDealloc:(void (^)(void *)) deallocBlock;

// get the held pointer of this object. I called it -cppObjectValue, so
// there was no confusion with -pointerValue.
-(void *) cppObjectValue;

@end

NSValue+CppObject.m

//
//  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];
}
于 2012-08-17T15:55:23.133 に答える
1

シングルトンではなく、静的などのメソッド呼び出し間で共有される変数を必要とするクラスがありましたが、各インスタンスには独自の変数が必要でした。

その場合、変数はオブジェクトの状態の一部であるため、インスタンス変数 (またはプロパティ) を使用するのが最も適切です。これは、12 のメソッドで使用されている場合でも、1 つのメソッドで使用されている場合でも、まさに 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

これをマクロにまとめる賢い方法を見つけることができるかもしれませんが、インスタンス変数の宣言を避けるだけでも大変なことのように思えます。

于 2012-08-17T04:47:37.530 に答える