1

I am sending a keyed archive as an attachment to an email using MFMailComposeController. My app that reads the email attachment gets the keyed archive but unarchiving it gives nil values. Details as follows:

The sending view controller:

    - (IBAction)saveAndSend:(id)sender {

    // Email Subject
    NSString *emailTitle = @"Test Email";
    // Email Content
    NSString *messageBody = @"TimeChime Trace Data";
    // To address
    NSArray *toRecipents = [NSArray arrayWithObject:@"summitpointsoftware@gmail.com"];

    if (![MFMailComposeViewController canSendMail]) {
        NSLog(@"Mail services are not available.");
        return;
    }
    MFMailComposeViewController *mc = [[MFMailComposeViewController alloc] init];
    mc.mailComposeDelegate = self;
    [mc setSubject:emailTitle];
    [mc setMessageBody:messageBody isHTML:NO];
    [mc setToRecipients:toRecipents];
    // attach NSData to message. Note: file name extension will be used by the mail client to launch
    // the handler with this extension in its info.plist.

    NSData *data = [[NSData alloc]init];
    data = [NSKeyedArchiver archivedDataWithRootObject:self.items];

    [mc addAttachmentData:data mimeType:@"application/CustomUTIHandler" fileName:@"traceData.nrc"];

    // Present mail view controller on screen
    [self presentViewController:mc animated:YES completion:NULL];

}

The array being archived before sending (array of NRCEventItem):

    Printing description of self->_items:
<__NSArrayM 0x1c045e390>(
Event Date/time: 2017-12-29 00:08:17 +0000, Event Sender: <AppDelegate: 0x1c40523c0>, Event Description: appl did become active,
Event Date/time: 2017-12-29 00:15:32 +0000, Event Sender: <HourlyChimeTableViewController: 0x10282fa00>, Event Description: triggerNotification,
Event Date/time: 2017-12-29 00:30:32 +0000, Event Sender: <HourlyChimeTableViewController: 0x10282fa00>, Event Description: triggerNotification,
Event Date/time: 2017-12-29 00:45:32 +0000, Event Sender: <HourlyChimeTableViewController: 0x10282fa00>, Event Description: triggerNotification,
Event Date/time: 2018-01-05 00:00:32 +0000, Event Sender: <HourlyChimeTableViewController: 0x10282fa00>, Event Description: triggerNotification,
Event Date/time: 2018-01-05 00:15:32 +0000, Event Sender: <HourlyChimeTableViewController: 0x10282fa00>, Event Description: triggerNotification,
Event Date/time: 2018-01-05 00:30:32 +0000, Event Sender: <HourlyChimeTableViewController: 0x10282fa00>, Event Description: triggerNotification,
Event Date/time: 2018-01-05 00:45:32 +0000, Event Sender: <HourlyChimeTableViewController: 0x10282fa00>, Event Description: triggerNotification
)

The EventItem class conforms to NSCoding:

    //
//  NRCEventItem.h
//  Hourly Chime2
//
//  Created by Nelson Capes on 12/12/17.
//  Copyright © 2017 Nelson Capes. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NRCEventItem : NSObject <NSCoding>
@property (nonatomic) NSDate *eventDateTime;
@property (nonatomic) NSString *eventSender;
@property (nonatomic) NSString *eventDescription;
@end

The info.plist of the receiving app:

enter image description here

The code for the receiving app that gets the email attachment and unarchives the data:

- (void) receiveNotificationFromAppDelegate:(NSNotification *) notification {
NSLog(@"userInfo trace %@",notification.userInfo[KTraceKey]);

self.items = (NSMutableArray*)[NSKeyedUnarchiver unarchiveObjectWithData:notification.userInfo[KTraceKey]];
[self.tableView reloadData];

}

The data after being unarchived:

    Printing description of path:
/private/var/mobile/Containers/Data/Application/CCDA0B96-8DD6-496E-B0A0-08B3C072E76A/Documents/Inbox/traceData-40.nrc
Printing description of items:
<__NSArrayM 0x1c4240720>(
Event Date/time: (null), Event Sender: (null), Event Description: (null),
Event Date/time: (null), Event Sender: (null), Event Description: (null),
Event Date/time: (null), Event Sender: (null), Event Description: (null),
Event Date/time: (null), Event Sender: (null), Event Description: (null),
Event Date/time: (null), Event Sender: (null), Event Description: (null),
Event Date/time: (null), Event Sender: (null), Event Description: (null),
Event Date/time: (null), Event Sender: (null), Event Description: (null),
Event Date/time: (null), Event Sender: (null), Event Description: (null)
)

Clearly, the NSKeyedUnarchiver recognized the data as being an array, and that the elements in the array are NRCEventItem. However, the properties in each element of the array are now nil. I also tested the archiving step by immediately unarchiving the archive, and the properties of the array were as before they were archived.

Conclusion: somehow, the data is being altered in transmission using the MFMailComposeViewController.

If anyone can shed any light on this, I would really appreciate it.

Nelson Capes
  • 411
  • 3
  • 12
  • Can you include the cost that implements the NSCoding protocol? Have you tried doing a round trip in the same app? And does that work? Does the NSData look different on the other end? – Dave Weston Dec 29 '17 at 04:02

1 Answers1

0

I found the problem (and it is not with MFMailComposeViewController). My custom class NRCEventItem did not have the NSCoding protocols implemented, so the mutable array that contained them was properly archived and unarchived, but the items were not. Here is the correct code for the item:

    //
//  NRCEventItem.m
//  Hourly Chime2
//
//  Created by Nelson Capes on 12/12/17.
//  Copyright © 2017 Nelson Capes. All rights reserved.
//

#import "NRCEventItem.h"
#import "constants.h"
@implementation NRCEventItem
-(void)encodeWithCoder:(NSCoder *)aCoder{

    [aCoder encodeObject:_eventSender forKey:kEventLoggerSender];
    [aCoder encodeObject:_eventDateTime forKey:KEventLoggerDateTime];
    [aCoder encodeObject:_eventDescription forKey:KEventLoggerEventDescription];

}
-(instancetype) initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    if (self){

        self.eventSender = [aDecoder decodeObjectForKey:kEventLoggerSender];
        self.eventDateTime = [aDecoder decodeObjectForKey:KEventLoggerDateTime];
        self.eventDescription = [aDecoder decodeObjectForKey:KEventLoggerEventDescription];

    }
    return self;
}
-(NSString *)description{
    return [NSString stringWithFormat:@"Event Date/time: %@, Event Sender: %@, Event Description: %@", self.eventDateTime, self.eventSender, self.eventDescription];
}
@end

I also ended up changing the code for archive and unarchive. Here is the code that works:

NSMutableData *data = [[NSMutableData alloc]init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
[archiver encodeObject:self.items forKey:@"items"];
[archiver finishEncoding];

NSMutableData *data = [[NSMutableData alloc]init];
data = notification.userInfo[KTraceKey];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data];
self.items = [unarchiver decodeObjectForKey:@"items"];
[unarchiver finishDecoding];

I found that there is an advantage of using NSKeyedArchiver and NSKeyedUnarchiver with encodeObject and decodeObject rather than archivedDataWithRootObject: the unarchiver will let you know if there is no data in the archive if you did not call finishEncoding. With the root object archiving and unarchiving, you don't get this notification.

I would suggest that using MFMailComposeViewController is a neat way of transferring data between apps running on separate devices. The key is implementing the info.plist, which is shown in my OP.

Thanks to Dave for suggesting a possible problem with the encoding!

Nelson Capes
  • 411
  • 3
  • 12