【问题标题】:NSObject not retainingNSObject 不保留
【发布时间】:2026-01-25 00:45:02
【问题描述】:

流程-

NSObject 类用于生成具有某些属性的卡片。这被添加到 MutableArray 并相应地使用。然而,在另一个类中确定手牌结果的函数之后,MutableArray 失去了它的所有值。

现在我知道 MutableArray 只是指向对象而不是持有它们,所以为了让它失去所有它的值,我假设对象被 ARC 扫过。

-(void)rankHand {
    NSString *echo = [Hand returnHandRank:_hand withString:false]; // 7 values in _hand
    // 0 values in _hand.
    NSLog(@"%@", echo);

}

断点后看问题,returnHandRank: withString:后出现问题

@interface Cards : NSObject

@property (nonatomic, strong) NSString *face;
@property (nonatomic, strong) NSString *suit;
@property (nonatomic, strong) NSString *symbol;
@property (nonatomic) int prime;
@property (nonatomic) int rankByInt;

+(NSMutableArray*)createDeck:(id)sender {
    [sender removeAllObjects];
    NSArray *faces = [[NSArray alloc] initWithObjects:@"A",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"10",@"J",@"Q",@"K", nil];
    NSArray *suits = [[NSArray alloc] initWithObjects:@"h",@"d",@"c",@"s", nil];
    NSArray *primes = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:41],[NSNumber numberWithInt:2],[NSNumber numberWithInt:3],[NSNumber numberWithInt:5],[NSNumber numberWithInt:7],[NSNumber numberWithInt:11],[NSNumber numberWithInt:13],[NSNumber numberWithInt:17],[NSNumber numberWithInt:19],[NSNumber numberWithInt:23],[NSNumber numberWithInt:29],[NSNumber numberWithInt:31],[NSNumber numberWithInt:37], nil];
    for (int i = 0; i < 52; i++) {
        Cards *card = [[Cards alloc]init];
        card.face = [NSString stringWithFormat:@"%@", faces[i % 13]];
        card.suit = [NSString stringWithFormat:@"%@", suits[i / 13]];
        card.rankByInt = i % 13;
        card.symbol = [Cards symbolForSuit:card.suit];
        card.prime = [[primes objectAtIndex:(i % 13)] intValue];

        [sender addObject:card];
    }
    [sender shuffle];

    return sender;
}

创建_deck,然后_hand被填充

[_hand addObject:[_deck objectAtIndex:0]];
[_hand addObject:[_deck objectAtIndex:1]];
[_hand addObject:[_deck objectAtIndex:3]];
[_hand addObject:[_deck objectAtIndex:4]];
[_hand addObject:[_deck objectAtIndex:5]];
[_hand addObject:[_deck objectAtIndex:7]];
[_hand addObject:[_deck objectAtIndex:9]];

returnHandRank: withString:Hand 类中的一个很长的函数。所以这就是为什么我假设他们没有被保留。

谁能详细说明?我认为从_deck 再次添加卡片毫无意义,这会是最好的解决方案吗?

编辑:添加returnHandRank: withString:

+(NSString *)returnHandRank:(id)cards withString:(BOOL)returnString {
    NSArray *combinations = [self returnCombinations];

    cards = [self organizeCardsRankOrder:cards];

    __block int maxRank = 0;
    __block int maxValue = 0;
    for (int i = 0; i < [combinations count]; i++) {
        NSArray *splitString = [combinations[i] componentsSeparatedByString:@" "]; // splits the combination string.

        NSArray *pointerArray = [[NSArray alloc] initWithObjects:
                                 [NSNumber numberWithInt:[splitString[0] intValue]],
                                 [NSNumber numberWithInt:[splitString[1] intValue]],
                                 [NSNumber numberWithInt:[splitString[2] intValue]],
                                 [NSNumber numberWithInt:[splitString[3] intValue]],
                                 [NSNumber numberWithInt:[splitString[4] intValue]],
                                 nil]; // turns the combinations into int values in an array.

        NSMutableArray *fiveCardHand = [[NSMutableArray alloc] initWithObjects:
                                 [cards objectAtIndex:[[pointerArray objectAtIndex:0] intValue]],
                                 [cards objectAtIndex:[[pointerArray objectAtIndex:1] intValue]],
                                 [cards objectAtIndex:[[pointerArray objectAtIndex:2] intValue]],
                                 [cards objectAtIndex:[[pointerArray objectAtIndex:3] intValue]],
                                 [cards objectAtIndex:[[pointerArray objectAtIndex:4] intValue]],
                                 nil]; // Create the 5 card hand for the combination loop we are in, we'll now check this to see what rank it returns.


        //Check for hand rank.

        fiveCardHand = [self organizeCardsRankOrder:fiveCardHand];

        NSArray *fiveCardHandOrganized = fiveCardHand;

        int strength = [self handRankWithFiveCards:fiveCardHandOrganized];

        if (strength > maxRank) {
            maxRank = strength;
            maxValue = 0;
        }

        int value = [self associateStrengthToHand:fiveCardHandOrganized andHand:strength];

        if (value > maxValue) {
            maxValue = value;
        }
    }

    if (returnString) {
        return [self handForStrengthWithStrength:maxRank];
    } else {
        return [NSString stringWithFormat:@"%i", maxValue];
    }
}

【问题讨论】:

  • "所以为了让它失去所有的价值,我假设这些物体正在被 ARC 扫荡"好吧,你错了。如果发生这种情况,那将毫无意义。显然,如果一个对象已添加到数组中,则该数组会保留该对象。 — 听起来您的 returnHandRank:withString 可能不会像您认为的那样做。但是出于某种原因,你选择不表现出来,即使你自己说这是问题的核心。这很难提供帮助。
  • @matt 好吧,我的印象是它是引用语义。但是,如果你说我错了,我想我错了。我已将函数添加到上述问题中。
  • _hand 实例变量在哪里声明以及分配到哪里?它可能在某处被空数组覆盖...

标签: objective-c function class nsobject retain


【解决方案1】:

最近出现了一些涉及组合的问题,因此除非您正在创建帐户,否则我们怀疑有功课在进行中...没问题,让我们看看我们能否为您指明正确的方向。但是我们无法回答这个问题,不是因为这可能是家庭作业,而是因为没有足够的信息来回答。

现在我知道 MutableArray 只是指向对象而不是持有它们,

到目前为止是正确的......

所以为了让它失去所有它的价值,我假设这些物体正在被 ARC 扫除。

但现在完全错了 :-( 您误解了 Objective-C 中的自动内存管理是如何工作的。首先忘记“保留”,现代基于 ARC 的管理是关于所有权 - 存储引用的变量声明对引用引用的对象的所有权。当它声明所有权时,变量具有属性strong,当它存储引用但不声明所有权时不声明所有权,则它具有属性 weak(还有一些其他所有权属性您稍后会遇到,暂时可以忽略它们)。默认情况下,对象引用变量具有属性 strong .

我们来打个比方:

  • 考虑一个气球(“物体”),它会飘走,除非它被按住;还有一只手(“变量”),它拿着东西。
  • 许多不同的手可以握住连接到同一个气球的绳子(参考)。
  • 如果手紧紧握住绳子(强力),气球就不会飘走。
  • 如果绳子只是放在手掌上(弱),除非至少另一只手紧紧握住另一根绳子,否则气球会飘走。
  • 只要至少一只手紧紧握住绳子,气球就不会飘走。
  • ARC 是微风,它吹走没有紧紧握住的气球。

一个未注解的变量默认为strong,所以当一个引用被存储在其中时,该变量断言被引用对象的所有权并且它不会被 ARC 清除。类的实例变量或标准(强)属性都断言所有权。所有标准集合(数组、字典、集合)都对集合中存储的引用所引用的对象声明所有权。

因此,如果您将引用存储在 NSMutableArray 中,只要引用保留在数组中,ARC 就不会清除引用的对象。如果你改变数组并删除一个引用,那么它所引用的对象将被 ARC 回收(返回到可用内存池)当且仅当没有其他对它的引用存储在强变量中.

只要对它的引用存储在强变量中,数组本身就会一直存在。当没有对数组的强引用时,数组本身将被 ARC 回收,在此过程中,存储在数组中的所有引用都将被删除,如果这些引用是被引用对象的最后一个强引用,它们也将被回收。

希望对您有所帮助并了解其工作原理将帮助您找出在哪里清空数组或丢失对数组本身的所有强引用;例如通过为引用数组的变量分配一个新的引用(或nil)。

现在让我们看看你的一些代码:

NSArray *suits = [[NSArray alloc] initWithObjects:@"h",@"d",@"c",@"s", nil];

这是旧式语法,您可以更轻松地使用 数组字面量创建NSArray@[ ... ]

NSArray *suits = @[@"h", @"d", @"c", @"s"];

没有NSMutableArray 字面量,因此您使用NSArray 来制作可变副本:[@[ ... ] mutableCopy] 或更短的@[ ... ].mutableCopy(对后者的使用意见不同)。 NSNumber 对象还有一个文字,您的代码:

[NSNumber numberWithInt:41]

可以简单地替换为@41

使用上述文字将使您的代码更短且更易于阅读。

现在你的声明:

card.face = [NSString stringWithFormat:@"%@", faces[i % 13]];

暗示了对引用和不可变对象如何工作的误解。 NSString 对象是不可变的,一旦创建,它的值就永远不会改变。 stringWithFormat: 方法根据其格式和参数构造一个 NSString,在本例中是单个字符串,因此您只需复制相当于以下内容的字符串:

card.face = [faces[i % 13] copy];

但是,不可变对象的副本只是原始对象。您知道faces 仅包含不可变字符串,因为您使用字符串文字创建它,所以上面的内容等价于:

card.face = faces[i % 13];

重要提示:您可以通过子类化将可变的NSMutableString 引用作为NSString 引用,因此此处删除copy 的最后一步仅有效 如果您知道引用是对 NSString 对象而不是 NSMutableString 对象。

facessuits 上使用直接索引后,您切换到长格式:

card.prime = [[primes objectAtIndex:(i % 13)] intValue];

以及其他一些地方。都可以换成[...],例如:

card.prime = [[primes[i % 13] intValue];

虽然您使用除法 (i / 13) 和余数 (i % 13) 都是正确的,但您可能需要考虑使用两个嵌套循环来避免它们,例如类似:

for(int suitRank = 0; suitRank < 4; suitRank++)
{  for(int cardRank = 0; cardRank < 13; cardRank++)
   {  // now use suitRank for i / 13 and cardRank for i % 13

以上内容只是为了让您的代码更短、更易读、更不容易出错而进行整理。现在是一个更严重的问题:

+(NSMutableArray*)createDeck:(id)sender {
   [sender removeAllObjects];

永远不要这样做!虽然id 使用它会降低编译器检查您的代码是否正确的能力,并且可能导致您的代码在运行时出错,因为编译器会捕捉到简单的错误。这里sender 显然是对可变数组的引用,声明如下:

+ (NSMutableArray *)createDeck:(NSMutableArray *)sender
{
   [sender removeAllObjects];

稍后(在应用上述文字使用之后)你有:

NSMutableArray *fiveCardHand = @[ cards[[pointerArray[0] intValue]],
                                  ...
                                ].mutableCopy;
//Check for hand rank.

fiveCardHand = [self organizeCardsRankOrder:fiveCardHand];

给你:

  1. 创建一个可变数组
  2. 将引用分配给fiveCardHand
  3. organizeCardsRankOrder:的结果覆盖fiveCardHand中的引用

所以这里你似乎没有改变了fiveCardHand引用的数组,而是将变量更改为引用一个不同的数组。你不需要使用可变数组来做到这一点,你正在改变持有引用的变量而不是被引用的数组。现在在这里使用了“出现”,因为您没有提供 organizeCardsRankOrder: 的代码,也许该方法确实改变了传递给它的数组,如果是这种情况,它不需要也返回它并且不需要赋值给变量。因此,请在此处仔细查看您的代码,并确定您是在改变数组还是只是变量,并相应地进行更改。

最后,您没有在问题中为_deck_hand 提供任何声明。通过命名约定,您可能会直接访问属性的支持变量(通常最好避免这样做),或者访问实例变量,这两者都是一些未指定的类。因此,我们无法为这些提供任何真正的帮助,只需检查它们是否连接到您在任何您期望的地方都使用 same 实例的实例 - 一个常见的早期错误是在一个实例,尝试从另一个实例读取它,然后想知道为什么值不同...

HTH,调试愉快!

【讨论】:

  • 感谢您非常深入的回复。我很感激。不幸的是,这不是家庭作业,这只是我想尝试的事情。我已经有一两年没有正确编程了,所以只是想重新开始。我也没有学过任何类型的编程,我在 2007 年左右自学了 PHP,然后转向了 Objective-C。因此,我的模式通常来自记忆而不是逻辑。但是,感谢您的回复,虽然它不一定回答预期的问题,但我会接受它,因为显然没有从中得出答案。