Работя върху първото си голямо 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 за картите.
Възможно ли е да имате някакви мисли по този въпрос? Благодаря за вашата помощ!