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


89

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

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

다음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.