Home > Articles > Programming > General Programming/Other Languages

This chapter is from the book

Migration Manager

Instead of letting a persistent store coordinator perform store migrations, you may want to manually migrate stores using an instance of NSMigrationManager. Using a migration manager still uses a mapping model; however, the difference is you have total control over the migration and the ability to report progress. To be certain that migration is being handled manually, automatic migration should be disabled.

Update Groceries as follows to disable automatic migration:

  1. Set the NSMigratePersistentStoresAutomaticallyOption option in the localStore variable of CDHelper.swift to false by changing the 1 to a 0.

Reporting on the progress of a migration is useful for keeping the user informed (and less annoyed) about a slow launch. Although most migrations should be fast, some large databases requiring complex changes can take a while to migrate. To keep the user interface responsive, the migration must be performed on a background thread. At the same time, the user interface has to be responsive to provide updates to the user. The challenge is to prevent the user from attempting to use the application during the migration. This is because the data won’t be ready yet, so you don’t want the user staring at a blank screen wondering what’s going on. This is where a migration progress view comes into play.

Update Groceries as follows to configure a migration View Controller:

  1. Select Main.storyboard.
  2. Drag a new View Controller onto the storyboard, placing it above the existing Navigation Controller.
  3. Drag a new Label and Progress View onto the new View Controller.
  4. Position the Progress View directly in the center of the View Controller and then position the Label above it in the center.
  5. Widen the Label and Progress View to the width of the View Controller margins, as shown in the center of Figure 3.10.

    Figure 3.10

    Figure 3.10 Migration View Controller

  6. Configure the Label with Centered text that reads Migration Progress 0%, as shown in the center of Figure 3.10.
  7. Configure the Progress View progress to 0.
  8. Select the View Controller and set its Storyboard ID to migration using Identity Inspector (Option+cmd.jpg+3).
  9. Optionally configure the following layout constraints by holding down the control key and dragging from the progress bar toward the applicable margin. You may skip this step if you’re uncomfortable with constraints as it is not critical.

    • Leading Space to Container Margin
    • Trailing Space to Container Margin
    • Center Vertically In Container
  10. Optionally configure the following layout constraints by holding down the control key and dragging from the progress label toward the applicable margin. You may skip this step if you’re uncomfortable with constraints as it is not critical.

    • Leading Space to Container Margin
    • Trailing Space to Container Margin
    • Vertical Spacing from the progress bar

Introducing MigrationVC.swift (Migration View Controller Code)

The new migration View Controller has UILabel and UIProgressView interface elements that need updating during a migration. This means that a way to refer to these interface elements in code is required. A new UIViewController subclass called MigrationVC should be created for this purpose.

Update Groceries as follows to add a MigrationVC file to a new group:

  1. Right-click the existing Groceries group and then select New Group.
  2. Set the new group name to View Controllers. This group will contain all of the view controllers. As a side note, feel free to move ViewController.swift to the trash because it is no longer required.
  3. Select the View Controllers group.
  4. Click File > New > File....
  5. Create a new iOS > Source > Cocoa Touch Class and then click Next.
  6. Set the subclass to UIViewController and the filename to MigrationVC.
  7. Ensure the language is Swift and then click Next.
  8. Ensure the Groceries target is checked and that the new file will be saved in the Groceries project directory; then click Create.
  9. Select Main.storyboard.
  10. Set the Custom Class of the new migration View Controller to MigrationVC using Identity Inspector (Option+cmd.jpg+3) while the View Controller is selected. This is in the same place as where the Storyboard ID was set.
  11. Show the Assistant Editor by clicking View > Assistant Editor > Show Assistant Editor (or pressing Option+cmd.jpg+Return).
  12. Ensure the Assistant Editor is automatically showing MigrationVC.swift. The top-right of Figure 3.11 shows what this looks like. If you need to, just click Manual or Automatic while the migration View Controller is selected and select MigrationVC.swift.

    Figure 3.11

    Figure 3.11 Creating storyboard-linked properties to MigrationVC.swift

  13. Hold down the control key while dragging a line from the migration progress label to the code in MigrationVC.swift on the line before the viewDidLoad function. When you let go of the mouse button, a pop-up appears. In the pop-up, set the Name to label and ensure the Storage is set to Strong before clicking Connect. Figure 3.11 shows the intended configuration.
  14. Repeat the technique in step 13 to create a linked UIProgressView variable from the progress view called progressView.

There should now be an @IBOutlet called label and an @IBOutlet called progressView in MigrationVC.swift. You may now switch back to the Standard Editor (cmd.jpg + return).

When a migration occurs, notifications that communicate progress need to be sent. For the progress bar to reflect the progress, a new function is required in MigrationVC.swift. In addition, this function needs to be called every time a progress update is observed. Listing 3.5 shows the new code involved in bold.

Listing 3.5 Migration View Controller (MigrationVC.swift)

import UIKit

class MigrationVC: UIViewController {

    @IBOutlet var label: UILabel!
    @IBOutlet var progressView: UIProgressView!

    // MARK: - MIGRATION
    func progressChanged (note:AnyObject?) {
        if let _note = note as? NSNotification {
            if let progress = _note.object as? NSNumber {
                let progressFloat:Float = round(progress.floatValue * 100)
                let text = "Migration Progress: \(progressFloat)%"
                print(text)

                dispatch_async(dispatch_get_main_queue(), {
                    self.label.text = text
                    self.progressView.progress = progress.floatValue
                })
            } else {print("\(__FUNCTION__) FAILED to get progress")}
        } else {print("\(__FUNCTION__) FAILED to get note")}
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: "progressChanged:", name: "migrationProgress", object: nil)
    }

    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(self, name: "migrationProgress", object: nil)
    }
}

The progressChanged function simply unwraps the progress notification and constructs a string with the migration completion percentage. It then updates the user interface with this information. Of course, none of this can happen without first adding an observer of the migrationProgress variable in the viewDidLoad function. When the view deinitializes, it is unregistered as an observer of the migrationProgress variable.

Update Groceries as follows to ensure migration progress is reported to the user:

  1. Replace all code in MigrationVC.swift with the code from Listing 3.5.

The user interface is now positioned to report migration progress to the user. The next step is to implement the code required to perform a manual migration.

Introducing CDMigration.swift (Core Data Migration Code)

To keep CDHelper.swift small, the code required to perform a managed migration is put in a new class called CDMigration.swift. The starting point to this class is shown in Listing 3.6.

Listing 3.6 Migration View Controller Shared Instance (CDMigration.swift shared)

import UIKit
import CoreData

private let _sharedCDMigration = CDMigration()
class CDMigration: NSObject {

    // MARK: - SHARED INSTANCE
    class var shared : CDMigration {
        return _sharedCDMigration
    }
}

Just like CDHelper.swift, CDMigration.swift has a shared function that makes it easy to use because you can call it from anywhere in the project via CDMigration.shared.

Update Groceries as follows to implement CDMigration.swift:

  1. Select the Generic Core Data Classes group.
  2. Click File > New > File....
  3. Create a new iOS > Source > Swift File and then click Next.
  4. Set the filename to CDMigration and ensure the Groceries target is checked.
  5. Ensure the Groceries project directory is open and then click Create.
  6. Replace the contents of CDMigration.swift with the code from Listing 3.6.

To handle migrations manually, three supporting functions are required. One function checks that a given store exists and another checks that it needs migrating. A successful migration generates a separate compatible store, so as soon as migration completes, this new store needs to replace the incompatible one. The final supporting function does exactly that—it replaces the incompatible store with the migrated store. Listing 3.7 shows the code involved with these three supporting functions.

Listing 3.7 Migration View Controller Supporting Functions (CDMigration.swift storeExistsAtPath, store, replaceStore)

// MARK: - SUPPORTING FUNCTIONS
func storeExistsAtPath(storeURL:NSURL) -> Bool {
    if let _storePath = storeURL.path {
        if NSFileManager.defaultManager().fileExistsAtPath(_storePath) {
            return true
        }
    } else {print("\(__FUNCTION__) FAILED to get store path")}
    return false
}
func store(storeURL:NSURL, isCompatibleWithModel model:NSManagedObjectModel) -> Bool {

    if self.storeExistsAtPath(storeURL) == false {
        return true // prevent migration of a store that does not exist
    }

    do {
        var _metadata:[String : AnyObject]?
        _metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(NSSQLiteStoreType, URL: storeURL, options: nil)
        if let metadata = _metadata {
            if model.isConfiguration(nil, compatibleWithStoreMetadata: metadata) {

                print("The store is compatible with the current version of the model")
                return true
            }
        } else {print("\(__FUNCTION__) FAILED to get metadata")}
    } catch {
        print("ERROR getting metadata from \(storeURL) \(error)")
    }
    print("The store is NOT compatible with the current version of the model")
    return false
}
func replaceStore(oldStore:NSURL, newStore:NSURL) throws {

    let manager = NSFileManager.defaultManager()

    do {
        try manager.removeItemAtURL(oldStore)
        try manager.moveItemAtURL(newStore, toURL: oldStore)
    }
}

The storeExistsAtPath function uses NSFileManager to determine whether a store exists at the given URL. It returns a Bool indicating the result.

The store:isCompatibleWithModel function first checks that a store exists at the given path. If there is no store, true is returned because this prevents a migration from being attempted. If a store exists at the given URL, it is checked for model compatibility against the given model. To do this, the model used to create the store is drawn from the store’s metadata and then compared to the given model via its isConfiguration:compatibleWithStoreMetadata function.

The replaceStore function uses NSFileManager to remove the incompatible store from the file system and then replaces it with the compatible store.

Update Groceries as follows to implement a new SUPPORTING FUNCTIONS section:

  1. Add the code from Listing 3.7 to the bottom of CDMigration.swift before the last curly brace.

When a migration is in progress, the value of the migration manager’s migrationProgress variable is constantly updated. This is information that the user needs to see, so a function is required to react whenever the migrationProgress value changes. Listing 3.8 shows a new function that posts a notification whenever this value changes.

Listing 3.8 Migration View Controller Progress Reporting (CDMigration.swift observeValueForKeyPath)

// MARK: - PROGRESS REPORTING
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {

    if object is NSMigrationManager, let manager = object as? NSMigrationManager {

        if let notification = keyPath {
            NSNotificationCenter.defaultCenter().postNotificationName(notification, object: NSNumber(float: manager.migrationProgress))
        }
    } else {print("observeValueForKeyPath did not receive a NSMigrationManager class")}
}

Update Groceries as follows to implement a new PROGRESS REPORTING section:

  1. Add the code from Listing 3.8 to the bottom of CDMigration.swift before the last curly brace.

The next function is where the actual migration happens. Most of this function is used to gather all the pieces required to perform a migration. Listing 3.9 shows the code involved.

Listing 3.9 Migration (CDMigration.swift migrateStore)

// MARK: - MIGRATION
func migrateStore(store:NSURL, sourceModel:NSManagedObjectModel, destinationModel:NSManagedObjectModel) {

    if let tempdir = store.URLByDeletingLastPathComponent {
        let tempStore = tempdir.URLByAppendingPathComponent("Temp.sqlite")
        let mappingModel = NSMappingModel(fromBundles: nil, forSourceModel: sourceModel, destinationModel: destinationModel)
        let migrationManager = NSMigrationManager(sourceModel: sourceModel, destinationModel: destinationModel)
        migrationManager.addObserver(self, forKeyPath: "migrationProgress", options: NSKeyValueObservingOptions.New, context: nil)

        do {

            try migrationManager.migrateStoreFromURL(store, type: NSSQLiteStoreType, options: nil,withMappingModel: mappingModel, toDestinationURL: tempStore, destinationType: NSSQLiteStoreType, destinationOptions: nil)
            try replaceStore(store, newStore: tempStore)

            print("SUCCESSFULLY MIGRATED \(store) to the Current Model")

        } catch {
            print("FAILED MIGRATION: \(error)")
        }
        migrationManager.removeObserver(self, forKeyPath: "migrationProgress")
    } else {print("\(__FUNCTION__) FAILED to prepare temporary directory")}
}

The migrateStore function needs to be given a store to migrate, a source model to migrate from, and destination model to migrate to. The source model could have been taken from the given store’s metadata; however, seeing as this step is performed first in another function, this approach saves repeated code.

The first thing migrateStore does is prepare four variables:

  • The tempdir variable holds the URL to the given store and is used to build a URL to a temporary store used for migration.
  • The tempStore variable holds the URL to the temporary store used for migration.
  • The mappingModel variable holds an instance of NSMappingModel specific to the models being migrated from and to. The migration will fail without a mapping model.
  • The migrationManager variable holds an instance of NSMigrationManager based on the source and destination models. An observer is added for the migrationProgress variable so that the observeValueForKeyPath function is called whenever the migrationProgress variable changes.

All these variables are then used to make a call to the migrateStoreFromURL function, which is responsible for migrating the given store to be compatible with the destination model. Once this is complete, the old incompatible store is removed and the new compatible store is put in its place.

Update Groceries as follows to implement a new MIGRATION section:

  1. Add the code from Listing 3.9 to the bottom of CDMigration.swift before the final closing curly brace.

The migration code that has just been implemented needs to be called from a background thread so that the user interface can be updated without freezing. This, along with the instantiation of the progress view that the user sees, is shown in Listing 3.10.

Listing 3.10 Migration Progress (CDMigration.swift migrateStoreWithProgressUI)

func migrateStoreWithProgressUI(store:NSURL, sourceModel:NSManagedObjectModel, destinationModel:NSManagedObjectModel) {

    // Show migration progress view preventing the user from using the app
    let storyboard = UIStoryboard(name: "Main", bundle: nil)

    if let initialVC = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController {

        if let migrationVC = storyboard.instantiateViewControllerWithIdentifier("migration") as? MigrationVC {

            initialVC.presentViewController(migrationVC, animated: false, completion: {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
                    print("BACKGROUND Migration started...")
                    self.migrateStore(store, sourceModel: sourceModel, destinationModel: destinationModel)
                    dispatch_async(dispatch_get_main_queue(), {

                        // trigger the stack setup again, this time with the upgraded store
                        let _ = CDHelper.shared.localStore
                        dispatch_after(2, dispatch_get_main_queue(), {
                            migrationVC.dismissViewControllerAnimated(false, completion: nil)
                        })
                    })
                })
            })
        } else {print("FAILED to find a view controller with a story board id of 'migration'")}
    } else {print("FAILED to find the root view controller, which is supposed to be a navigation controller")}
}

The migrateStoreWithProgressUI function uses a storyboard identifier to instantiate and present the migration view. Once the view is blocking user interaction the migration can begin. The migrateStore function is called on a background thread. Once migration is complete, the localStore is loaded as usual, the migration view is dismissed, and normal use of the application can resume.

Update Groceries as follows to implement the migrateStoreWithProgressUI function:

  1. Add the code from Listing 3.10 to the MIGRATION section at the bottom of CDMigration.swift before the last curly brace.

The final piece of code required in CDMigration.swift is used to migrate the store if necessary. This function is called from the setupCoreData function of CDHelper.swift, which is run as a part of initialization. Listing 3.11 shows the code involved.

Listing 3.11 Migration (CDMigration.swift migrateStoreIfNecessary)

func migrateStoreIfNecessary (storeURL:NSURL, destinationModel:NSManagedObjectModel) {

    if storeExistsAtPath(storeURL) == false {
        return
    }

    if store(storeURL, isCompatibleWithModel: destinationModel) {
        return
    }

    do {
        var _metadata:[String : AnyObject]?
        _metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(NSSQLiteStoreType, URL: storeURL, options: nil)
        if let metadata = _metadata, let sourceModel = NSManagedObjectModel.mergedModelFromBundles([NSBundle.mainBundle()], forStoreMetadata: metadata) {
            self.migrateStoreWithProgressUI(storeURL, sourceModel: sourceModel, destinationModel: destinationModel)
        }
    } catch {
        print("\(__FUNCTION__) FAILED to get metadata \(error)")
    }
}

Once it’s established that the given store exists, a model compatibility check is performed and the store is migrated if necessary. The model used to create the given store is drawn from the store’s metadata. This is then given to the migrateStoreWithProgressUI function.

Update Groceries as follows to implement the migrateStoreIfNecessary function:

  1. Add the code from Listing 3.11 to MIGRATION section at the bottom of CDMigration.swift before the last curly brace.

When CDHelper.swift initializes, a call is made to setupCoreData. This is an ideal time to check that the localStore is compatible with the current model, before it’s needed. The new code required in the setupCoreData is shown in bold in Listing 3.12.

Listing 3.12 Migration During Setup (CDHelper.swift setupCoreData)

func setupCoreData() {

    // Model Migration
    if let _localStoreURL = self.localStoreURL {
        CDMigration.shared.migrateStoreIfNecessary(_localStoreURL, destinationModel: self.model)
    }

    // Load Local Store
    _ = self.localStore
}

Update Groceries as follows to ensure a manual migration is triggered as required:

  1. Replace the setupCoreData function of CDHelper.swift with the code from Listing 3.12.

Currently the localStore variable of CDHelper.swift always tries to return a store. If it tried to return a store that wasn’t compatible with the current model, the application would throw an error. To prevent this, a check is needed to see whether the store needs migrating before it is loaded. This check is needed only when migration is handled manually, so the bold code in Listing 3.13 wouldn’t be required otherwise.

Listing 3.13 Triggering Migration Manager (CDHelper.swift localStore)

lazy var localStore: NSPersistentStore? = {

    let useMigrationManager = true
    if let _localStoreURL = self.localStoreURL {
        if useMigrationManager == true &&
            CDMigration.shared.storeExistsAtPath(_localStoreURL) &&
            CDMigration.shared.store(_localStoreURL, isCompatibleWithModel: self.model) == false {
            return nil // Don't return a store if it's not compatible with the model
        }
    }

    let options:[NSObject:AnyObject] = [NSSQLitePragmasOption:["journal_mode":"DELETE"],
                                       NSMigratePersistentStoresAutomaticallyOption:0,
                                       NSInferMappingModelAutomaticallyOption:0]
    var _localStore:NSPersistentStore?
    do {
        _localStore = try self.coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: self.localStoreURL, options: options)
        return _localStore
    } catch {
        return nil
    }
}()

Update Groceries as follows to ensure a localStore is not returned when the migration manager is used and a manual migration is required:

  1. Replace the localStore variable in CDHelper.swift with the code from Listing 3.13.

For progress to be shown to the user, the interface needs to be ready before a migration is triggered. This means that the first call to anything Core Data related should be made from an existing view after it has loaded. To demonstrate the migration process, a small amount of code needs to be applied to the existing table view. The only table view in the application so far is the Prepare table view, where the user adds items to the shopping list. Listing 3.14 shows the minimal code involved that triggers a store model migration. Note that the table view won’t be configured to display anything until later in the book.

Listing 3.14 The Prepare Table View Controller (PrepareTVC.swift)

import UIKit

class PrepareTVC: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        // Trigger Demo Code
        if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
            appDelegate.demo()
        }
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 0
    }
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 0
    }
}

The PrepareTVC.swift code is the bare minimum code required to show a table view. You might notice that the view is configured to return no rows or sections, as the intent for the moment is just to show model migration progress.

Update Groceries as follows to implement PrepareTVC.swift:

  1. Right-click the existing Groceries group and then select New Group.
  2. Set the new group name to Table View Controllers.
  3. Select the Table View Controllers group.
  4. Click File > New > File....
  5. Create a new iOS > Source > Swift File and then click Next.
  6. Set the filename to PrepareTVC and ensure the Groceries target is checked.
  7. Ensure the Groceries project directory is open and then click Create.
  8. Replace the contents of PrepareTVC.swift with the code from Listing 3.14.
  9. Select Main.storyboard.
  10. Set the Custom Class of the Table View Controller to PrepareTVC using Identity Inspector (Option+cmd.jpg+3) while the Table View Controller is selected.
  11. Remove the call to CDHelper.shared from the application:didFinishLaunchingWithOptions function of AppDelegate.swift. This code would otherwise trigger a migration before the user interface was ready.
  12. Remove the call to demo() from the application:applicationDidBecomeActive function of AppDelegate.swift. This code would otherwise trigger a migration before the user interface was ready.

Almost everything is in place to perform a manual migration; however, a new managed object model and mapping model are required to show what attributes map to where.

Update Groceries as follows to prepare the new model:

  1. Add a model version called Model 4 based on Model 3.
  2. Set Model 4 as the current model.
  3. Select Model 4.xcdatamodel.
  4. Delete the Amount entity.
  5. Add a new entity called Unit with a String attribute called name.
  6. Set the default value of the name attribute to New Unit.
  7. Create an NSManagedObject subclass of the Unit entity. When it comes time to save the class file, don’t forget to check the Groceries target and ensure that the Data Model group is selected.
  8. Create a new mapping model with Model 3 as the source and Model 4 as the target. When it comes time to save the mapping model file, don’t forget to check the Groceries target and save the mapping model as Model3toModel4.
  9. Select Model3toModel4.xcmappingmodel.
  10. Select the Unit entity mapping.
  11. Set the Source of the Unit entity to Amount and the Value Expression of the name destination attribute to $source.xyz. You should see the Unit entity mapping automatically renamed to AmountToUnit, as shown in Figure 3.12.

    Figure 3.12

    Figure 3.12 Mapping model for AmountToUnit

You’re almost ready to perform a migration; however, the fetch request in the demo function still refers to the old Amount entity. Listing 3.15 shows an updated version of this function.

Listing 3.15 Fetching Test Unit Data (AppDelegate.swift demo)0

func demo () {
    let context = CDHelper.shared.context
    let request = NSFetchRequest(entityName: "Unit")
    request.fetchLimit = 50

    do {
        if let units = try context.executeFetchRequest(request) as? [Unit] {
            for unit in units {
                print("Fetched Unit Object \(unit.name!)")
            }
        }
    } catch {
        print("ERROR executing a fetch request: \(error)")
    }
}

Update Groceries as follows to refer to the Unit entity instead of the Amount entity:

  1. Replace the demo function of AppDelegate.swift with the code shown in Listing 3.15. This code just fetches 50 Unit objects from the persistent store.

The migration manager is finally ready! Run the application and pay close attention! You should see the migration manager flash before your eyes, alerting you to the progress of the migration. The progress is also shown in the console log (see Figure 3.13).

Figure 3.13

Figure 3.13 Visible migration progress

Examine the contents of the ZUNIT table in the LocalStore.sqlite file using the techniques discussed in Chapter 2. The expected result is shown in Figure 3.14.

Figure 3.14

Figure 3.14 Successful use of migration manager

If you reproduced the results shown in Figure 3.14, give yourself a pat on the back because you successfully implemented three types of model migration! The rest of the book uses lightweight migrations, so it needs to be re-enabled. Before you continue, close the SQLite Database Browser.

Update Groceries as follows to re-enable lightweight migration:

  1. Set useMigrationManager to false in the localStore variable of CDHelper.swift.
  2. Set the NSMigratePersistentStoresAutomaticallyOption option in the localStore variable of CDHelper.swift to true by changing the 0 to a 1.
  3. Set the NSInferMappingModelAutomaticallyOption option in the localStore variable of CDHelper.swift to true by changing the 0 to a 1.
  4. Comment out the call to migrateStoreIfNecessary from the setupCoreData function of CDHelper.swift.
  5. Replace the code in the demo function of AppDelegate.swift with a call to CDHelper.shared. This ensures that the Core Data stack is set up without a reliance on particular entities.

The old mapping models and NSManagedObject subclasses of entities that don’t exist anymore are no longer needed. Although you could remove them, leave them in the project for reference sake.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020