You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从XML plist字典提取有序Key数组?规避NSDictionary无序且不修改服务器

解决方法:从Plist XML中提取有序Key数组

你遇到的核心问题是NSDictionary本身是无序集合——系统的PropertyListSerialization解析Plist时,会自动把dict转成NSDictionary,自然就丢失了原XML里key的顺序。要在不改服务器的前提下解决,直接绕开plist转dict的流程,用XML解析器直接读取<key>标签的内容并按顺序保存即可。

下面是具体实现方案,分Swift和Objective-C两种常用场景:

Swift 实现

自定义一个XML解析器代理类,专门收集根级dict里的key:

class OrderedPlistKeyParser: NSObject, XMLParserDelegate {
    private(set) var orderedKeys: [String] = []
    private var currentKey: String?
    private var inRootDict = false // 跟踪是否处于根级dict中

    func parse(xmlData: Data) -> [String] {
        let parser = XMLParser(data: xmlData)
        parser.delegate = self
        parser.parse()
        return orderedKeys
    }

    // 开始解析标签时触发
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        if elementName == "dict" {
            // 标记进入根级dict(第一个dict标签)
            if !inRootDict {
                inRootDict = true
            }
        } else if elementName == "key" && inRootDict {
            // 开始读取key内容
            currentKey = ""
        }
    }

    // 读取标签内的字符内容
    func parser(_ parser: XMLParser, foundCharacters string: String) {
        currentKey?.append(string)
    }

    // 结束解析标签时触发
    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        if elementName == "dict" && inRootDict {
            // 离开根级dict
            inRootDict = false
        } else if elementName == "key" && inRootDict, 
                  let key = currentKey?.trimmingCharacters(in: .whitespacesAndNewlines), 
                  !key.isEmpty {
            // 收集处理后的key
            orderedKeys.append(key)
            currentKey = nil
        }
    }
}

// 使用方式
if let xmlString = """
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>key1</key>
<array>
<string></string>
<string>title1</string>
<string>disabled</string>
</array>
<key>key2</key>
<array>
<string></string>
<string>title2</string>
<string>disabled</string>
</array>
<key>myKey3</key>
<array>
<string></string>
<string>title3</string>
</array>
<key>key4</key>
<array>
<string></string>
<string>title4</string>
<string>disabled</string>
</array>
<key>key5</key>
<array>
<string></string>
<string>title5</string>
<string>disabled</string>
</array>
</dict>
</plist>
""", let xmlData = xmlString.data(using: .utf8) {
    let parser = OrderedPlistKeyParser()
    let orderedKeys = parser.parse(xmlData: xmlData)
    print(orderedKeys) // 输出 ["key1","key2","myKey3","key4","key5"]
}

Objective-C 实现

同样的思路,用NSXMLParser来实现:

@interface OrderedPlistKeyParser : NSObject <NSXMLParserDelegate>
@property (nonatomic, strong, readonly) NSMutableArray<NSString *> *orderedKeys;
@property (nonatomic, strong) NSMutableString *currentKey;
@property (nonatomic, assign) BOOL inRootDict;
@end

@implementation OrderedPlistKeyParser

- (instancetype)init {
    self = [super init];
    if (self) {
        _orderedKeys = [NSMutableArray array];
        _inRootDict = NO;
    }
    return self;
}

- (NSArray<NSString *> *)parseXMLData:(NSData *)xmlData {
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
    parser.delegate = self;
    [parser parse];
    return [self.orderedKeys copy];
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict {
    if ([elementName isEqualToString:@"dict"]) {
        if (!self.inRootDict) {
            self.inRootDict = YES;
        }
    } else if ([elementName isEqualToString:@"key"] && self.inRootDict) {
        self.currentKey = [NSMutableString string];
    }
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    [self.currentKey appendString:string];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if ([elementName isEqualToString:@"dict"] && self.inRootDict) {
        self.inRootDict = NO;
    } else if ([elementName isEqualToString:@"key"] && self.inRootDict) {
        NSString *trimmedKey = [self.currentKey stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        if (trimmedKey.length > 0) {
            [self.orderedKeys addObject:trimmedKey];
        }
        self.currentKey = nil;
    }
}

@end

// 使用方式
NSString *xmlString = @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n<key>key1</key>\n<array>\n<string></string>\n<string>title1</string>\n<string>disabled</string>\n</array>\n<key>key2</key>\n<array>\n<string></string>\n<string>title2</string>\n<string>disabled</string>\n</array>\n<key>myKey3</key>\n<array>\n<string></string>\n<string>title3</string>\n</array>\n<key>key4</key>\n<array>\n<string></string>\n<string>title4</string>\n<string>disabled</string>\n</array>\n<key>key5</key>\n<array>\n<string></string>\n<string>title5</string>\n<string>disabled</string>\n</array>\n</dict>\n</plist>";
NSData *xmlData = [xmlString dataUsingEncoding:NSUTF8StringEncoding];
OrderedPlistKeyParser *parser = [[OrderedPlistKeyParser alloc] init];
NSArray *orderedKeys = [parser parseXMLData:xmlData];
NSLog(@"%@", orderedKeys); // 输出 ["key1","key2","myKey3","key4","key5"]

补充说明

  • 这个方案直接解析XML结构,完全保留原文件中<key>标签的出现顺序,不需要修改服务器端的Plist内容。
  • 代码里加入了inRootDict判断,只会收集根级dict的key,避免嵌套dict(比如某个array里的子dict)的key干扰结果。如果你的Plist没有嵌套dict,也可以去掉这个判断,简化代码。

内容的提问来源于stack exchange,提问作者JP Aquino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:18:52