iOS CPU usage skyrockets here - ios

I have an issue with my app where it spikes up to over 100% CPU usage when gathering data. I have done everything I can think of to reign in the processing so that it doesn't lock the device up, and still continues to get the data that's needed.
Our customers have some big databases, so downloading the entire database is out the question. We are also using REMObjects as middleware, so I have to work with that. What I did was figure out what data was needed for the user, and setup a series of calls to gather that information. I think that where my problem lies is in the fact that the database can only process up to 1500 items in a call.
Here is a sample query string that is getting sent to the server.
SELECT COMMUNICATIONID, PHONEEXTENTION, PHONEDESC, PHONETIMETOCALL, PHONENUMBER
FROM PHONE WHERE COMMUNICATIONID IN (3761, 3793, 5530, 4957, 4320, 1914, 3715, 6199, 5548,
5580, 5994, 5867, 1437, 4943, 6217, 3765, 2442, 227, 4084, 977, 6822, 5680, 263, 4502,
327, 6112, 136, 7053, 5571, 6958, 6799, 5525, 6530, 4779, 604, 2182, 6198, 3792, 6071,
4383, 5866, 7444, 1309, 226, 4083, 5916, 1295, 626, 1249, 1950, 2141, 3369, 326, 135,
6780, 5411, 5938, 4424, 6034, 649, 6179, 5861, 4778, 5479, 2181, 6197, 3791, 5815, 6070,
6420, 7935, 4542, 4319, 6679, 4942, 4082, 4974, 5533, 5788, 5597, 976, 3764, 1917, 6202,
134, 6779, 3768, 5410, 5665, 7880, 7052, 6033, 5492, 6815, 3118, 4218, 5110, 6529, 6115,
6069, 348, 4318, 4382, 1498, 6406, 4941, 7443, 2376, 4623, 5755, 5532, 6201, 6392, 625,
7270, 4977, 6396, 6524, 5664, 7051, 725, 6032, 6701, 6160, 5491, 5937, 6273, 1875, 6114,
5477, 6528, 5573, 4936, 6705, 2180, 3758, 5527, 5368, 5814, 7328, 7424, 429, 5991, 1434,
6391, 6200, 7283, 5868, 5900, 228, 4085, 6109, 1106, 5791, 692, 6095, 7210, 2893, 1188,
6814, 4217, 5572, 3757, 5813, 3694, 796, 605, 6486, 128, 4144, 5722, 5754, 1915, 5676,
5549, 5581, 4976, 5917, 5822, 2174, 6158, 1633, 4566, 5267, 4885, 4503, 1874, 6113, 5476,
4425, 4871, 5526, 6531, 7886, 1496, 5194, 127, 4780, 5721)
That string is created by the following method, which then sends it asynchronously to the server. There is one issue in this method that I know is a problem, but I haven't had the time to circle back to it and devise a better solution. I am using respondsToSelector and performSelector to process additional methods based on the table that we're gathering details from.
- (void)processRequest
{
if( requestQueue.count == 0 )
return;
if( processingQueue.count > 3 )
return;
Request *request = requestQueue[0];
[requestQueue removeObjectAtIndex:0];
DADataTable *source = request.source;
NSString *destTableName = request.destTableName;
NSString *sourceKey = request.sourceKey;
NSString *query = request.query;
NSArray *destKeys = request.destKeys;
NSString *originMethodName = request.originMethodName;
NSArray *destinationMethods = request.destinationMethods;
NSString *message = request.loadingMessage;
[[NSNotificationCenter defaultCenter] postNotificationName:#"GATHERINGDATA" object:nil];
// Cycle through the rows in the source table and extract the keys we need.
// originMethodName is needed because some tables require additional checks
// to determine what kind of key we are working with
// sourceKey is the string that holds the key we're looking for, which is
// used on tables that don't need specific filtering
NSSet *set = [self getSourceSet:source originMethodName:originMethodName sourceKey:sourceKey];
// getLists takes the set generated by getSourceSet and converts the list of
// ids into a comma separated list of items suitable for passing into a query
// Currently there is a 1400 item limit per list to keep from exceeding the server
// limit, which is currently 1500
NSArray *lists = [self getLists:set];
NSString *msg = #"Loading Data";
NSLog(#"%#", message);
for( NSString *tList in lists ) {
if( tList.length == 0 ) {
NSLog(#"%# not needed", originMethodName);
continue;
}
query = [query stringByAppendingFormat:#"%#)", tList];
NSLog(#"%#: %#", destTableName, query);
DAAsyncRequest __block *r = [fda beginGetDataTableWithSQL:query withBlock:^(DADataTable *table){
DADataTable *destination = [tables valueForKey:destTableName];
if( tables.count == 0 ) destination = table;
else if( [destination rowCount] > 0 )
//dispatch_async(queue, ^(){
[destination mergeTable:table withPrimaryKeys:destKeys];
//});
else
destination = table;
[[NSUserDefaults standardUserDefaults] setValue:msg forKey:#"LoadingMessage"];
[[NSNotificationCenter defaultCenter] postNotificationName:InitialViewControllerNotificationLoadingUpdate object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"UpdateStatus" object:nil];
//dispatch_async(queue, ^(){
[tables setValue:destination forKey:destTableName];
//});
for( NSString *method in destinationMethods) {
SEL tMethod = NSSelectorFromString(method);
if( [self respondsToSelector:tMethod]) {
[self performSelector:tMethod withObject:table];
}
}
if( [self busy] &&
[[source name] isEqualToString:DataAccessTableCustomer])
{
[[NSUserDefaults standardUserDefaults] setValue:nil forKey:#"FETCHINGJOB"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"JOBFETCHED" object:nil];
}
if( [[[NSUserDefaults standardUserDefaults] valueForKey:#"FETCHINGCONTACT"] isEqualToString:#"YES"] &&
([[source name] isEqualToString:DataAccessTablePerson] ||
[[source name] isEqualToString:DataAccessTableOrganization] ||
[[source name] isEqualToString:DataAccessTableOrganizationAddress]))
{
[[NSUserDefaults standardUserDefaults] setValue:nil forKey:#"FETCHINGCONTACT"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"CONTACTFETCHED" object:nil];
}
[processingQueue removeObject:r];
[[NSNotificationCenter defaultCenter] postNotificationName:#"PROCESSREQUEST" object:nil];
}];
[processingQueue addObject:r];
}
}
Any help here will be greatly appreciated! Thanks for taking the time to look.

Yes. Basically the golden rule is: do not optimize prematurely.
But anyhow. My first guess would be: replace NSString query with NSMutableString query. Because you are creating 1500 NSString objects on the heap with always increasing length, just to throw them away in the next loop. An NSMutalbeString keeps up the memory for a much longer time when appending - and you are always talking to the same object. '(Then use appendFormat without the re-assignment instead of stringByAppendingFormat with assignment!)
Look here: Is NSMutableString's -appendString: method an efficient way to build up a large string?

Having your CPU climb to over 100% is not necessarily a bad thing. On a device with multiple cores (iPhone 4s and later) the CPU utilization is 100% times the number of cores. So on a 4s max is 200%, and on a 5s it's 400%.
Doing a bunch of processing in a loop maximizes CPU usage on that thread, since the code runs at full speed until it's done. That's normal and appropriate.
Are you seeing laggy UI performance? That's what you should use as your gauge that you need to improve things.
My suggestion would be to rewrite your code to run on a GCD background queue. First try default priority (the same priority as the UI.) On a multi-core machine, that will tie up one of the cores. On an iPhone 4, though, it might make the UI laggy. In that case you could switch to the next-lower priority, which would make it take longer, but give a higher priority to the UI.
You could then optimize your code if needed. User defaults is not the most efficient way to handle state data in a loop. You might try removing the user defaults calls and switch to saving data in an instance variable or, in a data container singleton if you need to pass info between objects. Also NSNotificationCenter has more overhead than delegate calls, block calls, or simple method calls.
However, I would not worry about those things until you determine that optimization is needed.

Related

NSURLSession request body passed by slow NSInputStream (bandwidth management)

Hi based on this answer I wrote subclass of NSInputStream and it works pretty well.
Now It turned out that I have scenario where I'm feeding to server large amount of data and to prevent starvation of other services I need control speed of feeding data. So I improved functinality of my subclass with following conditions:
when data should be postponed, hasBytesAvailable returns NO and reading attempts ends with zero bytes read
when data can be send, - read:maxLength: allows to read some maximum amount data at once (by default 2048).
when - read:maxLength: returns zero bytes read, needed delay is calculated and after that delay NSStreamEventHasBytesAvailable event is posted.
Here is interesting parts of code (it is mixed with C++):
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len {
if (![self isOpen]) {
return kOperationFailedReturnCode;
}
int delay = 0;
NSInteger readCount = (NSInteger)self.cppInputStream->Read(buffer, len, delay);
if (readCount<0) {
return kOperationFailedReturnCode;
}
LOGD("Stream") << __PRETTY_FUNCTION__
<< " len: " << len
<< " readCount: "<< readCount
<< " time: " << (int)(-[openDate timeIntervalSinceNow]*1000)
<< " delay: " << delay;
if (!self.cppInputStream->IsEOF()) {
if (delay==0)
{
[self enqueueEvent: NSStreamEventHasBytesAvailable];
} else {
NSTimer *timer = [NSTimer timerWithTimeInterval: delay*0.001
target: self
selector: #selector(notifyBytesAvailable:)
userInfo: nil
repeats: NO];
[self enumerateRunLoopsUsingBlock:^(CFRunLoopRef runLoop) {
CFRunLoopAddTimer(runLoop, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes);
}];
}
} else {
[self setStatus: NSStreamStatusAtEnd];
[self enqueueEvent: NSStreamEventEndEncountered];
}
return readCount;
}
- (void)notifyBytesAvailable: (NSTimer *)timer {
LOGD("Stream") << __PRETTY_FUNCTION__ << "notifyBytesAvailable time: " << (int)(-[openDate timeIntervalSinceNow]*1000);
[self enqueueEvent: NSStreamEventHasBytesAvailable];
}
- (BOOL)hasBytesAvailable {
bool result = self.cppInputStream->HasBytesAvaible();
LOGD("Stream") << __PRETTY_FUNCTION__ << ": " << result << " time: " << (int)(-[openDate timeIntervalSinceNow]*1000);
return result;
}
I wrote some test for that and it worked.
Problem appeared when I used this stream with NSURLSession as source of body of HTTP request. From logs I can see that NSURLSession tries to read everything at once. On first read I return limited portion of data. Immediately after that NSURLSession asks if there are bytes available (I return NO).
After some time (for example 170 ms), I'm sending notification that bytes are now available but NSURLSession doesn't respond to that and do not invoke any method of my stream class.
Here is what I see in logs (when running some test):
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper open]
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 1 time: 0
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper read:maxLength:] len: 32768 readCount: 2048 time: 0 delay: 170
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 0 time: 0
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 0 time: 0
09:32:14990[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper hasBytesAvailable]: 0 time: 0
09:32:15161[0x7000002a0000] D/Stream: -[CSCoreFoundationCppInputStreamWrapper notifyBytesAvailable:]notifyBytesAvailable time: 171
Where time is amount of milliseconds since stream has been opened.
Looks looks NSURLSession is unable to handle input streams with limited data rate.
Does anyone else had similar problem?
Or has alternative concept how to achieve bandwidth management on NSURLSession?
solutions tha I can support is:
using NSURLSessionStreamTask, from iOS9 and OSX10.11.
using ASIHTTPRequest instead.
Unfortunately, NSInputStream is a class cluster. That makes subclassing hard. And in the case of NSInputStream, any subclasses are completely unsupported and are likely to fail in fascinating ways. (See http://blog.bjhomer.com/2011/04/subclassing-nsinputstream.html for details.)
Instead of subclassing NSInputStream, you should use a bound pair of streams and create your own data provider class to feed data into it. To do this:
Call CFStreamCreateBoundPair.
Cast the resulting CFReadStream object to an NSInputStream pointer.
Cast the CFWriteStream object to an NSOutputStream pointer.
Pass the input stream when you create the upload task or request object.
Create a class that uses a timer to periodically pass the next chunk of data to the output stream.
If you do this, the data your data provider class passes to the NSOutputStream will become available for reading from the NSInputStream on the other end.

How to view function return in Xcode while debugging?

For example, if I want to know the return of
[NSKeyedArchiver archiveRootObject:self.privateItems toFile:[self.itemArchPath absoluteString]];
What could I do ?
If you want to see the result of [NSKeyedArchiver archiveRootObject:self.privateItems toFile:[self.itemArchPath absoluteString]] you could just wrap its result in a simple conditional statement and print a message: that method returns a boolean, so it's either true or false.
Example:
bool result = [NSKeyedArchiver archiveRootObject:self.privateItems toFile:[self.itemArchPath absoluteString]];
if (result) {
NSLog(#"It worked!");
} else {
NSLog(#"It failed!");
}
If you mean you want to check what was saved, then you should probably either print the path you saved to and look at it in on your Mac (if you're using the simulator) or try re-loading the object to make sure it matches what you expected.
In lldb and in Xcode, if you "step-out" of some function, when the step-out completes, we'll show the return value of the function you just left.
In Xcode, on stop after step out, the first element of the Locals view (called "Return Value") will be the return value of the function you just stepped out of.
If you are in command-line lldb the same thing will show up in the thread part of the stop printing:
(lldb) fin
Process 43838 stopped
* thread #1: tid = 0x849c80, 0x0000000100000f5b SimpleStepOut`main(argc=1, argv=0x00007fff5fbff5b8) + 27 at main.c:18, queue = 'com.apple.main-thread', stop reason = step out
Return value: (int) $0 = 5
frame #0: 0x0000000100000f5b SimpleStepOut`main(argc=1, argv=0x00007fff5fbff5b8) + 27 at main.c:18
15
16 int main(int argc, const char * argv[]) {
17 // insert code here...
-> 18 printf("Hello, World - %d!\n", return_five());
19 return 0;
20 }
Note, if you've customized your frame-format, you may not have this element, it's thread.return-value.
It's a little harder to do this when a "step in/out" happens to step out of the function, so for now it only works if you leave the function by stepping out.
TwoStraws answer is correct.
If, however, you are looking to find the return value (result in TwoStraws's answer) while debugging and only while debugging, you can step into the call to archiveRootObject:toFile: and then hit F8. That'll step out of the function and (usually) include a pseudo-local variable named "return" that will hold the return value from the call.

How to display json data in UIWebview in iOS

I am trying to display json data in UIWebview.This is my json data
["{\"id\":\"6\",\"title\":null,\"description\":null,\"year\":\"2012\",\"date\":null}","{\"id\":\"4\",\"title\":\"Predictions proved correct in the year 2013\",\"description\":\"*<\\\/font> Financial system in America for this year is not indicated progress this year too. More problems will creep up.
\\r\\n*<\\\/font> Aggressive stage of Telangaana agitation- formation of seperate Telangaana state- bifurcation of state - sure indications are \\r\\n\\r\\npredicted.
\\r\\n*<\\\/font> Bad days for congress party was predicted long back. The same situations will continue.
\\r\\n*<\\\/font> The Gujarath CM - Sri Narendra Modi - Bright future is indicated. ( Care should be taken on health aspects).
\\r\\n*<\\\/font> Still danger is indicated for Indoneshia and Sumitra Islands\",\"year\":\"2013\",\"date\":null}"
{\"id\":\"3\",\"title\":\"2013-2014 Sri Vijayanama samvasthara Predictions\",\"description\":\"
జోతిస్యం అక్షర సత్యం
\\r\\nతెలంగాణా ఏర్పాటును గ్రహగతులు అనుసరించి మాత్రమే ఈ ఫలితము చెప్పడం జరిగింది . చరిత్రతమకమెన తెలంగాణా విషయంలో ములుగు సిద్ధాంతి వార్త పంచాంగంలో వ్రాసిన విధంగా అక్షరాల జరిగింది.
ప్రేత్యక తెలంగాణా విసయంలో అనేక రకాల మలుపులు తిరిగి ఎక్కడఎన ఆటంకం వస్తుందేమోనని బావించిన సందర్బాలు కూడా ఉన్నాయ్.
2011- 2012 శ్రీ ఖరనామ సవత్సరం వార్త పంచాంగంలో (58 వ పేజి , 18 లైనులో ) 2013-2014 శ్రీ విజయనామ సవత్సరం వార్త పంచాంగంలో తెలంగానా ఏర్పడుతుందని (40 వ పేజి 5 వ లైనులో )సిద్ధాంతి గారు వ్రాయడం జరిగింది
\\r\\n\\r\\nగమనిక : ఎంతోమంది జ్యోతిష్య, శాస్త్రవేత్తలు, సిద్దంతులు తలక్రిందులుగా తపస్సు చేసిన తెలంగాణా రాదనే చెప్పారు. ఒక్క ములుగు సిద్ధాంతి మాత్రమే మొదటి నుంచి చెబుతూనే వచ్చారు .\",\"year\":\"2014\",\"date\":null}"]
Now i need to display like this:
Financial system in America for this year is not indicated progress this year too. More problems will creep up.
*Aggressive stage of Telangaana agitation- formation of seperate Telangaana state- bifurcation of state - sure indications are predicted.
Bad days for congress party was predicted long back. The same situations will continue.
The Gujarath CM - Sri Narendra Modi - Bright future is indicated. ( Care should be taken on health aspects).
Still danger is indicated for Indoneshia and Sumitra Islands.
జోతిస్యం అక్షర సత్యం
తెలంగాణా ఏర్పాటును గ్రహగతులు అనుసరించి మాత్రమే ఈ ఫలితము చెప్పడం జరిగింది . చరిత్రతమకమెన తెలంగాణా విషయంలో ములుగు సిద్ధాంతి వార్త పంచాంగంలో వ్రాసిన విధంగా అక్షరాల జరిగింది.
ప్రేత్యక తెలంగాణా విసయంలో అనేక రకాల మలుపులు తిరిగి ఎక్కడఎన ఆటంకం వస్తుందేమోనని బావించిన సందర్బాలు కూడా ఉన్నాయ్.
2011- 2012 శ్రీ ఖరనామ సవత్సరం వార్త పంచాంగంలో (58 వ పేజి , 18 లైనులో ) 2013-2014 శ్రీ విజయనామ సవత్సరం వార్త పంచాంగంలో తెలంగానా ఏర్పడుతుందని (40 వ పేజి 5 వ లైనులో ) సిద్ధాంతి గారు వ్రాయడం జరిగింది
గమనిక : ఎంతోమంది జ్యోతిష్య, శాస్త్రవేత్తలు, సిద్దంతులు తలక్రిందులుగా తపస్సు చేసిన తెలంగాణా రాదనే చెప్పారు. ఒక్క సిద్ధాంతి మాత్రమే మొదటి నుంచి చెబుతూనే వచ్చారు
I am writing like this:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if(connection==urlConnection)
{
strResponse=[[NSString alloc]initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(#"%#",strResponse);
NSError *error;
jsonDict=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:&error];
NSLog(#"%#",jsonDict);
NSLog(#"object class == %#",[jsonDict class]);
stringArray=[jsonDict valueForKey:#"description"];
NSLog(#"%#",stringArray);
webView=[[UIWebView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width,self.view.frame.size.height)];
[webView loadHTMLString:strResponse baseURL:nil];
[self.view addSubview:webView];
}
}
-(void)loadData
{
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults valueForKey:#"id"];
[defaults valueForKey:#"title"];
[defaults valueForKey:#"description"];
[defaults synchronize];
NSURL *url=[NSURL URLWithString:#"url"];
NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:url];
urlConnection=[[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES];
}
- (void)viewDidLoad{
[super viewDidLoad];
NSString *html = [NSString stringWithFormat:#"<html><p>here you json data</p></html>"];
[myWebView loadHTMLString:html baseURL:nil];
}
I hope it helps.

Process a RACSequence or RACSignal in batches

Update 3/5/14: RACSequence is deprecated in ReactiveCocoa 3.0 (see comments), but I'm still curious about the best way to process data in batches, either using RACSequence or RACSignal.
I'm trying to process a potentially large array of values using ReactiveCocoa. My idea is to split the input sequence into smaller sequences up to a maximum size, and then process them separately (potentially in parallel).
I've written a category method on RACSequence to do the chunking:
// Returns a sequence of sequences of at most `size` objects
- (RACSequence *)chunk:(NSInteger)size
{
if ([self head] == nil) {
return [RACSequence empty];
}
RACSequence *chunk = [self take:size];
RACSequence *rest = [self skip:size];
return [RACSequence sequenceWithHeadBlock:^id {
return chunk;
} tailBlock:^RACSequence *{
return [rest chunk:size];
}];
}
This works, but it's extremely slow for large sequences. I wrote an iterative version and a utility to compare the two approaches. Here's the difference for a sequence of 10000 numbers broken into smaller sequences of up to 30 objects:
$ time ./main i 10000 30 # Iterative
2014-03-04 10:48:28.845 main[59637:507] Number of chunks: 334
real 0m0.012s
user 0m0.007s
sys 0m0.004s
$ time ./main r 10000 30 # Recursive
2014-03-04 10:48:45.423 main[59645:507] Number of chunks: 334
real 0m15.513s
user 0m15.133s
sys 0m0.378s
Is there a better way to process a RACSequence in smaller batches?

Kiwi stub isn't working with NSNumber, ends in SIGKILL

I'm not sure if I'm doing it right but I'm trying to stub an NSNumber property on a core data object.
Here's my test example:
it(#"should say 1 / ? with 1 point", ^{
mockCard = [KWMock nullMockForClass:[Card class]];
[mockCard stub:#selector(points) andReturn:[NSNumber numberWithInt:1]];
controller.card = mockCard;
[[controller.lblCount.text should] equal:#"1 / ?"];
});
And my source code:
-(void)setCard:(Card *)aCard{
if ([card.points intValue] == 1) {
lblCount.text = #"1 / ?";
}
}
Running this causes a SIGKIL error in the writeObjectValueToInvocationReturnValue method.
Am I missing something?
Update
attempted to change the stub to:
[mockCard stub:#selector(points) andReturn:theValue(1)]
...
[FAILED], wrapped stub value type (i) could not be converted to the target type (v)
This is bug in Kiwi and is described here: https://github.com/allending/Kiwi/issues/63

Resources