使用 NSMutableArray 实现二维数组

45

我需要在Objective-C中创建一个可变的二维数组。

比如说,我有:

NSMutableArray *sections;
NSMutableArray *rows;

sections 中的每个条目都由一个数组 rows 组成。 rows 是一个包含对象的数组。

我想要做这样的事情:

[ sections[i] addObject: objectToAdd]; //I want to add a new row

有没有一种方法在 Objective-C 中实现这样的效果: 第0部分,行:obj1、obj2、obj3 第1部分,行:obj4、obj5、obj6、obj7...

8个回答

88

首先,在使用之前,您必须分配和初始化对象,类似这样:NSMutableArray * sections = [[NSMutableArray alloc] initWithCapacity:10]; 对于行,您需要每个行一个对象,而不是单个的 NSMutableArray * rows;

其次,根据您使用的Xcode版本(4.4+引入了下标,即section[i] & section[i] = …),您可能需要使用 [sections objectAtIndex:i] 进行读取,使用 [section replaceObjectAtIndex:i withObject: objectToAdd] 进行写入。

第三,数组不能有空洞,即 obj1, nil, obj2。您必须为每个索引提供实际对象。如果您确实需要放置空值,可以使用NSNull对象。

此外,请记住,您还可以在普通C数组中存储Objective-C对象:

id table[lnum][rnum];
table[i][j] = myObj;

5
建议使用普通的C数组,特别是在数组维度预先已知的情况下。 - timbo
希望我能再次回答++关于NSNull对象填充空洞的问题。我刚意识到它的重要性。 - Old McStopher
现在您可以使用下标索引。 - uchuugaka
在ARC下,您不能再将对象存储在C数组中。 - Tricertops
@iMartin:那不正确。我刚刚做了。没有任何警告、崩溃等。但是,如果要在结构体中存储Objective-C对象,则需要使用__unsafe_unretained。否则,clang会抛出错误。 - Regexident
显示剩余2条评论

17

如果你想使用数组完成这个任务,你可以初始化 sections 数组,然后添加一个如下的行数组:

NSMutableArray *sections = [[NSMutableArray alloc] init];
NSMutableArray *rows = [[NSMutableArray alloc] init];
//Add row objects here

//Add your rows array to the sections array
[sections addObject:rows];
如果你想在特定的索引位置添加这个 "rows" 对象,请使用以下代码:
//Insert your rows array at index i
[sections insertObject:rows atIndex:i];
你可以通过检索数组来修改这个行数组:
//Retrieve pointer to rows array at index i
NSMutableArray *rows = [sections objectAtIndex:i]
//modify rows array here

你可以创建一个名为 Section 的类,其中包含一个名为 rowsNSMutableArray 成员;然后你可以将你的行存储在这个数组中,并将 Section 对象存储在一个数组中:

@interface Section : NSObject {
    NSMutableArray *rows;
}

接下来你只需创建 Section 项目,然后在类内部创建方法以添加/删除行项目。最后,将所有的 Sections 项目打包到另一个数组中:

Section *aSection = [[Section alloc] init];
//add any rows to your Section instance here

NSMutableArray *sections = [[NSMutableArray alloc] init];
[sections addObject:aSection];

如果您想为每个Section实例添加更多属性,则此方法将变得更加有用。


9

这种语言不支持多维对象数组,但您可以使用一个充满NSMutableArrays的NSMutableArray来创建一个实现此功能的类,如下所示。 我没有尝试编译它或任何东西,只是打字。

@interface SectionArray : NSObject {
  NSMutableArray *sections;
}
- initWithSections:(NSUInteger)s rows:(NSUInteger)r;
+ sectionArrayWithSections:(NSUInteger)s rows:(NSUInteger)r;
- objectInSection:(NSUInteger)s row:(NSUInteger)r;
- (void)setObject:o inSection:(NSUInteger)s row:(NSUInteger)r;
@end
@implementation SectionArray
- initWithSections:(NSUInteger)s rows:(NSUInteger)r {
  if ((self = [self init])) {
    sections = [[NSMutableArray alloc] initWithCapacity:s];
    NSUInteger i;
    for (i=0; i<s; i++) {
      NSMutableArray *a = [NSMutableArray arrayWithCapacity:r];
      for (j=0; j<r; j++) {
        [a setObject:[NSNull null] atIndex:j];
      }
      [sections addObject:a];
    }
  }
  return self;
}
+ sectionArrayWithSections:(NSUInteger)s rows:(NSUInteger)r {
  return [[[self alloc] initWithSections:s rows:r] autorelease];
}
- objectInSection:(NSUInteger)s row:(NSUInteger)r {
  return [[sections objectAtIndex:s] objectAtIndex:r];
}
- (void)setObject:o inSection:(NSUInteger)s row:(NSUInteger)r {
  [[sections objectAtIndex:s] replaceObjectAtIndex:r withObject:0];
}
@end

您可以这样使用它:
SectionArray *a = [SectionArray arrayWithSections:10 rows:5];
[a setObject:@"something" inSection:4 row:3];
id sameOldSomething = [s objectInSection:4 row:3];

6

如果我们要重提这个问题,那么与文字字面意思的收集句法和视觉格式解释相关的内容会对此有所帮助。

如果有人想知道,这是可以实现的:

NSMutableArray *multi = [@[ [@[] mutableCopy] , [@[] mutableCopy] ] mutableCopy];
multi[1][0] = @"Hi ";
multi[1][1] = @"There ";
multi[0][0] = @"Oh ";
multi[0][1] = @"James!";        
NSLog(@"%@%@%@%@", multi[0][0], multi[1][0], multi[1][1], multi[0][1]);

结果:"嗨,詹姆斯!"

当然,问题在于尝试类似 multi[3][5] = @"?" 的操作会导致无效的索引异常,因此我为NSMutableArray编写了一个分类。

@interface NSMutableArray (NullInit)
+(NSMutableArray *)mutableNullArrayWithSize:(NSUInteger)size;
+(NSMutableArray *)mutableNullArraysWithVisualFormat:(NSString *)string;
@end

@implementation NSMutableArray (NullInit)

+(NSMutableArray *)mutableNullArrayWithSize:(NSUInteger)size
{
    NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:size];
    for (int i = 0; i < size; i++) {
        [returnArray addObject:[NSNull null]];
    }
    return returnArray;
}

+(NSMutableArray *)mutableNullArraysWithVisualFormat:(NSString *)string
{
    NSMutableArray *returnArray = nil;
    NSRange bitRange;
    if ((bitRange = [string rangeOfString:@"^\\[\\d+]" options:NSRegularExpressionSearch]).location != NSNotFound) {
        NSUInteger size = [[string substringWithRange:NSMakeRange(1, bitRange.length - 2)] integerValue];
        if (string.length == bitRange.length) {
            returnArray = [self mutableNullArrayWithSize:size];
        } else {
            returnArray = [[NSMutableArray alloc] initWithCapacity:size];
            NSString *nextLevel = [string substringWithRange:NSMakeRange(bitRange.length, string.length - bitRange.length)];
            NSMutableArray *subArray;
            for (int i = 0; i < size; i++) {
                subArray = [self mutableNullArraysWithVisualFormat:nextLevel];
                if (subArray) {
                    [returnArray addObject:subArray];
                } else {
                    return nil;
                }
            }
        }
    } else {
        return nil;
    }
    return returnArray;
}

@end

正如您所见,我们有一个方便的方法来创建一个完全由 NSNull 填充的数组,以便您可以毫无顾虑地设置索引。

其次,还有一种递归方法,它可以解析具有可视化格式的字符串,例如: [3][12](3 x 12 数组)。 如果您的字符串在某些方面无效,则该方法将返回 nil,但如果有效,则会获得指定大小的整个多维数组。

以下是一些示例:

NSMutableArray *shrub = [NSMutableArray mutableNullArrayWithSize:5];
NSMutableArray *tree = [NSMutableArray mutableNullArraysWithVisualFormat:@"[3][12]"]; // 2-Dimensional Array
NSMutableArray *threeDeeTree = [NSMutableArray mutableNullArraysWithVisualFormat:@"[3][5][6]"]; // 3-Dimensional Array
NSMutableArray *stuntedTree = [NSMutableArray mutableNullArraysWithVisualFormat:@"[6][4][k]"]; // Returns nil

您可以将任意数量的维度传递到可视化格式方法中,然后使用文字语法访问它们,例如:
NSMutableArray *deepTree = [NSMutableArray mutableNullArraysWithVisualFormat:@"[5][3][4][2][7]"];
deepTree[3][2][1][0][5] = @"Leaf";
NSLog(@"Look what's at 3.2.1.0.5: %@", deepTree[3][2][1][0][5]);

无论如何,这只是一个练习而已;考虑到我们正在创建Objective-C对象指针的多维数组,这可能在总体上相当有效。


1
对于带有空洞的集合,我使用 NSMutableDictionary,其中键是索引集,例如:[dict setObject:o forKey: @[ @2, @5]] 这样就不会到处出现 NSNull - Tricertops
@iMartin 嗯,当您使用数组作为键调用 setObject 时,它会做什么呢?它是否将对象设置为数组中每个键的值?此外,我认为您想要执行 @(2) 来使其成为数字,没有括号似乎是无效的。 - Matt Mc
它在没有 () 的情况下是有效的。如果您使用数字数组作为键,则对象与给定的索引组合相关联。仅此而已。它只是普通的键。 - Tricertops
@iMartin 没错,不带()也是有效的。我之前的理解有误。我不知道你可以使用数字数组作为键,这很棒!你有参考资料、文章或示例代码吗?我想了解更多。 - Matt Mc
您可以使用任何支持复制的对象作为键,并且当对象相等时,它们返回相同的“-hash”,因此这种方法可以正确地工作。数组不需要是相同的实例,但必须具有相同的逻辑内容。 - Tricertops

5

感谢Jack提供的代码,我对它进行了一些修改;在我的一个项目中,我需要一个多维的nsmutablearray来存储字符串,目前它仍有一些需要修复的问题,但目前只能处理字符串,我将在这里发布它,欢迎提出建议,因为我目前只是一个objective c的初学者...

@interface SectionArray : NSObject {

  NSMutableArray *sections;    

}
- initWithSections:(NSUInteger)intSections:(NSUInteger)intRow;
+ sectionArrayWithSections:(NSUInteger)intSections:(NSUInteger)intRows;
- objectInSection:(NSUInteger)intSection:(NSUInteger)intRow;
- (void)setObject:(NSString *)object:(NSUInteger)intSection:(NSUInteger)intRow;
@end

@implementation SectionArray

- initWithSections:(NSUInteger)intSections:(NSUInteger)intRow {
    NSUInteger i;
    NSUInteger j;

    if ((self = [self init])) {
        sections = [[NSMutableArray alloc] initWithCapacity:intSections];
        for (i=0; i < intSections; i++) {
            NSMutableArray *a = [NSMutableArray arrayWithCapacity:intRow];
            for (j=0; j < intRow; j++) {
                [a insertObject:[NSNull null] atIndex:j];
            }
            [sections addObject:a];
        }
    }
    return self;    
}
- (void)setObject:(NSString *)object:(NSUInteger)intSection:(NSUInteger)intRow {
    [[sections objectAtIndex:intSection] replaceObjectAtIndex:intRow withObject:object];
}
- objectInSection:(NSUInteger)intSection:(NSUInteger)intRow {
    return [[sections objectAtIndex:intSection] objectAtIndex:intRow];
}
+ sectionArrayWithSections:(NSUInteger)intSections:(NSUInteger)intRows {
    return [[self alloc] initWithSections:intSections:intRows] ;
}
@end

这个运行良好!!!

我目前是这样使用它的

SectionArray *secA = [[SectionArray alloc] initWithSections:2:2];
[secA setObject:@"Object":0:0];
[secA setObject:@"ObjectTwo":0:1];
[secA setObject:@"ObjectThree":1:0];
[secA setObject:@"ObjectFour":1:1];

NSString *str = [secA objectInSection:1:1];

NSLog(@" object is = %@" , str);

再次感谢Jack!!

0

虽然这是一个旧帖子,但我重新编写了Jack的代码,使其1.可以编译,2.按照2D c数组[行][列]的顺序排列,而不是他所拥有的[部分(列)][行]。给你!

TwoDArray.h:

#import <Foundation/Foundation.h>

@interface TwoDArray : NSObject

@property NSMutableArray *rows;

- initWithRows:(NSUInteger)rows columns:(NSUInteger)columns;
+ sectionArrayWithRows:(NSUInteger)rows columns:(NSUInteger)columns;
- objectInRow:(NSUInteger)row column:(NSUInteger)column;
- (void)setObject:(id)obj inRow:(NSUInteger)row column:(NSUInteger)column;

@end

TwoDArray.m:

#import "TwoDArray.h"

@implementation TwoDArray

- (id)initWithRows:(NSUInteger)rows columns:(NSUInteger)columns {
    if ((self = [self init])) {
        self.rows = [[NSMutableArray alloc] initWithCapacity: rows];
        for (int i = 0; i < rows; i++) {
            NSMutableArray *column = [NSMutableArray arrayWithCapacity:columns];
            for (int j = 0; j < columns; j++) {
                [column setObject:[NSNull null] atIndexedSubscript:j];
            }
            [self.rows addObject:column];
        }
    }
    return self;
}
+ (id)sectionArrayWithRows:(NSUInteger)rows columns:(NSUInteger)columns {
    return [[self alloc] initWithRows:rows columns:columns];
}
- (id)objectInRow:(NSUInteger)row column:(NSUInteger)column {
    return [[self.rows objectAtIndex:row] objectAtIndex:column];
}
- (void)setObject:(id)obj inRow:(NSUInteger)row column:(NSUInteger)column {
    [[self.rows objectAtIndex:row] replaceObjectAtIndex:column withObject:obj];
}
@end

1
如果你要重新启动这个旧的线程,至少使用现代的东西,比如文字数组语法,在这里有一些好的链接:https://dev59.com/bmvXa4cB1Zd3GeqPFxt2#13482653。我认为你甚至可以做像`myArray[0][3]`这样的事情,虽然我可能错了。 - Matt Mc
@MattMc:感谢提供链接!我在这方面经验不足,只是想分享一下对我有用的东西。不过我肯定会去看看的。 - Matt Cooper
当然可以:) 我采纳了自己的建议,编写了一种包括文字语法等的方法,以便您可以了解。请看下方。 - Matt Mc

0

知悉:Jack的代码需要大量修改才能正常运行。除了其他问题之外,autorelease可能会导致在访问数据之前释放它,并且他的用法调用了类方法arrayWithSections:rows:,而实际上它被定义为sectionArrayWithSections:rows:

如果有机会,我可能会尝试发布真正可用的代码。


-1

这是我为了初始化数组的数组所做的事情。

NSMutableArray *arrayOfArrays = [[NSMutableArray alloc] initWithCapacity:CONST];

for (int i=0; i<=CONST; i++) {
    [arrayOfArrays addObject:[NSMutableArray array]];
}

稍后在代码中,我可以简单地这样写:
[[arrayOfArrays objectAtIndex:0] addObject:myObject];

1st: 你没有为这个帖子添加任何新的有价值的内容。 2nd: 如果我们使用NSArray来谈论二维数组,必须说明如何处理空条目。 - vikingosegundo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接