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];
}
}
Related
My app offers the option to download 3430 high resolution images from our server, each image of size 50k - 600k bytes.
The original approach was to just download all of them - but we realized that gave a lot of NSURLErrorTimedOut errors and crashed our program. We've now implemented it such that we download all of the images, but in batches of 100 images at a time.
- (void)batchDownloadImagesFromServer:(BOOL)downloadHiResImages
{
[UIApplication sharedApplication].idleTimerDisabled = YES;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
[self generateImageURLList:YES];
[leafletImageLoaderQueue removeAllObjects];
numberOfThumbnailLeft = [uncachedThumbnailArray count];
numberOfHiResImageLeft = [uncachedHiResImageArray count];
NSLog(#"DEBUG: In batchDownloadImagesFromServer numberOfThumbnailLeft %ul , numberOfHiResImageLeft %ul ", numberOfThumbnailLeft, numberOfHiResImageLeft);
numberOfImagesToDownload = numberOfThumbnailLeft;
if (downloadHiResImages)
{
numberOfImagesToDownload += numberOfHiResImageLeft;
}
if (numberTotalToDownload < 0) {
numberTotalToDownload = numberOfHiResImageLeft;
}
int midBatchCt = 0;
// start where we stopped
NSArray *subArray;
NSRange batchRange;
batchRange.location = 0;//uncachedHiResIndex;
NSInteger uncachedNumber = [uncachedHiResImageArray count];
NSLog(#"uncachedHiResIndex and numberTotalToDownload: %d %d", uncachedHiResIndex, numberTotalToDownload);
if (uncachedHiResIndex >= numberTotalToDownload || batchRange.location >= uncachedNumber) {
// we have reached the end of the uncached hires images
NSLog(#" END of download total to download=%ld , uncachedNumber=%ld, # not downloaded is %ld", (long)numberTotalToDownload, uncachedNumber, (long)numberFailedToDownload);
return;
}
if (batchRange.location+100 > uncachedNumber) {
NSInteger imagesUntilEnd = uncachedNumber -1;
batchRange.length = imagesUntilEnd;
NSLog(#"this is uncached number: %d this is uncachedhiresindex:%d and this images until end:%d ", uncachedNumber, uncachedHiResIndex, imagesUntilEnd);
} else {
batchRange.length = 100;
}
NSLog(#" NEW RANGE is from %lul to %lul ", (unsigned long)batchRange.location, batchRange.length);
subArray = [uncachedHiResImageArray subarrayWithRange:batchRange];
if (downloadHiResImages)
{
for ( LeafletURL* aLeafletURL in subArray )
{
LeafletImageLoader* hiResImageLoader = [[LeafletImageLoader alloc] initWithDelegate:self];
[leafletImageLoaderQueue addObject:hiResImageLoader]; // do this before making connection!! //
[hiResImageLoader loadImage:aLeafletURL isThumbnail:NO isBatchDownload:YES];
//// Adding object to array already retains it, so it's safe to release it here. ////
[hiResImageLoader release];
midBatchCt++;
uncachedHiResIndex++;
if (midBatchCt == 10) {
NSLog(#" Waiting for queued images to download...");
NSLog(#" REMOVED from QUEUE %lul , UncachedIndex %lul", numberRemovedFromQueue, uncachedHiResIndex);
break;
}
}
}
if ( [leafletImageLoaderQueue count] == 0 && numberRemovedFromQueue == numberTotalToDownload) {
if([delegate respondsToSelector:#selector(requestDidBatchDownloadImages:)])
{
[delegate requestDidBatchDownloadImages:self];
}
}
}
This has resolved most of our issues. However, we would like to test for network connectivity before even beginning batch downloading. I found a low level ping library that gives accurate round-trip timing results. Using the demo code from GBPing as a reference, I wrote my own code to ping our server before we call batchDownloadImagesFromServer.
- (IBAction)preloadAll:(id)sender
{
self.ping = [GBPing new];
self.ping.host = kDefaultDataServer;
self.ping.delegate = self;
self.ping.timeout = 1;
self.ping.pingPeriod = 0.9;
// setup the ping object (this resolves addresses etc)
[self.ping setupWithBlock:^(BOOL success, NSError *error) {
if (success) {
// start pinging
[self.ping startPinging];
// stop it after 5 seconds
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"stop it");
[self.ping stop];
self.ping = nil;
});
} else {
UIAlertController * alert = [UIAlertController alertControllerWithTitle:#"Internet Connection"
message:#"Not strong enough internet connection"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* OKButton = [UIAlertAction
actionWithTitle:#"Ok"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
[downloadManager batchDownloadImagesFromServer:YES];
}];
[alert addAction:OKButton];
[self presentViewController:alert animated:NO completion:nil];
}
}];
}
I am completely new to networking. How do I determine the payload size and timeout length for my test considering the batch size and the size of the images?
Timeout length is per request. It is just the time the networking code will wait for a reply before it gives up. This shouldn't be too short, but for most system API, timeout length is something around a minute or more, which is probably too long.
Also, note that you will still get time out errors if connectivity is bad, so whatever caused your crashes needs to be fixed. You have to be able to recover from time-outs.
You're not giving much information about your crash (what kind of crash is it? What backtrace do you get?), but I can see three obvious things that may be happening:
You did your downloading in a tight loop without an #autoreleasepool {} block inside it. This means that all your downloaded file data accumulated in RAM and blew your app's memory limit. Be sure to put autorelease pools in long-running loops.
You were doing these downloads on the main thread. The main thread is for the UI and short actions. If your main thread code does anything that takes longer than a few seconds, UIApplication will not get to process touch events from the user (and other occurrences) and the operating system will shoot it down as being unresponsive. Offload longer operations onto a dispatch queue (or use some other way to move the actions off the main thread, e.g. by using asynchronous API).
You are flooding your server with requests, and some DDoS-protection inside it decides to just ignore requests from you for a few minutes as a form of self-protection. Many servers have limits on how many requests they will accept from a client within a given period of time, or how many open connections a client may have at the same time.
I think you would be much better served by showing the code that performs the actual download. You should not need to get accurate ping timing information to download a bunch of image files.
Assuming one or more of the above possibilities are true, I'd suggest you implement your download code like this:
Create a list of all file URLs that need to be downloaded.
Write your code so that it downloads these URLs sequentially. I.e. do not let it start downloading a file until the previous one has finished (or failed and you decided to skip it for now).
Use NSURLSession's support for downloading an individual file to a folder, don't use the code to get an NSData and save the file yourself. That way, your application doesn't need to be running while the download finishes.
Ensure that you can tell whether a file has already been downloaded or not, in case your download gets interrupted, or the phone is restarted in mid-download. You can e.g. do this by comparing their names (if they are unique enough), or saving a note to a plist that lets you match a downloaded file to the URL where it came from, or whatever constitutes an identifying characteristic in your case.
At startup, check whether all files are there. If not, put the missing ones in above download list and download them sequentially, as in #2.
Before you start downloading anything (and that includes downloading the next file after the previous download has finished or failed), do a reachability check using the Reachability API from Apple's SystemConfiguration.framework. That will tell you whether the user has a connection at all, and whether you're on WiFi or cellular (in general, you do not want to download a large number of files via cellular, most cellular connections are metered).
If your images are stored on separate servers, or they are comparatively small and there is more overhead setting up the connection than actually downloading the data, you could modify the code to download several images at once, but usually if you're downloading more than 4 images from a server at the same time, you'll likely not see a performance benefit, as every additional image will just reduce the amount of bandwidth available for the others.
I am using a Bluno microcontroller to send / receive data from an iPhone, and everything is working as it should, but I would like to update the text of a UILabel with the real time data that is being printed from the Serial.print(numTicks); statement. If I stop the flowmeter the UILabel gets updated with the most current value, but I would like to update this label in realtime. I am not sure if this is a C / Arduino question or more of a iOS / Objective-C question. The sketch I'm loading on my Bluno looks like the following, https://github.com/ipatch/KegCop/blob/master/KegCop-Bluno-sketch.c
And the method in question inside that sketch looks like the following,
// flowmeter stuff
bool getFlow4() {
// call the countdown function for pouring beer
// Serial.println(flowmeterPin);
flowmeterPinState = digitalRead(flowmeterPin);
// Serial.println(flowmeterPinStatePinState);
volatile unsigned long currentMillis = millis();
// if the predefined interval has passed
if (millis() - lastmillis >= 250) { // Update every 1/4 second
// disconnect flow meter from interrupt
detachInterrupt(0); // Disable interrupt when calculating
// Serial.print("Ticks:");
Serial.print(numTicks);
// numTicks = 0; // Restart the counter.
lastmillis = millis(); // Update lastmillis
attachInterrupt(0, count, FALLING); // enable interrupt
}
if(numTicks >= 475 || valveClosed == 1) {
close_valve();
numTicks = 0; // Restart the counter.
valveClosed = 0;
return 0;
}
}
On the iOS / Objective-C side of things I'm doing the following,
- (void)didReceiveData:(NSData *)data Device:(DFBlunoDevice *)dev {
// setup label to update
_ticks = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[_tickAmount setText:[NSString stringWithFormat:#"Ticks:%#",_ticks]];
[_tickAmount setNeedsDisplay];
NSLog(#"ticks = %#",_ticks);
}
Basically I would like to update the value of the UILabel while the flowmeter is working.
UPDATE
I just tested the functionality again with the serial monitor within the Arduino IDE, and I got the same if not similar results as to what I got via Xcode and the NSLog statements. So this leads me to believe something in the sketch is preventing the label from updating in real time. :/ Sorry for the confusion.
I am sure this has already occured to someone but I could not find a trusted source (on Stackoverflow or elsewhere) to solve this.
I have to download a (finite) number of files. I don't know their size prior to downloading them. Their size can greatly change and I don't know it before beginning the download.
It can often happen that I have 9 really small files and 1 really big file.
If I use NSProgress with its "children" feature, I will very quickly show a completion of 90%, then the UI will appear to be stuck (even though it is not the case) since the last file is much bigger.
Here is an example code where I simulate a large file.
- (void)viewDidLoad
{
[super viewDidLoad];
nbTasks = 10;
mainProgress = [NSProgress progressWithTotalUnitCount:nbTasks];
[self launch];
}
- (void)launch {
for (int i = 0; i < nbTasks; ++i) {
[mainProgress becomeCurrentWithPendingUnitCount:1];
[self launchWithNumber:i];
[mainProgress resignCurrent];
}
}
- (void)launchWithNumber:(int)count {
int max = count == 0 ? 100 : 10;
NSProgress *localProgress = [NSProgress progressWithTotalUnitCount:max];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
for (int i = 0; i < max; ++i) {
localProgress.completedUnitCount++;
[NSThread sleepForTimeInterval:1];
}
});
}
How would you handle this case where you cannot change the becomeCurrentWithPendingUnitCount since you don't know its weight prior to begin downloading ?
EDIT: This is what I do for now:
First of all: I lied. I don't download files but data from a database, but it's the same problem in the end.
Before creating my root NSProgress, I download the number of objects from my backend (SELECT count(*) FROM ...). With this I can call becomeCurrentWithPendingUnitCount with the correct "weight" and the progress appears more "smooth".
Of course, this is far from ideal for me since I have to wait some time before showing the first NSProgress, and it makes me do more queries to the backend, but at least the experience is a bit better.
If I could remove this it would be much better. Any ideas?
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)
I want to make live streaming for more then two users on different devices and get api from opentok i had download demo app from ( https://github.com/opentok/OpenTok-iOS-Hello-World) and this is not webrtc, i had run application with key, session and token with disables of pear to pear,
And its working fine for two live streaming but while i tray to connect third stream i am not able to getting that,
I found staring in demo app that (On iPad 2 / 3 / 4, the limit is four streams. An app can have up to four simultaneous subscribers, or one publisher and up to three subscribers.)
with this i am testing with three iPads and got just two on screen
so how to make this more then two stream at a time in three iPads
The project you linked (OpenTok-iOS-Hello-World) is built to just subscribe to one stream. Just as a proof of concept, you can get two subscribers on screen pretty simply by just modifying a few methods and adding an instance variable in ViewController.m
Create a variable that tracks the number of subscribers:
#implementation ViewController {
OTSession* _session;
OTPublisher* _publisher;
OTSubscriber* _subscriber;
int _numSubscribers; // **NEW**
}
Initialize the variable in the initialization method:
- (void)viewDidLoad
{
[super viewDidLoad];
_session = [[OTSession alloc] initWithSessionId:kSessionId
delegate:self];
_numSubscribers = 0; // **NEW**
[self doConnect];
}
Make sure we aren't subscribing to our own stream:
static bool subscribeToSelf = NO;
Modify stop caring about whether there is already a subscriber in this session delegate method:
- (void)session:(OTSession*)mySession didReceiveStream:(OTStream*)stream
{
NSLog(#"session didReceiveStream (%#)", stream.streamId);
// See the declaration of subscribeToSelf above.
if ( (subscribeToSelf && [stream.connection.connectionId isEqualToString: _session.connection.connectionId])
||
(!subscribeToSelf && ![stream.connection.connectionId isEqualToString: _session.connection.connectionId])
) {
// ** Changing if statement **
if (_numSubscribers < 2) {
_subscriber = [[OTSubscriber alloc] initWithStream:stream delegate:self];
_numSubscribers++;
}
}
}
Place the subscribers next to one another, taking up a little less width:
- (void)subscriberDidConnectToStream:(OTSubscriber*)subscriber
{
NSLog(#"subscriberDidConnectToStream (%#)", subscriber.stream.connection.connectionId);
// ** Calculate the frame **
CGRect subFrame = CGRectMake(0, widgetHeight, widgetWidth / 2, widgetHeight)
if (_numSubscribers == 2) subFrame = CGRectOffset(subFrame, widgetWidth / 2, 0);
[subscriber.view setFrame:subFrame];
[self.view addSubview:subscriber.view];
}
NOTE: This solution doesn't result in a stable App. It should get you to a point where you can see both subscribers as long as you don't disconnect any of the iPads in between. To finish this off, you will need to store the OTSubscribers created in session:didRecieveStream: in a collection like NSArray, handle removing the right subscriber(s) and decrementing the _numSubscribers in session:didDropStream:, and thinking about how you want the updateSubscriber method to work instead.
If you look at the source code for hello world in the viewcontroller file line 93, you will see that it is only creating one subscriber. To have multiple subscribers simple create an array or hash object to store multiple subscribers.