Необходими са насоки относно разпределянето на c структури и масиви в купчина в Objective-c

Работя върху първото си голямо Objective-c приложение, игра на карти. Поради различни причини избрах да поддържам състоянието на моята игра, като използвам C структура, която съдържа int, bools, масиви и различни указатели. (За любопитните, правя това, защото в крайна сметка ще трябва да създам и унищожа хиляди и хиляди от тези gameStates и исках да бъда възможно най-близо до „голия метал“.) Структурата на gameState се дефинира, както следва:

struct GameState {
    // changing game state properties
    int score;
    int moves;
    bool won;

    int numberOfCards;
    int numberOfPiles;

    // doneness of piles
    bool *pileDoneness;
    bool *pileDisabled;

    // array of cards (each item is a pointer to a card)
    Card *cards;

    // defining piles (each card in the pile is a pointer to a pointer Card in the cards array)
    Card ***piles;

    // previous gameStates
    struct GameState *previous;
};

Една бележка, точно преди да бъде направен ход в игра, правя копие на gameState (всички стойности с изключение на предишния) и задавам gameState->previous на копието.

Една карта е просто цяло число, където различни байтове представляват различни свойства на картата (боя, лице нагоре/надолу, стойност и т.н.).

Имам и клас MDCard и клас MDGameState. И двата класа имат само класови методи и аз никога не създавам екземпляр от нито един от тях. Те имат методи върху себе си, които могат да създават и манипулират съответно карти и състояния на играта. За да обясните, можете да обърнете карта, като използвате този код:

// assume card already exists
[MDCard flip:card];

Мога да получа цвета на карта като този:

Color color = [MDCard color:card];

Добре. Сега обектът MDGameState също има метод на клас за създаване на нов gameInstance. Това е кодът му:

+(GameState *)newGameStateWithNumberOfPiles:(u_int)numberOfPiles andNumberOfCards:(u_int)numberOfCards{
    // create our new gameState struct
    GameState *gameState = malloc(sizeof(GameState));

    // array of state of doneness of piles
    gameState->pileDoneness = malloc(sizeof(bool) * numberOfPiles);
    for (int i = 0 ; i < numberOfPiles ; i++) gameState->pileDoneness[i] = false;

    gameState->pileDisabled = malloc(sizeof(bool) * numberOfPiles);
    for (int i = 0 ; i < numberOfPiles ; i++) gameState->pileDisabled[i] = false;

    // array of pointers to cards
    **gameState->cards = malloc(sizeof(Card) * numberOfCards);**
    for (int i = 0 ; i < numberOfCards ; i++) gameState->cards[i] = 0;

    // array of array of pointers to Cards in cards array
    gameState->piles = malloc(sizeof(Card **) * numberOfPiles);
    for (int p = 0 ; p < numberOfPiles ; p++){
        gameState->piles[p] = malloc(sizeof(Card *) * numberOfCards);
        for(int c = 0 ; c < numberOfCards ; c++){
            gameState->piles[p][c] = nil;
        }
    }

    gameState->moves = 0;
    gameState->score = 0;
    gameState->won = false;

    // piles
    gameState->numberOfPiles = numberOfPiles;
    gameState->numberOfCards = numberOfCards;
    gameState->previous = nil;

    return gameState;
}

Текущото ми разбиране е, че gameState ще бъде разпределен в купчината. Всъщност променливата gameState е просто указател към разпределената памет в купчината. Тази разпределена памет в купчината съдържа различни стойности като брой ходове, резултат, спечелени и т.н. Тя също така съдържа указатели към друга разпределена памет в купчината за gameState масиви от карти, купчини и т.н. Т.Е.: всички mallocs са разпределяне на памет в купчината.

Знам, че трябва да освободя паметта за това gameState, когато свърша с него.

Чудя се дали Objective-C ARC проследява препратки към тази структура gameState? Какво ще кажете за масивите, които препраща? В момента предполагам, че отговорът е не.

Предполагам, че щом gameState се върне от newGameStateWithNumberOfPiles:andNumberOfCards:, че класът MDGameState вече няма препратка към него. Дали това е правилно?

Предполагам, че обектът, който завършва с препратка към gameState, е отговорен за освобождаването на паметта си, когато приключи с gameState?

Имам нужда обектът gameState да живее толкова дълго, колкото потребителят играе играта. Отново имайте предвид, че всеки път, когато потребителят направи ход, се прави копие на gameState и се задава като предишното gameState. Това е с цел отмяна.

Създадох функция в MDGameState, която трябва да освободи цялата памет, използвана от тази структура:

+ (void)freeGameState:(GameState *)gameState recurse:(BOOL)recurse {
    if(recurse && gameState->previous != nil){
        [MDGameState freeGameState:gameState->previous recurse:recurse];
    }

    for (int p = 0 ; p < gameState->numberOfPiles ; p++){
        free(gameState->piles[p]);
    }

    free(gameState->piles);
    free(gameState->pileDoneness);
    free(gameState->pileDisabled);
    free(gameState->cards);
}

Имайте предвид, че в някои случаи може да искам да освободя текущото gameState, но не и предишните състояния. Например, ако отменя ход.

И така, когато потребител започне нова игра, gameState се създава и се връща към моя viewController, който пази тази препратка. Когато потребителят започне друга игра, първо извиквам [MDGameState freeGameState:gameState recurse:YES], за да изчистя напълно предишното gameState и след това създавам изцяло ново gameState, използвайки [MDGameState newGameStateWithNumberOfPiles:13 andNumberOfCards:52].

Мисля, че правилно почиствам след себе си. Инструментите обаче ми казват, че имам изтичане на памет. Той специално подчертава удебелената линия в newGameStateWithNumberOfPiles:andNumberOfCards: примера по-горе. Това се случва почти веднага щом стартирам нова игра. Той също така подчертава идентичен код във функцията, която прави копие на gameState. Не разбирам защо този malloc е подчертан, а двата над него не са. Ако променя реда на mallocs в тази функция, тя все още подчертава malloc за картите.

Възможно ли е да имате някакви мисли по този въпрос? Благодаря за вашата помощ!


person Doug Hughes    schedule 16.02.2013    source източник
comment
За протокола: по-добре използвайте обекти за това. Objective-C не е много по-бавен от обикновения C, но е по-удобен за определени задачи.   -  person    schedule 17.02.2013
comment
Напълно разбирам. Това също е малко експеримент, който да ми помогне да науча по-добре C и как се свързва с Objective-C.   -  person Doug Hughes    schedule 17.02.2013
comment
Сега това е много по-добър аргумент. В този случай няма да продължа да цепя косата.   -  person    schedule 17.02.2013


Отговори (1)


Вие сте прав, ARC не управлява паметта, разпределена с malloc().

Имате изтичане на информация в метода +freeGameState:recurse:, защото никога не извиквате free(gameState), което означава, че всеки един gameState е изтекъл.

Правилно, класът MDGameState няма да има препратка към gameState след връщане от +newGameStateWithNumberOfPiles:andNumberOfCards.

Да, извикващият newGameStateWithNumberOfPiles:andNumberOfCards е отговорен за освобождаването на паметта, когато това е направено с gameState.

person monoxygen    schedule 16.02.2013
comment
Прав си, въпреки че се оказа, че имам две изтичания на памет. Другият беше във функция, която създава картите за gameState-›карти. Разпределих масив от нулеви (добре, 0) карти при създаването на gameState и след това ги замених с различен масив от разбъркани карти. Изобщо не трябваше да разпределям празния масив и не го освободих, когато го замених, оттук и изтичането. Благодаря ти за помощта! - person Doug Hughes; 17.02.2013