【问题标题】:core data in a static library for the iPhoneiPhone静态库中的核心数据
【发布时间】:2010-12-15 06:11:36
【问题描述】:

我构建了一个大量使用 Core Data 框架的静态库。我可以在我的外部项目中成功使用该库,但前提是我在主项目中包含 .xcdatamodel 文件。这不太理想,因为库的目的是尽可能隐藏实现细节。

在单独的question 中,我被告知我不能将资源与库捆绑在一起(现在这对我来说完全有意义)。

那么有没有办法以编程方式让模型被“发现”而不必将模型包含在主项目中?

【问题讨论】:

    标签: iphone core-data static-libraries nsbundle


    【解决方案1】:

    Sascha 的回答让我走上了正轨。将静态库中已编译的.mom 文件合并到宿主项目中的.mom 文件中相对简单。这是一个简单的例子:

    1. 创建一个新的 XCode 静态库 项目名为MyStaticLibrary

    2. MyStaticLibrary 中创建一个名为MyStaticLibraryModels.xcdatamodel 的.xcdatamodel 文件,添加一些Entitys,然后生成头文件和实现。当您构建MyStaticLibrary 目标时,您将生成一个libMyStaticLibrary.a 二进制文件,但它不会包含已编译的.mom 文件。为此,我们必须创建一个包。

    3. MacOS X > Cocoa 下创建一个Loadable Bundle 类型的新构建目标,我们将新目标称为MyStaticLibraryModels

    4. MyStaticLibraryModels.xcdatamodel 拖到 MyStaticLibraryModels 目标的 Compile Sources 构建阶段。当您构建MyStaticLibraryModels 目标时,您将生成一个名为MyStaticLibraryModels.bundle 的文件,其中将包含已编译的NSManagedObjectModel 文件MyStaticLibraryModels.mom

    5. 在构建MyStaticLibraryMyStaticLibraryModels 目标后,将libMyStaticLibrary.a(连同任何关联的模型头文件)和MyStaticLibraryModels.bundle 拖到您的宿主项目MyAwesomeApp 中。

    6. MyAwesomeApp 使用CoreData,拥有自己的.xcdatamodel 文件,该文件将在其自己的构建过程中编译成.mom 文件。我们希望将这个.mom 文件与我们在MyStaticLibraryModels.bundle 中导入的文件合并。在MyAwesomeApp 项目中的某处,有一个方法返回MyAwesomeApps NSManagedObjectModel。 Apple 为该方法生成的模板如下所示:

    ...

    - (NSManagedObjectModel *)managedObjectModel {
      if (managedObjectModel_ != nil) {
        return managedObjectModel_;
      }
      NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MyAwesomeApp" withExtension:@"momd"];
      managedObjectModel_ = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];    
      return managedObjectModel_;
    }
    

    我们将对此进行更改以合并并返回我们的 NSManagedObjectModels、MyAwesomApps 和 MyStaticLibraryModels,作为一个单独的组合 NSManagedObjectModel,如下所示:

    - (NSManagedObjectModel *)managedObjectModel {
      if (managedObjectModel_ != nil) {
        return managedObjectModel_;
      }
    
      NSMutableArray *allManagedObjectModels = [[NSMutableArray alloc] init];
    
      NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MyAwesomeApp" withExtension:@"momd"];
      NSManagedObjectModel *projectManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
      [allManagedObjectModels addObject:projectManagedObjectModel];
      [projectManagedObjectModel release];
    
      NSString *staticLibraryBundlePath = [[NSBundle mainBundle] pathForResource:@"MyStaticLibraryModels" ofType:@"bundle"];
      NSURL *staticLibraryMOMURL = [[NSBundle bundleWithPath:staticLibraryBundlePath] URLForResource:@"MyStaticLibraryModels" withExtension:@"mom"];
      NSManagedObjectModel *staticLibraryMOM = [[NSManagedObjectModel alloc] initWithContentsOfURL:staticLibraryMOMURL];
      [allManagedObjectModels addObject:staticLibraryMOM];
      [staticLibraryMOM release];
    
      managedObjectModel_ = [NSManagedObjectModel modelByMergingModels:allManagedObjectModels];
      [allManagedObjectModels release];
    
      return managedObjectModel_;
    }
    

    这将返回合并后的NSManagedObjectModel 与来自MyAwesomeAppMyStaticLibraryEntitys。

    【讨论】:

    • 我正在使用 Xcode4 并按照上述步骤,我设法创建了一个包含妈妈的包,包含并用于创建协调器和上下文。但是,当我尝试插入一个我确定它存在的新实体时,我收到错误消息“无法找到名为...的实体”。另外,如果我尝试计算所有模型实体,我得到 0。有什么猜测吗?
    • 很好的解决方案。 @prairedogg 掩盖(我错过了)的关键是:Xcode 将创建一个复制资源阶段,但 Xcode 被硬编码为忽略静态库目标中的复制资源阶段。如果您将捆绑包包含在您的库中 - 什么都不会发生。您必须将您的捆绑包单独包含到您的最终应用程序中(当您考虑它时很明显 - 我们正在解决 Xcode 无法复制它应该首先复制的东西的错误 ;))。
    • 请注意,上面的某些路径已更改。您的访问者应该更改一些代码,例如添加路径组件,而不仅仅是替换字符串。我正在使用 xcode6 beta 和 ios8 sdk。
    • 这是我情况的唯一正确答案。面向 iOS 7.1 的 Xcode 5.1。
    • 这是在 Xcode 5 中执行此操作的分步教程:bharathnagarajrao.wordpress.com/2014/02/14/…
    【解决方案2】:

    我还创建了自己的使用 Core Data 的静态库。除了静态库之外,我在项目中有另一个捆绑目标,其中我有一个 Copy Bundle Resources 项,它将一些图像和类似的东西复制到捆绑和一个编译源构建阶段,我正在编译 xcdatamodel。

    最终的捆绑包将包含所有必要的文件。在依赖于静态库的主项目中,您还必须包含该捆绑包。您的主项目现在可以访问使用核心数据所需的 mom 文件。

    要将核心数据与捆绑包中的妈妈一起使用,您必须在代码中创建一个合并的托管对象模型(它可能是主项目也有一些核心数据模型):

    - (NSManagedObjectModel *) mergedManagedObjectModel { if (!mergedManagedObjectModel) { NSMutableSet *allBundles = [[[NSMutableSet alloc] init] autorelease]; [allBundles addObjectsFromArray: [NSBundle allBundles]]; [allBundles addObjectsFromArray: [NSBundle allFrameworks]]; mergedManagedObjectModel = [[NSManagedObjectModel mergedModelFromBundles: [allBundles allObjects]] retain]; } return mergedManagedObjectModel; }

    只需包含捆绑包,您就不必提供 xcdatamodel,只需要包含已编译的 mom 文件。

    【讨论】:

    • Sascha -- 这工作得相当好。 MOM 文件在 XCode 中仍然是可读的,但至少比显示一个漂亮的数据模型图要好。
    • Sascha - 我知道您在这里回答已经有一段时间了,但我想知道您是否可以详细说明如何设置目标和构建阶段,以便使用静态库的项目可以查看和包含编译的妈妈文件。我目前正在构建一个也使用 CoreData 的静态框架,但我不知道如何做到这一点。我正在使用 lipo 将两个单独的 .a 文件(设备和模拟器版本)编译成一个静态库包。
    【解决方案3】:

    我也有一些带有 coredata 的库。 我找到了这个模板来管理带有嵌入资源的框架

    在新项目上使用非常简单(在现有项目上更难应用) 但是对于框架构建来说,这真的很酷:-)

    https://github.com/kstenerud/iOS-Universal-Framework

    【讨论】:

      【解决方案4】:

      Sascha Konietzke 的解决方案运行良好,但需要提供一个重要的警告才能使其发挥作用。需要先加载包含模型的bundle,否则将不会被包含在数组中并合并到MOM中。

      在他的情况下,他可能已经从包中访问过资源,因此包在执行此代码之前已经加载。

      【讨论】:

        【解决方案5】:

        Prairiedogg 的回答有点过时了,这里有一个在 Xcode 5 中这样做的教程:http://bharathnagarajrao.wordpress.com/2014/02/14/working-with-core-data-in-a-static-library/

        【讨论】:

        • 模型在库中时如何管理核心数据模型更新?
        【解决方案6】:

        请注意,除了使用 xcdatamodel/mom 文件之外,您还可以在代码中创建模型(特别是如果您有一个简单的模型),这样您就不需要为资源创建额外的包。这是一个简单的示例,其中一个表包含两个属性:

        - (NSManagedObjectModel *)coreDataModel
        {
            NSManagedObjectModel *model = [NSManagedObjectModel new];
        
            NSEntityDescription *eventEntity = [NSEntityDescription new];
            eventEntity.name = @"EventEntity";
            eventEntity.managedObjectClassName = @"EventEntity";
        
            NSAttributeDescription *dateAttribute = [NSAttributeDescription new];
            dateAttribute.name = @"date";
            dateAttribute.attributeType = NSDateAttributeType;
            dateAttribute.optional = NO;
        
            NSAttributeDescription *typeAttribute = [NSAttributeDescription new];
            typeAttribute.name = @"type";
            typeAttribute.attributeType = NSStringAttributeType;
            typeAttribute.optional = NO;
        
            eventEntity.properties = @[dateAttribute, typeAttribute];
            model.entities = @[eventEntity];
        
            return model;
        }
        

        这是一个关于从代码创建模型的教程:https://www.cocoanetics.com/2012/04/creating-a-coredata-model-in-code/

        同样基于这种方法,我创建了一个小型且易于使用的库,它可能满足您的需求,名为 LSMiniDB,因此您也可以查看它。

        在我的例子中,在使用 NSManagedObject 子类的属性时,我在控制台上收到了诸如“警告:动态访问器无法找到 @property 实现...”之类的警告。我能够通过将这些属性移动到类接口/实现而不是将它们放在单独文件中的类别中来解决这个问题(当前,xcode 默认情况下生成此代码,该代码拆分为单独的文件 ClassName+CoreDataClass 和 ClassName+CoreDataProperties 与一个类以及每个子类的类别)。

        【讨论】:

          【解决方案7】:

          不,在 iPhone 应用程序中使用非 Apple 框架的限制确实改变了相对于 OS X 的依赖游戏。大多数 iPhone“框架”(例如 Google 的 Mac 工具箱、Core Plot 等)实际上 推荐 您在主应用程序项目中包含源代码而不是链接产品(即静态库)。我认为社区的共识是,在 iPhone 上,期望框架的消费者必须做一些“手动”工作才能使用你的库是可以的。在您的情况下,这包括主项目中的 xcdatamodel 文件。与大多数 Objective-C 一样,告诉您的用户不要使用实现细节并保留它。

          【讨论】:

            【解决方案8】:

            萨沙回答的 Swift 2 版本:

            lazy var managedObjectModel: NSManagedObjectModel = {
                // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
                var allBundles = NSMutableSet()
                allBundles.addObjectsFromArray(NSBundle.allBundles())
                allBundles.addObjectsFromArray(NSBundle.allFrameworks())
            
                let model =  NSManagedObjectModel.mergedModelFromBundles(allBundles.allObjects as? [NSBundle])
            
                return model!
            }()
            

            【讨论】:

              猜你喜欢
              • 2014-12-15
              • 1970-01-01
              • 1970-01-01
              • 2014-03-29
              • 2010-12-20
              • 2010-11-04
              • 2012-03-28
              • 2012-02-16
              • 1970-01-01
              相关资源
              最近更新 更多