Related
i use linphone SDK in my application. When i start to run app crash happening immediately.
After app start it's call [Fastaddreesbook init] which called [FastAddreesBook reload] which called [FastAddreesBook loadData] which called [FastAddreesBook normalizeSipURI] and exc_bad_access Crash happen in this method:
LinphoneAddress* linphoneAddress = linphone_core_interpret_url([LinphoneManager getLc], [address UTF8String]);
Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
/* FastAddressBook.h
*
* Copyright (C) 2011 Belledonne Comunications, Grenoble, France
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef __IPHONE_9_0
#import <Contacts/Contacts.h>
#endif
#import "FastAddressBook.h"
#import "LinphoneManager.h"
#import "BundleLocalData.h"
#import "AppUtil.h"
#import "WebserviceUtil.h"
#import "ContactEntry.h"
#import "ContactsViewController.h"
#implementation FastAddressBook
static void sync_address_book (ABAddressBookRef addressBook, CFDictionaryRef info, void *context);
+ (NSString*)getContactDisplayName:(ABRecordRef)contact {
NSString *retString = nil;
if (contact) {
CFStringRef lDisplayName = ABRecordCopyCompositeName(contact);
if(lDisplayName != NULL) {
retString = [NSString stringWithString:(NSString*)lDisplayName];
CFRelease(lDisplayName);
}
}
return retString;
}
+ (UIImage*)squareImageCrop:(UIImage*)image
{
UIImage *ret = nil;
// This calculates the crop area.
float originalWidth = image.size.width;
float originalHeight = image.size.height;
float edge = fminf(originalWidth, originalHeight);
float posX = (originalWidth - edge) / 2.0f;
float posY = (originalHeight - edge) / 2.0f;
CGRect cropSquare = CGRectMake(posX, posY,
edge, edge);
CGImageRef imageRef = CGImageCreateWithImageInRect(image.CGImage, cropSquare);
ret = [UIImage imageWithCGImage:imageRef
scale:image.scale
orientation:image.imageOrientation];
CGImageRelease(imageRef);
return ret;
}
+ (UIImage*)getContactImage:(ABRecordRef)contact thumbnail:(BOOL)thumbnail {
UIImage* retImage = nil;
if (contact && ABPersonHasImageData(contact)) {
CFDataRef imgData = ABPersonCopyImageDataWithFormat(contact, thumbnail?
kABPersonImageFormatThumbnail: kABPersonImageFormatOriginalSize);
retImage = [UIImage imageWithData:(NSData *)imgData];
if(imgData != NULL) {
CFRelease(imgData);
}
if (retImage != nil && retImage.size.width != retImage.size.height) {
[LinphoneLogger log:LinphoneLoggerLog format:#"Image is not square : cropping it."];
return [self squareImageCrop:retImage];
}
}
return retImage;
}
- (ABRecordRef)getContact:(NSString*)address {
#synchronized (addressBookMap){
return (ABRecordRef)addressBookMap[address];
}
}
+ (BOOL)isSipURI:(NSString*)address {
return [address hasPrefix:#"sip:"] || [address hasPrefix:#"sips:"];
}
+ (NSString*)appendCountryCodeIfPossible:(NSString*)number {
if (![number hasPrefix:#"+"] && ![number hasPrefix:#"00"]) {
NSString* lCountryCode = [[LinphoneManager instance] lpConfigStringForKey:#"countrycode_preference"];
if (lCountryCode && lCountryCode.length>0) {
//append country code
return [lCountryCode stringByAppendingString:number];
}
}
return number;
}
+ (NSString*)normalizeSipURI:(NSString*)address {
NSString *normalizedSipAddress = nil;
LinphoneAddress* linphoneAddress = linphone_core_interpret_url([LinphoneManager getLc], [address UTF8String]);
if(linphoneAddress != NULL) {
char *tmp = linphone_address_as_string_uri_only(linphoneAddress);
if(tmp != NULL) {
normalizedSipAddress = [NSString stringWithUTF8String:tmp];
ms_free(tmp);
}
linphone_address_destroy(linphoneAddress);
}
return normalizedSipAddress;
}
+ (NSString*)normalizePhoneNumber:(NSString*)address {
NSMutableString* lNormalizedAddress = [NSMutableString stringWithString:address];
[lNormalizedAddress replaceOccurrencesOfString:#" "
withString:#""
options:0
range:NSMakeRange(0, lNormalizedAddress.length)];
[lNormalizedAddress replaceOccurrencesOfString:#"("
withString:#""
options:0
range:NSMakeRange(0, lNormalizedAddress.length)];
[lNormalizedAddress replaceOccurrencesOfString:#")"
withString:#""
options:0
range:NSMakeRange(0, lNormalizedAddress.length)];
[lNormalizedAddress replaceOccurrencesOfString:#"-"
withString:#""
options:0
range:NSMakeRange(0, lNormalizedAddress.length)];
return [FastAddressBook appendCountryCodeIfPossible:lNormalizedAddress];
}
+ (BOOL)isAuthorized {
//addme // return !ABAddressBookGetAuthorizationStatus || ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized;
return ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized;
}
- (FastAddressBook*)init {
if ((self = [super init]) != nil) {
addressBookMap = [[NSMutableDictionary alloc] init];
addressBook = nil;
[self reload];
}
self.needToUpdate = FALSE;
if ([CNContactStore class]) {
//ios9 or later
CNEntityType entityType = CNEntityTypeContacts;
if([CNContactStore authorizationStatusForEntityType:entityType] == CNAuthorizationStatusNotDetermined) {
CNContactStore * contactStore = [[CNContactStore alloc] init];
// nslo(#"CNContactStore requesting authorization");
[contactStore requestAccessForEntityType:entityType completionHandler:^(BOOL granted, NSError * _Nullable error) {
// LOGD(#"CNContactStore authorization granted");
}];
} else if([CNContactStore authorizationStatusForEntityType:entityType]== CNAuthorizationStatusAuthorized) {
// LOGD(#"CNContactStore authorization granted");
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateAddressBook:) name:CNContactStoreDidChangeNotification object:nil];
}
return self;
}
-(void) updateAddressBook:(NSNotification*) notif {
// LOGD(#"address book has changed");
self.needToUpdate = TRUE;
}
- (void) checkContactListForJogvoiceList {
// if (![BundleLocalData isLoadingJogvoiceContactList]) {
// [BundleLocalData setLoadingJogvoiceContactList:true];
int maxPhoneNumberSubmit = 200;
NSArray *lContacts = (NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook);
NSMutableDictionary *phoneNumberContactsDictionary = [[NSMutableDictionary alloc] init];
NSMutableArray *allPhoneNumberList = [[NSMutableArray alloc] init];
for (id lPerson in lContacts) {
ABRecordRef person = (ABRecordRef)lPerson;
NSArray *phoneList = [AppUtil getContactPhoneList:person];
for (NSString* phoneNumber in phoneList) {
NSMutableArray* contactList = phoneNumberContactsDictionary[phoneNumber];
if (!contactList) {
contactList = [[NSMutableArray alloc] init];
}
[contactList addObject:(__bridge ABRecordRef)person];
phoneNumberContactsDictionary[phoneNumber] = contactList;
}
[allPhoneNumberList addObjectsFromArray:phoneList];
if (allPhoneNumberList.count >= maxPhoneNumberSubmit) {
[self checkContactList:allPhoneNumberList phoneNumberContactsDictionary:phoneNumberContactsDictionary];
}
}
if (allPhoneNumberList.count > 0) {
[self checkContactList:allPhoneNumberList phoneNumberContactsDictionary:phoneNumberContactsDictionary];
}
// ABAddressBookUnregisterExternalChangeCallback(addressBook, sync_address_book, self);
// [BundleLocalData setLoadingJogvoiceContactList:false];
// }
}
-(void) checkContactList:(NSMutableArray*)allPhoneNumberList phoneNumberContactsDictionary:(NSMutableDictionary*)phoneNumberContactsDictionary {
[WebserviceUtil apiGetUsersRegistered:[NSArray arrayWithArray:allPhoneNumberList]
success:^(AFHTTPRequestOperation *operation, id responseObject){
NSDictionary* response = responseObject;
for (id phoneNumber in allPhoneNumberList) {
NSNumber *status = response[phoneNumber];
if (status.intValue == 1) { // registered
NSArray* contactList = phoneNumberContactsDictionary[phoneNumber];
for (int index = 0; index < contactList.count; index++) {
ABRecordRef contact = (__bridge ABRecordRef) contactList[index];
[self saveContact:phoneNumber contact:contact];
}
}
}
[self saveAddressBook];
[allPhoneNumberList removeAllObjects];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// nothing
}];
}
-(void) saveContact:(NSString*)phoneNumber contact:(ABRecordRef)contact {
if(contact == NULL || phoneNumber == NULL) {
return;
}
ABMultiValueRef lMap = ABRecordCopyValue(contact, kABPersonInstantMessageProperty);
if (!lMap) {
return;
}
BOOL avafoneAlready = false;
for(int i = 0; i < ABMultiValueGetCount(lMap); ++i) {
ABMultiValueIdentifier identifier = ABMultiValueGetIdentifierAtIndex(lMap, i);
CFDictionaryRef lDict = ABMultiValueCopyValueAtIndex(lMap, i);
if(CFDictionaryContainsKey(lDict, kABPersonInstantMessageServiceKey)) {
if(CFStringCompare((CFStringRef)[LinphoneManager instance].contactSipField, CFDictionaryGetValue(lDict, kABPersonInstantMessageServiceKey), kCFCompareCaseInsensitive) == 0) {
avafoneAlready = true;
}
} else {
//check domain
LinphoneAddress* address = linphone_address_new(((NSString*)CFDictionaryGetValue(lDict,kABPersonInstantMessageUsernameKey)).UTF8String);
if (address) {
if ([[ContactSelection getSipFilter] compare:#"*" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
avafoneAlready = true;
} else {
NSString* domain = [NSString stringWithCString:linphone_address_get_domain(address)
encoding:[NSString defaultCStringEncoding]];
if ([domain compare:[ContactSelection getSipFilter] options:NSCaseInsensitiveSearch] == NSOrderedSame) {
avafoneAlready = true;
}
}
linphone_address_destroy(address);
}
}
CFRelease(lDict);
if(avafoneAlready) {
avafoneAlready = true;
break;
}
}
CFRelease(lMap);
if (avafoneAlready) {
return;
}
NSString *value = [NSString stringWithFormat:#"900%#", phoneNumber];
ContactEntry *entry = nil;
ABMultiValueRef lcMap = ABRecordCopyValue(contact, kABPersonInstantMessageProperty);
if(lcMap != NULL) {
lMap = ABMultiValueCreateMutableCopy(lcMap);
CFRelease(lcMap);
} else {
lMap = ABMultiValueCreateMutable(kABStringPropertyType);
}
ABMultiValueIdentifier index;
NSError* error = NULL;
CFStringRef keys[] = { kABPersonInstantMessageUsernameKey, kABPersonInstantMessageServiceKey};
CFTypeRef values[] = { [value copy], [LinphoneManager instance].contactSipField };
CFDictionaryRef lDict = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 2, NULL, NULL);
if (entry) {
index = ABMultiValueGetIndexForIdentifier(lMap, entry.identifier);
ABMultiValueReplaceValueAtIndex(lMap, lDict, index);
} else {
CFStringRef label = (CFStringRef)[NSString stringWithString:(NSString*)kABPersonPhoneMobileLabel];
ABMultiValueAddValueAndLabel(lMap, lDict, label, &index);
}
if (!ABRecordSetValue(contact, kABPersonInstantMessageProperty, lMap, (CFErrorRef*)&error)) {
[LinphoneLogger log:LinphoneLoggerLog format:#"Can't set contact with value [%#] cause [%#]", value,error.localizedDescription];
CFRelease(lMap);
} else {
if (entry == nil) {
entry = [[[ContactEntry alloc] initWithData:index] autorelease];
}
CFRelease(lDict);
CFRelease(lMap);
/*check if message type is kept or not*/
lcMap = ABRecordCopyValue(contact, kABPersonInstantMessageProperty);
lMap = ABMultiValueCreateMutableCopy(lcMap);
CFRelease(lcMap);
index = ABMultiValueGetIndexForIdentifier(lMap, entry.identifier);
lDict = ABMultiValueCopyValueAtIndex(lMap,index);
// if(!CFDictionaryContainsKey(lDict, kABPersonInstantMessageServiceKey)) {
/*too bad probably a gtalk number, storing uri*/
NSString* username = CFDictionaryGetValue(lDict, kABPersonInstantMessageUsernameKey);
LinphoneAddress* address = linphone_core_interpret_url([LinphoneManager getLc]
,username.UTF8String);
if(address){
char* uri = linphone_address_as_string_uri_only(address);
CFStringRef keys[] = { kABPersonInstantMessageUsernameKey, kABPersonInstantMessageServiceKey};
CFTypeRef values[] = { [NSString stringWithCString:uri encoding:[NSString defaultCStringEncoding]], [LinphoneManager instance].contactSipField };
CFDictionaryRef lDict2 = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 2, NULL, NULL);
ABMultiValueReplaceValueAtIndex(lMap, lDict2, index);
if (!ABRecordSetValue(contact, kABPersonInstantMessageProperty, lMap, (CFErrorRef*)&error)) {
[LinphoneLogger log:LinphoneLoggerLog format:#"Can't set contact with value [%#] cause [%#]", value,error.localizedDescription];
}
CFRelease(lDict2);
linphone_address_destroy(address);
ms_free(uri);
}
// }
CFDictionaryRef lDict = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 2, NULL, NULL);
ABMultiValueReplaceValueAtIndex(lMap, lDict, index);
CFRelease(lMap);
}
CFRelease(lDict);
}
- (void)saveAddressBook {
if( addressBook != nil ){
NSError* err = nil;
if( !ABAddressBookSave(addressBook, (CFErrorRef*)err) ){
Linphone_warn(#"Couldn't save Address Book");
}
}
}
- (void)reload {
NSLog(#"Fastadd reload first is loaded");
CFErrorRef error;
// create if it doesn't exist
if (addressBook == nil) {
addressBook = ABAddressBookCreateWithOptions(NULL, &error);
}
if (addressBook != nil) {
__weak FastAddressBook *weakSelf = self;
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
if (!granted) {
Linphone_warn(#"Permission for address book acces was denied: %#", [(__bridge NSError *)error description]);
return;
}
ABAddressBookRegisterExternalChangeCallback(addressBook, sync_address_book, (__bridge void *)(weakSelf));
dispatch_async(dispatch_get_main_queue(), ^(void) {
[weakSelf loadData];
});
});
} else {
Linphone_warn(#"Create AddressBook failed, reason: %#", [(__bridge NSError *)error localizedDescription]);
}
/*
//method1
if(addressBook != nil) {
ABAddressBookUnregisterExternalChangeCallback(addressBook, sync_address_book, self);
CFRelease(addressBook);
addressBook = nil;
}
NSError *error = nil;
addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
if(addressBook != NULL) {
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
ABAddressBookRegisterExternalChangeCallback (addressBook, sync_address_book, self);
[self loadData];
});
} else {
[LinphoneLogger log:LinphoneLoggerError format:#"Create AddressBook: Fail(%#)", [error localizedDescription]];
}
*/
}
- (void)loadData {
ABAddressBookRevert(addressBook);
#synchronized (addressBookMap) {
[addressBookMap removeAllObjects];
//melog
NSLog(#"Fastadd loaddata is loaded");
NSArray *lContacts = (NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook);
for (id lPerson in lContacts) {
// Phone
{
ABMultiValueRef lMap = ABRecordCopyValue((ABRecordRef)lPerson, kABPersonPhoneProperty);
if(lMap) {
for (int i=0; i<ABMultiValueGetCount(lMap); i++) {
CFStringRef lValue = ABMultiValueCopyValueAtIndex(lMap, i);
CFStringRef lLabel = ABMultiValueCopyLabelAtIndex(lMap, i);
CFStringRef lLocalizedLabel = ABAddressBookCopyLocalizedLabel(lLabel);
NSString* lNormalizedKey = [FastAddressBook normalizePhoneNumber:(NSString*)lValue];
NSString* lNormalizedSipKey = [FastAddressBook normalizeSipURI:lNormalizedKey];
if (lNormalizedSipKey != NULL) lNormalizedKey = lNormalizedSipKey;
addressBookMap[lNormalizedKey] = lPerson;
CFRelease(lValue);
if (lLabel) CFRelease(lLabel);
if (lLocalizedLabel) CFRelease(lLocalizedLabel);
}
CFRelease(lMap);
}
}
// SIP
{
ABMultiValueRef lMap = ABRecordCopyValue((ABRecordRef)lPerson, kABPersonInstantMessageProperty);
if(lMap) {
for(int i = 0; i < ABMultiValueGetCount(lMap); ++i) {
CFDictionaryRef lDict = ABMultiValueCopyValueAtIndex(lMap, i);
BOOL add = false;
if(CFDictionaryContainsKey(lDict, kABPersonInstantMessageServiceKey)) {
CFStringRef contactSipField = (CFStringRef)[LinphoneManager instance].contactSipField;
if (!contactSipField) {
contactSipField = CFStringCreateWithCString(NULL, "SIP", kCFStringEncodingMacRoman);
}
if(CFStringCompare(contactSipField, CFDictionaryGetValue(lDict, kABPersonInstantMessageServiceKey), kCFCompareCaseInsensitive) == 0) {
add = true;
}
} else {
add = true;
}
if(add) {
CFStringRef lValue = CFDictionaryGetValue(lDict, kABPersonInstantMessageUsernameKey);
NSString* lNormalizedKey = [FastAddressBook normalizeSipURI:(NSString*)lValue];
if(lNormalizedKey != NULL) {
addressBookMap[lNormalizedKey] = lPerson;
} else {
addressBookMap[(NSString*)lValue] = lPerson;
}
/*
NSString *lValue =
(__bridge NSString *)CFDictionaryGetValue(lDict, kABPersonInstantMessageUsernameKey);
NSString *lNormalizedKey = [FastAddressBook normalizeSipURI:lValue];
if (lNormalizedKey != NULL) {
[addressBookMap setObject:(__bridge id)(lPerson)forKey:lNormalizedKey];
} else {
[addressBookMap setObject:(__bridge id)(lPerson)forKey:lValue];
}
*/
}
CFRelease(lDict);
}
CFRelease(lMap);
}
}
}
CFRelease(lContacts);
}
[[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneAddressBookUpdate object:self];
}
void sync_address_book (ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
FastAddressBook* fastAddressBook = (FastAddressBook*)context;
[fastAddressBook loadData];
}
- (void)dealloc {
ABAddressBookUnregisterExternalChangeCallback(addressBook, sync_address_book, self);
CFRelease(addressBook);
[addressBookMap release];
[super dealloc];
}
#end
P.S.:
-i use non-arc project
-zombie enable too but nothing change.
-Ivalue is will and thats because crash happening.
Debug Console:
warning: could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available.
LinphoneAddress* linphoneAddress = linphone_core_interpret_url([LinphoneManager getLc], [address UTF8String]);
address is nil. Figure out why that is and you've got your crash source. It likely should be nil as it is probably an optional field in the original record.
That code is a bit of a mess, btw. It isn't following the standard patterns (lots of method prefixed with get, for example). It really should be modernized and have ARC enabled.
Trying to import an XML file that has questions, answers, and categories for how-to questions. When I attempt to run this, there are supposed to be 3 categories (and they're visible in the XML file) of LB, OP, and DP. When I run the app, it seems to assign all how-tos to the LB category. What am I missing that's causing all to be assigned to the LB category? Thanks.
howToXMLImporter.h
#import <UIKit/UIKit.h>
#import <libxml/tree.h>
#class howToXMLImporter, HowTos, HowToCategory, HowToCategoryCache;
#protocol howToXMLImporterDelegate <NSObject>
#optional
- (void)importerDidSave:(NSNotification *)saveNotification;
- (void)importerDidFinishParsingData:(howToXMLImporter *)importer;
- (void)importer:(howToXMLImporter *)importer didFailWithError:(NSError *)error;
#end
#interface howToXMLImporter : NSOperation {
#private
id <howToXMLImporterDelegate> __unsafe_unretained delegate;
// Reference to the libxml parser context
xmlParserCtxtPtr context;
NSURLConnection *xmlConnection;
BOOL done;
BOOL parsingAHowTo;
BOOL storingCharacters;
NSMutableData *characterBuffer;
HowTos *currentHowTo; // Current how-to importer working with
// The number of parsed how-tos is tracked so that the autorelease pool for the parsing thread can be periodically emptied to keep the memory footprint under control.
NSUInteger countForCurrentBatch;
NSManagedObjectContext *insertionContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
NSEntityDescription *howToEntityDescription;
HowToCategoryCache *theCache;
NSURL *aogURL; // Source of the how-to XML file
}
#property (nonatomic, retain) NSURL *aogURL;
#property (nonatomic, assign) id <howToXMLImporterDelegate> delegate;
#property (nonatomic, retain) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#property (nonatomic, retain, readonly) NSManagedObjectContext *insertionContext;
#property (nonatomic, retain, readonly) NSEntityDescription *howToEntityDescription;
#property (nonatomic, retain, readonly) HowToCategoryCache *theCache;
- (void)main;
#end
howToXMLImporter.m
#import "howToXMLImporter.h"
#import "HowTos.h"
#import "HowToCategory.h"
#import "HowToCategoryCache.h"
#import <libxml/tree.h>
// Function prototypes for SAX callbacks.
static void startElementSAX(void *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes);
static void endElementSAX(void *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI);
static void charactersFoundSAX(void *context, const xmlChar *characters, int length);
static void errorEncounteredSAX(void *context, const char *errorMessage, ...);
// Forward reference. The structure is defined in full at the end of the file.
static xmlSAXHandler simpleSAXHandlerStruct;
// Class extension for private properties and methods.
#interface howToXMLImporter ()
#property BOOL storingCharacters;
#property (nonatomic, retain) NSMutableData *characterBuffer;
#property BOOL done;
#property BOOL parsingAHowTo;
#property NSUInteger countForCurrentBatch;
#property (nonatomic, retain) HowTos *currentHowTo;
#property (nonatomic, retain) NSURLConnection *xmlConnection;
#property (nonatomic, retain) NSDateFormatter *dateFormatter;
#end
static double lookuptime = 0;
#implementation howToXMLImporter
// Pull in these variables
#synthesize aogURL, delegate, persistentStoreCoordinator;
#synthesize xmlConnection, done, parsingAHowTo, storingCharacters, currentHowTo, countForCurrentBatch, characterBuffer;
- (void)main {
if (delegate && [delegate respondsToSelector:#selector(importerDidSave:)]) {
[[NSNotificationCenter defaultCenter] addObserver:delegate selector:#selector(importerDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.insertionContext];
}
done = NO;
self.characterBuffer = [NSMutableData data];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:aogURL];
// create the connection with the request and start loading the data
xmlConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
// This creates a context for "push" parsing in which chunks of data that are not "well balanced" can be passed to the context for streaming parsing.
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, (__bridge void *)(self), NULL, 0, NULL);
if (xmlConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
xmlFreeParserCtxt(context);
self.characterBuffer = nil;
self.dateFormatter = nil;
self.xmlConnection = nil;
self.currentHowTo = nil;
theCache = nil;
NSError *saveError = nil;
NSAssert1([insertionContext save:&saveError], #"Unhandled error saving managed object context in import thread: %#", [saveError localizedDescription]);
if (delegate && [delegate respondsToSelector:#selector(importerDidSave:)]) {
[[NSNotificationCenter defaultCenter] removeObserver:delegate name:NSManagedObjectContextDidSaveNotification object:self.insertionContext];
}
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(importerDidFinishParsingData:)]) {
[self.delegate importerDidFinishParsingData:self];
}
}
- (NSManagedObjectContext *)insertionContext {
if (insertionContext == nil) {
insertionContext = [[NSManagedObjectContext alloc] init];
[insertionContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return insertionContext;
}
- (void)forwardError:(NSError *)error {
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(importer:didFailWithError:)]) {
[self.delegate importer:self didFailWithError:error];
}
}
- (NSEntityDescription *)howToEntityDescription {
if (howToEntityDescription == nil) {
howToEntityDescription = [NSEntityDescription entityForName:#"HowTo" inManagedObjectContext:self.insertionContext];
}
return howToEntityDescription;
}
- (HowToCategoryCache *)theCache {
if (theCache == nil) {
theCache = [[HowToCategoryCache alloc] init];
theCache.managedObjectContext = self.insertionContext;
}
return theCache;
}
- (HowTos *)currentHowTo {
if (currentHowTo == nil) {
currentHowTo = [[HowTos alloc] initWithEntity:self.howToEntityDescription insertIntoManagedObjectContext:self.insertionContext];
}
return currentHowTo;
}
// Forward errors to the delegate.
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self performSelectorOnMainThread:#selector(forwardError:) withObject:error waitUntilDone:NO];
done = YES; // Ends the run loop.
}
// Called when a chunk of data has been downloaded.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Process downloaded chunk of data.
xmlParseChunk(context, (const char *)[data bytes], [data length], 0);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Signal the context that parsing is complete by passing "1" as the last parameter.
xmlParseChunk(context, NULL, 0, 1);
context = NULL;
done = YES; // Ends the run loop.
}
static const NSUInteger kImportBatchSize = 20;
- (void)finishedCurrentHowTo {
parsingAHowTo = NO;
self.currentHowTo = nil;
countForCurrentBatch++;
if (countForCurrentBatch == kImportBatchSize) {
NSError *saveError = nil;
NSAssert1([insertionContext save:&saveError], #"Unhandled error saving managed object context in how-to import thread: %#", [saveError localizedDescription]);
countForCurrentBatch = 0;
}
}
// Character data is appended to a buffer until the current element ends.
- (void)appendCharacters:(const char *)charactersFound length:(NSInteger)length {
[characterBuffer appendBytes:charactersFound length:length];
}
- (NSString *)currentString {
// Create a string with the character data using UTF-8 encoding
NSString *currentString = [[NSString alloc] initWithData:characterBuffer encoding:NSUTF8StringEncoding];
[characterBuffer setLength:0];
return currentString;
}
#end
// XML element names and their string lengths for parsing comparison; include null terminator
static const char *kName_HowTos = "How_Tos"; // Container tag for product
static const NSUInteger kLength_HowTos = 8;
static const char *kName_Question = "how-to_question"; // Didn't include how-to_id for now
static const NSUInteger kLength_Question = 16;
static const char *kName_Answer = "how-to_answer";
static const NSUInteger kLength_Answer = 14;
static const char *kName_Category = "how-to_category";
static const NSUInteger kLength_Category = 16;
static void startElementSAX(void *parsingContext, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI,
int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) {
howToXMLImporter *importer = (__bridge howToXMLImporter *)parsingContext;
// The second parameter to strncmp is the name of the element, which we know from the XML schema of the feed. The third parameter to strncmp is the number of characters in the element name, plus 1 for the null terminator.
if (!strncmp((const char *)localname, kName_HowTos, kLength_HowTos)) {
importer.parsingAHowTo = YES;
// May want to set default variable values here
} else if (importer.parsingAHowTo &&
(
(!strncmp((const char *)localname, kName_Question, kLength_Question) ||
!strncmp((const char *)localname, kName_Answer, kLength_Answer) ||
!strncmp((const char *)localname, kName_Category, kLength_Category)
)
)
) {
importer.storingCharacters = YES;
}
}
// This callback is invoked when the parser reaches the end of a node
static void endElementSAX(void *parsingContext, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI) {
howToXMLImporter *importer = (__bridge howToXMLImporter *)parsingContext;
if (importer.parsingAHowTo == NO) return;
if (!strncmp((const char *)localname, kName_HowTos, kLength_HowTos)) {
[importer finishedCurrentHowTo];
} else if (!strncmp((const char *)localname, kName_Question, kLength_Question)) {
importer.currentHowTo.question = importer.currentString;
NSLog(#"Question: %#",importer.currentHowTo.question);
} else if (!strncmp((const char *)localname, kName_Answer, kLength_Answer)) {
importer.currentHowTo.answer = importer.currentString;
NSLog(#"Answer: %#",importer.currentHowTo.answer);
} else if (!strncmp((const char *)localname, kName_Category, kLength_Category)) {
double before = [NSDate timeIntervalSinceReferenceDate];
HowToCategory *category = [importer.theCache howToCategoryWithName:importer.currentString];
double delta = [NSDate timeIntervalSinceReferenceDate] - before;
lookuptime += delta;
importer.currentHowTo.category = category;
NSLog(#"Category: %#",importer.currentHowTo.category.name);
}
importer.storingCharacters = NO;
}
// This callback is invoked when the parser encounters character data inside a node. Importer class determines how to use the character data.
static void charactersFoundSAX(void *parsingContext, const xmlChar *characterArray, int numberOfCharacters) {
howToXMLImporter *importer = (__bridge howToXMLImporter *)parsingContext;
// A state variable, "storingCharacters", is set when nodes of interest begin and end. Determines whether character data is handled or ignored.
if (importer.storingCharacters == NO) return;
[importer appendCharacters:(const char *)characterArray length:numberOfCharacters];
}
static void errorEncounteredSAX(void *parsingContext, const char *errorMessage, ...) {
// Handle errors as appropriate
NSCAssert(NO, #"Unhandled error encountered during SAX parse.");
}
HowToCategoryCache.h
#import <Foundation/Foundation.h>
#class HowToCategory;
#interface HowToCategoryCache : NSObject {
NSManagedObjectContext *managedObjectContext;
NSUInteger cacheSize; // Number of objects that can be cached
NSMutableDictionary *cache; // A dictionary holds the actual cached items
NSEntityDescription *howToCategoryEntityDescription;
NSPredicate *howToCategoryNamePredicateTemplate;
NSUInteger accessCounter; // Counter used to determine the least recently touched item.
// Some basic metrics are tracked to help determine the optimal cache size for the problem.
CGFloat totalCacheHitCost;
CGFloat totalCacheMissCost;
NSUInteger cacheHitCount;
NSUInteger cacheMissCount;
}
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#property NSUInteger cacheSize;
#property (nonatomic, strong) NSMutableDictionary *cache;
#property (nonatomic, strong, readonly) NSEntityDescription *howToCategoryEntityDescription;
#property (nonatomic, strong, readonly) NSPredicate *howToCategoryNamePredicateTemplate;
- (HowToCategory *)howToCategoryWithName:(NSString *)name;
#end
HowToCategoryCache.m
#import "HowToCategoryCache.h"
#import "HowToCategory.h"
// CacheNode is a simple object to help with tracking cached items
#interface HowToCacheNode : NSObject {
NSManagedObjectID *objectID;
NSUInteger accessCounter;
}
#property (nonatomic, strong) NSManagedObjectID *objectID;
#property NSUInteger accessCounter;
#end
#implementation HowToCacheNode
#synthesize objectID, accessCounter;
#end
#implementation HowToCategoryCache
#synthesize managedObjectContext, cacheSize, cache;
- (id)init {
self = [super init];
if (self != nil) {
cacheSize = 15;
accessCounter = 0;
cache = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (cacheHitCount > 0) NSLog(#"average cache hit cost: %f", totalCacheHitCost/cacheHitCount);
if (cacheMissCount > 0) NSLog(#"average cache miss cost: %f", totalCacheMissCost/cacheMissCount);
howToCategoryEntityDescription = nil;
howToCategoryNamePredicateTemplate = nil;
}
// Implement the "set" accessor rather than depending on #synthesize so that we can set up registration for context save notifications.
- (void)setManagedObjectContext:(NSManagedObjectContext *)aContext {
if (managedObjectContext) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:managedObjectContext];
}
managedObjectContext = aContext;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(managedObjectContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:managedObjectContext];
}
// When a managed object is first created, it has a temporary managed object ID. When the managed object context in which it was created is saved, the temporary ID is replaced with a permanent ID. The temporary IDs can no longer be used to retrieve valid managed objects. The cache handles the save notification by iterating through its cache nodes and removing any nodes with temporary IDs.
- (void)managedObjectContextDidSave:(NSNotification *)notification {
HowToCacheNode *cacheNode = nil;
NSMutableArray *keys = [NSMutableArray array];
for (NSString *key in cache) {
cacheNode = [cache objectForKey:key];
if ([cacheNode.objectID isTemporaryID]) {
[keys addObject:key];
}
}
[cache removeObjectsForKeys:keys];
}
- (NSEntityDescription *)howToCategoryEntityDescription {
if (howToCategoryEntityDescription == nil) {
howToCategoryEntityDescription = [NSEntityDescription entityForName:#"HowToCategory" inManagedObjectContext:managedObjectContext];
}
return howToCategoryEntityDescription;
}
static NSString * const kCategoryNameSubstitutionVariable = #"NAME";
- (NSPredicate *)categoryNamePredicateTemplate {
if (howToCategoryNamePredicateTemplate == nil) {
NSExpression *leftHand = [NSExpression expressionForKeyPath:#"name"];
NSExpression *rightHand = [NSExpression expressionForVariable:kCategoryNameSubstitutionVariable];
howToCategoryNamePredicateTemplate = [[NSComparisonPredicate alloc] initWithLeftExpression:leftHand rightExpression:rightHand modifier:NSDirectPredicateModifier type:NSLikePredicateOperatorType options:0];
}
return howToCategoryNamePredicateTemplate;
}
#define USE_CACHING // Undefine this macro to compare performance without caching
- (HowToCategory *)howToCategoryWithName:(NSString *)name {
NSTimeInterval before = [NSDate timeIntervalSinceReferenceDate];
#ifdef USE_CACHING
// check cache
HowToCacheNode *cacheNode = [cache objectForKey:name];
if (cacheNode != nil) {
cacheNode.accessCounter = accessCounter++; // cache hit, update access counter
HowToCategory *category = (HowToCategory *)[self.managedObjectContext objectWithID:cacheNode.objectID];
totalCacheHitCost += ([NSDate timeIntervalSinceReferenceDate] - before);
cacheHitCount++;
return category;
}
#endif
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // cache missed, fetch from store - if not found in store there is no category object for the name and we must create one
[fetchRequest setEntity:self.howToCategoryEntityDescription];
NSLog(#"Fetch Request: %#",fetchRequest);
NSPredicate *predicate = [self.howToCategoryNamePredicateTemplate predicateWithSubstitutionVariables:[NSDictionary dictionaryWithObject:name forKey:kCategoryNameSubstitutionVariable]];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSAssert1(fetchResults != nil, #"Unhandled error executing fetch request in import thread: %#", [error localizedDescription]);
HowToCategory *category = nil;
if ([fetchResults count] > 0) {
category = [fetchResults objectAtIndex:0]; // get category from fetch
} else if ([fetchResults count] == 0) {
// category not in store, must create a new category object
category = [[HowToCategory alloc] initWithEntity:self.howToCategoryEntityDescription insertIntoManagedObjectContext:managedObjectContext];
category.name = name; //name
// Set the sort order for the category
if ([category.name isEqualToString:#"LB"]) {
category.order = [NSNumber numberWithInt:1];
} else if ([category.name isEqualToString:#"DP"]) {
category.order = [NSNumber numberWithInt:2];
} else if ([category.name isEqualToString:#"OP"]) {
category.order = [NSNumber numberWithInt:3];
}
}
#ifdef USE_CACHING
// add to cache
// first check to see if cache is full
if ([cache count] >= cacheSize) {
// evict least recently used (LRU) item from cache
NSUInteger oldestAccessCount = UINT_MAX;
NSString *key = nil, *keyOfOldestCacheNode = nil;
for (key in cache) {
HowToCacheNode *tmpNode = [cache objectForKey:key];
if (tmpNode.accessCounter < oldestAccessCount) {
oldestAccessCount = tmpNode.accessCounter;
keyOfOldestCacheNode = key;
}
}
// retain the cache node for reuse
cacheNode = [cache objectForKey:keyOfOldestCacheNode];
// remove from the cache
if (keyOfOldestCacheNode != nil)
[cache removeObjectForKey:keyOfOldestCacheNode];
} else {
// create a new cache node
cacheNode = [[HowToCacheNode alloc] init];
}
cacheNode.objectID = [category objectID];
cacheNode.accessCounter = accessCounter++;
[cache setObject:cacheNode forKey:name];
#endif
totalCacheMissCost += ([NSDate timeIntervalSinceReferenceDate] - before);
cacheMissCount++;
return category;
}
#end
Recently modified my app to include a tab view controller. I'm now unable to load all of the categories that are in my SQLite database when the app launches and goes to the Categories table view nav controller on app load (CategoriesViewController). There are 6 category names that it should display; sometimes the app displays 0 rows, other times it displays 4. Modifying the XML file that this pulls data from, I got it to pull 6 categories (after a few times of 0 and fast execution speed). Leads me to think this has to do with timing of execution vs. time until the table view is loaded with data. Any ideas on what I need to change to get CategoriesViewController to display the table only after all data is read? Here's the code; please let me know if there's anything else that you'd need to see. Thanks!
CategoriesViewController.h
#import <UIKit/UIKit.h>
#interface CategoriesViewController : UITableViewController
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
- (void)fetch; // Fetches categories from data store
#end
CategoriesViewController.m
#import "CategoriesViewController.h"
#import "ProductsViewController.h"
#import "Category.h"
#import "AppDelegate.h"
#interface CategoriesViewController () // Categories View Controller interface
#property (nonatomic, strong) ProductsViewController *productController;
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
#property (weak, nonatomic) IBOutlet UIImageView *logoImageView;
#end
#pragma mark -
#implementation CategoriesViewController
- (void)viewDidLoad {
[super viewDidLoad];
_logoImageView.image = [UIImage imageNamed:#"company.jpg"];
[self fetch]; // Fetch categories from our data store
}
- (void)fetch {
NSError *error = nil;
BOOL success = [self.fetchedResultsController performFetch:&error]; // Fetch results; if error, assign error code, output message
NSAssert2(success, #"Unhandled error performing fetch at CategoriessViewController.m, line %d: %#", __LINE__, [error localizedDescription]);
}
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Category" inManagedObjectContext:app.managedObjectContext]]; // Fetch categories
NSArray *sortDescriptors = nil;
NSString *sectionNameKeyPath = nil;
sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:#"order" ascending:YES]];
[fetchRequest setSortDescriptors:sortDescriptors];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:app.managedObjectContext
sectionNameKeyPath:sectionNameKeyPath
cacheName:nil];
}
return _fetchedResultsController;
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)table {
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *kCellIdentifier = #"CategoryCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath];
Category *category = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:NSLocalizedString(#"%#", #"%#"), category.name];
return cell;
}
// Pass managed object to ProductsViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"showProducts"]) {
ProductsViewController *detailsController = (ProductsViewController *)[segue destinationViewController];
NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
detailsController.category = [self.fetchedResultsController objectAtIndexPath:selectedIndexPath];
AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[[segue destinationViewController] setManagedObjectContext:app.managedObjectContext];
}
}
#end
AppDelegate.h
#import <UIKit/UIKit.h>
#import "companyXMLImporter.h"
#interface AppDelegate : NSObject <UIApplicationDelegate, companyXMLImporterDelegate>
#property (nonatomic, strong) UIWindow *window;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (NSDate *)lasModificationDateOfFileAtURL:(NSURL *)url;
#end
AppDelegate.m
#import "AppDelegate.h"
#import "ProductsViewController.h"
#import "CategoriesViewController.h"
#interface AppDelegate()
#property (nonatomic, strong) ProductsViewController *productsViewController;
#property (nonatomic, strong) CategoriesViewController *categoriesViewController;
// Properties for the importer and its background processing
#property (nonatomic, strong) companyXMLImporter *importer;
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#property (nonatomic, strong) NSString *persistentStorePath;
#end
#implementation AppDelegate
static NSString * const kLastStoreUpdateKey = #"LastStoreUpdate"; // Identifies update object in user defaults storage
static NSTimeInterval const kRefreshTimeInterval = 3600;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSDate *lastUpdate = [[NSUserDefaults standardUserDefaults] objectForKey:kLastStoreUpdateKey];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:NSLocalizedString(#"Products XML", #"Products XML")]];
NSDate *lastModifiedDate = [self lasModificationDateOfFileAtURL:url];
if (lastUpdate == nil || lastModifiedDate >= lastUpdate || -[lastUpdate timeIntervalSinceNow] > kRefreshTimeInterval) {
if ([[NSFileManager defaultManager] fileExistsAtPath:self.persistentStorePath]) {
NSError *error = nil;
BOOL oldStoreRemovalSuccess = [[NSFileManager defaultManager] removeItemAtPath:self.persistentStorePath error:&error];
NSAssert3(oldStoreRemovalSuccess, #"Unhandled error adding persistent store in %s at line %d: %#", __FUNCTION__, __LINE__, [error localizedDescription]);
}
// Object to retrieve, parse, and import into CoreData store
self.importer = [[companyXMLImporter alloc] init];
self.importer.delegate = self;
// pass coordinator so importer can create its own managed object context
self.importer.persistentStoreCoordinator = self.persistentStoreCoordinator;
// URL for products XML file
self.importer.companyURL = [NSURL URLWithString:[NSString stringWithFormat:NSLocalizedString(#"Products XML", #"Products XML")]];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self.operationQueue addOperation:self.importer];
}
}
- (NSOperationQueue *)operationQueue {
if (_operationQueue == nil) {
_operationQueue = [[NSOperationQueue alloc] init];
}
return _operationQueue;
}
- (NSDate *)lasModificationDateOfFileAtURL:(NSURL *)url {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
// Get only header
request.HTTPMethod = #"HEAD";
NSHTTPURLResponse *response = nil;
NSError *error = nil;
[NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
NSLog(#"Error: %#", error.localizedDescription);
return nil;
} else if([response respondsToSelector:#selector(allHeaderFields)]) {
NSDictionary *headerFields = [response allHeaderFields];
NSString *lastModification = [headerFields objectForKey:#"Last-Modified"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"EEE, dd MMM yyyy HH:mm:ss zzz"];
return [formatter dateFromString:lastModification];
}
return nil;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator == nil) {
NSURL *storeUrl = [NSURL fileURLWithPath:self.persistentStorePath];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]];
NSError *error = nil;
NSPersistentStore *persistentStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
NSAssert3(persistentStore != nil, #"Unhandled error adding persistent store in %s at line %d: %#", __FUNCTION__, __LINE__, [error localizedDescription]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext == nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[self.managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return _managedObjectContext;
}
- (NSString *)persistentStorePath {
if (_persistentStorePath == nil) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths lastObject];
_persistentStorePath = [documentsDirectory stringByAppendingPathComponent:#"Company.sqlite"];
}
return _persistentStorePath;
}
- (void)importerDidSave:(NSNotification *)saveNotification {
if ([NSThread isMainThread]) {
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
// [self.categoriesViewController fetch];
[self.productsViewController fetch];
} else {
[self performSelectorOnMainThread:#selector(importerDidSave:) withObject:saveNotification waitUntilDone:NO];
}
}
// Main-thread import completion processing
- (void)handleImportCompletion {
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:kLastStoreUpdateKey];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.importer = nil;
}
- (void)importerDidFinishParsingData:(companyXMLImporter *)importer {
[self performSelectorOnMainThread:#selector(handleImportCompletion) withObject:nil waitUntilDone:NO];
}
// Process errors received in delegate callback
- (void)handleImportError:(NSError *)error {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.importer = nil;
NSString *errorMessage = [error localizedDescription];
NSString *alertTitle = NSLocalizedString(#"Error", #"Title for alert displayed when download or parse error occurs.");
NSString *okTitle = NSLocalizedString(#"OK ", #"OK Title for alert displayed when download or parse error occurs.");
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:alertTitle
message:errorMessage
delegate:nil
cancelButtonTitle:okTitle
otherButtonTitles:nil];
[alertView show];
}
- (void)importer:(companyXMLImporter *)importer didFailWithError:(NSError *)error {
[self performSelectorOnMainThread:#selector(handleImportError:) withObject:error waitUntilDone:NO];
}
#end
companyXMLImporter.h
#import <UIKit/UIKit.h>
#import <libxml/tree.h>
#class companyXMLImporter, Product, Category, CategoryCache;
#protocol companyXMLImporterDelegate <NSObject>
#optional
- (void)importerDidSave:(NSNotification *)saveNotification; // Posted when saved
- (void)importerDidFinishParsingData:(companyXMLImporter *)importer; // Called when parsing is finished
- (void)importer:(companyXMLImporter *)importer didFailWithError:(NSError *)error; // Called if error
#end
#interface companyXMLImporter : NSOperation {
#private
id <companyXMLImporterDelegate> __unsafe_unretained delegate;
// Reference to the libxml parser context
xmlParserCtxtPtr context;
NSURLConnection *xmlConnection;
BOOL done;
BOOL parsingAProduct;
// Used for getting character data from XML elements
BOOL storingCharacters;
NSMutableData *characterBuffer;
Product *currentProduct;
NSUInteger countForCurrentBatch;
NSManagedObjectContext *insertionContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
NSEntityDescription *productEntityDescription;
CategoryCache *theCache;
NSURL *companyURL;
}
#property (nonatomic, retain) NSURL *companyURL;
#property (nonatomic, assign) id <companyXMLImporterDelegate> delegate;
#property (nonatomic, retain) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#property (nonatomic, retain, readonly) NSManagedObjectContext *insertionContext;
#property (nonatomic, retain, readonly) NSEntityDescription *productEntityDescription;
#property (nonatomic, retain, readonly) CategoryCache *theCache;
- (void)main;
#end
companyXMLImporter.m
#import "companyXMLImporter.h"
#import "Product.h"
#import "Category.h"
#import "CategoryCache.h"
#import <libxml/tree.h>
// Function prototypes for SAX callbacks
static void startElementSAX(void *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes);
static void endElementSAX(void *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI);
static void charactersFoundSAX(void *context, const xmlChar *characters, int length);
static void errorEncounteredSAX(void *context, const char *errorMessage, ...);
static xmlSAXHandler simpleSAXHandlerStruct; // Forward reference. Structure defined in full at end of file
#interface companyXMLImporter ()
#property BOOL storingCharacters;
#property (nonatomic, retain) NSMutableData *characterBuffer;
#property BOOL done;
#property BOOL parsingAProduct;
#property NSUInteger countForCurrentBatch;
#property (nonatomic, retain) Product *currentProduct;
#property (nonatomic, retain) NSURLConnection *xmlConnection;
#property (nonatomic, retain) NSDateFormatter *dateFormatter;
#end
static double lookuptime = 0;
#implementation companyXMLImporter
#synthesize companyURL, delegate, persistentStoreCoordinator;
#synthesize xmlConnection, done, parsingAProduct, storingCharacters, currentProduct, countForCurrentBatch, characterBuffer;
- (void)main {
if (delegate && [delegate respondsToSelector:#selector(importerDidSave:)]) {
[[NSNotificationCenter defaultCenter] addObserver:delegate selector:#selector(importerDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.insertionContext];
}
done = NO;
self.characterBuffer = [NSMutableData data];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:companyURL];
xmlConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self]; // create connection with request and start loading data
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, (__bridge void *)(self), NULL, 0, NULL);
if (xmlConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release thread resources
xmlFreeParserCtxt(context);
self.characterBuffer = nil;
self.dateFormatter = nil;
self.xmlConnection = nil;
self.currentProduct = nil;
theCache = nil;
NSError *saveError = nil;
NSAssert1([insertionContext save:&saveError], #"Unhandled error saving managed object context in import thread: %#", [saveError localizedDescription]);
if (delegate && [delegate respondsToSelector:#selector(importerDidSave:)]) {
[[NSNotificationCenter defaultCenter] removeObserver:delegate name:NSManagedObjectContextDidSaveNotification object:self.insertionContext];
}
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(importerDidFinishParsingData:)]) {
[self.delegate importerDidFinishParsingData:self];
}
}
- (NSManagedObjectContext *)insertionContext {
if (insertionContext == nil) {
insertionContext = [[NSManagedObjectContext alloc] init];
[insertionContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return insertionContext;
}
- (void)forwardError:(NSError *)error {
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(importer:didFailWithError:)]) {
[self.delegate importer:self didFailWithError:error];
}
}
- (NSEntityDescription *)productEntityDescription {
if (productEntityDescription == nil) {
productEntityDescription = [NSEntityDescription entityForName:#"Product" inManagedObjectContext:self.insertionContext];
}
return productEntityDescription;
}
- (CategoryCache *)theCache {
if (theCache == nil) {
theCache = [[CategoryCache alloc] init];
theCache.managedObjectContext = self.insertionContext;
}
return theCache;
}
- (Product *)currentProduct {
if (currentProduct == nil) {
currentProduct = [[Product alloc] initWithEntity:self.productEntityDescription insertIntoManagedObjectContext:self.insertionContext];
}
return currentProduct;
}
// Forward errors to delegate
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self performSelectorOnMainThread:#selector(forwardError:) withObject:error waitUntilDone:NO];
done = YES; // End run loop
}
// Called when a chunk of data has been downloaded
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Process downloaded chunk of data
xmlParseChunk(context, (const char *)[data bytes], [data length], 0);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Signal the context that parsing is complete by passing "1" as last parameter
xmlParseChunk(context, NULL, 0, 1);
context = NULL;
done = YES; // End the loop
}
static const NSUInteger kImportBatchSize = 20;
- (void)finishedCurrentProduct {
parsingAProduct = NO;
self.currentProduct = nil;
countForCurrentBatch++;
if (countForCurrentBatch == kImportBatchSize) {
NSError *saveError = nil;
NSAssert1([insertionContext save:&saveError], #"Unhandled error saving managed object context in import thread: %#", [saveError localizedDescription]);
countForCurrentBatch = 0;
}
}
// Character data appended to a buffer until current element ends.
- (void)appendCharacters:(const char *)charactersFound length:(NSInteger)length {
[characterBuffer appendBytes:charactersFound length:length];
}
- (NSString *)currentString {
// Create a string with character data using UTF-8 encoding
NSString *currentString = [[NSString alloc] initWithData:characterBuffer encoding:NSUTF8StringEncoding];
[characterBuffer setLength:0];
return currentString;
}
#end
// XML element names, string lengths for parsing
static const char *kName_App = "App"; // Product container tag
static const NSUInteger kLength_App = 4;
static const char *kName_Sku = "prod_sku";
static const NSUInteger kLength_Sku = 9;
static const char *kName_Name = "prod_name";
static const NSUInteger kLength_Name = 10;
static const char *kName_Description = "prod_description";
static const NSUInteger kLength_Description = 17;
static const char *kName_Category = "prod_category";
static const NSUInteger kLength_Category = 14;
static const char *kName_Upc = "prod_upc";
static const NSUInteger kLength_Upc = 9;
static const char *kName_CountryCode = "prod_code_destination";
static const NSUInteger kLength_CountryCode = 22;
static const char *kName_Webpage = "prod_html_link";
static const NSUInteger kLength_Webpage = 15;
static const char *kName_Manual = "prod_manual";
static const NSUInteger kLength_Manual = 12;
static const char *kName_QuickStart = "prod_quick_start";
static const NSUInteger kLength_QuickStart = 17;
static const char *kName_Thumbnail = "prod_thumbnail";
static const NSUInteger kLength_Thumbnail = 15;
static const char *kName_MainImage = "prod_image_main";
static const NSUInteger kLength_MainImage = 16;
static const char *kName_SecondaryImage = "prod_image_secondary";
static const NSUInteger kLength_SecondaryImage = 21;
// Invoked when importer finds beginning of a node
static void startElementSAX(void *parsingContext, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI,
int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) {
companyXMLImporter *importer = (__bridge companyXMLImporter *)parsingContext;
if (!strncmp((const char *)localname, kName_App, kLength_App)) {
importer.parsingAProduct = YES;
} else if (importer.parsingAProduct && ((!strncmp((const char *)localname, kName_Sku, kLength_Sku) || !strncmp((const char *)localname, kName_Name, kLength_Name) || !strncmp((const char *)localname, kName_Description, kLength_Description) || !strncmp((const char *)localname, kName_Category, kLength_Category) || !strncmp((const char *)localname, kName_Upc, kLength_Upc) || !strncmp((const char *)localname, kName_CountryCode, kLength_CountryCode) || !strncmp((const char *)localname, kName_Webpage, kLength_Webpage) || !strncmp((const char *)localname, kName_Manual, kLength_Manual) || !strncmp((const char *)localname, kName_QuickStart, kLength_QuickStart) || !strncmp((const char *)localname, kName_Thumbnail, kLength_Thumbnail) || !strncmp((const char *)localname, kName_MainImage, kLength_MainImage) || !strncmp((const char *)localname, kName_SecondaryImage, kLength_SecondaryImage)))
) {
importer.storingCharacters = YES;
}
}
// Invoked when parse reaches end of a node
static void endElementSAX(void *parsingContext, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI) {
companyXMLImporter *importer = (__bridge companyXMLImporter *)parsingContext;
if (importer.parsingAProduct == NO) return;
if (!strncmp((const char *)localname, kName_App, kLength_App)) {
[importer finishedCurrentProduct];
} else if (!strncmp((const char *)localname, kName_Name, kLength_Name)) {
importer.currentProduct.name = importer.currentString;
} else if (!strncmp((const char *)localname, kName_Category, kLength_Category)) {
double before = [NSDate timeIntervalSinceReferenceDate];
Category *category = [importer.theCache categoryWithName:importer.currentString];
double delta = [NSDate timeIntervalSinceReferenceDate] - before;
lookuptime += delta;
importer.currentProduct.category = category;
} else if (!strncmp((const char *)localname, kName_Sku, kLength_Sku)) {
importer.currentProduct.sku = importer.currentString;
} else if (!strncmp((const char *)localname, kName_Description, kLength_Description)) {
importer.currentProduct.prodDescription = importer.currentString;
} else if (!strncmp((const char *)localname, kName_Upc, kLength_Upc)) {
importer.currentProduct.upc = importer.currentString;
} else if (!strncmp((const char *)localname, kName_CountryCode, kLength_CountryCode)) {
importer.currentProduct.countryCode = importer.currentString;
} else if (!strncmp((const char *)localname, kName_Webpage, kLength_Webpage)) {
importer.currentProduct.webpage = importer.currentString;
} else if (!strncmp((const char *)localname, kName_Manual, kLength_Manual)) {
importer.currentProduct.manual = importer.currentString;
} else if (!strncmp((const char *)localname, kName_QuickStart, kLength_QuickStart)) {
importer.currentProduct.quickStart = importer.currentString;
} else if (!strncmp((const char *)localname, kName_Thumbnail, kLength_Thumbnail)) {
importer.currentProduct.thumbURLString = importer.currentString;
} else if (!strncmp((const char *)localname, kName_MainImage, kLength_MainImage)) {
importer.currentProduct.mainImgURLString = importer.currentString;
} else if (!strncmp((const char *)localname, kName_SecondaryImage, kLength_SecondaryImage)) {
importer.currentProduct.secondaryImgURLString = importer.currentString;
}
importer.storingCharacters = NO;
}
// Invoked when parser encounters character data inside a node
static void charactersFoundSAX(void *parsingContext, const xmlChar *characterArray, int numberOfCharacters) {
companyXMLImporter *importer = (__bridge companyXMLImporter *)parsingContext;
// storingCharacters set when nodes of interest begin/end – determines whether character data handled/ignored
if (importer.storingCharacters == NO) return;
[importer appendCharacters:(const char *)characterArray length:numberOfCharacters];
}
// Error handling
static void errorEncounteredSAX(void *parsingContext, const char *errorMessage, ...) {
// Handle errors as appropriate
NSCAssert(NO, #"Unhandled error encountered during SAX parse.");
}
static xmlSAXHandler simpleSAXHandlerStruct = {
NULL, /* internalSubset */
NULL, /* isStandalone */
NULL, /* hasInternalSubset */
NULL, /* hasExternalSubset */
NULL, /* resolveEntity */
NULL, /* getEntity */
NULL, /* entityDecl */
NULL, /* notationDecl */
NULL, /* attributeDecl */
NULL, /* elementDecl */
NULL, /* unparsedEntityDecl */
NULL, /* setDocumentLocator */
NULL, /* startDocument */
NULL, /* endDocument */
NULL, /* startElement*/
NULL, /* endElement */
NULL, /* reference */
charactersFoundSAX, /* characters */
NULL, /* ignorableWhitespace */
NULL, /* processingInstruction */
NULL, /* comment */
NULL, /* warning */
errorEncounteredSAX, /* error */
NULL,/* fatalError //: unused error() get all the errors */
NULL, /* getParameterEntity */
NULL, /* cdataBlock */
NULL, /* externalSubset */
XML_SAX2_MAGIC, //
NULL,
startElementSAX, /* startElementNs */
endElementSAX, /* endElementNs */
NULL, /* serror */
};
You should not need to call reloadData at all to get the data to display in the table ... and you're calling it twice, in viewDidLoad and in fetch. I would get rid of these.
It does sound like you have a timing problem. Can you explain more about how the data gets into the database? When you say,
In modifying the XML file that this database pulls data from ...
I take it that you mean that when your app starts up, it reads from an XML file which is packaged with your app, and writes this information into Core Data / Sqlite. If that is indeed what you do, then maybe the problem is that this process is trying to run at the same time as your CategoriesViewController is loading. Where is the code which reads the XML into Core Data / Sqlite? Where do you call it from? Is it a synchronous or an asynchronous call?
In my app I need to get the wifi details, with this I got the connected wifi details, I want to get the wifi list, so I used the private API SOLStumbler, with this I am getting an exc_bad_access error in
apple80211Open(&airportHandle);
actual code is:
NSMutableDictionary *networks; //Key: MAC Address (BSSID)
void *libHandle;
void *airportHandle;
int (*apple80211Open)(void *);
int (*apple80211Bind)(void *, NSString *);
int (*apple80211Close)(void *);
int (*associate)(void *, NSDictionary*, NSString*);
int (*apple80211Scan)(void *, NSArray **, void *);
- (id)init
{
self = [super init];
networks = [[NSMutableDictionary alloc] init];
libHandle = dlopen("/System/Library/SystemConfiguration/IPConfiguration.bundle/IPConfiguration", RTLD_LAZY);
// libHandle = dlopen("/System/Library/SystemConfiguration/WiFiManager.bundle/WiFiManager", RTLD_LAZY);
// libHandle = dlopen("/System/Library/PrivateFrameworks/MobileWiFi.framework/MobileWiFi", RTLD_LAZY);
char *error;
if (libHandle == NULL && (error = dlerror()) != NULL) {
NSLog(#"err ...r%s",error);
exit(1);
}
else{
NSLog(#"not null");
}
apple80211Open = dlsym(libHandle, "Apple80211Open");
apple80211Bind = dlsym(libHandle, "Apple80211BindToInterface");
apple80211Close = dlsym(libHandle, "Apple80211Close");
apple80211Scan = dlsym(libHandle, "Apple80211Scan");
applecopy= dlsym(libHandle, "Apple80211GetInfoCopy");
apple80211Open(&airportHandle);
apple80211Bind(airportHandle, #"en0");
return self;
}
Above Method not accepted by apple because they are using private API,
Please try to used below method to get the SSID and BSSID without using Private API.
- (id)fetchSSIDInfo {
NSArray *ifs = (__bridge_transfer NSArray *)CNCopySupportedInterfaces();
NSLog(#"Supported interfaces: %#", ifs);
NSDictionary *info;
for (NSString *ifnam in ifs) {
info = (__bridge_transfer NSDictionary *)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam);
NSLog(#"%# => %#", ifnam, info);
if (info && [info count]) { break; }
}
return info;}
Hi I am building a game that uses the gamekit framework but I am having trouble sending to int using the "sendDataToAllPlayers", it cannot distinguish between the two int that I am sending. Here is some of my code:
typedef enum {
kMessageTypeRandomNumber = 0,
kMessageQN = 0,
kMessageTypeGameBegin,
kMessageTypeSelectAnswer1,
kMessageTypeSelectAnswer2,
kMessageTypeSelectAnswer3,
kMessageTypeGameOver
} MessageType;
typedef struct {
MessageType messageType;
} Message;
typedef struct {
Message message;
uint32_t randomNumber;
int SelectedQ;
} MessageRandomNumber;
the following is the send methods:
-(void)sendTheSelectedRandomQuestionWithQuestion {
MessageRandomNumber message;
message.message.messageType = kMessageQN;
message.SelectedQ = randomSelectedQuestion;
NSData *data = [NSData dataWithBytes:&message length:sizeof(MessageRandomNumber)];
[self sendData:data];
}
- (void)sendRandomNumber {
//ourRandom = arc4random()%100;
MessageRandomNumber message;
message.message.messageType = kMessageTypeRandomNumber;
message.randomNumber = ourRandom;
NSData *data = [NSData dataWithBytes:&message length:sizeof(MessageRandomNumber)];
[self sendData:data];
}
- (void)sendData:(NSData *)data {
NSError *error;
BOOL success = [[GCHelper sharedInstance].match sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error];
if (!success) {
NSLog(#"Error sending init packet");
[self matchEnded];
}
}
the following is the didreceivedatamethod:
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
//Store away other player ID for later
if (otherPlayerID == nil) {
otherPlayerID = playerID;
}
Message *message = (Message *) [data bytes];
if (message->messageType == kMessageQN) {
NSLog(#"Received The Selected Question To Display");
debugLabel.text = #"received the selected q";
MessageRandomNumber * messageSelectedQuestion = (MessageRandomNumber *) [data bytes];
NSLog(#"The Selected Question is number: %ud",messageSelectedQuestion->SelectedQ);
randomSelectedQuestion = messageSelectedQuestion->SelectedQ;
[self displayTheSlectedQuestion];
} else if (message->messageType == kMessageTypeRandomNumber) {
MessageRandomNumber * messageInit = (MessageRandomNumber *) [data bytes];
NSLog(#"Received random number: %ud, ours %ud", messageInit->randomNumber, ourRandom);
bool tie = false;
if (messageInit->randomNumber == ourRandom) {
//NSLog(#"TIE!");
ourRandom = arc4random();
tie = true;
[self sendRandomNumber];
} else if (ourRandom > messageInit->randomNumber) {
NSLog(#"We are player 1");
isPlayer1 = YES;
//[self sendTheSelectedRandomQuestionWithQuestion];
} else {
NSLog(#"We are player 2");
isPlayer1 = NO;
}
if (!tie) {
receivedRandom = YES;
if (gameState == kGameStateWaitingForRandomNumber) {
[self setGameState:kGameStateWaitingForStart];
}
[self tryStartGame];
}
}
}
}
but for some mysterious reason every time I call the sendTheSelectedRandomQuestionWithQuestion when it is received it thinks that it is randomNumber and not SelectedQ? Can anyone help me please?
Ok, just figured out the problem. It should be:
typedef enum {
kMessageTypeRandomNumber = 0,
kMessageQN = 1,
kMessageTypeGameBegin,
kMessageTypeSelectAnswer1,
kMessageTypeSelectAnswer2,
kMessageTypeSelectAnswer3,
kMessageTypeGameOver
} MessageType;
Instead of:
typedef enum {
kMessageTypeRandomNumber = 0,
kMessageQN = 0,
kMessageTypeGameBegin,
kMessageTypeSelectAnswer1,
kMessageTypeSelectAnswer2,
kMessageTypeSelectAnswer3,
kMessageTypeGameOver
} MessageType;