Here is part from my code:
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect frame = [[UIScreen mainScreen] bounds];
_webView = [[UIWebView alloc] initWithFrame:frame];
[_webView setHidden:NO];
[self.view addSubview:_webView];
_vk = [[DPVkontakteCommunicator alloc] initWithWebView:_webView];
DPVkontakteUserAccount *user;
NSString *accessToken = [[NSUserDefaults standardUserDefaults]
objectForKey:#"accessToken"];
NSInteger userId = [[[NSUserDefaults standardUserDefaults]
objectForKey:#"userId"] integerValue];
user = [[DPVkontakteUserAccount alloc]
initUserAccountWithAccessToken:accessToken
userId:userId];
NSLog(#"%#", user);
[user setSuccessBlock:^(NSDictionary *dictionary)
{
NSLog(#"%#", dictionary);
}];
NSDictionary *options = #{#"uid":#"1"};
// [user usersGetWithCustomOptions:#{#"uid":#"1"}]; // Zombie
[user usersGetWithCustomOptions:options]; // Not zombie
// __block NSDictionary *options = #{};
//
// [_vk startOnCancelBlock:^{
// NSLog(#"Cancel");
// } onErrorBlock:^(NSError *error) {
// NSLog(#"Error: %#", error);
// } onSuccessBlock:^(DPVkontakteUserAccount *account) {
// NSLog(#"account:%#", account);
//
// [account setSuccessBlock:^(NSDictionary *dictionary)
// {
// NSLog(#"%#", dictionary);
// }];
//
// [account docsGetUploadServerWithCustomOptions:options];
// }];
}
and here is the part which processes the userGetWithCustomOptions: method:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSString *methodName = NSStringFromSelector([anInvocation selector]);
NSDictionary *options;
[anInvocation getArgument:&options
atIndex:2];
NSArray *parts = [self parseMethodName:methodName];
NSString *vkURLMethodSignature = [NSString stringWithFormat:#"%#%#.%#",
kVKONTAKTE_API_URL,
parts[0],
parts[1]];
// appending params to URL
NSMutableString *fullRequestURL = [vkURLMethodSignature mutableCopy];
[fullRequestURL appendString:#"?"];
[options enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
{
[fullRequestURL appendFormat:#"%#=%#&", key, [obj encodeURL]];
}];
[fullRequestURL appendFormat:#"access_token=%#", _accessToken];
// performing HTTP GET request to vkURLMethodSignature URL
NSURL *url = [NSURL URLWithString:fullRequestURL];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation;
operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:urlRequest
success:^(NSURLRequest *request,
NSHTTPURLResponse *response,
id JSON)
{
_successBlock(JSON);
}
failure:^(NSURLRequest *request,
NSHTTPURLResponse *response,
NSError *error,
id JSON)
{
_errorBlock(error);
}];
[operation start];
}
problem is that when I am using "options" variable - it works fine, but when using direct values - it fails, app crashes. Using Profile I have found that method call directs to deallocated object.
Why this happens?
There is no other code that can help.
ViewController.m code: https://gist.github.com/AndrewShmig/5398546
DPVkontakteUserAccount.m: https://gist.github.com/AndrewShmig/5398557
The problem is that the parameter of getArgument: is type void *. And you are passing &value, which is NSDictionary * __strong * (pointer to a strong reference) to it. The cast is valid because it is possible to assign any non-object pointer to and from void * without any warnings.
When you pass a "pointer to strong" to a function, that means the function should expect the pointer to a "strong reference", and when the function exits, it should preserve the fact that the pointer points to a "strong reference". What this means is that if the function changes the reference (pointed to by the pointer), it must first release the previous value and then retain the new value.
However, what does getArgument:atIndex: do with its void * argument? It is agnostic about the thing pointed to, and simply copies the value into the memory pointed to. Therefore, it does not do any of this retain and release stuff. Basically, it performs a plain-old pre-ARC non-retaining assignment into your value variable.
So why is it crashing? What is happening is that value is at first nil, and then inside the getArgument:atIndex:, it assigns the new value into it, but it does not retain it. However, ARC assumes that it has been retained, since value is a strong reference. So at the end of the scope, ARC releases it. This is an over-release, since it was never retained.
The solution is to not pass a "pointer to strong" into getArgument:, because that method does not know anything about "strong". Instead, pass a "pointer to unsafe_unretained" or "pointer to void" into it, and then convert it to a strong reference later:
NSDictionary * __unsafe_unretained temp;
[anInvocation getArgument:&temp atIndex:2];
NSDictionary *options = temp; // or you can just use temp directly if careful
or alternately:
void *temp;
[anInvocation getArgument:&temp atIndex:2];
NSDictionary *options = (__bridge NSDictionary *)temp;
Related
Basically, I have an array of urls as strings, and as I loop through this array, if the element is a url for an image, I want to turn that url into a UIImage object and add it to another array. This is very slow though since I have to request the data for each URL. I've tried using dispatch_async as I show below but it doesn't seem to make any difference at all.
The key is that when I add these objects to my other array, whether they are images or something else they have to stay in order. Can anyone offer any guidance?
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i=0; i<[slides count]; i++){
__block NSString *mediaURLString = [primaryPhoto objectForKey:#"url"];
if ([self mediaIsVideo:mediaURLString]){
***some code***
}
else{ //if media is an image
dispatch_group_async(group, queue, ^{
mediaURLString = [mediaURLString stringByAppendingString:#"?w=1285&h=750&q=150"];
NSURL *url = [NSURL URLWithString:mediaURLString];
[mutableMedia addObject:url];
NSURL *url = ((NSURL *)self.mediaItem);
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response;
NSError *error;
NSData *urlData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
UIImage *image = [[UIImage alloc] initWithData:urlData];
[mutableMedia replaceObjectAtIndex:i withObject:image];
});
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
try this:
[self performSelectorInBackground:#selector(WebServiceCallMethod) withObject:nil];
and create one method like this
-(void)WebServiceCallMethod
{
mediaURLString = [mediaURLString stringByAppendingString:#"?w=1285&h=750&q=150"];
NSURL *url = [NSURL URLWithString:mediaURLString];
[mutableMedia addObject:url];
NSURL *url = ((NSURL *)self.mediaItem);
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *response;
NSError *error;
NSData *urlData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
UIImage *image = [[UIImage alloc] initWithData:urlData];
[mutableMedia replaceObjectAtIndex:i withObject:image];
}
Hope it Helps!!
Do yourself a favor and don't use +sendSynchronousRequest:... Try something like this instead:
dispatch_group_t group = dispatch_group_create();
for (int i=0; i<[slides count]; i++)
{
__block NSString *mediaURLString = [primaryPhoto objectForKey:#"url"];
if ([self mediaIsVideo:mediaURLString]){
***some code***
}
else
{
//if media is an image
mediaURLString = [mediaURLString stringByAppendingString:#"?w=1285&h=750&q=150"];
NSURL *url = [NSURL URLWithString:mediaURLString];
[mutableMedia addObject:url];
NSURL *url = ((NSURL *)self.mediaItem);
NSURLRequest *request = [NSURLRequest requestWithURL:url];
dispatch_group_enter(group);
[NSURLConnection sendAsynchronousRequest: request queue: [NSOperationQueue mainQueue] completionHandler:
^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
if (data.length && nil == connectionError)
{
UIImage *image = [[UIImage alloc] initWithData:data];
[mutableMedia replaceObjectAtIndex:i withObject:image];
}
dispatch_group_leave(group);
}];
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Do stuff here that you want to have happen after all the images are loaded.
});
This will start asynchronous requests for all your URLs. When each request finishes, it will run its completion handler which will update your array, and when all requests have finished, the block in the dispatch_group_notify call will be executed.
This approach has the advantage that you can call it from the main thread, all individual completion blocks will be run on the main thread (thus ensuring thread-safety for the mutableMedia array (at least as far as this code goes)) and the final completion block will also be run on the main thread, so you can do whatever you need to update the UI directly.
There is a nifty solution using dispatch lib. The code below should stand for itself.
The basic idea is that an array contains "input" objects which each will be "transformed" via an asynchronous unary task - one after the other. The final result of the whole operation is an array of the transformed objects.
Everything here is asynchronous. Every eventual result will be passed in a completion handler which is a block where the result is passed as a parameter to the call-site:
typedef void (^completion_t)(id result);
The asynchronous transform function is a block which takes the input as a parameter and returns a new object - via a completion handler:
typedef void (^unary_async_t)(id input, completion_t completion);
Now, the function transformEach takes the input values as an NSArray parameter inArray, the transform block as parameter transform and the completion handler block as parameter completion:
static void transformEach(NSArray* inArray, unary_async_t transform, completion_t completion);
The implementation is a follows:
static void do_each(NSEnumerator* iter, unary_async_t transform,
NSMutableArray* outArray, completion_t completion)
{
id obj = [iter nextObject];
if (obj == nil) {
if (completion)
completion([outArray copy]);
return;
}
transform(obj, ^(id result){
[outArray addObject:result];
do_each(iter, transform, outArray, completion);
});
}
static void transformEach(NSArray* inArray, unary_async_t transform,
completion_t completion) {
NSMutableArray* outArray = [[NSMutableArray alloc] initWithCapacity:[inArray count]];
NSEnumerator* iter = [inArray objectEnumerator];
do_each(iter, transform, outArray, completion);
}
And build and run the following example
int main(int argc, const char * argv[])
{
#autoreleasepool {
// Example transform:
unary_async_t capitalize = ^(id input, completion_t completion) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(1);
if ([input respondsToSelector:#selector(capitalizedString)]) {
NSLog(#"processing: %#", input);
NSString* result = [input capitalizedString];
if (completion)
completion(result);
}
});
};
transformEach(#[#"a", #"b", #"c"], capitalize, ^(id result){
NSLog(#"Result: %#", result);
});
sleep(10);
}
return 0;
}
will print this to the console:
2013-07-31 15:52:49.786 Sample2[1651:1603] processing: a
2013-07-31 15:52:50.789 Sample2[1651:1603] processing: b
2013-07-31 15:52:51.792 Sample2[1651:1603] processing: c
2013-07-31 15:52:51.793 Sample2[1651:1603] Result: (
A,
B,
C
)
You can easily create a category for NSArray which implements, say a
-(void) asyncTransformEachWithTransform:(unary_async_t)transform
completion:(completion_t)completionHandler;
method.
Have fun! ;)
Edit:
IFF you ask how this applies to your problem:
The array of URLs is the input array. In order to create the transform block, simply wrap your asynchronous network request in an asynchronous method, say:
`-(void) fetchImageWithURL:(NSURL*)url completion:(completion_t)completionHandler;`
Then wrap method fetchImageWithURL:completion: into a appropriate transform block:
unary_async_t fetchImage = ^(id url, completion_t completion) {
[self fetchImageWithURL:url completion:^(id image){
if (completion)
completion(image); // return result of fetch request
}];
};
Then, somewhere in your code (possible a view controller) assuming you implemented the category for NSArray, and your array of urls is property urls:
// get the images
[self.urls asyncTransformEachWithTransform:fetchImage completion:^(id arrayOfImages) {
// do something with the array of images
}];
I've got 2 classes, MPRequest and MPModel.
The MPModel class has a method to lookup something from the core data store, and if not found, creates an MPRequest to retrieve it via a standard HTTP request (The method in MPModel is static and not and instance method).
What I want is to be able to get a progress of the current HTTP request. I know how to do this, but I'm getting a little stuck on how to inform the view controller. I tried creating a protocol, defining a delegate property in the MPRequest class, altering the method in MPModel to accept this delegate, and in turn passing it to the MPRequest when it is created.
This is fine, however ARC is then releasing this delegate whilst the request is running and thus doesn't do what I want. I'm trying to avoid making my delegate object a strong reference in case it throws up any reference cycles but I don't know any other way of doing this.
To start the request, from my view controller I'm running
[MPModel findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
....
} sortedBy:#"name" ascending:YES delegate:self]
Inside the findAllWithBlock method, I have
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
...
}
And in the MPRequest class I have the following property defined :
#property (nonatomic, weak) NSObject<MPRequestDelegate> *delegate;
Any ideas or suggestions?
As requested, here is some more code on how things are being called :
In the view controller :
[MPPlace findAllWithBlock:^(NSFetchedResultsController *controller, NSError *error) {
_placesController = controller;
[_listView reloadData];
[self addAnnotationsToMap];
[_loadingView stopAnimating];
if (_placesController.fetchedObjects.count > 0) {
// We've got our places, but if they're local copies
// only, new ones may have been added so just update
// our copy
MPSyncEngine *engine = [[MPSyncEngine alloc] initWithClass:[MPPlace class]];
engine.delegate = self;
[engine isReadyToSync:YES];
[[MPSyncManager sharedSyncManager] registerSyncEngine:engine];
[[MPSyncManager sharedSyncManager] sync];
}
} sortedBy:#"name" ascending:YES delegate:self];
Here, self is never going to be released for obvious reasons, so I don't see how this is the problem.
Above, MPPlace is a subclass of MPModel, but the implementation of the findAllWithBlock:sortedBy:ascending:delegate: is entirely in MPModel
The method within MPModel looks like this
NSManagedObjectContext *context = [[MPCoreDataManager sharedInstance] managedObjectContext];
[context performBlockAndWait:^{
__block NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([self class])];
[request setSortDescriptors:#[[[NSSortDescriptor alloc] initWithKey:key ascending:asc]]];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:nil];
[controller performFetch:&error];
if (!controller.fetchedObjects || controller.fetchedObjects.count == 0) {
// Nothing found or an error, query the server instead
NSString *url = [NSString stringWithFormat:#"%#%#", kMP_BASE_API_URL, [self baseURL]];
MPRequest *objRequest = [MPRequest requestWithURL:url];
objRequest.delegate = delegate;
[objRequest setRequestMethod:#"GET"];
[MPUser signRequest:objRequest];
[objRequest submit:^(MPResponse *resp, NSError *err) {
if (err) {
block(nil, err);
} else {
NSArray *objects = [self createListWithResponse:resp];
objects = [MPModel saveAllLocally:objects forEntityName:NSStringFromClass([self class])];
[controller performFetch:&error];
block(controller, nil);
}
}];
} else {
// Great, we found something :)
block (controller, nil);
}
}];
The delegate is simply being passed on to the MPRequest object being created. My initial concern was that the MPRequest object being created was being released by ARC (which I guess it probably is) but it didn't fix anything when I changed it. I can't make it an iVar as the method is static.
The submit method of the request looks like this :
_completionBlock = block;
_responseData = [[NSMutableData alloc] init];
[self prepareRequest];
[self prepareRequestHeaders];
_connection = [[NSURLConnection alloc] initWithRequest:_urlRequest
delegate:self];
And when the app starts downloading data, it calls :
[_responseData appendData:data];
[_delegate requestDidReceive:(float)data.length ofTotal:_contentLength];
Where _contentLength is simply a long storing the expected size of the response.
Got it working. It was partly an issue with threading, where the core data thread was ending before my request, me looking at the output from a different request entirely, and the way ARC handles memory in blocks.
Thanks for the help guys
I've got this parsing operation that currently works fine, but I've started to notice that it is freezing up my UI slightly so I'm trying to refactor and get this done asynchronously. I'm having some issues however and was hoping someone could point me in the right direction. Here's my current (synchronous) code:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
return nil;
}
NSArray *events = [eventsData valueForKeyPath:#"resultsPage.results"];
if (events == nil) {
//parsing error
return nil;
}
NSLog(#"events looks like %#", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:#"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:#"location.lat"];
event.longitude = [object valueForKeyPath:#"location.lng"];
event.title = [object valueForKeyPath:#"displayName"];
event.venue = [object valueForKeyPath:#"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:#"uri"]];
event.artist = [object valueForKeyPath:#"performance.artist.displayName"];
event.date = [object valueForKeyPath:#"start.datetime"];
[formattedEvents addObject:event];
}
return [NSArray arrayWithArray:formattedEvents];
}
I've been looking into NSOperationQueue's and I'm struggling to find a solution as I'd like to return an array from this method and operation queues are not meant to have return values. I'm also looking at GCD and i've got somethinbg like this:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
dispatch_queue_t backgroundQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block NSMutableArray *mutable = [NSMutableArray array];
dispatch_async(backgroundQueue, ^{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
mutable = nil;
}
NSArray *events = [eventsData valueForKeyPath:#"resultsPage.results"];
if (events == nil) {
//parsing error
mutable = nil;
}
NSLog(#"events looks like %#", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:#"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:#"location.lat"];
event.longitude = [object valueForKeyPath:#"location.lng"];
event.title = [object valueForKeyPath:#"displayName"];
event.venue = [object valueForKeyPath:#"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:#"uri"]];
event.artist = [object valueForKeyPath:#"performance.artist.displayName"];
event.date = [object valueForKeyPath:#"start.datetime"];
[formattedEvents addObject:event];
}
mutable = [NSMutableArray arrayWithArray:formattedEvents];
});
return [mutable copy];
}
For some reason, this seems to be returning the object before the parsing has finished however, as I'm gettting no data out of that mutable object, but I'm noticing that the parsing is indeed occurring (i'm logging out the results). can anyone give me an idea about how to get this asynch stuff going?
Thanks!!
You primary problem is that by their very nature asynchronous operations can't synchronously return a result. Instead of returning an array from -eventsFromJSON:, you should provide a way for the caller to receive a callback when the results are finished. There are two common approaches to this in Cocoa.
You can create a delegate with an associated delegate protocol including a method like -parser:(Parser *)parser didFinishParsingEvents:(NSArray *)events, then have your parser call this method on its delegate when parsing is finished.
Another solution is to allow the caller to provide a completion block to be executed when parsing is complete. So, you might do something like this:
- (void)eventsFromJSON:(NSString *)objectNotation completionHandler:(void (^)(NSArray *events))completionHandler)
{
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
NSMutableArray *mutable = [NSMutableArray array];
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
// Snip...
mutable = [NSMutableArray arrayWithArray:formattedEvents];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler([mutable copy]);
});
});
}
Then you can call this code some thing like this:
- (void)parseJSONAndUpdateUI // Or whatever you're doing
{
NSString *jsonString = ...;
Parser *parser = [[Parser alloc] init];
[parser parseEventsFromJSON:jsonString completionHandler:^(NSArray *events){
// Update UI with parsed events here
}];
}
I like the second, block-based approach better. It makes for less code in most cases. The code also reads closer to the synchronous approach where the method just returns an array, since the code that uses the resultant array simply follows the method call (albeit indented since it's in the completion block's scope).
I would recommend using a completion block that you pass into your parse method. This way you don't have to return a value, but can do what you need to with the information once it is parsed. You just have to make sure you use GCD again to put the completion block on the main thread.
You could also post a notification on the main thread once the operation is complete that contains the array in userInfo.
Returning a value will not work however for asynchronous operations.
You are getting a returned object before the parsing has finished because your return [mutable copy] is outside of the dispatch_async block. Since dispatch_async functions asynchronously, it will return immediately, and then calls your return [mutable copy] (which is empty because it's not done parsing).
I am relatively new to Objective-C and now I have a problem in my iPhone app that I don't fully understand.
I try to use a NSMutableDictionary, this does not seem to work as i expect for some reason. When I run the debugger and do po numberToCallerMap to see the dictionary, I get an exception. I have read the documentation for NSMutableDictionary on how to initialize it, but I can not see what I am doing wrong. Help and advice are appreciated. The variable causing me problem is numberToCallerMap, here is the relevant function:
- (void)setData:(NSString*)value{
[list release];
list = [[NSMutableArray alloc] init];
SBJSON *json = [[[SBJSON alloc] init] autorelease];
NSMutableDictionary* numberToCallerMap;
CallerInfo* caller;
NSDictionary* callerInfo;
#try {
NSArray *array = (NSArray*)[json objectWithString:value];
// reading all the items in the array one by one
numberToCallerMap = [NSMutableDictionary dictionary];
for (id *item in array) {
// if the item is NSDictionary (in this case ... different json file will probably have a different class)
NSDictionary *dict2 = (NSDictionary *) item;
CallInfo *data = [CallInfo alloc];
[data initFromDictionary:dict2];
callerInfo = (NSDictionary*)[dict2 valueForKey:#"caller"] ;
//Here, we want the phonenumber to be part of the CallerInfo object instead.
// It is sent from the server as part of the Call-object
NSString* number = (NSString*)[dict2 valueForKey:#"phoneNumber"];
[callerInfo setValue:number forKey:#"phoneNumber"];
caller = (CallerInfo*)[numberToCallerMap valueForKey:number];
if(caller == nil || [caller isKindOfClass:[NSNull class]]){
caller = [CallerInfo alloc];
[caller initFromDictionary:callerInfo];
[numberToCallerMap setValue:caller forKey:number];
[list insertObject:caller atIndex:0];
}
[caller addRecentCall:data];
}
}
#catch (NSException * e) {
[list release];
list = [[NSMutableArray alloc] init];
}
#finally {
[numberToCallerMap release];
}
}
This is probably not the only problem, but you are not alloc-ing your numberToCallerMap dictionary, you are getting it from a convenience class method -- [NSMutableDictionary dictionary] -- that returns it autoreleased. So you should not call release on it yourself.
What I'm trying to get is to search for the Anime Titile's ID, compare the length and perform some action afterwards. Here is what I get in the debugger:
2010-08-09 14:30:48.818 MAL Updater OS X[37415:a0f] Detected : Amagami SS - 06
2010-08-09 14:30:48.821 MAL Updater OS X[37415:a0f] http://mal-api.com/anime/search?q=Amagami%20SS
2010-08-09 14:30:49.635 MAL Updater OS X[37415:a0f] 8676
2010-08-09 14:30:49.636 MAL Updater OS X[37415:a0f] -[NSCFNumber length]: unrecognized selector sent to instance 0x384aa40
2010-08-09 14:30:49.637 MAL Updater OS X[37415:a0f] -[NSCFNumber length]: unrecognized selector sent to instance 0x384aa40
The code in question:
if ([self detectmedia] == 1) { // Detects Media from MPlayer via LSOF
NSLog(#"Detected : %# - %#", DetectedTitle, DetectedEpisode);
NSString * AniID = [self searchanime]; // Perform a Search Operation and Returns the ID of the time from JSON
NSLog(#"%#",AniID);
if (AniID.length > 0) { // Compare the length of AniID to make sure it contains a ID
// Other Action here
}
//Release Detected Title and Episode
[DetectedTitle release];
[DetectedEpisode release];
}
SearchAnime method:
-(NSString *)searchanime{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//Escape Search Term
NSString * searchterm = (NSString *)CFURLCreateStringByAddingPercentEscapes(
NULL,
(CFStringRef)DetectedTitle,
NULL,
(CFStringRef)#"!*'();:#&=+$,/?%#[]",
kCFStringEncodingUTF8 );
//Set Search API
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://mal-api.com/anime/search?q=%#",searchterm]];
NSLog(#"%#",[NSString stringWithFormat:#"http://mal-api.com/anime/search?q=%#",searchterm]);
//Release searchterm
[searchterm release];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
//Ignore Cookies
[request setUseCookiePersistence:NO];
//Set Token
[request addRequestHeader:#"Authorization" value:[NSString stringWithFormat:#"Basic %#",[defaults objectForKey:#"Base64Token"]]];
//Perform Search
[request startSynchronous];
// Get Status Code
int statusCode = [request responseStatusCode];
NSString *response = [request responseString];
if (statusCode == 200 ) {
return [self RegExSearchTitle:response]; // Returns ID as NSString
}
else {
return #"";
}
}
RegExSearchTitle
-(NSString *)RegExSearchTitle:(NSString *)ResponseData {
OGRegularExpressionMatch *match;
OGRegularExpression *regex;
//Set Detected Anime Title
regex = [OGRegularExpression regularExpressionWithString:DetectedTitle];
NSEnumerator *enumerator;
// Initalize JSON parser
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSArray *searchdata = [parser objectWithString:ResponseData error:nil];
for (id obj in searchdata) {
// Look in every RegEx Entry until the extact title is found.
enumerator = [regex matchEnumeratorInString:[obj objectForKey:#"title"]];
while ((match = [enumerator nextObject]) != nil) {
// Return the AniID for the matched title
return [obj objectForKey:#"id"];
}
}
// Nothing Found, return nothing
return #"";
}
This behavior is unusual because I have compared the NSString's length in the past and it never failed on me. I am wondering, what is causing the problem?
The declared return type of RegExSearchTitle is NSString *, but that doesn’t force the returned object to actually be an NSString. The "id" element of obj (from the JSON) is a number, so an NSNumber is being returned. The compiler can’t warn you about this because it doesn’t know what classes will be found in a collection.
There are other bugs in the code. Having an unconditional return in a while statement in a for statement does not make sense.
On a side note, by convention Objective-C method names start with a lowercase letter.
Well, it's because you assigned an NSNumber to AniID, not an NSString. NSNumber doesn't have a length method.