NSArray에 c-struct를 넣는 가장 좋은 방법은 무엇입니까?


89

c 구조를 저장하는 일반적인 방법은 무엇입니까 NSArray? 장점, 단점, 메모리 처리?

특히, 차이 무엇 valueWithBytesvalueWithPointer - 저스틴 아래 메기에 의해 제기.

다음valueWithBytes:objCType: 은 향후 독자 를 위한 Apple의 토론 링크입니다 .

측면 적 사고와 성능에 대한 더 많은 것을보기 위해 Evgen은 C ++STL::vector 에서 사용하는 문제를 제기했습니다 .

(흥미로운 문제가 발생합니다. STL::vector"배열의 깔끔한 처리"를 최소화 할 수있는 훨씬 더 가벼우면서도 다르지 않은 빠른 c 라이브러리가 있습니까?)

그래서 원래 질문은 ...

예를 들면 :

typedef struct _Megapoint {
    float   w,x,y,z;
} Megapoint;

그래서 : 자신의 구조를.에 저장하는 정상적이고 가장 좋은 관용적 방법은 무엇이며 NSArray, 그 관용구에서 메모리를 어떻게 처리합니까?

특히 구조체를 저장하는 일반적인 관용구를 찾고 있습니다. 물론 새로운 작은 수업을 만들어 문제를 피할 수 있습니다. 그러나 실제로 구조체를 배열에 넣는 일반적인 관용구를 알고 싶습니다.

BTW는 아마도 NSData 접근 방식입니다. 최고가 아닙니다 ...

Megapoint p;
NSArray *a = [NSArray arrayWithObjects:
    [NSData dataWithBytes:&p length:sizeof(Megapoint)],
    [NSData dataWithBytes:&p length:sizeof(Megapoint)],
    [NSData dataWithBytes:&p length:sizeof(Megapoint)],
        nil];

BTW를 참조 지점으로 사용하고 Jarret Hardie 덕분에 다음과 같이 저장하는 방법 CGPoints과 유사한 방법이 있습니다 NSArray.

NSArray *points = [NSArray arrayWithObjects:
        [NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
        [NSValue valueWithCGPoint:CGPointMake(6.9, 6.9)],
        nil];

( How can I add CGPoint objects to an NSArray the easy way? )


NSData로 변환하기위한 코드는 괜찮을 것입니다. 그리고 메모리 누수가 없어야합니다 .... 그러나 구조체 Megapoint p [3]의 표준 C ++ 배열을 사용하는 것이 좋습니다.
Swapnil Luktuke 2010

질문이 2 일이되기 전까지는 현상금을 추가 할 수 없습니다.
Matthew Frederick

1
valueWithCGPoint는 OSX에서 사용할 수 없습니다. UIKit의 그것의 부분
lppier

@Ippier valueWithPoint는 OS X에서 사용할 수 있습니다
Schpaencoder

답변:


159

NSValue 는 CoreGraphics 구조를 지원할뿐만 아니라 자신의 구조에도 사용할 수 있습니다. 클래스가 NSData단순한 데이터 구조 보다 가볍기 때문에 그렇게하는 것이 좋습니다 .

다음과 같은 표현식을 사용하십시오.

[NSValue valueWithBytes:&p objCType:@encode(Megapoint)];

그리고 가치를 되 찾으려면 :

Megapoint p;
[value getValue:&p];

4
@Joe Blow @Catfish_Man 실제로 구조를 복사하는 p것이지 포인터가 아닙니다. 이 @encode지침은 구조가 얼마나 큰지에 대해 필요한 모든 정보를 제공합니다. 해제하면 NSValue(또는 배열이 해제 될 때) 구조의 사본이 파괴됩니다. getValue:그동안 사용했다면 괜찮습니다. "숫자 및 가치 프로그래밍 주제"의 "값 사용"섹션을 참조하십시오. developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
Justin Spahr-Summers

1
@Joe Blow 런타임에 변경할 수 없다는 점을 제외하면 대부분 정확 합니다. 항상 완전히 알려진 C 유형을 지정하고 있습니다. 더 많은 데이터를 참조하여 "더 커질"수 있다면 아마도 포인터를 사용하여 구현할 수 있으며는 @encode해당 포인터로 구조를 설명 하지만 실제로 변경 될 수있는 가리키는 데이터를 완전히 설명 하지는 않습니다 .
Justin Spahr-Summers

1
NSValue이 해제 될 때 자동으로 구조체의 메모리를 해제? 이에 대한 문서는 약간 불분명합니다.
devios1

1
그래서 완전히 명확하게 NSValue말하면 자신에게 복사하는 데이터를 소유하고 ARC에서 해제하는 것에 대해 걱정할 필요가 없습니다.
devios1

1
@devios 맞습니다. NSValue실제로는 "메모리 관리"를 수행하지 않습니다. 내부적으로 구조 값의 복사본을 가지고 있다고 생각할 수 있습니다. 예를 들어, 구조에 중첩 된 포인터가 포함 된 경우 NSValue해제하거나 복사하거나 그 포인터로 아무것도 수행 할 수 없습니다. 주소를 그대로 복사하여 그대로 둡니다.
Justin Spahr-Summers 2015 년

7

NSValue경로 를 고수하는 것이 좋지만 structNSArray (및 Cocoa의 다른 컬렉션 개체)에 일반 'ol 데이터 유형 을 저장하고 싶다면 Core Foundation 및 수신자 부담 브리징을 사용하여 간접적으로 수행 할 수 있습니다. .

CFArrayRef(및 변경 가능한 대응 물 CFMutableArrayRef)은 개발자가 배열 객체를 만들 때 더 많은 유연성을 제공합니다. 지정된 이니셜 라이저의 네 번째 인수를 참조하십시오.

CFArrayRef CFArrayCreate (
    CFAllocatorRef allocator,
    const void **values,
    CFIndex numValues,
    const CFArrayCallBacks *callBacks
);

이를 통해 CFArrayRef객체가 Core Foundation의 메모리 관리 루틴을 전혀 사용하지 않거나 자신의 메모리 관리 루틴을 사용 하도록 요청할 수 있습니다 .

필수 예 :

// One would pass &kCFTypeArrayCallBacks (in lieu of NULL) if using CF types.
CFMutableArrayRef arrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
NSMutableArray *array = (NSMutableArray *)arrayRef;

struct {int member;} myStruct = {.member = 42};
// Casting to "id" to avoid compiler warning
[array addObject:(id)&myStruct];

// Hurray!
struct {int member;} *mySameStruct = [array objectAtIndex:0];

위의 예는 메모리 관리와 관련된 문제를 완전히 무시합니다. 구조 myStruct는 스택에 생성되므로 함수가 종료되면 파괴됩니다. 배열에는 더 이상 존재하지 않는 객체에 대한 포인터가 포함됩니다. 자체 메모리 관리 루틴 (따라서 옵션이 제공되는 이유)을 사용하여이 문제를 해결할 수 있지만 참조 계산, 메모리 할당, 할당 해제 등의 어려운 작업을 수행해야합니다.

이 솔루션을 권장하지는 않지만 다른 사람이 관심을 가질 경우 여기에 보관합니다. :-)


스택 대신 힙에 할당 된 구조를 사용하는 방법은 다음과 같습니다.

typedef struct {
    float w, x, y, z;
} Megapoint;

// One would pass &kCFTypeArrayCallBacks (in lieu of NULL) if using CF types.
CFMutableArrayRef arrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
NSMutableArray *array = (NSMutableArray *)arrayRef;

Megapoint *myPoint = malloc(sizeof(Megapoint);
myPoint->w = 42.0f;
// set ivars as desired..

// Casting to "id" to avoid compiler warning
[array addObject:(id)myPoint];

// Hurray!
Megapoint *mySamePoint = [array objectAtIndex:0];

변경 가능한 배열 (적어도이 경우)은 빈 상태로 생성되므로 저장할 값에 대한 포인터가 필요하지 않습니다. 이는 내용이 생성시 "고정"되어 있으므로 값이 초기화 루틴에 전달되어야하는 불변 배열과는 다릅니다.
Sedate Alien

@Joe Blow : 메모리 관리에 관한 훌륭한 지적입니다. 혼란스러워하는 것이 맞습니다. 위에 게시 한 코드 샘플은 함수의 스택을 덮어 쓰는시기에 따라 신비한 충돌을 일으킬 수 있습니다. 내 솔루션을 어떻게 사용할 수 있는지 자세히 설명하기 시작했지만 Objective-C의 자체 참조 계산을 다시 구현하고 있다는 것을 깨달았습니다. 간결한 코드에 대해 사과드립니다. 적성 문제가 아니라 게으름입니다. 다른 사람이 읽을 수없는 코드를 작성할 필요가 없습니다. :)
Sedate Alien

를 "누출"(더 나은 단어를 원하기 위해) 기꺼이했다면 struct확실히 한 번 할당하고 나중에 해제 할 수는 없습니다. 편집 된 답변에 이에 대한 예를 포함했습니다. 또한 myStruct힙에 할당 된 구조에 대한 포인터와 구별되는 것처럼 스택에 할당 된 구조이기 때문에에 대한 오타가 아닙니다 .
Sedate Alien

4

c 구조체를 추가하는 유사한 방법은 포인터를 저장하고 포인터를 역 참조하는 것입니다.

typedef struct BSTNode
{
    int data;
    struct BSTNode *leftNode;
    struct BSTNode *rightNode;
}BSTNode;

BSTNode *rootNode;

//declaring a NSMutableArray
@property(nonatomic)NSMutableArray *queues;

//storing the pointer in the array
[self.queues addObject:[NSValue value:&rootNode withObjCType:@encode(BSTNode*)]];

//getting the value
BSTNode *frontNode =[[self.queues objectAtIndex:0] pointerValue];

3

괴상한 느낌이 들거나 실제로 생성 할 클래스가 많은 경우 : 때때로 objc 클래스를 동적으로 구성하는 것이 유용합니다 (ref :) class_addIvar. 이런 식으로 임의 유형에서 임의의 objc 클래스를 만들 수 있습니다. 필드별로 필드를 지정하거나 구조체의 정보를 전달할 수 있습니다 (실제로 NSData를 복제합니다). 때로는 유용하지만 대부분의 독자에게는 '재미있는 사실'일 것입니다.

여기에 어떻게 적용할까요?

class_addIvar를 호출하고 새 클래스에 Megapoint 인스턴스 변수를 추가하거나 런타임에 Megapoint 클래스의 objc 변형을 합성 할 수 있습니다 (예 : Megapoint의 각 필드에 대한 인스턴스 변수).

전자는 컴파일 된 objc 클래스와 동일합니다.

@interface MONMegapoint { Megapoint megapoint; } @end

후자는 컴파일 된 objc 클래스와 동일합니다.

@interface MONMegapoint { float w,x,y,z; } @end

ivar를 추가 한 후 메서드를 추가 / 합성 할 수 있습니다.

수신 측에 저장된 값을 읽으려면 합성 방법을 사용하고, object_getInstanceVariable , 또는 valueForKey:(보통의 NSNumber 또는 NSValue 표현으로 이러한 스칼라 인스턴스 변수를 변환합니다).

btw : 귀하가받은 모든 답변은 유용합니다. 일부는 상황 / 시나리오에 따라 더 좋고 / 나쁘고 / 잘못된 답변입니다. 메모리, 속도, 유지 관리 용이성, 전송 또는 보관 용이성 등과 관련된 특정 요구 사항에 따라 주어진 경우에 가장 적합한 것이 결정되지만 모든면에서 이상적인 '완벽한'솔루션은 없습니다. 'NSArray에 c-struct를 넣는 가장 좋은 방법'은 없으며 ' 특정 시나리오, 사례 또는 요구 사항 집합 에 대해 NSArray에 c-struct를 넣는 가장 좋은 방법 입니다. '입니다. 지정합니다.

또한 NSArray는 포인터 크기 (또는 더 작은) 유형에 대해 일반적으로 재사용 가능한 배열 인터페이스이지만 여러 가지 이유로 c-structs에 더 적합한 다른 컨테이너가 있습니다 (std :: vector는 c-structs의 일반적인 선택입니다).


사람들의 배경도 작용합니다. 어떻게 그 구조체를 사용해야하는지에 따라 일부 가능성이 사라집니다. 4 개의 float는 매우 안전하지만 구조 레이아웃은 아키텍처 / 컴파일러에 따라 너무 많이 달라서 인접 메모리 표현 (예 : NSData)을 사용하고 작동 할 것으로 기대합니다. 가난한 사람의 objc serializer는 실행 시간이 가장 느릴 가능성이 있지만 모든 OS X 또는 iOS 장치에서 Megapoint를 저장 / 열기 / 전송해야하는 경우 가장 호환됩니다. 내 경험상 가장 일반적인 방법은 단순히 objc 클래스에 구조체를 넣는 것입니다. 만약 당신이 단지 (계속)이 모든 것을 겪고 있다면
justin

(계속) 새로운 컬렉션 유형을 배우지 않기 위해서만이 모든 번거 로움을 겪고 있다면 새 컬렉션 유형을 배워야합니다 =) std::vector(예를 들어) C / C ++ 유형, 구조체 및 클래스를 보유하는 데 더 적합합니다. NSArray. NSValue, NSData 또는 NSDictionary 유형의 NSArray를 사용하면 많은 할당과 런타임 오버 헤드를 추가하는 동안 많은 유형 안전성을 잃게됩니다. C를 고수하고 싶다면 일반적으로 스택에서 malloc 및 / 또는 배열을 사용하지만 std::vector대부분의 복잡한 문제를 숨 깁니다.
justin

사실, 언급했듯이 배열 조작 / 반복을 원한다면 stl (C ++ 표준 라이브러리의 일부)이 적합합니다. 선택할 수있는 유형이 더 많고 (예 : 삽입 / 제거가 읽기 액세스 시간보다 더 중요한 경우) 컨테이너를 조작하는 수많은 기존 방법이 있습니다. 또한-C ++의 베어 메모리가 아닙니다. 컨테이너와 템플릿 함수는 유형을 인식하고 컴파일시 확인됩니다. NSData / NSValue 표현에서 임의의 바이트 문자열을 가져 오는 것보다 훨씬 안전합니다. 또한 경계 검사와 대부분 자동 메모리 관리 기능이 있습니다. (계속)
justin

(계속) 이와 같은 낮은 수준의 작업이 많을 것으로 예상되면 지금 바로 배워야하지만 배우는 데 시간이 걸립니다. 이 모든 것을 objc 표현으로 감싸면 많은 성능과 형식 안전성을 잃고 컨테이너와 그 값에 액세스하고 해석하기 위해 훨씬 더 많은 상용구 코드를 작성하게됩니다. 정확히 원하는 작업).
justin

그것은 당신이 처분 할 수있는 또 다른 도구 일뿐입니다. objc를 C ++, C ++를 objc, c를 C ++ 또는 기타 여러 조합과 통합하는 데 문제가있을 수 있습니다. 어떤 경우에도 언어 기능을 추가하고 여러 언어를 사용하는 것은 적은 비용으로 이루어집니다. 모든 방향으로 진행됩니다. 예를 들어, objc ++로 컴파일 할 때 빌드 시간이 늘어납니다. 또한 이러한 소스는 다른 프로젝트에서 쉽게 재사용되지 않습니다. 물론 언어 기능을 다시 구현할 수 있지만 이것이 최선의 해결책은 아닙니다. objc 프로젝트에 C ++를 통합하는 것은 괜찮습니다. 같은 프로젝트에서 objc와 c 소스를 사용하는 것만 큼 '지저분합니다'. (계속
justin

3

여러 abis / 아키텍처에서이 데이터를 공유하는 경우 가난한 사람의 objc serializer를 사용하는 것이 가장 좋습니다.

Megapoint mpt = /* ... */;
NSMutableDictionary * d = [NSMutableDictionary new];
assert(d);

/* optional, for your runtime/deserialization sanity-checks */
[d setValue:@"Megapoint" forKey:@"Type-Identifier"];

[d setValue:[NSNumber numberWithFloat:mpt.w] forKey:@"w"];
[d setValue:[NSNumber numberWithFloat:mpt.x] forKey:@"x"];
[d setValue:[NSNumber numberWithFloat:mpt.y] forKey:@"y"];
[d setValue:[NSNumber numberWithFloat:mpt.z] forKey:@"z"];

NSArray *a = [NSArray arrayWithObject:d];
[d release], d = 0;
/* ... */

... 특히 구조가 시간이 지남에 따라 (또는 대상 플랫폼에 따라) 변경 될 수있는 경우. 다른 옵션만큼 빠르지는 않지만 일부 조건 (중요한 것으로 지정하지 않았는지 여부를 지정하지 않았 음)에서는 중단 될 가능성이 적습니다.

직렬화 된 표현이 프로세스를 종료하지 않으면 임의 구조체의 크기 / 순서 / 정렬이 변경되지 않아야하며 더 간단하고 빠른 옵션이 있습니다.

두 경우 모두 이미 ref-counted 개체 (NSData, NSValue와 비교)를 추가하고 있으므로 Megapoint를 보유하는 objc 클래스를 만드는 것이 많은 경우 정답입니다.


@Joe Blow는 직렬화를 수행하는 무언가입니다. 참고 : en.wikipedia.org/wiki/Serialization , parashift.com/c++-faq-lite/serialization.html , Apple의 "Archives and Serializations Programming Guide".
저스틴

xml 파일이 무언가를 올바르게 표현한다고 가정하면 예-사람이 읽을 수있는 직렬화의 일반적인 형태 중 하나입니다.
justin

0

C / C ++ 유형에 std :: vector 또는 std :: list를 사용하는 것이 좋습니다. 처음에는 NSArray보다 빠르기 때문에 두 번째에는 속도가 충분하지 않은 경우 언제든지 직접 만들 수 있습니다. STL 컨테이너에 대한 할당자를 사용하여 더욱 빠르게 만듭니다. 모든 최신 모바일 게임, 물리 및 오디오 엔진은 STL 컨테이너를 사용하여 내부 데이터를 저장합니다. 정말 빠르기 때문입니다.

그것이 당신을위한 것이 아니라면-NSValue에 대한 사람들의 좋은 답변이 있습니다-나는 그것이 가장 수용 가능하다고 생각합니다.


STL은 C ++ 표준 라이브러리에 부분적으로 포함 된 라이브러리입니다. en.wikipedia.org/wiki/Standard_Template_Library cplusplus.com/reference/stl/vector
Evgen

흥미로운 주장입니다. STL 컨테이너와 Cocoa 컨테이너 클래스의 속도 이점에 대한 기사 링크가 있습니까?
Sedate Alien

다음은 NSCFArray 대 std :: vector에 대한 흥미로운 읽기입니다. 게시물의 예에서 ridiculousfish.com/blog/archives/2005/12/23/array , 가장 큰 손실은 (일반적으로) 요소 당 objc 객체 표현을 만드는 것입니다 (예 : Megapoint를 포함하는, NSValue, NSData 또는 Objc 유형에는 할당 및 참조 계산 시스템에 삽입이 필요합니다. 연속적으로 할당 된 메가 포인트의 별도 백업 저장소를 사용하는 특수 CFArray에 메가 포인트를 저장하는 Sedate Alien의 접근 방식을 사용하면 실제로이를 방지 할 수 있습니다 (두 예제 모두 해당 접근 방식을 설명하지 않음). (계속)
justin

그러나 NSCFArray 대 벡터 (또는 다른 stl 유형)를 사용하면 동적 디스패치, 인라인되지 않은 추가 함수 호출, 수많은 유형 안전성 및 최적화 프로그램이 시작할 수있는 많은 기회에 대한 추가 오버 헤드가 발생합니다. 기사는 삽입, 읽기, 걷기, 삭제에 중점을 둡니다. 기회는 16 바이트로 정렬 된 c-array보다 빠르지 않을 것입니다. Megapoint pt[8];이것은 C ++의 옵션이며 특수한 C ++ 컨테이너 (예 :)입니다. std::array또한 예제에서 특수 정렬을 추가하지 않습니다 (16 바이트가 선택됨). Megapoint의 크기이기 때문에). (계속)
justin

std::vector여기에 약간의 오버 헤드가 추가되고 하나의 할당 (필요한 크기를 알고있는 경우)이 추가됩니다. 그러나 이것은 케이스의 99.9 % 이상이 필요로하는 것보다 금속에 더 가깝습니다. 일반적으로 크기가 고정되어 있거나 적절한 최대 값이없는 한 벡터를 사용합니다.
저스틴

0

c 구조체를 NSArray에 넣는 대신 NSData 또는 NSMutableData에 구조체의 ac 배열로 넣을 수 있습니다. 그들에 액세스하려면 할 것입니다

const struct MyStruct    * theStruct = (const struct MyStruct*)[myData bytes];
int                      value = theStruct[2].integerNumber;

또는 설정하려면

struct MyStruct    * theStruct = (struct MyStruct*)[myData mutableBytes];
theStruct[2].integerNumber = 10;


0

구조의 경우 속성을 추가하고 구문을 objc_boxable사용 @()하여 다음을 호출하지 않고 NSValue 인스턴스에 구조를 넣을 수 있습니다 valueWithBytes:objCType:.

typedef struct __attribute__((objc_boxable)) _Megapoint {
    float   w,x,y,z;
} Megapoint;

NSMutableArray<NSValue*>* points = [[NSMutableArray alloc] initWithCapacity:10];
for (int i = 0; i < 10; i+= 1) {
    Megapoint mp1 = {i + 1.0, i + 2.0, i + 3.0, i + 4.0};
    [points addObject:@(mp1)];//@(mp1) creates NSValue*
}

Megapoint unarchivedPoint;
[[points lastObject] getValue:&unarchivedPoint];
//or
// [[points lastObject] getValue:&unarchivedPoint size:sizeof(Megapoint)];

-2

Obj C 객체는 몇 가지 요소가 추가 된 C 구조체입니다. 따라서 사용자 정의 클래스를 생성하면 NSArray에 필요한 C 구조체 유형이 생깁니다. NSObject가 C 구조체 내에 포함하는 추가 요소가없는 C 구조체는 NSArray로 소화 할 수 없습니다.

NSData를 래퍼로 사용하면 원본 구조체가 아닌 구조체의 복사본 만 저장할 수 있습니다.


-3

C-Structures 이외의 NSObject 클래스를 사용하여 정보를 저장할 수 있습니다. 그리고 NSObject를 NSArray에 쉽게 저장할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.