为iPhone上的Core Data程序创建唯一标识符

65

我在理解Core Data方面遇到了一些困难。如何创建一个具有唯一ID的新条目? 在SQL中,我只需将一个字段声明为自动递增字段即可。但我在这里没有看到类似的东西,但我可能只是漏看了什么。我只想要一个自动递增的NSInteger字段,因此当我手动向数据库添加项目时,我将拥有某种参考。

8个回答

156

虽然我同意你的观点,但在Core Data背后存在一种ID机制。ID确实由Core Data管理,但可以使用以下方式检索它们:

NSManagedObjectID *moID = [managedObject objectID];

更多信息请参见: Core Data编程指南


27
很遗憾,提问者从未接受过这样一个精彩的答案。 - RonLugge
这正是我2年后所需要的东西。另外,使用NSManagedObjectID的URIRepresentation,您可以将它们轻松存储在NSUserDefaults中,这对于我在缓存方案中是必要的。^1 - Ryan Poolos
10
请注意,objectID 可以并且经常在迁移时发生更改。不要依赖该值在应用程序的不同启动之间保持一致。- Marcus S Zarra 在另一个帖子中的评论。 - Mateus
2
不要使用objectID。首先,它可能是临时的,并且在NSManagedObject保存时会更改。其次,在应用程序启动之间可能不一致。 - vomi
1
对象 ID 在数据库迁移或更改表名后也可能会发生变化。 - Justin
在Swift中等价的是什么? - Jayprakash Dubey

30

这不是CoreData的工作方式。

在CoreData中,你创建实体的实例。每个实例都是独特的。然后根据需要检索和操作实例。CoreData会为你处理持久性,包括唯一标识实例。

远离你所知道的传统关系型数据库的一切。

CoreData是一个非常强大的技术,提供了远超过仅仅数据库持久化的功能。如果你采用它,它可以节省你很多代码并且表现出色。


6
我不同意“远离你所知道的一切”的说法。有些情况下,你可能会遇到没有区分属性的唯一实体(例如删除不遵守规则的服务器)。在这种情况下,使用自动递增会很有用。 - ray
1
你读了OP问题中的最后一句话吗(“我将拥有对它们的某种引用”)?你不需要自动递增字段来实现这个。每个实体都有唯一标识符,已经内置在Core Data中。当然,你可能想要一个自动递增字段的原因,但为了唯一标识实体而这样做只是重复造轮子。当然,你可能会为了从客户端到服务器的关联而这样做,但只有在服务器无法使用NSManagedObjectID已支持的URI时才这样做。 - bbum
@DanBeaulieu 叹气。我真希望苹果不要总是破坏链接。我会尽力找时间来修复它们。 - bbum

14

这个类函数将为您的id属性(必须是整数属性)返回下一个可用数字

我称它为自动递增值生成器。我仍然同意其他人的看法,即可以使用objectID,但有时您只需要数字。

您可以将此函数放在实体的NSManagedObject子类中:

+(NSNumber *)nextAvailble:(NSString *)idKey forEntityName:(NSString *)entityName inContext:(NSManagedObjectContext *)context{


    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSManagedObjectContext *moc = context;
    NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:moc];

    [request setEntity:entity];
    // [request setFetchLimit:1];

    NSArray *propertiesArray = [[NSArray alloc] initWithObjects:idKey, nil];
    [request setPropertiesToFetch:propertiesArray];
    [propertiesArray release], propertiesArray = nil;

    NSSortDescriptor *indexSort = [[NSSortDescriptor alloc] initWithKey:idKey ascending:YES];
    NSArray *array = [[NSArray alloc] initWithObjects:indexSort, nil];
    [request setSortDescriptors:array];
    [array release], array = nil;
    [indexSort release], indexSort = nil;

    NSError *error = nil;
    NSArray *results = [moc executeFetchRequest:request error:&error];
    // NSSLog(@"Autoincrement fetch results: %@", results);
    NSManagedObject *maxIndexedObject = [results lastObject];
    [request release], request = nil;
    if (error) {
        NSLog(@"Error fetching index: %@\n%@", [error localizedDescription], [error userInfo]);
    }
    //NSAssert3(error == nil, @"Error fetching index: %@\n%@", [error localizedDescription], [error userInfo]);

    NSInteger myIndex = 1;
    if (maxIndexedObject) {
        myIndex = [[maxIndexedObject valueForKey:idKey] integerValue] + 1;
    }

    return [NSNumber numberWithInteger:myIndex];
}

Swift 5.0

func nextAvailble(_ idKey: String, forEntityName entityName: String, in context: NSManagedObjectContext) -> NSNumber? {

    let req = NSFetchRequest<NSFetchRequestResult>.init(entityName: entityName)
    let entity = NSEntityDescription.entity(forEntityName: entityName, in: context)
    req.entity = entity
    req.fetchLimit = 1
    req.propertiesToFetch = [idKey]
    let indexSort = NSSortDescriptor.init(key: idKey, ascending: false)
    req.sortDescriptors = [indexSort]

    do {
        let fetchedData = try context.fetch(req)
        let firstObject = fetchedData.first as! NSManagedObject
        if let foundValue = firstObject.value(forKey: idKey) as? NSNumber {
            return NSNumber.init(value: foundValue.intValue + 1)
        }

        } catch {
            print(error)

        }
    return nil

}

绝妙的解决方案,伙计。干杯!! - Manann Sseth
4
为了提高效率,按降序对索引进行排序,然后将获取限制设置为1。 例如:NSSortDescriptor *indexSort = [[NSSortDescriptor alloc] initWithKey:idKey ascending:NO]; 然后取消注释 request.fetchLimit = 1; - Jason Moore
几乎完美!有了@JasonMoore的优化,更加出色! - John

8

以下是CoreData对象的三种唯一ID表示方式:

NSManagedObjectID *taskID = [task objectID];
NSURL *taskUniqueURLKey = task.objectID.URIRepresentation;
NSString *taskUniqueStringKey = task.objectID.URIRepresentation.absoluteString;

注意:如果您想将上述唯一键用作索引,请确保在使用它之前将对象保存到上下文中,否则对象ID将是临时ID,稍后将被替换(例如,在保存后)。有关NSManagedObjectID类的详细信息,请参阅苹果文档:

- (BOOL)isTemporaryID;    // indicates whether or not this ID will be replaced later, 
such as after a save operation (temporary IDs are assigned to newly inserted objects 
and replaced with permanent IDs when an object is written to a persistent store); most
 IDs return NO

6

请看 NSProcessInfo / ProcessInfo,我们可以从中获取唯一标识。

Objective-C 代码片段:

[[NSProcessInfo processInfo] globallyUniqueString]

Swift 代码片段:

let uniqueId = ProcessInfo().globallyUniqueString
print("uniqueId: \(uniqueId)")

截图

苹果文档:

进程的全局ID。该ID包括主机名、进程ID和时间戳,这确保了该ID在网络中是唯一的,如果您不想让人们猜测ID,这非常方便。


4

核心数据用于持久性框架,该框架抽象了主键。

每个NSManagedObject都有自己的内置唯一键,在其objectID属性中可用。

此id在内部用于链接实体的关系。无需像在SQL中所做的那样维护自己的id作为主键。

您始终可以通过NSManagedObject.objectID获取id,并使用NSMananagedObjectContext.objectWithId获取对象上下文


2

更新@Lukasz在Swift中的答案:

static func nextAvailble(_ idKey: String, forEntityName entityName: String, inContext context: NSManagedObjectContext) -> Int {
    let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: entityName)
    fetchRequest.propertiesToFetch = [idKey]
    fetchRequest.sortDescriptors = [NSSortDescriptor(key: idKey, ascending: true)]

    do {
        let results = try context.fetch(fetchRequest)
        let lastObject = (results as! [NSManagedObject]).last

        guard lastObject != nil else {
            return 1
        }

        return lastObject?.value(forKey: idKey) as! Int + 1

    } catch let error as NSError {
        //handle error
    }

    return 1 
}

2
如果我们想与需要自增ID来保证数据唯一性的远程数据库同步,该怎么办?如果需要,有没有实现自增ID的方法。我知道Core Data并不需要自增ID,但我需要从远程数据库导入数据,然后再次上传并确保ID不变。

2
在这种情况下,我会使用两个ID,并将服务器自增ID存储为核心数据模型的属性。 - Tony
使用服务器端用于唯一标识记录的内容;在其上添加自增数字肯定可以工作(Lukasz提供了一个非常低效的解决方案),但这只是将来可能出现问题的另一个细节。 - bbum

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