Sharing Data Locally between ios apps

本文介绍了一种在iOS应用之间安全高效地共享数据的方法,利用自定义URL方案和UIPasteboard实现离线状态下的数据传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://enharmonichq.com/sharing-data-locally-between-ios-apps/

code: https://github.com/EnharmonicHQ/AppDataSharing

In the sandboxed world of iOS development sharing data between applications can prove difficult. There are a number of reasons you may want your application to share data with other applications.

  • Releasing a paid app upgrade with a new SKU.
  • Moving user data to a universal binary.
  • Releasing a suite of complementary applications.
  • Partnerships with other developers.

Since iOS developers can’t share data directly through the file system, they need to find alternate solutions for their applications. Some common solutions include:

UIDocumentInteractionController

  • UIDocumentInteractionController — Allows the user to open a document in any other application that registers as being able to handle a particular document Uniform Type Identifier (UTI). The UIDocumentInteractionController has been used in the past as a means of opening a document in other applications on the device, for example, opening email attachments from the Mail app. Unfortunately, the UIDocumentInteractionController‘s UI displays only six applications. You cannot guarantee that your application will appear in the list. While the UIDocumentInteractionController has not been deprecated, theUIActivityViewController provides a more flexible replacement as of iOS 6.0.
    • Availability: iOS 3.2+
    • Pros
      • Allows sharing of common data types with a wide array of applications.
    • Cons
      • Allows control of the type of data sent to the UIDocumentInteractionController, but not the destinations.
      • Requires additional user interaction.
      • Limited number of data destinations may cause your application not to display in the list.

UIActivityViewController

  • UIActivityViewController — Allows the user to perform a number of actions with an array of data. For example they may print, email, copy, post to social media, or open in another application. You may create your own UIActivity subclasses to provide custom services to the user.

    • Availability: iOS 6.0+
    • Pros
      • Great for sharing common data types with a wide array of applications and social media.
      • Can supply an array of items for application to an activity. Objects should conform to UIActivityItemSource protocol.
      • Has the ability to set excluded activity types.
      • Paging UI allows for more data destinations than UIDocumentInteractionController.
    • Cons
      • You must define a custom activity type to restrict “Open In…” destinations of common data types.
      • Requires additional user interaction.
  • Shared Keychain Access — Allows you to securely store data to a shared keychain that other applications that are part of a suite of applications can access. All applications that share keychain access must use the same app ID prefix. For an example of shared keychain access in action. See Apple’s GenericKeychain sample code.

    • Availability: iOS 3.0+
    • Pros
      • Secure access to data.
    • Cons
      • You can only share data between applications that share a common app ID prefix.
      • The Keychain API on the iOS Simulator comes from OS X, which has different API than that of the iOS device.
  • Custom URL Scheme — Allows data to pass between applications using simple URLs.

    • Availability: iOS 3.0+
    • Pros
      • No network connection required.
      • Great for small amounts of data that you can easily encode into an escaped, legal URL.
    • Cons
      • You must encode data into an escaped legal URL. > Note: base64 encoding has seen common use turning serializable data into a string value. However, base64 strings may include characters that are invalid for use in URLs. You might consider using base64url. See Base 64 Encoding with URL and Filename Safe Alphabet for more information.
  • Web Service – Sync data through third party (e.g. Dropbox) or custom built web service.

    • Availability: iOS 2.0+
    • Pros
      • Useful for sharing and otherwise distributing large amounts of data.
    • Cons
      • Requires a network connection.
      • Web service implementation overhead.

One of the above solutions above may prove a great fit for your application, but they all leave room for another potential solution. What if you wanted tighter control over where the data goes and needed to do so while offline? You can accomplish these goals by combining custom URL schemes with a privateUIPasteboard.

  • UIPasteboard + URL Scheme – Share data locally on the device between installed applications.
    • Availability: iOS 3.0+. Note: This article takes advantage of API from iOS 4.2+.
    • Pros
      • Network connection not required.
      • Limit sharing to a small number of applications.
      • Share data programmatically with minimal user interaction.
      • Specify the destination of the data.
      • Check availability of a specific destination application.
      • Launch destination application from the current app.
      • Flexibility in the size of data shared.
    • Cons
      • Not well suited to open sharing of data. Note: UIActivityViewController may be a better fit if general sharing is needed.
      • Small implementation overhead.

A pasteboard allows an application to share data within the application or between applications. Most users have tacitly encountered a pasteboard while copying and pasting text from one place to another. You have three types of pasteboards available for use within your applications:

  1. General Pasteboard (UIPasteboardNameGeneral) — the general pasteboard is used for generic copy paste operations using all kinds of data.
  2. Find Pasteboard (UIPasteboardFind) — the find pasteboard is typically used for search operations. For example, holding the most recent search criteria from a UISearchBar.
  3. Custom Pasteboard — a custom pasteboard is intended for use within your own application or family of applications. You can give a custom pasteboard a unique identifier or let the system automatically generate one for you to use.

When sharing custom data objects between applications you should avoid using the general pasteboard for a couple reasons. First, when the application moves data to and from the pasteboard, the user is not explicitly initiating copy and paste operations as two distinct actions. Second, the user may have placed data on the general pasteboard for some other purpose. Perhaps they are copying and pasting a person’s name into one app and wants that data to remain on the pasteboard for later use. If the user goes to another application after you have placed data onto the pasteboard you could have inadvertently replaced or wiped out the data they were working with.

Looking at the description of the find pasteboard you can see that it just isn’t the right fit for this purpose since as it is reserved for text entered into aUISearchBar.

The custom pasteboard is best for the purpose of semi-privately sharing custom data between two applications. Custom pasteboards are identified using unique names. They can persist data beyond the application that creates them, allowing a pasteboard to hold onto data after the application is terminated or even after rebooting the device.

When writing or reading data to and from a pasteboard, you must specify a pasteboard type. Pasteboard types typically use a uniform type identifier (UTI) to identify the type of data going into and being retrieved from the pasteboard. See Apple’s UIPasteboard Class Reference documentation for additional information.

Person Editor Application UI __Person Viewer__ Application UI

To illustrate this method of using UIPasteboard to share application data, you will implement custom UIPasteboard and URL schemes in a sample Xcode project called AppDataSharing.

Download AppDataSharing Baseline Project

The sample project contains two target applications, each running in its own sandbox. The first application target, the Editor allows the user to edit information about a person, such as the person’s first name, last name, and date of birth. The second application target, the Person Viewer, displays a person’s name and date of birth in a pair of text labels. You can build and run each application by selecting the appropriate Scheme in the toolbar as shown below.

Scheme Selection

URL types tell the system that an application can handle a particular kind of URL. For example, a terminal application might register as being able to handle a URL scheme that begins with ‘ssh’. Applications are free to define almost any arbitrary URL type. In fact, if you have the Facebook app installed on an iOS device you can see it in action by using the fb url scheme. Simply open a Safari browser and enter fb:// into the address bar. You’ll see that the Facebook app launches on the device. See Akosma.com’s list of custom URL schemes for more examples.

What happens if an application isn’t installed on the device? You can see what happens using Safari with another URL scheme. This time enter foobar:// in the address bar. If there are no applications on the device registered to handle foobar:// URLs, you will see an error message like the one pictured below.

Safari Error Message

You will use the URL handling API in this sample as a signal to indicate that one application can handle data from another as follows:

  1. You will define a custom URL type for your application.
  2. You will check to see if another application on the device can handle this custom URL type.

__Person Viewer__ Target Info

  1. Open the AppDataSharing Xcode project.
  2. With the project selected in the source list, navigate to the Person Viewer target.
  3. Select the Info tab.
  4. Click the disclosure triangle to expand the URL Types section.
  5. Click the + button to add a new URL scheme.
  6. Set the identifier attribute to match the target application identifier. In this example you set it to com.EnharmonicHQ.Viewer.
  7. Set the URL Schemes attribute to match the target application identifier. In this example you set it to com.EnharmonicHQ.Viewer.
    > Note: You are free to use almost any string for the URL scheme, but you are using the application identifier in attempt to avoid collisions with other applications that may want to use the same URL Scheme. Make sure you choose a fairly unique scheme to avoid launching the wrong application.
  8. Set the Role attribute to Viewer. This tells the system that the application can read and present items associated with the given url type, but cannot manipulate or save them. The URL Type should look like the picture below.

Configured URL Type

Build and run the Person Viewer in the simulator. You can now launch the application from Safari’s address bar by typing this URL:com.EnharmonicHQ.Viewer://.

Before attempting to open a given URL, an application can make sure the system can handle the URL. This allows you to present enabled/disabled UI based on availability of another application that can handle the URL. After the Person Editor application has determined that the Person Viewer application is installed on the device and able to handle your custom URL scheme the Person Editor can launch the Person Viewer.

In the AppDataSharing Xcode project open ENHPersonEditorViewController.m and add the following line between the #import statements and the class extension to hold the custom URL scheme. We like to use the Bundle ID for this to guarantee uniqueness.

#import "ENHPersonEditorViewController.h"
#import "ENHPerson.h"

static NSString *kViewerURLScheme = @"com.EnharmonicHQ.Viewer"; 

@interface ENHPersonEditorViewController () <UITextFieldDelegate>
...

Now add a viewWillAppear: implementation as follows:

@implementation ENHPersonEditorViewController
...

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    UIBarButtonItem *actionButtonItem = [[UIBarButtonItem alloc] 
          initWithBarButtonSystemItem:UIBarButtonSystemItemAction
                               target:self
                               action:@selector(actionButtonItemTapped:)];
    NSString *urlString = [NSString stringWithFormat:@"%@://", 
                                    kViewerURLScheme];
    NSURL *url = [NSURL URLWithString:urlString];
    [actionButtonItem setEnabled:[[UIApplication sharedApplication] 
                                   canOpenURL:url]];
    [self.navigationItem setRightBarButtonItem:actionButtonItem];
}

...

Next the Person Editor application will need to launch the Viewer Application. add the following IBAction method.

-(IBAction)actionButtonItemTapped:(id)sender
{
    NSString *urlString = [NSString stringWithFormat:@"%@://", kViewerURLScheme];
    NSURL *url = [NSURL URLWithString:urlString];
    if ([[UIApplication sharedApplication] canOpenURL:url])
    {
        [[UIApplication sharedApplication] openURL:url];
    }
}

Build and run the Person Editor application. You should see an action button added to the right side of the top navigation bar. If the Person Editorapplication detects that another app can open com.EnharmonicHQ.Viewer:// URLs the button should enable. Tapping the action button should cause thePerson Viewer application to launch.

Having the ability to detect the availability of another application and subsequently launch the app represents a great first step. Now its time to get to the meat and potatoes of moving data from the Person Editor to the Person Viewer.

The AppDataSharing project has a basic data model for a person in the EHNPerson object. The ENHPerson class has a dataRepresentation method you can use to quickly get an NSData object comprised of the values from the given ENHPerson object. At this point you could simply throw the data representation up onto a general pasteboard and pull the data off of the pasteboard from the Viewer application. This approach could work, but with a few potential pitfalls. What happens as the applications evolve and the data model changes? Is this the only data model object that will need sharing? Is there other binary data (e.g. image data) that needs sharing? Is there other information that you might want to add to help the destination application process the incoming data? You can address these issues and provide a bit of future-proofing by wrapping the data in a container object.

Packaging the data in a container object is relatively simple. First, identify what you want added to the package. Here are the items you will add to the package object:

  • Source Application Name
  • Source Application Identifier
  • Source Application Version
  • Source Application Build
  • Payload

In the AppDataSharing project create a new NSObject subclass called ENHAppDataPackage, and add it to both the Person Editor and Person Viewertargets as shown below.

Adding Data Package Class to Targets

Update the ENHAppDataPackage.h file as follows:

#import <Foundation/Foundation.h>
...

@interface ENHAppDataPackage : NSObject <NSCoding>

// Metadata
@property (copy, nonatomic, readonly) NSString *sourceApplicationName;
@property (copy, nonatomic, readonly) NSString *sourceApplicationIdentifier;
@property (copy, nonatomic, readonly) NSString *sourceApplicationVersion;
@property (copy, nonatomic, readonly) NSString *sourceApplicationBuild;

// Application Data
@property (strong, nonatomic, readonly) NSData *payload;

-(id)initWithSourceApplicationName:(NSString *)sourceApplicationName
       sourceApplicationIdentifier:(NSString *)sourceApplicationIdentifier
          sourceApplicationVersion:(NSString *)sourceApplicationVersion
            sourceApplicationBuild:(NSString *)sourceApplicationBuild
                           payload:(NSData *)payload;

+(ENHAppDataPackage *)dataPackageForCurrentApplicationWithPayload:(NSData *)payload;

@end

The above code declares several properties to hold the application data and associated metadata. All properties are marked as readonly to make sure that packages are initialized only with the designated initializer and that the package contents disallow modification from outside of the class. Next a beefy designated initializer populates all the attributes of the ENHAppDataPackage object. Finally, to make things a little easier we’ve provided a convenience method in the starter project that takes a payload data object and turns it into an NSData object.

Switch to the ENHAppDataPackage.m file, and add the following class extension just above the implementation:

@interface ENHAppDataPackage ()

// Metadata
@property (copy, nonatomic, readwrite) NSString *sourceApplicationName;
@property (copy, nonatomic, readwrite) NSString *sourceApplicationIdentifier;
@property (copy, nonatomic, readwrite) NSString *sourceApplicationVersion;
@property (copy, nonatomic, readwrite) NSString *sourceApplicationBuild;

// Application Data
@property (strong, nonatomic, readwrite) NSData *payload;
@end

@implementation ENHAppDataPackage

...

This code takes all of the properties that were publicly declared as readonly, and redeclares them as readwrite properties for internal use within the class.

Add the following method implementations to initialize the ENHAppDataObject:

-(id)initWithSourceApplicationName:(NSString *)sourceApplicationName
       sourceApplicationIdentifier:(NSString *)sourceApplicationIdentifier
          sourceApplicationVersion:(NSString *)sourceApplicationVersion
            sourceApplicationBuild:(NSString *)sourceApplicationBuild
                           payload:(NSData *)payload
{
    self = [super init];
    if (self)
    {
        [self setSourceApplicationName:sourceApplicationName];
        [self setSourceApplicationIdentifier:sourceApplicationIdentifier];
        [self setSourceApplicationVersion:sourceApplicationVersion];
        [self setSourceApplicationBuild:sourceApplicationBuild];
        [self setPayload:payload];
    }

    return self;
}

+(ENHAppDataPackage *)dataPackageForCurrentApplicationWithPayload:(NSData *)payload
{
    NSDictionary *infoPlist = [[NSBundle mainBundle] infoDictionary];
    NSString *currentApplicationName = [infoPlist valueForKey:@"CFBundleDisplayName"];
    NSString *currentApplicationIdentifier = [infoPlist valueForKey:@"CFBundleIdentifier"];
    NSString *currentApplicationVersion = [infoPlist valueForKey:@"CFBundleShortVersionString"];
    NSString *currentApplicationBuild = [infoPlist valueForKey:@"CFBundleVersion"];

    ENHAppDataPackage *package = [[[self class] alloc] initWithSourceApplicationName:currentApplicationName
                                                         sourceApplicationIdentifier:currentApplicationIdentifier
                                                            sourceApplicationVersion:currentApplicationVersion
                                                              sourceApplicationBuild:currentApplicationBuild
                                                                             payload:payload];

    return package;
}

Synthesize the accessors as follows:

#pragma mark - Accessors
@synthesize sourceApplicationName = _sourceApplicationName;
@synthesize sourceApplicationIdentifier = _sourceApplicationIdentifier;
@synthesize sourceApplicationVersion = _sourceApplicationVersion;
@synthesize sourceApplicationBuild = _sourceApplicationBuild;
@synthesize payload = _payload;

To encode the entire package into a single file for placement on the pasteboard, the ENHAppDataPackage class will implement the two methods declared by the NSCoding protocol initWithCoder: and encodeWithCoder:. Add the following method implementations to ENHAppDataPackage.m:

#pragma mark - NSCoding

-(void)encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeObject:self.sourceApplicationName forKey:kENHSourceApplicationNameKey];
    [encoder encodeObject:self.sourceApplicationIdentifier forKey:kENHSourceApplicationIdentifierKey];
    [encoder encodeObject:self.sourceApplicationVersion forKey:kENHSourceApplicationVersionKey];
    [encoder encodeObject:self.sourceApplicationBuild forKey:kENHSourceApplicationBuildKey];
    [encoder encodeObject:self.payload forKey:kENHPayloadKey];
}

-(id)initWithCoder:(NSCoder *)decoder
{
    NSString *sourceApplicationName = [decoder decodeObjectForKey:kENHSourceApplicationNameKey];
    NSString *sourceApplicationIdentifier = [decoder decodeObjectForKey:kENHSourceApplicationIdentifierKey];
    NSString *sourceApplicationVersion = [decoder decodeObjectForKey:kENHSourceApplicationVersionKey];
    NSString *sourceApplicationBuild = [decoder decodeObjectForKey:kENHSourceApplicationBuildKey];
    NSData *payload = [decoder decodeObjectForKey:kENHPayloadKey];

    return [self initWithSourceApplicationName:sourceApplicationName
                   sourceApplicationIdentifier:sourceApplicationIdentifier
                      sourceApplicationVersion:sourceApplicationVersion
                        sourceApplicationBuild:sourceApplicationBuild
                                       payload:payload];
}

Next add the following method declarations to the ENHAppDataPackage.h file:

-(NSData *)dataRepresentation;
+(ENHAppDataPackage *)unarchivePackageData:(NSData *)data;

These two data helper methods will make it easy to access the archived version of the data package and then subsequently unarchive the package on the other side. Add their method implementations to ENHAppDataPackage.m as follows:

#pragma mark - Data Helpers

-(NSData *)dataRepresentation
{
    NSMutableData *data = [[NSMutableData alloc] init];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    [archiver encodeObject:self forKey:kENHPackageDataKey];
    [archiver finishEncoding];

    return [NSData dataWithData:data];
}

+(ENHAppDataPackage *)unarchivePackageData:(NSData *)data
{
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    ENHAppDataPackage *package = [unarchiver decodeObjectForKey:kENHPackageDataKey];
    [unarchiver finishDecoding];

    return package;
}

Uniform type identifiers uniquely identify a particular type of data format. For example, the UTI of a text document is public.text. The public.text UTI is just one of many UTIs that are declared system-wide in iOS. You can see a complete list of common system identifiers in the System-Declared Uniform Type Identifiers documentation.

Note: When using system-defined UTIs in your code, you should use the constants defined in UTCoreTypes.h in theMobileCoreServices framework when available, rather than the actual UTI strings.

The AppDataSharing project will package the ENHPerson object into an ENHAppDataPackage object. The ENHAppDataPackage doesn’t fit into any of the system defined UTIs, so the ENHAppDataPackage object will need a custom UTI.

Open the ENHAppDataPackage.h file and add the following between the #import and @interface:

#import <Foundation/Foundation.h>
...
extern NSString *kENHAppDataPackageUTI;
...
@interface ENHAppDataPackage : NSObject <NSCoding>

Open the ENHAppDataPackage.m file and define the UTI just above the static key definitions.

#import "ENHAppDataPackage.h"
...
NSString *kENHAppDataPackageUTI = @"com.EnharmonicHQ.AppDataSharing.DataPackage";
...
static NSString *kENHPackageDataKey = @"kENHPackageDataKey";

Apple recommends that custom UTIs use reverse-DNS notation to ensure name uniqueness between UTIs (for example, com.myCompany.myApp.myType). Since UTIs use reverse-DNS notation, only ASCII characters (A–Z, a–z), the digits 0 through 9, the dot (“.”), and the hyphen (“-”) are allowed. See the Uniform Type Identifiers Overview documentation for additional information.

Note: Any illegal character appearing in a UTI string will cause the system to reject it as invalid. Be aware that no errors are generated for invalid UTIs.

The AppDataSharing project needs a way to manage the process of moving the data from one app to the next. Here are the steps needed to complete the process.

  1. The source application packages the data object for sharing.
  2. The source application creates a custom UIPasteboard with a unique identifier.
  3. The source application writes package data to the custom UIPasteboard and specifies a UTI for the data.
  4. The source application launches the destination application.
  5. The destination application finds the custom UIPasteboard.
  6. The destination application reads the package data from the custom UIPasteboard.
  7. The destination application unpacks the shared data object.
  8. The destination application informs interested objects that the shared data object is available.
  9. The destination application handles the data.
  10. The destination application cleans up the pasteboard data.

Add a new NSObject subclass called ENHAppDataSharingController to the project. Add the new class to both the Person Editor and Person Viewertargets.

Open the ENHAppDataSharingController.h file and replace the boilerplate code with the following:

#import <Foundation/Foundation.h>

@class ENHAppDataPackage;

typedef void(^ENHAppDataSharingSendDataHandler)(BOOL *sent, NSError *error);
typedef void(^ENHAppDataSharingHandler)(ENHAppDataPackage *retrievedPackage, NSError *error);

extern NSString *kReadPasteboardDataQuery;

typedef enum
{
    ENHAppDataSharingErrorTypeNoApplicationAvailableForScheme = 100,
    ENHAppDataSharingErrorTypeNoPasteboardForName = 200,
    ENHAppDataSharingErrorTypeNoDataFound = 300,
} ENHAppDataSharingErrorType;

@interface ENHAppDataSharingController : NSObject

+(void)sendDataToApplicationWithScheme:(NSString *)scheme
                           dataPackage:(ENHAppDataPackage *)dataPackage
                     completionHandler:(ENHAppDataSharingSendDataHandler)completionHandler;

+(void)handleSendPasteboardDataURL:(NSURL *)sendPasteboardDataURL
                 completionHandler:(ENHAppDataSharingHandler)completionHandler;

@end

In the above code, a pair of block typedefs outline the completion handlers. The header also sets up a constant data query string and a set of error codes for passing into the completion handlers. The header also declares two class methods. One for sending outbound data packages from the source application and another to handle inbound data in the destination application.

Switch to the ENHAppDataSharingController.m file and and add the following above the @implementation.

#import "ENHAppDataPackage.h"

NSString *kReadPasteboardDataQuery = @"ReadPasteboardData";
NSString *const AppDataSharingErrorDomain = @"AppDataSharingErrorDomain"; 

When the source application creates a private pasteboard it will need to have a unique name. You could choose any unique string you wish. This project will allow the system to automatically generate and assign a unique name. There is one hitch in this idea; how does the destination application know which pasteboard name to use when it wants to retrieve the data? The solution is to pass the pasteboard name in the URL used to launch the destination application.

Add the following method implementation to ENHAppDataSharingController.m:

#pragma mark - URLs

+(NSURL *)sendPasteboardDataURLForScheme:(NSString *)scheme pasteboardName:(NSString *)pasteboardName
{
    NSString *urlString = [NSString stringWithFormat:@"%@://?%@#%@", scheme, kReadPasteboardDataQuery, pasteboardName];

    return [NSURL URLWithString:urlString];
}

This method generates a URL using a scheme, the predefined kReadPasteboardDataQuery string, and the pasteboard name. You can see that the url follows the following format: scheme://?query#fragment. In this string the ‘?’ denotes the start of a query and the ‘#’ marks the beginning of a fragment. This makes it easy to parse the URL in the destination application. You can place the ReadPasteboardData string in the query portion of an NSURL and the pasteboard name into the fragment portion.

Add the following method implementation to ENHAppDataSharingController.m:

+(void)sendDataToApplicationWithScheme:(NSString *)scheme
                           dataPackage:(ENHAppDataPackage *)dataPackage
                     completionHandler:(ENHAppDataSharingSendDataHandler)completionHandler;
{
    NSError *error = nil;

    // Setup the Pasteboard
    UIPasteboard *pasteboard = [UIPasteboard pasteboardWithUniqueName];
    [pasteboard setPersistent:YES]; // Makes sure the pasteboard lives beyond app termination.
    NSString *pasteboardName = [pasteboard name];

    // Write The Data
    NSData *data = [dataPackage dataRepresentation];
    NSString *pasteboardType = kENHAppDataPackageUTI;
    [pasteboard setData:data forPasteboardType:pasteboardType];

    // Launch the destination app
    NSURL *sendingURL = [[self class] sendPasteboardDataURLForScheme:scheme pasteboardName:pasteboardName];
    if ([[UIApplication sharedApplication] canOpenURL:sendingURL])
    {
        completionHandler(YES, nil);
        [[UIApplication sharedApplication] openURL:sendingURL];
    }
    else
    {
        [pasteboard setData:nil forPasteboardType:pasteboardType];
        [pasteboard setPersistent:NO];

        NSDictionary *errorInfoDictionary = @{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%@ %@", 
            NSLocalizedString(@"No application was found to handle the url:", nil), sendingURL]};
        error = [NSError errorWithDomain:AppDataSharingErrorDomain
                                    code:ENHAppDataSharingErrorTypeNoApplicationAvailableForScheme
                                userInfo:errorInfoDictionary];
    }

    completionHandler(NO, error);
}

This method performs the bulk of the work required to send data to the destination application.

  • First you create a unique pasteboard that will persist beyond the lifetime of the source application.
  • Then, you write the data to the new pasteboard and the system attempts to launch the destination application using your special query URL. If none of the applications installed on the device are capable of opening the URL the method clears out the pasteboard and removes the pasteboard persistence.
  • Finally, the completion handler is called back.

To verify that our application was launched with the appropriate URL, add the following method implementation to ENHAppDelegate.m:

- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation
{
    NSLog(@"Application launched with URL: %@", url);
}

Build and run the Person Editor application to load it into the simulator. Next, build and run the Person Viewer application. While the Person Viewerapplication is running, return to the home screen (Shift-⌘-H) and launch the Person Editor application.

Populate the Person Editor UI with a first name, last name, and date of birth. Now, tap the action button in the navigation bar to launch the Person Viewerapplication. In the Xcode console you will see a log message similar to the one below.

Console output of the __Person Viewer__ Application when launched via URL

The application receiving the data package will next need to know how to handle your special URL in order to pull it from the correct pasteboard. Add the following method implementation to ENHAppDataSharingController.m:

+(void)handleSendPasteboardDataURL:(NSURL *)sendPasteboardDataURL
                 completionHandler:(ENHAppDataSharingHandler)completionHandler;
{
    NSString *query = [sendPasteboardDataURL query];
    NSString *pasteboardName = [sendPasteboardDataURL fragment];
    NSAssert2(([query isEqualToString:kReadPasteboardDataQuery] && pasteboardName), 
        @"Malformed or incorrect url sent to %@. URL: %@", 
        NSStringFromSelector(_cmd), sendPasteboardDataURL);

    ENHAppDataPackage *dataPackage = nil;
    NSError *error = nil;

    NSString *pasteboardType = kENHAppDataPackageUTI;
    UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:pasteboardName create:NO];
    if (pasteboard)
    {
        NSData *data = [pasteboard dataForPasteboardType:pasteboardType];
        if (data)
        {
            dataPackage = [ENHAppDataPackage unarchivePackageData:data];
        }
        else
        {
            NSDictionary *errorInfoDictionary = @{NSLocalizedDescriptionKey: [NSString stringWithFormat:
                @"%@ %@", NSLocalizedString(@"No data found on pasteboard with name:", nil), 
                pasteboardName]};
            error = [NSError errorWithDomain:AppDataSharingErrorDomain
                                        code:ENHAppDataSharingErrorTypeNoDataFound
                                    userInfo:errorInfoDictionary];
        }
        [pasteboard setData:nil forPasteboardType:pasteboardType];
        [pasteboard setPersistent:NO];
    }
    else
    {
        NSDictionary *errorInfoDictionary = @{NSLocalizedDescriptionKey: 
            [NSString stringWithFormat:@"%@ %@", 
            NSLocalizedString(@"No pasteboard found for name:", nil), pasteboardName]};
        error = [NSError errorWithDomain:AppDataSharingErrorDomain
                                    code:ENHAppDataSharingErrorTypeNoPasteboardForName
                                userInfo:errorInfoDictionary];
    }
    completionHandler(dataPackage, error);
}

This method starts out by asserting that an appropriate URL was provided. It then attempts to find the named private pasteboard and retrieve the package of data. If the system cannot find the pasteboard or if the pasteboard holds no data, an error is generated. Finally the data package (or error) is passed to the completion handler. Also note that when finished with the pasteboard this method cleans up by setting the pasteboard contents to nil.

Add the following imports to ENHPersonEditorViewController.m:

// Data Sharing
#import "ENHAppDataPackage.h"
#import "ENHAppDataSharingController.h"

Replace the actionButtonItemTapped: implementation in the ENHPersonEditorViewController.m file with the following:

-(IBAction)actionButtonItemTapped:(id)sender
{
    NSData *personData = [self.person dataRepresentation];
    ENHAppDataPackage *package = [ENHAppDataPackage dataPackageForCurrentApplicationWithPayload:personData];
    [ENHAppDataSharingController sendDataToApplicationWithScheme:kViewerURLScheme
                                                     dataPackage:package
                                               completionHandler:^(BOOL *sent, NSError *error) {
        if (sent)
        {
            NSLog(@"Data Package Sent");
        }
        else if (error)
        {
            NSLog(@"Error sending data package: %@", [error localizedDescription]);
        }
    }];
}

This action method now packages up the ENHPerson object and sends it off to the Person Viewer application. The completion handler then logs whether the package was sent successfully and will log any errors.

The project just needs to implement some handling code to receive the package data. Replace the application:openURL:sourceApplication:annotation:method implementation of ENHAppDelegate.m with the following:

- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation
{
    NSLog(@"Application launched with URL: %@", url);
    [[NSNotificationCenter defaultCenter] postNotificationName:@"ENHHandleOpenURLNotification" object:url];
    return YES;
}

The app delegate doesn’t handle the data directly, instead it posts a notification to interested objects that the application has a new URL to open. Update theENHPersonDetailViewController.m file with the following imports and code and replace the existing viewDidLoad: implementation:

#import "ENHAppDataSharingController.h"
#import "ENHAppDataPackage.h"
...

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self setTitle:NSLocalizedString(@"Viewer", nil)];
    [self.nameLabel setText:@""];
    [self.dateOfBirthLabel setText:@""];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleOpenURL:)
                                                 name:@"ENHHandleOpenURLNotification"
                                               object:nil];
}

-(void)handleOpenURL:(NSNotification *)notification
{
    NSURL *url = [notification object];
    if ([[url query] isEqualToString:kReadPasteboardDataQuery])
    {
        [ENHAppDataSharingController handleSendPasteboardDataURL:url
                                               completionHandler:^(ENHAppDataPackage *retrievedPackage, NSError *error) {
            if (retrievedPackage)
            {
                NSData *packageData = [retrievedPackage payload];
                ENHPerson *person = [ENHPerson personWithData:packageData];
                [self setPerson:person];
            }
            else
            {
                NSLog(@"Error handling pasteboard data url: %@", [error localizedDescription]);
            }
        }];
    }
}

Build and run the Person Viewer to install the revised application on the simulator/device. You won’t see any visible changes in the Person Viewerapplication, at least not yet…

Build and run the Person Editor application. Set the First NameLast Name, and Date of Birth attributes. Click the action button in the top right. The Person Viewer application should now launch and populate the user interface with data created in the Person Editor.

To add a small amount of security to the project, replace the application:openURL:sourceApplication:annotation: method implementation inENHAppDelegate.m with the following:

- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation
{
    NSLog(@"Application launched with URL: %@", url);
    if ([sourceApplication hasPrefix:@"com.EnharmonicHQ"])
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"ENHHandleOpenURLNotification" object:url];
        return YES;
    }

    return NO;
}

This small change checks that the URL came from an application with a matching prefix to the application identifier.

While this is not totally secure, it can prevent unintended behavior in the application. To learn more about securing data read Properly encrypting with AES with CommonCrypto by Rob Napier.

Download AppDataSharing Solution Project

Moving data from the Person Editor application to the Person Viewer works well, but what if you launch the Person Viewer without any data to view? Use the same URL handling strategies to allow the Person Viewer to request data from the Person Editor application. Below is an outline of the key steps you will need to address.

  • Add a new URL type and scheme in the Person Editor application target.
  • Create a new query URL that uses the scheme you created for the Person Editor target.
  • In the Person Viewer application check to see if the Person Editor application is installed and is capable of handling the new scheme.
  • Enable UI based on handling capability.
  • Setup URL handler.

The project provided a small amount of future-proofing by sending some metadata along with each data package. This metadata could be used to ensure thePerson Viewer is capable of handling the version of the inbound data. Extend the ENHPerson class with your custom attributes, and add appropriate handling code to the Person Viewer. You might also consider adding a payload object classname attribute to the ENHAppDataPackage for additional future-proofing where you might have multiple payload types.

Having the device swap back and forth between applications can provide a jarring user experience. Therefore, we strongly recommend that you keep application switching to a minimum. If you find that your own projects need to frequently switch between applications to get the job done, please consider using an alternative solution (e.g. web service, shared keychain access, etc.).

  1. Apple’s Generic Keychain Sample Code
  2. Base 64 Encoding with URL and Filename Safe Alphabet
  3. UIPasteboard Class Reference
  4. AppDataSharing Baseline Project
  5. A list of some Custom URL Schemes
  6. System-Declared Uniform Type Identifiers
  7. Uniform Type Identifiers Overview
  8. Properly encrypting with AES with CommonCrypto
  9. AppDataSharing Solution Project
  10. Creative Commons BY-NC-ND License
  11. AppDataSharing Project MIT License

Special thanks to Jim Turner for suggesting UIPasteboard as a way to move data and inspiring this article. I’ve lost count of how many beers I owe you by now.

This “Tutorial: Sharing Data Locally Between iOS Apps” article is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

The code in the AppDataSharing sample project is licensed under MIT License.

基于数据挖掘的音乐推荐系统设计与实现 需要一个代码说明,不需要论文 采用python语言,django框架,mysql数据库开发 编程环境:pycharm,mysql8.0 系统分为前台+后台模式开发 网站前台: 用户注册, 登录 搜索音乐,音乐欣赏(可以在线进行播放) 用户登陆时选择相关感兴趣的音乐风格 音乐收藏 音乐推荐算法:(重点) 本课题需要大量用户行为(如播放记录、收藏列表)、音乐特征(如音频特征、歌曲元数据)等数据 (1)根据用户之间相似性或关联性,给一个用户推荐与其相似或有关联的其他用户所感兴趣的音乐; (2)根据音乐之间的相似性或关联性,给一个用户推荐与其感兴趣的音乐相似或有关联的其他音乐。 基于用户的推荐和基于物品的推荐 其中基于用户的推荐是基于用户的相似度找出相似相似用户,然后向目标用户推荐其相似用户喜欢的东西(和你类似的人也喜欢**东西); 而基于物品的推荐是基于物品的相似度找出相似的物品做推荐(喜欢该音乐的人还喜欢了**音乐); 管理员 管理员信息管理 注册用户管理,审核 音乐爬虫(爬虫方式爬取网站音乐数据) 音乐信息管理(上传歌曲MP3,以便前台播放) 音乐收藏管理 用户 用户资料修改 我的音乐收藏 完整前后端源码,部署后可正常运行! 环境说明 开发语言:python后端 python版本:3.7 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:pycharm
MPU6050是一款广泛应用在无人机、机器人和运动设备中的六轴姿态传感器,它集成了三轴陀螺仪和三轴加速度计。这款传感器能够实时监测并提供设备的角速度和线性加速度数据,对于理解物体的动态运动状态至关重要。在Arduino平台上,通过特定的库文件可以方便地与MPU6050进行通信,获取并解析传感器数据。 `MPU6050.cpp`和`MPU6050.h`是Arduino库的关键组成部分。`MPU6050.h`是头文件,包含了定义传感器接口和函数声明。它定义了类`MPU6050`,该类包含了初始化传感器、读取数据等方法。例如,`begin()`函数用于设置传感器的工作模式和I2C地址,`getAcceleration()`和`getGyroscope()`则分别用于获取加速度和角速度数据。 在Arduino项目中,首先需要包含`MPU6050.h`头文件,然后创建`MPU6050`对象,并调用`begin()`函数初始化传感器。之后,可以通过循环调用`getAcceleration()`和`getGyroscope()`来不断更新传感器读数。为了处理这些原始数据,通常还需要进行校准和滤波,以消除噪声和漂移。 I2C通信协议是MPU6050与Arduino交互的基础,它是一种低引脚数的串行通信协议,允许多个设备共享一对数据线。Arduino板上的Wire库提供了I2C通信的底层支持,使得用户无需深入了解通信细节,就能方便地与MPU6050交互。 MPU6050传感器的数据包括加速度(X、Y、Z轴)和角速度(同样为X、Y、Z轴)。加速度数据可以用来计算物体的静态位置和动态运动,而角速度数据则能反映物体转动的速度。结合这两个数据,可以进一步计算出物体的姿态(如角度和角速度变化)。 在嵌入式开发领域,特别是使用STM32微控制器时,也可以找到类似的库来驱动MPU6050。STM32通常具有更强大的处理能力和更多的GPIO口,可以实现更复杂的控制算法。然而,基本的传感器操作流程和数据处理原理与Arduino平台相似。 在实际应用中,除了基本的传感器读取,还可能涉及到温度补偿、低功耗模式设置、DMP(数字运动处理器)功能的利用等高级特性。DMP可以帮助处理传感器数据,实现更高级的运动估计,减轻主控制器的计算负担。 MPU6050是一个强大的六轴传感器,广泛应用于各种需要实时运动追踪的项目中。通过 Arduino 或 STM32 的库文件,开发者可以轻松地与传感器交互,获取并处理数据,实现各种创新应用。博客和其他开源资源是学习和解决问题的重要途径,通过这些资源,开发者可以获得关于MPU6050的详细信息和实践指南
### Kali Linux on iOS: Installation and Compatibility Kali Linux is a Debian-based distribution designed for advanced penetration testing, security auditing, digital forensics, and reverse engineering tasks. However, due to the restrictive nature of Apple's iOS ecosystem, installing Kali Linux directly onto an iOS device involves significant challenges[^2]. Apple enforces strict app sandboxing policies that prevent unauthorized execution of custom binaries or scripts outside its App Store guidelines. This makes it impossible to run full-fledged distributions like Kali natively without jailbreaking the device. #### Jailbroken Devices For users who have jailbroken their devices, tools such as **iSH Shell** provide limited access to running lightweight Linux environments via containerization techniques. While iSH supports Alpine Linux by default, more complex setups may allow partial functionality similar to Kali through additional configuration steps: 1. Install `iSH` from Cydia (the package manager used after jailbreaking). 2. Use chroot commands within iSH to simulate basic Linux operations. 3. Attempt porting specific utilities compatible with ARM architectures supported by iOS hardware versions[^3]. However, this approach does not fully replicate all features available in standard desktop installations because many core components rely heavily upon x86/x64 architecture optimizations unavailable under mobile platforms' constraints. #### Alternative Solutions Without Jailbreaks If avoiding jailbreaks altogether while still gaining some cybersecurity capabilities provided traditionally only inside Kali itself then consider these alternatives instead: - Utilize cloud services offering remote terminal sessions connected securely over SSH protocols where actual processing happens server-side rather than locally at client end point reducing dependency towards local system resources significantly thus enabling even small handheld gadgets perform heavy computational works effortlessly regardless underlying OS type present thereon currently active session period duration limit applies accordingly depending chosen service provider terms conditions agreed beforehand signing contract agreement formals etcetera... Example Python Script Demonstrating Connection Establishment Using Paramiko Library For Secure Shell Communication Purposes Only : ```python import paramiko ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect('your-cloud-instance-ip', username='username', password='password') stdin, stdout, stderr = ssh.exec_command('ls') print(stdout.read().decode()) ssh.close() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值