Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

Blocks

Blocks are similar in many ways to functions, with the important exception that blocks have direct access to variables in their surrounding context. "Direct access" means that a block can use variables declared in its surrounding context even though those variables are not passed into the block through an argument list. Blocks are declared as follows:

^(argument_list){ body };

Blocks begin with a caret (^), followed by an argument list enclosed in parentheses and one or more statements enclosed in curly brackets. This expression is called a block literal, and is defined as follows:

  • Blocks can return a value by using a return statement in the body.
  • You don't have to specify the return type; the compiler determines the return type by looking at the body of the block.
  • If the block doesn't have arguments, the argument list is written as (void).
  • Block literals don't have a name; that is why they are sometimes called anonymous functions.

The following is a simple block example that takes an integer, doubles it, and returns the doubled value:

^(int n){ return n*2; };

You can call a block literal directly by appending values for its arguments surrounded in parentheses:

int j = ^(int n){ return n*2; }( 9 ); // j is now 18

This works, but it is a bit silly; you could get the same result by simply coding the following:

int j = 2 * 9;

In normal use, a block literal is assigned to a variable typed as pointer to a block, or used as an argument to a function or a method that takes a pointer to block argument.

Block Pointers

A variable that is a block pointer (a pointer to a block) is declared as follows:

   return_type (^name)(list of argument types);

This should look familiar; it has exactly the same form as the declaration of a function pointer, except that the * has been replaced with a ^.

The following example illustrates how to declare a block pointer, assign a block to it, and then call the block through the block pointer:

int (^doubler)(int);  // doubler is typed as a pointer to a block

doubler = ^(int n){ return n*2; };

int j = doubler( 9 );  // j is now 18

You can use block pointers as arguments to a function or a method:

// someFunction is a function that takes a block pointer as an argument

void someFunction( int (^blockArg)(int) );
int (^doubler)(int)= ^(int n){ return n*2; };

someFunction( doubler );

You can also use a block literal directly in a function call or an Objective-C message expression:

void someFunction( int (^blockArg)(int) );

someFunction( ^(int n){ return n*2; } );

Access to Variables

A block has:

  • Read-only access to automatic variables visible in its enclosing scope1
  • Read/write access to static variables declared in a function and to external variables
  • Read/write access to special variables declared as block variables

Here is a simple example of a block accessing a local variable in its enclosing scope:

int j = 10;

int (^blockPtr)(int) = ^(int n){ return j+n; };

int k = blockPtr( 5 );  // k is now 15

The value that a block uses for a local variable from its context is the value that local variable has when the flow of execution passes over the block literal, as shown here:

1 int j = 10;
2
3 int (^blockPtr)(int) = ^(int n){ return j+n; };
4
5 j = 20;
6
7 int k = blockPtr ( 5 );  // k is 15, not 25 as you might expect

In the preceding code:

  • Line 1: The local variable j is set to 10.
  • Line 3: The block blockPtr is defined. blockPtr has access to the local variable j. The value of j that blockPtr uses is bound to the value that j has when the program execution passes over this line. In effect, blockPtr now has a private copy of j that is set to 10.
  • Line 5: To show that blockPtr's value of j was bound in Line 3, j is reset to 20.
  • Line 7: The blockPtr is evaluated with an argument of 5 resulting in a return value of 10 (the value of j at the time Line 3 is executed) + 5 (the argument) = 15.

A block's access to local variables is read-only. The following code, which attempts to set the value of j from inside blockPtr, results in a compiler error because blockPtr's access to the local variable j is read-only:

int j = 10;

void (^blockPtr)(void) = ^(void){ j = 20; };

The compiler gives blocks access to static and external variables by pointer. The value the block sees for this type of variable is the value the variable has when the block is executed, not when the block is defined:

static int j = 10;

int (^blockPtr)(int) = ^(int n){ return j+n; };

j = 20;

int k = blockPtr ( 5 ); // k is 25

Block Variables

Variables declared with the new type modifier __block are called block variables:

__block int integerBlockVariable;

Block variables are visible to, and are shared and mutable by, any block defined in their scope. For example:

1 __block int j = 10;
2
3 void (^blockPtr_1)(void) = ^(void){ j += 15; };
4 void (^blockPtr_2)(void) = ^(void){ j += 25; };
5
6 blockPtr_1(); // j is now 25
7 blockPtr_2(); // j is now 50

In the preceding code:

  • Line 1: j is declared as a __block variable and set to 10.
  • Line 3: blockPtr_1 is defined. Because j has been declared as a __block variable, blockPtr_1 is permitted to set the value of j.
  • Line 4: Similarly, blockPtr_2 is permitted to set the value of j. Both blockPtr_1 and blockPtr_2 share read-write access to the variable j.
  • Line 6: b lockPtr_1 is evaluated, incrementing j by 15, resulting in a value of 25 for j.
  • Line 7: blockPtr_2 is evaluated, incrementing j by 25, resulting in a value of 50 for j.

Block variables start out on the stack like any other automatic variable. But if you copy a block that references a block variable, the block variable is moved from the stack to the heap along with the block (see the section Copying Blocks).

Blocks Are Stack Based

When you define a block literal inside a function or a method, the compiler creates a structure on the stack that holds the values of any local variables the block references, the addresses of read/write variables it references, and a pointer to block's executable code.

Blocks have the same lifetime as automatic variables. When the block literal goes out of scope, it is undefined, just like an automatic variable that has gone out of scope. Scope for a block literal is defined in the same way as scope for an automatic variable (see Chapter 2, "More About C Variables"): If a block literal is defined inside a compound statement (between a pair of curly brackets), the block literal goes out of scope when the program execution leaves the compound statement. If a block literal is defined in a function, it goes out of scope when the function returns. The following code is incorrect:

int (^doubler)(int);
{
   ...
   doubler = ^(int n){ return n*2; };
   ...
}
...
int j = doubler( 9 );  // WRONG! Bug!

In the preceding example, j is undefined. At the point where doubler(9) is executed, the block that the doubler variable points to has gone out of scope and the block may have been destroyed.

Global Blocks

You can also assign a block literal to a file-scope block pointer variable:

#import <Foundation/Foundation.h>

void (^logObject)(id) =
  ^(id obj){ NSLog( @"Object Description: %@", [obj description]); };

// logObject( someObj ) may be used anywhere in the file.

The compiler creates global-scope blocks in low memory like any other file-scope variable. Global blocks never go out of scope.

Blocks Are Objective-C Objects

It may seem surprising, but blocks are also Objective-C objects. A newly created block is the only example of an Objective-C object that is created on the stack. Blocks are instances of one of several private subclasses of NSObject. Apple doesn't provide the header for the block classes so you can't subclass them or do much of anything with them in an Objective-C sense except send them copy, retain, release, and autorelease messages. Copying and memory management for blocks are covered in the next sections.

Copying Blocks

One of the main uses of blocks is to pass a chunk of work (some code plus some context) out of the current scope for processing at a later time. Passing a block to a function or method that you call is safe (as long as that function or method is going to execute on the same thread). But what happens if you want to pass a block to a different thread or pass a block out of the current scope as a return value? When the current function or method returns, its stack frame is destroyed. Any blocks that were defined in its scope become invalid.

To preserve a block, you must copy it. When you copy a block, the copy is created on the heap. The heap-based copy is then safe to return up the stack to the calling function or pass off to another thread.

If you are coding in straight C, you can use the Block_copy() function, as follows:

int(^doublerCopy)(int) = Block_copy( ^(int n){ return n * 2; } );

In Objective-C, you can send a copy message to the block:

int(^doublerCopy)(int) = [^(int n){ return n * 2; } copy];

The two preceding examples are equivalent. In either statement, you could use a block pointer instead of the block literal.

When you copy a block, the new block gets copies of the values of any automatic variables that the block references. (The block accesses automatic variables by value. The value of the variable is copied into the block object when it is created.)

Memory Management for Blocks

If you copy a block with Block_copy(), you must eventually balance that call with a call to Block_release(). If you use the Objective-C copy message and you are using reference counting, you must balance the copy message with a release or an autorelease:

int(^getDoublerBlock())(int)
{
  int(^db)(int) = ^(int n){ return 2*n; };

  // The returned block is autoreleased. This balances the copy
  // and makes getDoublerBlock conform to the naming convention
  // for memory management.
  return [[db copy] autorelease];
}
...

int(^doubler )(int) = getDoublerBlock();  // Get the block

int sevenDoubled = doubler(7); // Use the block

Don't mix calls to Block_copy() and Block_release() with the Objective-C's copy and release messages.

If a block references a variable that holds an object, that object is retained when the block is copied and released when the block is released.

When copying a block inside a method body, the rules are slightly more complicated:

  • A direct reference to self in a block that is being copied causes self to be retained.
  • A reference to an object's instance variable (either directly or through an accessor method) in a block that is being copied causes self to be retained.
  • A reference to an object held in a local variable in a method causes that object, but not self, to be retained.

You should be careful when copying a block. If the code that copies the block is inside a method and the block refers to any of the object's instance variables, the copy causes self to be retained. It is easy to set up a retain cycle that prevents the object from ever being deallocated,

Listing 16.3 shows the interface section for a class that has an instance variable name to store a name, and a method logMyName to log that name. logMyName uses a block stored in the instance variable loggingBlock to do the actual logging.

Example 16.3. ObjectWithName.h

#import <Foundation/Foundation.h>

@interface ObjectWithName : NSObject
{
  NSString *name;
  void (^loggingBlock)(void);
}

- (void) logMyName;
- (id) initWithName:(NSString*) inName;

@end

Listing 16.4 shows the corresponding implementation file.

Example 16.4. ObjectWithName.m

 1 #import "ObjectWithName.h"
 2
 3 @implementation ObjectWithName
 4
 5 - (id) initWithName:(NSString*) inputName
 6 {
 7   if (self = [super init] )
 8     {
 9       name = [inputName copy];
10       loggingBlock = [^(void){ NSLog( @"%@", name ); } copy];
11     }
12   return self;
13 }
14
15 - (void) logMyName
16 {
17   loggingBlock();
18 }
19
20 - (void) dealloc
21 {
22   [loggingBlock release];
23   [name release];
24   [super dealloc];
25 }

ObjectWithName is a very simple class. However, this version of ObjectWithName has a retain cycle. If you create an ObjectWithName object, it won't be deallocated when you release it.

The problem is Line 10 of Listing 16.4:

loggingBlock = [^(void){ NSLog( @"%@", name ); } copy];

To store the block in the instance variable loggingBlock, you must copy the block literal and assign the copy to the instance variable. This is because the block literal goes out of scope when initWithName: returns. Copying the block puts the copy on the heap (like a normal Objective-C object). However, the block literal references the instance variable name, so the copy causes self to be retained, setting up a retain cycle. The block now has ownership of the object and the object has ownership of the block (because it has copied the block). The object's reference count never goes to zero and its dealloc method is never called.

You can fix this problem by changing Line 10 of Listing 16.4 so it reads as follows:

loggingBlock = [^(void){ NSLog( @"%@", inputName ); } copy];

With this change, the block copying operation retains the input argument inputName rather than the instance variable name. Because the block no longer references any of the object's instance variables, self is not retained and there is no retain cycle. The object will still have the same behavior because name and inputName have the same content.

Traps

Because blocks are stack-based objects, they present some traps for the unwary programmer. The following snippet of code is incorrect:

void(^loggingBlock)(void);

BOOL canWeDoIt = ...

// WRONG

if ( canWeDoIt )
  loggingBlock = ^(void){ NSLog( @"YES" ); };
else
  loggingBlock = ^(void){ NSLog( @"NO" ); };

// Possible crash
loggingBlock();

At the end of this snippet, loggingBlock is undefined. The if and else clauses of an if statement and the bodies of loops are separate lexical scopes, even if they are single statements and not compound statements. When the program execution leaves the scope, the compiler is free to destroy the block and leave loggingBlock pointing at garbage.

To fix this code, you must copy the block, and then remember to release it when you are finished:

void(^loggingBlock)(void);

BOOL canWeDoIt = ...

if ( canWeDoIt )
  loggingBlock = [^(void){ NSLog( @"YES" ); } copy];
else
  loggingBlock = [^(void){ NSLog( @"NO" ); } copy];

// Remember to release loggingBlock when you are finished

This example is also incorrect:

NSMutableArray *array = ...

// WRONG!

[array addObject: ^(void){ doSomething; }];
return array; //

Recall that objects added to collection objects receive a retain message; however in this case, the retain doesn't help because retain is a no-op for a stack-based block. Again, to fix the problem, you must copy the block:

NSMutableArray *array = ...
[array addObject: [[^(void){ doSomething; } copy] autorelease]];
return array;

In the preceding code snippet, the copy message puts a copy of the block on the heap. The autorelease message balances the copy. The retain message that the copied block receives when it is placed in the array is balanced by a release message when the block is later removed from the array or when the array is deallocated.

Blocks in Cocoa

Beginning with Mac OS X Snow Leopard (v 10.6), Apple has started deploying blocks throughout the Cocoa frameworks. This section briefly describes three areas where Apple has added features that use blocks.

Concurrency with NSOperationQueue

Concurrent (multithreaded) programming is very difficult to do correctly. To make it easier for programmers to write error-free multithreaded programs, Apple has introduced Grand Central Dispatch (GCD). GCD implements concurrency by creating and managing a thread pool. A thread pool is a group of threads that can be assigned to various tasks and reused when the task is finished. GCD hides the details of managing the thread pool and presents a relatively simple interface to programmers.

The Cocoa class NSOperationQueue provides a high-level interface to GCD. The idea is simple: You create an NSOperationQueue and add units of work, in the form of blocks, for the queue to execute. Underneath NSOperationQueue, GCD arranges to execute the block on a separate thread:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

[queue addOperationWithBlock: ^(void){ doSomething; } ];

// doSomething will now execute on a separate thread
  • A block passed to GCD (either through NSOperationQueue or through the low-level C interface) must have the form:

    void (^block)(void)
    

    It must not take arguments or return a value.

  • The GCD mechanism takes care of copying blocks submitted to it and releases them when no longer needed.

Collection Classes

The Foundation collection classes now have methods that enable you to apply a block to every object in the collection. NSArray has the following method:

- (void)enumerateObjectsUsingBlock:
       (void (^)(id obj, NSUInteger idx, BOOL *stop))block

This method calls block once for each object in the array; the arguments to block are:

  • obj, a pointer to the current object.
  • idx, the index of the current object (idx is the equivalent of the loop index in an ordinary for loop).
  • stop, a pointer to a BOOL. If the block sets stop to YES, -enumerateObjectsUsingBlock: terminates when the block returns. It is the equivalent of a break statement in an ordinary C loop.

Listing 16.5 uses -enumerateObjectsUsingBlock: to log a description of every object in an array.

Example 16.5. DescribeArrayContents.m

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  NSArray *array =
      [NSArray arrayWithObjects: @"dagger", @"candlestick",
           @"wrench", @"rope", nil];

  void (^loggingBlock)(id obj, NSUInteger idx, BOOL *stop) =
      ^(id obj, NSUInteger idx, BOOL *stop)
          { NSLog( @"Object number %d is a %@",
               idx, [obj description] ); };

  [array enumerateObjectsUsingBlock: loggingBlock];

  [pool drain];
  return 0;
}

If you build and run this program, you should see the following result:

DescribeArrayContents [50642:a0b] Object number 0 is a dagger
DescribeArrayContents [50642:a0b] Object number 1 is a candlestick
DescribeArrayContents [50642:a0b] Object number 2 is a wrench
DescribeArrayContents [50642:a0b] Object number 3 is a rope

Did-End Callbacks

I haven't said much about AppKit in this book, but I'll assume that you are familiar with saving files on Mac OS X. You select File > Save in an app, and if this is the first time the file is saved, a save sheet appears so you can name the file and select the location where it will be saved. You make your choices and click Save, or if you've changed your mind, you can click Cancel. After clicking one of the buttons, the sheet slides up and disappears.

When you invoke the method that begins the sheet, Cocoa gives you the chance to register some code to be executed when the user dismisses the sheet. (This is where you put the code that actually saved the file to disk.)

Prior to Mac OS X Snow Leopard (v 10.6), a Save sheet was started with this rather formidable method:

- (void)beginSheetForDirectory:(NSString *)path
         file:(NSString *)name
         modalForWindow:(NSWindow *)docWindow
         modalDelegate:(id)modalDelegate
         didEndSelector:(SEL)didEndSelector
         contextInfo:(void *)contextInfo

When the user dismisses the sheet, the sheet sends the object registered as modalDelegate, the message represented by the selector didEndSelector. Typically, the modalDelegate is the object that initiates the panel. didEndSelector has the form:

- (void)savePanelDidEnd:(NSSavePanel *)sheet
             returnCode:(int)returnCode
            contextInfo:(void *)contextInfo;
  • sheet is a pointer to the NSSavePanel object itself.
  • returnCode is an integer that specifies which button the user clicked on.
  • contextInfo is a blind pointer to the information passed to beginSheetForDirectory: ... when it was invoked. This is how you pass information from the object that invoked the sheet to the code responsible for acting on the user's input.

For Mac OS X Snow Leopard and beyond, the preceding method has been deprecated3 and replaced with the following method:

- (void)beginSheetModalForWindow:(NSWindow *)window
               completionHandler:(void (^)(NSInteger result))handler

You simply pass in a block to be executed when the sheet is dismissed. The block can capture any required context so the blind contexInfo pointer is not required.

Style Issues

Placing the statements of a block literal on a single line makes debugging difficult; for example:

^(void){doStuff; doMoreStuff; evenMore; keepGoing; lastStatement;}

You can set a debugger breakpoint on doStuff; but there is no way to step through or set a breakpoint on any of the other statements in the block. If you stop on doStuff; and try to step, the debugger jumps to the line following the block literal—making it impossible to debug the subsequent lines in the literal. If your block literal is non-trivial and may require debugging, you should put the statements in the block's body on separate lines, as follows:

^(void){doStuff;
        doMoreStuff;
        evenMore;
        keepGoing;
        lastStatement;}

As noted earlier, you can place a block literal directly in a function or method call:

someFunction( otherArgs, ^(void){ doStuff;
                                  doMoreStuff;
                                  evenMore;
                                  keepGoing;
                                  lastStatement;} );

You could also assign the block to a block pointer variable and use the block pointer as the argument. Which you choose is a matter of preference: Some people are annoyed at creating an extra variable (which the compiler will probably optimize away), whereas others find that putting the block literal inside the function call makes the code hard to read.

  • + Share This
  • 🔖 Save To Your Account

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