How to set up 2 core data stacks that use the same in-memory persistent store? - core-data

Setup:
My app uses core data & cloud kit mirroring.
For unit tests, I want to mock iCloud mirroring by setting cloudKitContainerOptions = nil of the NSPersistentStoreDescription of the persistent store used.
To mock mirroring, I want to setup a 2nd core data stack that uses the same persistent store as the normal data stack.
Additionally, the SQL persistent store is replaced by an NSInMemoryStoreType persistent store.
Problem:
I did not manage to use the same in-memory persistent store for 2 core data stacks.
Both stacks are created with a NSPersistentCloudKitContainer.
Both use the same NSPersistentStoreDescription with the same file URL, although this file URL is apparently ignored for an in-memory persistent store.
Thus, both containers use different in-memory persistent stores, and it is not possible to mock iCloud mirroring to a single persistent store.
Question:
I the intended setup possible, and if so, how?
PS: I know that I probably could use the same SQL store by specifying the same file UrL. But this had the disadvantage that the store persisted between different unit tests, and had to be reset at the beginning of each test.

It is indeed possible to set up 2 core data stacks that use the same in-memory persistent store, but they will have not all properties as an SQLite store.
Here is my test setup for the 2 core data stacks:
let coreDataCloudKitContainer = CoreDataCloudKitContainer(name: appName, privateStoreType: .persistentStore)
// let coreDataCloudKitContainer = CoreDataCloudKitContainer(name: appName, privateStoreType: .nullDevice)
// let coreDataCloudKitContainer = CoreDataCloudKitContainer(name: appName, privateStoreType: .inMemory)
coreDataManager.persistentContainer = coreDataCloudKitContainer
let privateStore = coreDataCloudKitContainer.privateStore!
let mockStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: CoreDataCloudKitContainer.managedObjectModel)
_ = try! mockStoreCoordinator.addPersistentStore(type: NSPersistentStore.StoreType(rawValue: privateStore.type),
configuration: privateStore.configurationName,
at: privateStore.url!,
options: [NSPersistentHistoryTrackingKey: true])
let viewContext = coreDataCloudKitContainer.viewContext
let mockViewContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
mockViewContext.persistentStoreCoordinator = mockStoreCoordinator
CoreDataCloudKitContainer is a subclass of NSPersistentCloudKitContainer. It has a static var managedObjectModel that is used during init of a CoreDataCloudKitContainer as well as the init of the 2nd NSPersistentStoreCoordinator (the model must only be loaded once or core data will get confused):
static var managedObjectModel: NSManagedObjectModel = {
guard let modelFile = Bundle.main.url(forResource: appName, withExtension: "momd") else { fatalError("Canot find model file") }
guard let model = NSManagedObjectModel(contentsOf: modelFile) else { fatalError("Cannot parse model file") }
return model
}()
Note also that addPersistentStore uses an option [NSPersistentHistoryTrackingKey: true] that is required if privateStore is defined with privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey). If it is not set, one will get an error CoreData: fault: Store opened without NSPersistentHistoryTrackingKey but previously had been opened with NSPersistentHistoryTrackingKey - Forcing into Read Only mode store at 'file://...
privateStore is initialized as
let privateStoreURL: URL
switch privateStoreType {
case .persistentStore:
privateStoreURL = CoreDataCloudKitContainer.appDefaultDirectoryURL.appendingPathComponent("Private.sqlite")
case .nullDevice, .inMemory:
privateStoreURL = URL(fileURLWithPath: "/dev/null")
}
print("privateStoreURL: \(privateStoreURL)")
let privateStoreDescription = NSPersistentStoreDescription(url: privateStoreURL)
privateStoreDescription.url = privateStoreURL
privateStoreDescription.configuration = privateConfigurationName
privateStoreDescription.timeout = timeout
privateStoreDescription.type = type
privateStoreDescription.isReadOnly = isReadOnly
privateStoreDescription.shouldAddStoreAsynchronously = shouldAddStoreAsynchronously
privateStoreDescription.shouldInferMappingModelAutomatically = shouldInferMappingModelAutomatically
privateStoreDescription.shouldMigrateStoreAutomatically = shouldMigrateStoreAutomatically
// The options below have to be set before loadPersistentStores
// Enable history tracking and remote notifications
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
if isTesting {
privateStoreDescription.cloudKitContainerOptions = nil
} else {
privateStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: kICloudContainerID)
privateStoreDescription.cloudKitContainerOptions!.databaseScope = .private
}
where type is NSSQLiteStoreType for privateStoreType == .persistentStore and .nullDevice, and NSInMemoryStoreType for privateStoreType == .inMemory. Note that the option .nullDevice creates an SQLite store in memory only, but .inMemory creates some store in memory, but it is not a SQLite store. Infos about a .nullDevice store can be found in this blog.
A warning:
Although it is possible to set up 2 core data stacks using the same in-memory persistent store, such a store has limitations. One is that modifying the store by one stack does not trigger an NSPersistentStoreRemoteChangeNotification, so that unit testing of iCloud mirroring is not possible. For that, one has to use a file based SQLite persistent store.

Related

SwiftUI: How to add CoreData record from Siri Intent

I am trying to create an Intent that saves a record to a CoreData database. The record will be created if I run the code from the main app, but not in the Intent.
Here is the code:
import Intents
import CoreData
import SwiftUI
let persistenceController = PersistenceController.shared
class IntentHandler: INExtension, DiaryIntentHandling
{
var moc = PersistenceController.shared.context
override func handler(for intent: INIntent) -> Any?
{
guard intent is DiaryIntent else
{
fatalError("Unknwonwn intent type: \(intent)")
}
return self
}
func handle(intent: DiaryIntent, completion: #escaping (DiaryIntentResponse) -> Void)
{
guard let message = message
else
{
completion(DiaryIntentResponse(code: .failure, userActivity: nil))
return
}
completion(DiaryIntentResponse.success(message: message))
let context = PersistenceController.shared.container.viewContext
let myRecord = MyRecord(context: context)
myRecord.timestamp = Date()
myRecord.message = message
do {
try context.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
func resolveMessage(for intent: DiaryIntent, with completion: #escaping (INStringResolutionResult) -> Void)
{
if let message = intent.message
{
completion(INStringResolutionResult.success(with: message))
}
else
{
completion(INStringResolutionResult.needsValue())
}
}
public func confirm(intent: DiaryIntent, completion: #escaping (DiaryIntentResponse) -> Void) {
completion(DiaryIntentResponse(code: .ready, userActivity: nil))
}
}
Do I need to share access to the CoreData database? How do I create the record?
App extensions work like separate apps, so you need to set up an app "group" to share data between them. It gives you a directory that's not part of your app's sandbox that your app and your app extensions can share. Using one requires some setup work:
Turn on app groups by adding the group entitlement. Apple has some documentation on this. I also have a somewhat old blog post that's still accurate as far as setting up the group.
Set up your persistent container use the group directory for Core Data. Normally it saves data in your app's sandbox, but you can tell it to use the app group directory. To do that,
Get a file URL for the directory using FileManager's function containerURL(forSecurityApplicationGroupIdentifier:). The argument is the same as your app group identifier.
Make sure this directory exists! It doesn't get created automatically. Use FileManager.default.fileExists(atPath:) to check if it exists, and if not, use FileManager.default.createDirectory(at:withIntermediateDirectories:attributes:) to create it.
Use a NSPersistentStoreDescription to tell your persistent container to use that URL for Core Data. That would be something like
let persistentContainer = NSPersistentContainer(name: containerName)
let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreUrl)
persistentStoreDescription.type = NSSQLiteStoreType
persistentContainer.persistentStoreDescriptions = [ persistentStoreDescription ]
After the previous step, the persistent container won't be able to find any data that's currently in Core Data. So:
If your app has not already been released, delete it from your test devices and simulators and then rebuild. You'll get a new persistent store in the app group directory.
If your app has already been released, add code to copy the persistent store from the current location to the new location. Do this before loading the persistent store, and only do it if the copy in the app group doesn't already exist (so you don't re-copy old data). The best way to do that is with the migratePersistentStore(_:to:options:withType:) function from NSPersistentStoreCoordinator. Don't just copy your SQLite file over, because that won't include all the data.

Performance degradation after upgrading to Microsoft.Azure.Cosmos.Table to access Azure table storage

We upgraded to the next version of SDK to access our Azure Table storage.
We observed performance degradation of our application after that. We even created test applications with identical usage pattern to isolate it, and still see this performance hit.
We are using .NET Framework code, reading data from Azure table.
Old client: Microsoft.WindowsAzure.Storage - 9.3.2
New client: Microsoft.Azure.Cosmos.Table - 1.0.6
Here is one of the sample tests we tried to run:
public async Task ComparisionTest1()
{
var partitionKey = CompanyId.ToString();
{
// Microsoft.Azure.Cosmos.Table
var storageAccount = Microsoft.Azure.Cosmos.Table.CloudStorageAccount.Parse(ConnectionString);
var tableClient = Microsoft.Azure.Cosmos.Table.CloudStorageAccountExtensions.CreateCloudTableClient(storageAccount);
var tableRef = tableClient.GetTableReference("UserStatuses");
var query = new Microsoft.Azure.Cosmos.Table.TableQuery<Microsoft.Azure.Cosmos.Table.TableEntity>()
.Where(Microsoft.Azure.Cosmos.Table.TableQuery.GenerateFilterCondition("PartitionKey", "eq", partitionKey));
var result = new List<Microsoft.Azure.Cosmos.Table.TableEntity>(20000);
var stopwatch = Stopwatch.StartNew();
var tableQuerySegment = await tableRef.ExecuteQuerySegmentedAsync(query, null);
result.AddRange(tableQuerySegment.Results);
while (tableQuerySegment.ContinuationToken != null)
{
tableQuerySegment = await tableRef.ExecuteQuerySegmentedAsync(query, tableQuerySegment.ContinuationToken);
result.AddRange(tableQuerySegment.Results);
}
stopwatch.Stop();
Trace.WriteLine($"Cosmos table client. Elapsed: {stopwatch.Elapsed}");
}
{
// Microsoft.WindowsAzure.Storage
var storageAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(ConnectionString);
var tableClient = storageAccount.CreateCloudTableClient();
var tableRef = tableClient.GetTableReference("UserStatuses");
var query = new Microsoft.WindowsAzure.Storage.Table.TableQuery<Microsoft.WindowsAzure.Storage.Table.TableEntity>()
.Where(Microsoft.WindowsAzure.Storage.Table.TableQuery.GenerateFilterCondition("PartitionKey", "eq", partitionKey));
var result = new List<Microsoft.WindowsAzure.Storage.Table.TableEntity>(20000);
var stopwatch = Stopwatch.StartNew();
var tableQuerySegment = await tableRef.ExecuteQuerySegmentedAsync(query, null);
result.AddRange(tableQuerySegment.Results);
while (tableQuerySegment.ContinuationToken != null)
{
tableQuerySegment = await tableRef.ExecuteQuerySegmentedAsync(query, tableQuerySegment.ContinuationToken);
result.AddRange(tableQuerySegment.Results);
}
stopwatch.Stop();
Trace.WriteLine($"Old table client. Elapsed: {stopwatch.Elapsed}");
}
}
Anyone observed it, any thoughts about it?
The performance issue will be resolved in Table SDK 1.0.7 as verified with large entity.
On 1.0.6 the workaround is to disable Table sdk trace by adding diagnostics section in app.config if it's a .NET framework app. It will still be slower than Storage sdk, but much better than without the workaround depending on the usage.
I think your data are stored in the legacy Storage Table. Just in case, if this is CosmosDB Table backed, you may get better performance if you set TableClientConfiguration.UseRestExecutorForCosmosEndpoint to True.
If it's the legacy Storage Table store, CosmosDB Table sdk 1.0.6 is about 15% slower than Storage Table sdk 9.3.3. In addition, it has an extra second overhead upon the first CRUD operation. Higher query duration has been resolved in 1.0.7, which is on-par with Storage SDK. The initialization second is still required why using CosmosDB Table sdk 1.0.7, which should be acceptable.
We are planning to release 1.0.7 during the week of 4/13.

NSPersistentStoreRemoteChangeNotification not getting fired

I am trying to perform history tracking in my CoreData+CloudKit project which uses NSPersistentCloudKitContainer. I have been following along with Apple's sample project
I want to perform certain task when the remote store has been updated. For this apple recommends enabling remote notification in the Signing & capabilities's Background Mode section of the app.
I have enabled History Tracking for my project as shown in Apple's sample project.
// turn on persistent history tracking
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
// ...
Also I have registered my store to listen for store changes.
// turn on remote change notifications
let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
description?.setOption(true as NSNumber,
forKey: remoteChangeKey)
// ...
Observer is also added to listen for NSPersistentStoreRemoteChangeNotification.
However there is no NSPersistentStoreRemoteChangeNotification being fired. To make sure there is no mistake in my implementation, I am have simply put breakpoints in #objc func storeRemoteChange(_ notification: Notification) the Apple's provided sample code but still I can not see any notification being fired and no breakpoints are activated.
I have understood the deduplication of the Tags done in the sample project and also tried testing it but without any success. Is it a bug in the Apple's implementation or am I missing any setup which is required?
My guess is you are observing the container instead of the store coordinator, add your observer like this:
NotificationCenter.default.addObserver(
self, selector: #selector(type(of: self).storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)
Note the last param container.persistentStoreCoordinator
And a warning, this notification comes in on all different threads so you be careful with concurrency. Just put a 5 second sleep in the method and you'll see on app launch 3 different threads call it. This is likely why in the example there is a historyQueue with maxOperationCount 1 to handle it.
Some notifications have NSPersistentHistoryTokenKey in the userInfo not sure why.
Debugging the sample app mentioned by the OP, I observed the following:
As of XCode Version 11.3 (11C29), there are SDK constants both for the option key (NSPersistentStoreRemoteChangeNotificationPostOptionKey) and for the notification name (.NSPersistentStoreRemoteChange), and these are reflected in the latest download of the sample code.
The sample app registers for the remote change notifications on the wrong object, so it never receives any. Changing the sender as per the accepted answer fixes this.
The app UI always updates to reflect changes received from the cloud, but those updates are prompted not by remote change notifications but by the app's NSFetchedResultsController delegate using the controllerDidChangeContent callback to refresh the UI.
The standard NSPersistentCloudKitContainer used by the sample app is doing automatic imports into the local persistent store of all the cloud-sent updates and, because the persistentStore is set up for history tracking and the viewContext is set up to auto-update to the latest generation of data, each import triggers a UI update.
Based on these observations, I wrote a small app from scratch based on the XCode template you get by specifying use of CoreData, CloudKit, and SwiftUI. I set up its persistent container and view context the same way they are set up in the sample app, and used SwiftUI's #FetchRequest wrapper to obtain the data in the master view display. Sure enough, I saw the exact same remote import behavior without using any remote change notifications, and the UI updated after each import.
I then confirmed that, as per the accepted answer, if I registered for remote change notifications correctly, they would be received. They seem to be sent after each receive and import operation in the NSPersistentCloudKit completes. Observing them is not needed to get notifications of the local data changes initiated by those imports.
I was able to reliably echo Core Data changes via iCloud between two devices in my project. But I reached a point where I needed access to the change history. Apple has nicely described the steps to set it up in Consuming Relevant Store Changes
I followed along and happily copy and pasted the relevant code into my app. But the NSPersistentStoreRemoteChange notification was not coming through. As in comedy, timing is everything. Per the documentation for persistentStoreDescriptions I
If you will be configuring custom persistent store descriptions, you
must set this property before calling
loadPersistentStores(completionHandler:)
I was configuring persistentStoreDescriptions inside of loadPersistentStores(completionHandler:) So the painfully obvious way to do it is setup the following code in the AppDelegate.
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentCloudKitContainer(name: "yourProjectNameGoesHere")
// turn on persistent history tracking
// https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSPersistentHistoryTrackingKey)
// turn on remote change notifications
let remoteChangeKey = "NSPersistentStoreRemoteChangeNotificationOptionKey"
description?.setOption(true as NSNumber,
forKey: remoteChangeKey)
// this will make background updates from iCloud available to the context.
container.viewContext.automaticallyMergesChangesFromParent = true
// call this LAST, after the persistentStoreDescriptions configuration.
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
Catch the notification from your view controller or model.
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(fetchChanges),
name: .NSPersistentStoreRemoteChange,
object: pc.persistentStoreCoordinator)
}
#objc func fetchChanges(note: Notification) {
print("Just received a NSPersistentStoreRemoteChange notification")
}
I don't know whether it's a bug. Simply downloading and running the Apple's Sample Project but the NSPersistentStoreRemoteChangeNotification is never fired.
I added one more observer for the same NSPersistentStoreRemoteChangeNotification in my AppDelegate and it is firing.
I added notification observer in AppDelegate and then simply call the StoreRemoteChange(_:) of the CoreDataStack. Also, Tag deduplication logic works properly.
Here is the code which I added in AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// The view controller hierarchy is defined in the main storyboard.
guard let splitViewController = window?.rootViewController as? UISplitViewController,
let navController = splitViewController.viewControllers[splitViewController.viewControllers.count - 1] as? UINavigationController,
let topViewController = navController.topViewController else {
return false
}
// Configure the splitViewController.
topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
splitViewController.delegate = self
splitViewController.preferredDisplayMode = .allVisible
// Observe Core Data remote change notifications.
NotificationCenter.default.addObserver(
self, selector: #selector(type(of: self).storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange, object: nil)
return true
}
#objc
func storeRemoteChange(_ notification: Notification) {
coreDataStack.storeRemoteChange(notification)
}
SwiftUI
Here's a way to be notified of CloudKit remote changes in a SwiftUI view, and, say, update the contents of a List that would depend on a #FetchRequest--not shown in the code for simplicity:
struct MyView: View {
#State var refresh = UUID()
var didRemoteChange = NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange).receive(on: RunLoop.main)
var body: some View {
List {
// ...
}
.id(refresh)
.onReceive(self.didRemoteChange) { _ in
self.refresh = UUID()
}
}
}
Note: .receive(on: RunLoop.main) is necessary in order to avoid modifying the UI from a background thread, as the remote event could (and will) otherwise fire from a background thread. Alternatively, .receive(on: DispatchQueue.main) can also be used.
For that to work, the NSPersistentCloudKitContainer needs to be set up to fire events when remote changes occur:
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "YourApp")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
//
// Generate notifications upon remote changes
//
container.persistentStoreDescriptions.forEach {
$0.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
}

Kusto Query from c#

I want to retrieve data from Kusto DB from c# app can any one help me on this.
I have knowledge on writing the Kusto queries but I need some help on pulling data from Azure Kusto DB hosted in Azure.
I tried the following code but it's not working:
var client = Kusto.Data.Net.Client.KustoClientFactory.CreateCslQueryProvider("https://help.kusto.windows.net/Samples;Fed=true");
var reader = client.ExecuteQuery("MyTable | count");
// Read the first row from reader -- it's 0'th column is the count of records in MyTable
// Don't forget to dispose of reader when done.
Could you please elaborate what's not working (what is the error message you're getting) with the code above?
In addition, a full (though simple) example can be found below:
// This sample illustrates how to query Kusto using the Kusto.Data .NET library.
//
// For the purpose of demonstration, the query being sent retrieves multiple result sets.
//
// The program should execute in an interactive context (so that on first run the user
// will get asked to sign in to Azure AD to access the Kusto service).
class Program
{
const string Cluster = "https://help.kusto.windows.net";
const string Database = "Samples";
static void Main()
{
// The query provider is the main interface to use when querying Kusto.
// It is recommended that the provider be created once for a specific target database,
// and then be reused many times (potentially across threads) until it is disposed-of.
var kcsb = new KustoConnectionStringBuilder(Cluster, Database)
.WithAadUserPromptAuthentication();
using (var queryProvider = KustoClientFactory.CreateCslQueryProvider(kcsb))
{
// The query -- Note that for demonstration purposes, we send a query that asks for two different
// result sets (HowManyRecords and SampleRecords).
var query = "StormEvents | count | as HowManyRecords; StormEvents | limit 10 | project StartTime, EventType, State | as SampleRecords";
// It is strongly recommended that each request has its own unique
// request identifier. This is mandatory for some scenarios (such as cancelling queries)
// and will make troubleshooting easier in others.
var clientRequestProperties = new ClientRequestProperties() { ClientRequestId = Guid.NewGuid().ToString() };
using (var reader = queryProvider.ExecuteQuery(query, clientRequestProperties))
{
// Read HowManyRecords
while (reader.Read())
{
var howManyRecords = reader.GetInt64(0);
Console.WriteLine($"There are {howManyRecords} records in the table");
}
// Move on to the next result set, SampleRecords
reader.NextResult();
Console.WriteLine();
while (reader.Read())
{
// Important note: For demonstration purposes we show how to read the data
// using the "bare bones" IDataReader interface. In a production environment
// one would normally use some ORM library to automatically map the data from
// IDataReader into a strongly-typed record type (e.g. Dapper.Net, AutoMapper, etc.)
DateTime time = reader.GetDateTime(0);
string type = reader.GetString(1);
string state = reader.GetString(2);
Console.WriteLine("{0}\t{1,-20}\t{2}", time, type, state);
}
}
}
}
}

Sharing a big String between an app and its extension

I have a text file of 5MB and I need to send from my application to a content blocker extension. I tried a simple group sharing but it's not working, ehre is the code.
In my app:
listText = NSString(data: content, encoding: NSUTF8StringEncoding)! as String
print("\(list): \(listText.characters.count)") // It works
if let userDefaults = NSUserDefaults(suiteName: "group.AG.App") {
print("In the group") // It works
userDefaults.setObject(listText, forKey:"test")
userDefaults.synchronize()
}
In my extension:
if let userDefaults = NSUserDefaults(suiteName: "group.AG.App") {
// Things happen here
if let test = userDefaults.stringForKey("test") {
// Nothing happens here
}
}
How would you do to share a big amount of text between an app and its extension?
Since you load the string from a file and you already use App Groups, I suggest to save that file in the shared container and retrieve its content in the extension.
You can save file in the shared group in the same way you do for your app container. You only have to get the URL for the group root:
Swift
let groupRoot = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("com.group.Armand-Grillet")
Objective-C
NSURL *groupRoot = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"com.group.Armand-Grillet"];
You can't retrieve the string because you set a NSString and you try to retrieve an array, try to use stringForKey:

Resources