UISwitch not being set properly based on values - ios

I am using several UISwitches and I can't get them to set correctly, for some odd reason.
I am pulling in JSON data which is just basically a bunch of integers in a certain sequence, based off of their values, I want a switch to be set to either on, or off. Here is part of the relevant code:
- (void)fetchData
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:#"http://10.101.10.101/get/channels"]];
NSError* error;
LightingData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
idData = [LightingData valueForKey:#"level"];
NSLog(#"Lighting Values: %#", [idData objectAtIndex:19]);
dispatch_async(dispatch_get_main_queue(), ^{
});
});
}
- (void)dataCheck
{
int x = 100;
if ([idData objectAtIndex:19 == x])
{
[fieldsOverheadStatus setOn:YES];
}
}
The dataCheck method continues for a while, setting about 20 switches.
I have all of my switches set to default as "off", but when I run the app, they all show as being "on".
I set breakpoints to see if the dataCheck method gets called, and it does. As you can see, I stuck a log in to see what the value of objectAtIndex:19 is and the value that is returned is 0 (not null). I checked the contents of idData and all of the values are correctly gotten.
Can anyone tell me why my switches are being changed, even when they shouldn't be? What am I doing wrong?
Let me know if anymore code is needed.

You’re checking the object in idData at the index (19 == x), i.e. false/NO, i.e. 0; the object at index 0 is non-nil, so you’re always setting the switch on. That line should probably be:
if ([[idData objectAtIndex:19] intValue] == x)

Related

iOS share extension does not receive data from Safari

I have an iOS share extension that needs the URL of the opened web page. Everything works good, especially in a simulator. But on a real device I have around 20-30% cases where the extension does not receive any data i.e.:
NSExtensionItem *inputItem = self.extensionContext.inputItems.firstObject;
NSItemProvider *item = inputItem.attachments.firstObject;
[item loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(NSDictionary *item, NSError *error) {
// here the error is sometimes not nil and thus the _baseURI ends up nil
_baseURI = [item[NSExtensionJavaScriptPreprocessingResultsKey] objectForKey:#"baseURI"];
}];
The error code is -100 with description "No item available for requested type identifier.". This happens mainly when I open the extension several times in a row without changing/refreshing the web page in the Safari.
In those situations I see a device log saying "iPhone kernel[0] : Sandbox: MobileSafari(7033) deny(1) file-read-data /private/var/containers/Bundle/Application/.../bundle.js" where the bundle.js is the javascript with the ExtensionPreprocessingJS object. The bundle.js declares the ExtensionPreprocessingJS object like this (extracted the relevant part):
ExtensionPreprocessingJS = {
run: function(arguments){
arguments.completionFunction({
"baseURI": document.baseURI
})
},
finalize: function(arguments){
}
}
In this situation, it could some time happen that when the extension is closed the next time opening the share dialog in Safari shows my extension with no icon. This happens on my testing iPhone 5s and iPhone 6 with iOS 9.3.
I think that the missing data is because of the system could not read the extension's JavaScript file, but why could this happen?
If you read the documentation for:
loadItemForTypeIdentifier(_:options:completionHandler:)
You'll see that:
The type information for the first parameter of your completionHandler
block should be set to the class of the expected type. For example,
when requesting text data, you might set the type of the first
parameter to NSString or NSAttributedString. An item provider can
perform simple type conversions of the data to the class you specify,
such as from NSURL to NSData or NSFileWrapper, or from NSData to
UIImage (in iOS) or NSImage (in OS X). If the data could not be
retrieved or coerced to the specified class, an error is passed to the
completion block’s.
Try this code to see what you recieve:
[item loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(id item, NSError *error) {
// here the error is sometimes not nil and thus the _baseURI ends up nil
_baseURI = [item[NSExtensionJavaScriptPreprocessingResultsKey] objectForKey:#"baseURI"];
}];
Note that item is not set to NSDictionary.

Why does my NSMutableArray contain nil on some indices?

Problem:
After I fill an NSMutableArray with objects and try to do something with it, it contains (id)0x0 on some indices. I thought adding nil to an NSMutableArray wasn't possible at all in Objective-C so I am wondering why this happens.
When does this happen?
'Sometimes' unfortunately. It is reproducible by downloading more than ~5000 tiles, just to get the amount high enough for a chance for this to occur. Even with more than 5000 tiles it sometimes goes flawlessly.
Context:
My app has a button which starts a download for map tiles for a specific region. The download happens parallel in background threads and reports back for every tile downloaded.
To allow for canceling the download, in my downloader singleton I have a temporary NSMutableArray which saves a hash from every tile downloaded. After canceling, I can use that list of hashes to delete every saved tile in the database.
Saving the hashes during downloading seems to go fine, but when I actually want to do anything with it (I use [_currentTileHashes copy] to change it to an NSArray to give to the delete method), it throws an NSInvalidArgumentExceptionon that line saying:
-[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[3402]
When I use the debugger to inspect the _currentTileHashes mutable array, I indeed see that one or two of the indices is actually nil or (id)0x0. This screenshot illustrates it:
Relevant code:
This code is from the callback for every tile download where it hashes the tile, adds it to the hashes array and calls back to the UI for progress:
- (void)tileCache:(RMTileCache *)tileCache didBackgroundCacheTile:(RMTile)tile withIndex:(NSUInteger)tileIndex ofTotalTileCount:(NSUInteger)totalTileCount {
DebugLog(#"Cached tile %lu of %lu.", (unsigned long)tileIndex, (unsigned long)totalTileCount);
if (_currentlyDownloading) {
float progress = (float)tileIndex / (float)totalTileCount;
NSDictionary *progressDict = #{#"progress" : [NSNumber numberWithFloat:progress],
#"routeId" : _downloadingRoute.routeId};
[_currentTileHashes addObject:[RMTileCache tileHash:tile]];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"routeTileDownloaded"
object:progressDict];
}
}
This is the way the tile gets hashed (this is from the Mapbox iOS SDK):
+ (NSNumber *)tileHash:(RMTile)tile
{
return [NSNumber numberWithUnsignedLongLong:RMTileKey(tile)];
}
uint64_t RMTileKey(RMTile tile)
{
uint64_t zoom = (uint64_t)tile.zoom & 0xFFLL; // 8bits, 256 levels
uint64_t x = (uint64_t)tile.x & 0xFFFFFFFLL; // 28 bits
uint64_t y = (uint64_t)tile.y & 0xFFFFFFFLL; // 28 bits
uint64_t key = (zoom << 56) | (x << 28) | (y << 0);
return key;
}
And finally, the code where the exception occurs:
- (void)tileCacheDidCancelBackgroundCache:(RMTileCache *)tileCache {
DebugLog(#"Finished canceling tile download");
[tileCache removeAllCachedImagesForTileHashes:[_currentTileHashes copy]];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"routeTileDownloadCanceled"
object:nil];
}
Tested on iOS 8.4, 8.4.1 (iPhone 6) and 7.1 (iPhone 4)
Feel free to ask for more clarification if something is unclear.
NSMutableArray is not thread safe, so updating an instance from multiple, concurrent, background downloads is likely to lead to corruption in your array - as you are seeing.
I would suggest using #synchronized to guard the array when you update it -
- (void)tileCache:(RMTileCache *)tileCache didBackgroundCacheTile:(RMTile)tile withIndex:(NSUInteger)tileIndex ofTotalTileCount:(NSUInteger)totalTileCount {
DebugLog(#"Cached tile %lu of %lu.", (unsigned long)tileIndex, (unsigned long)totalTileCount);
if (_currentlyDownloading) {
float progress = (float)tileIndex / (float)totalTileCount;
NSDictionary *progressDict = #{#"progress" : [NSNumber numberWithFloat:progress],
#"routeId" : _downloadingRoute.routeId};
#synchronized(_currentTileHashes) {
[_currentTileHashes addObject:[RMTileCache tileHash:tile]];
}
[[NSNotificationCenter defaultCenter]
postNotificationName:#"routeTileDownloaded"
object:progressDict];
}
}

iOS looping error

So I'm having a weird problem with loops in while developing in iOS. There are over hundreds of lines of code, so instead of boring you with it, I'm going to describe the problem.
I am trying to upload events from Facebook to an Microsoft Azure server. To do so, I made a class called FacebookUploader. In that class, I loop through the events of Facebook (and set all the variables to the class. ie. the name of the first event is self.event, when loops continues and goes to the second event, that events name is self.event now). What I expected iOS to do was to go through each event in the loop, upload it, and then move onto the next one.
This worked while I was just uploading the event. Now, I'm also first checking the database to see if the event is already there. And then when I get confirmation that it's not there, I upload it. However, when it comes time to upload it, Facebook Uploader has already finished looping through all the events and only the last event gets uploaded (multiple times, one for each event that should be uploaded).
I've pinpointed the error, now I'm asking, what am I doing wrong? Should the variable not be associated with the class and instead be local? Or is there a way to stop running the loop until we get confirmation from the server that the event is not there? Your help is much appreciated.
MSQuery *query = [self.table queryWithPredicate: [NSPredicate predicateWithFormat:#"fb_id == %#", self.id]];
[query readWithCompletion:^(MSQueryResult *result, NSError *error) {
//not in table, add it
NSArray *items = [result.items mutableCopy];
if (items == nil || [items count] == 0) {
NSDictionary *newItem;
if(self.end_time != nil) {
newItem = #{#"fb_id": self.id, #"event_name": self.name, #"event_location": self.location, #"event_start": self.start_time, #"event_end": self.end_time, #"event_description": self.descriptionOfEvent, #"picture_url": self.picture_url, #"event_latitude": self.venue_latitude, #"event_longitude": self.venue_longitude, #"popularity": self.popularity};
}
else {
newItem = #{#"fb_id": self.id, #"event_name": self.name, #"event_location": self.location, #"event_start": self.start_time, #"event_description": self.descriptionOfEvent, #"picture_url": self.picture_url, #"event_latitude": self.venue_latitude, #"event_longitude": self.venue_longitude, #"popularity": self.popularity};
}
[self.table insert:newItem completion:^(NSDictionary *result, NSError *error) {
// The result contains the new item that was inserted,
// depending on your server scripts it may have additional or modified
// data compared to what was passed to the server.
if(error) {
NSLog(#"ERROR %#", error);
} else {
NSLog(#"Todo Item: %#", [result objectForKey:#"event_name"]);
}
}];
}
The answer to the question was given by Paulw11 above. By allocating the variables outside of the loop, I was creating multiple copies of the variables, causing my error. Allocating the variable inside of the loop fixed the error.

Firebase how to check transaction success or fail?

I am trying to update a firebase node in transaction, simple stuff. Followed the doc:
https://www.firebase.com/docs/ios/guide/saving-data.html
Firebase* upvotesRef = [[Firebase alloc] initWithUrl: #"https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes"];
[upvotesRef runTransactionBlock:^FTransactionResult *(FMutableData *currentData) {
NSNumber *value = currentData.value;
if (currentData.value == [NSNull null]) {
value = 0;
}
[currentData setValue:[NSNumber numberWithInt:(1 + [value intValue])]];
return [FTransactionResult successWithValue:currentData];
}];
The big question is:
How do I check the outcome of this transaction(success/fail)? I wish to make some UI changes depends on the outcome of it.
There is another method in SDK document appears to have a callback, but it did not explain which value should I check against. And it says something about the method could be run multiple times. How do I make sure when it gives the "final" result?
https://www.firebase.com/docs/ios-api/Classes/Firebase.html#//api/name/runTransactionBlock:andCompletionBlock:
Sorry I am such a beginner that apple style doc really doesn't come together without some examples.
If you just want to wait for the final value, runTransactionBlock:andCompletionBlock: is the method you want to look at. Here's some example code:
[upvotesRef runTransactionBlock:^FTransactionResult *(FMutableData *currentData) {
NSNumber *value = currentData.value;
if (currentData.value == [NSNull null]) {
value = 0;
}
[currentData setValue:[NSNumber numberWithInt:(1 + [value intValue])]];
return [FTransactionResult successWithValue:currentData];
} andCompletionBlock:^(NSError *error, BOOL committed, FDataSnapshot *snapshot) {
if (error) {
NSLog(#"Error: %#", error);
}
if (committed) {
NSLog(#"Data committed");
}
NSLog(#"Final Value: %#", snapshot.value);
}];
That last value there, snapshot.value is where you can get your final value. It's the same sort of FDataSnapshot that you get if you use observeEventType:withBlock:
If something goes wrong, you'll get an error.
If your data is committed, committed will be YES. If you returned [FTransactionResult abort] in your transaction block instead of [FTransactionResult successWithValue:], committed will be NO.
This means, if you read the counter at 4 and try to update it. You might try to update the counter at the same time someone else does. If yours get in first, snapshot.value will be 5. If the other person's update gets in before you do, the snapshot.value will be 6.
You probably wanted to get up to 6 no matter who upvoted first. To do this, you need to add an observer. The code for that might look like:
[upvotesRef observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"New Value: %#", snapshot.value);
}];
With this, you don't need the completion block to find out the final value because every time the transaction blocks, the observer block will fire. In the example scenario above, it would fire once for 5 and once for 6 no matter who upvoted first. You do need the completion block if you want to find out whether or not your particular transaction succeeded and not just what value is at that location now.
And, just to be complete, there is one more method called runTransactionBlock:andCompletionBlock:withLocalEvents:. If others are also writing to the same location, the transaction block may run multiple times. This happens if it finds out that it is running on stale data. When it runs successfully on fresh data, it will call the completion block. However, you'll find that each time it runs, it will fire any observer blocks at that location. If you don't want this to happen, you should pass NO to withLocalEvents:. Your Firebase will trigger events at that location whenever a confirmed write goes through, but your local transaction's temporary writes, which are unconfirmed, will not.
Looking back to the example where you and another person are trying to upvote at the same time. By default, the observer will fire as soon as you try to update the count from 4 to 5. The actual transaction might fail because someone else pushed the upvote count from 4 to 5 at the same time. Your transaction block would then run again with the new data, 5, and see that it should push the count to 6. With local events set to NO, the observer will fire after the server lets you know someone else pushed the upvote count from 4 to 5, and not when you try to update the count from 4 to 5.
This isn't a huge deal with something simple like upvotes where everyone is just incrementing, but if you could potentially be pushing different data from other users, any observers at the location may see the data jump around before finally settling.

Error using semaphore token to run code after a group of asynchronous web service calls

I am in a situation where I need to call multiple web service requests at a time to call to my server to delete messages on the server and I am having a difficult time trying to figure out the best way to trigger some methods to refresh my data at the completion of these group of web service calls.
From what I have researched using a semaphore counter should work for what I am wanting to do, but I am running into a problem where upon calling dispatch_release() on my semaphore token my application crashes with this -
libdispatch.dylib`_dispatch_semaphore_dispose$VARIANT$mp:
0x3c03ed70: push {r7, lr}
0x3c03ed72: ldr r1, [r0, #40]
0x3c03ed74: mov r7, sp
0x3c03ed76: ldr r2, [r0, #36]
0x3c03ed78: cmp r1, r2
0x3c03ed7a: bge 0x3c03ed7e ; _dispatch_semaphore_dispose$VARIANT$mp + 14
0x3c03ed7c: trap
Everything i've found on this problem points to the semaphore token being referenced by something but I can't see what would have a reference to the token.
- (void)deleteMultipleThreadsForMessages:(NSArray *)messages withCompletion:(void(^)(BOOL allThreadsDeleted))completion
{
NSDictionary *userDictionary = [[MSMKeychainAccess sharedKeychain] returnUserDictionary];
long messagesCount = [messages count] - 1;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(messagesCount);
BOOL (^isLastRequest)(void) = ^BOOL (void) {
long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
if (0 == result) {
return false;
}
dispatch_release(semaphore);
return true;
};
for (MSMMessageDataModel *message in messages) {
NSDictionary *dictionary = #{#"license": userDictionary[#"license"],
#"messageID" : message.msgID};
NSLog(#"Called this many times! %#", message.msgID);
[[MSMClient sharedClient] soapPostsWithCompletion:^(NSDictionary *response, NSError *error) {
if (error) {
isLastRequest();
completion(NO);
} else {
if (isLastRequest()) {
completion(YES);
}
}
} andRequest:[[[MSMRequestsController alloc] init] createGetMessagesSOAPCallAndSendRequest:#"DeleteThread"
withParameters:dictionary]];
}
}
EDIT
Thanks for the great answers. As Dustin said I was attempting to use dispatch_semaphore for something that it should not be used for. I accepted his answer because it was simple to implement and didn't need any re-structure of what i'm doing currently to send my web services. I now have some good reading material though about dispatch_groups in general though!
Thanks for all your help!
I'm not sure this is the most efficient way to solve your problem, but just looking through your code, one problem you might be running into is that you're creating the semaphore with a count and trying to release it when that count is less than the initial count. In GCD, always create a semaphore with 0 and then signal the semaphore to the correct number of "resources" you need to count. The reason is because semaphores can't be destroyed/released if their count is less than the initial count. It kinda makes sense if you think of it as a resource counter. Having a semaphore number less than the initial count means that you have a worker still using one of the resources.
You can see this code here http://opensource.apple.com/source/libdispatch/libdispatch-187.7/src/semaphore.c. The code that will throw the exception in _dispatch_semaphore_dispose is:
if (dsema->dsema_value < dsema->dsema_orig) {
DISPATCH_CLIENT_CRASH(
"Semaphore/group object deallocated while in use");
}
This is exactly what dispatch_group is designed to address. You dispatch several blocks to a group, and when they have all completed, another block will be executed (or you can wait on them if you need a synchronous behavior).
First see Waiting on Groups of Queued Tasks in the Concurrency Programming Guide, and see the dispatch_group functions in the GCD reference. To see them in action, see the JuliaCell example from Chapter 13 of iOS:PTL. Cocoa Samurai also has some examples.
Even if you can't actually dispatch the blocks to a group (it may not work with how MSMClient operates), you can still use dispatch groups manually by calling dispatch_group_enter() and dispatch_group_leave() to get the same behavior you're trying to get from the semaphore.
As a side note, BOOL is not the same as bool. BOOL return YES and NO, which are different than true and false (which I assume means you're compiling this as ObjC++, which always makes me shudder, but that's a different issue). Mixing them can matter because they can be (and sometimes are) different sizes. I've had to crashes due to that personally.
This is an incorrect usage for dispatch_semaphore -- it isn't meant to do what you are attempting. What you need is a counting variable that is thread safe. You can get this using __sync_sub_and_fetch.
- (void)deleteMultipleThreadsForMessages:(NSArray *)messages withCompletion:(void(^)(BOOL allThreadsDeleted))completion
{
NSDictionary *userDictionary = [[MSMKeychainAccess sharedKeychain] returnUserDictionary];
__block long messagesCount = [messages count];
for (MSMMessageDataModel *message in messages) {
NSDictionary *dictionary = #{#"license": userDictionary[#"license"],
#"messageID" : message.msgID};
NSLog(#"Called this many times! %#", message.msgID);
[[MSMClient sharedClient] soapPostsWithCompletion:^(NSDictionary *response, NSError *error) {
long which = __sync_sub_and_fetch(&messageCount, 1);
if(which == 0)
completion(error == nil);
} andRequest:[[[MSMRequestsController alloc] init] createGetMessagesSOAPCallAndSendRequest:#"DeleteThread"
withParameters:dictionary]];
}
}
__sync_sub_and_fetch tells the CPU that you want it to take latest version of 'messageCount' from all threads (and cores), subtract 1, and give you the result.

Resources