Loading 10k+ rows in iphone SQLite (FMDB) - iphone

I am creating a dictionary app and I am trying to load the terms into an iphone dictionary for use. The terms are defined from this table (SQLite):
id -> INTEGER autoincrement PK
termtext -> TEXT
langid -> INT
normalized -> TEXT
Normalized is used because I am writing in GREEK and I don't have icu on the sqlite engine for searching diacritics, so I am making a termtext diacritics/case insensitive. It is also the main search field, in contrast of termtext which could be the "view" field.
I have defined an class (like a POJO) like this:
terms.h
#import <Foundation/Foundation.h>
#interface Terms : NSObject {
NSUInteger termId; // id
NSString* termText; // termtext
NSUInteger langId; // langid
NSString* normalized; // normalized
}
#property (nonatomic, copy, readwrite) NSString* termText;
#property (nonatomic, copy, readwrite) NSString* normalized;
#property (assign, readwrite) NSUInteger termId;
#property (assign, readwrite) NSUInteger langId;
#end
terms.c
#import "Terms.h"
#implementation Term
#synthesize termId;
#synthesize termText;
#synthesize langId;
#synthesize normalized;
#end
Now in my code I use FMDB as the wrapper for the SQLite database. I load the terms using the following code:
[... fmdb defined database as object, opened ]
NSMutableArray *termResults = [[[NSMutableArray alloc] init] autorelease];
FMResultSet *s = [database executeSQL:#"SELECT id, termtext, langid, normalized FROM terms ORDER BY normalized ASC"];
while ([s next]) {
Term* term = [[Terms alloc] init];
term.termId = [s intForColumn:#"id"];
[... other definitions]
[termResults addObject:term];
[term release];
}
The whole termResults is then loaded to a UITableView (on viewdidload) but the loading takes up to 5 seconds every time I start my app. Is there any way to speedup that process? I have indexed id, termText and normalized on SQLite.
*** UPDATE: added cellForRowAtIndexPath ****
[.. standard cell definition...]
// Configure the cell
Term* termObj = [self.termResults objectAtIndex:indexPath.row];
cell.textLabel.text = termObj.termText;
return cell;

Since you seem to load the whole DB into memory anyway (and think it is a must) the whole point of using SQLite is almost gone.
So if the data is static (does not change), I would turn the DB into objects once, then serialize the objects (implement the NSCoding protocol) and store this array (or better yet, dictionary) using writeToFile:atomically:. Once you have that file (do that during development) you can easily load it at runtime with arrayWithContentsOfFile: which should be faster in this case.

Loading that much data into your app is going to take time. The best approach to take would be to load the data from the db on a separate thread to the main application thread. As each item is loaded, post a message back to the main thread to add an item to the array backing the table view. This way, your app load time will be short and your UI will be responsive whilst the data is loading from the db. Here's the basic idea:
NSMutableArray *termResults = [NSMutableArray array];
// Load each item from the db on a separate thread using a block
dispatch_queue_t globalQueue = dispatch_get_global_queue();
dispatch_async(globalQueue, ^() {
// Init the database
...
// Load the items from the db
FMResultSet *s = [database executeSQL:#"SELECT id, termtext, langid, normalized FROM terms ORDER BY normalized ASC"];
while ([s next]) {
Term* term = [Term new];
term.termId = [s intForColumn:#"id"];
// Add the loaded item to termResults on the main thread
// and refresh the table view
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
dispatch_async(mainDispatchQueue, ^() {
// Add the loaded item to termResults
[termResults addObject:term];
// Refresh the table view. This will only reload the visible items.
[tableView reloadData];
});
[term release];
}
});
Hope this helps.

Not a problem to read 10K records from sqlite into UITableView. First, I couldn't see reasons to do that. Second, you couldn't avoid time gap due to a lot of memory allocations. Memory allocation is a system call. And system calls are often slow. During load ios will send memory warnings to run apps resulting in longer gap. What will you do if you will need a million records? sqlite is used to avoid such memory data structures at all. If you see problems using unicode in sqlite on iphone (icu) - read here. I use this technic in several of my apps and all of them were approved to AppStore without problems.

Related

objective c perform selector in background and autoreleasepool

I am developing an iphone application which has some data stored in a sqllite database. When my view loads i would like to load the data from the database on a background thread. The problem is the application keeps crashing and i dont know why.
The code:
-(id) init
{
if((self=[super init]))
{
[self performSelectorInBackground:#selector(loadList) withObject:nil];
}
}
-(void) loadList
{
#autoreleasepool
{
Loader * loader = [[Loader alloc] init];
NSMutableArray * array = [loader getItemList];
[array retain];
NSLog(#"Got %d items",[array count]);
[self performSelectorOnMainThread:#selector(createList:) withObject:array waitUntilDone:false];
[loader release];
}
}
-(void) createList: (NSMutableArray*) array
{
items = array;
int i;
Item * it;
for(i = 0; i < [items count]; i++)
{
it = [items objectAtIndex: i];
[it getName]; // crashes
// populate the list
}
}
Loader returns a NSMutableArray with Item objects. The application crashes when i call the item getName (which returns a NSString*). From what i understand it crashes because the item name properties is being released. What am i doing wrong?
Thanks!
It's likely to be a problem with whatever type of object you're using to populate array.
I'm unable to find finger-on-paper proof but I'm confident that performSelectorOnMainThread:withObject:waitUntilDone: retains its object. However if each of the items in array keeps a reference to loader then they need to take responsibility for retaining that object. It looks like you're attempting to keep it alive manually but — as Chuck alludes to — your call to performSelector... will return instantly and not wait for the call you've made to complete.
This particular bug appears to be that you're passing waitUntilDone:NO, so the array is being released immediately and consequently so are its items.
But in general, UIKit is not thread-safe, so this is just a touchy design. I would probably put the loading of this stuff in another class that handles the task for you instead of right in the view.
I'd put a breakpoint on the line:
it = [items objectAtIndex: i];
Then type
po it
in the debugger, and see what's in the name field. As a guess, I'd say one of two things: 1) the field that getName returns isn't initialized with an object (i.e. isn't a real NSString *) or that you're getting a C string from SQLite (which is what it usually returns) and you're trying to treat it as an NSString *. If it's the latter you can use [myCString stringWithUTF8String] to convert the C string into an NSString *

IPhone autoreleasing a returned NSMutableArray

I'm still relatively new to iPhone development but thought I understood the basic principals of memory management. I have a class method that returns an NSMutableArray, I'm calling alloc and init on the object therefore know I'm responsible for releasing it. However because I'm returning the array I assumed I was supposed to use autorelease when creating the object instead of releasing it.
+(NSMutableArray *)getStations:(int)stationType {
if(database == nil){
[self openDataBase];
}
// Create a temporary array to hold the returned objects
NSMutableArray *holder = [[[NSMutableArray alloc] init] autorelease];
// Check if the statement has been defined
if(select4 == nil) {
const char *sql = "SELECT station_id, station_name, AVG(test_percent) FROM stations LEFT JOIN tests USING (station_id) WHERE station_type = ? GROUP BY station_id ORDER BY station_name ASC";
if(sqlite3_prepare_v2(database, sql, -1, &select4, NULL) != SQLITE_OK){
NSLog(#"Error while creating detail view statement. '%s'", sqlite3_errmsg(database));
}
}
sqlite3_bind_int(select4, 1, stationType);
// Check if the statement executed correctly
while(sqlite3_step(select4) == SQLITE_ROW) {
NSInteger primaryKey = sqlite3_column_int(select4, 0);
Tests *station = [[Tests alloc] initWithPrimaryKey:primaryKey];
station.station_name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(select4, 1)];
station.average_score = [NSNumber numberWithDouble:sqlite3_column_double(select4, 2)];
[holder addObject:station];
[station release];
}
// Reset the detail statement.
sqlite3_reset(select4);
// Return the holder array
return holder;
}
There's the basic code - XCode no longer indicates a potential memory leak but it crashes everytime that code executes saying message sent to deallocated instance. Any help would be appreciated I've spent ages googling and can't see what's wrong with the code. I did find this thread but it doesn't appear to be the answer to my question - crash happens when NSMutableArray is returned?
The code you've posted appears to be managing memory correctly – you've got a one-to-one relationship between retains and (auto)releases, and you're making a textbook use of autorelease – so the problem is probably that the code calling this method needs to retain the resulting array before the autorelease pool kicks in and yanks the rug out from under you.
If your code is assigning the NSMutableArray to an ivar you've declared with #property, that ivar needs to be declared as
#property (retain) NSMutableArray *myStations;
If you're doing something else to store the array, you may need to call [myStations retain]. Your table view code will also need to release the array, probably in its dealloc method.
If you want to use the returned NSMutableArray as a data source to fill in rows in a table view, then you're going to need to retain it in your UITableView class (or your UITableViewDataSource delegate class). It's going to be called repeatedly whenever the view is scrolled or otherwise needs updating.
Easiest thing to do is make it a retained property in that class.
#property (nonatomic, retain) Tests * stationArray;
Then, say, if you want to get your data in your viewDidLoad method:
self.stationArray = [self getStations: self.stationID]; // property retains
Access it in numberOfRowsInSection:
return self.stationArray.count;
Access it in cellForRowAtIndexPath:
Tests *station = [self.stationArray objectAtIndex:indexPath.row];
And, of course, in dealloc...
[stationArray release];
The autorelease in the method is correct (not an init or copy method), but the class will need to retain it if it wants to use it later on, after the current event.

JSON - Php/SQL iPhone APP Questions/Help

Hey group, first time posting here
I am somewhat new to the JSON/PHP/mySQL world, but been around iPhone designing for the past few years, though this topic of conversation is a while new area I am entering.
I have done JSON iPhone examples that allow me to create a UITableView and display the JSON data into the TableViewCells (CustomCells) and displays the data (NSDictionary)into UILabels
The problem I am having now, is that I want an APP that displays this information from the JSON into just a couple of UILabel's on a regular UIViewController and not a UITableView
any help would greatly be appreciated,
the example I used and learned for JSON and UITABLE was from http://tempered.mobi/%20
I used that example from my app, but incorporated a few other things like CUSTOM cells
however now when the USER selects the specific CELL I want it to load specific data from another JSON file, and cannot get it to load in a UILabel or UITextView in a UIViewController
HELP :-)
Not sure if I completely understand your query, when the user clicks on a table cell you want the app to do a request for data in JSON format and display it in labels on a view? correct?
If so, then I'd modify the didSelectRowAtIndexPath method. Use the index to determine which JSON request to fire, load the results into object/array/dictionary and then set the labels text from this data source.
Apologizes if I mis-understood your question.
Here is my coding to it, this is the XIB that is loaded once the TABLEViewCell is selected, i apologize in advanced for the formatting, I dont know why it is not coming out properly as I am simply copying and pasting from Xcode
.h File, all I added was
IBOutlet UILabel *homeTeam;
IBOutlet UITextView *homeTeam2;
IBOutlet UILabel *awayTeam;
NSArray *rows;
}
#property (retain, nonatomic) NSArray *rows;
(added note: I obviously hooked up the UILabels and UIText View to my labels and text view in that specific XIB) and have tested using the homeTeam.text = #""; (in the Implementation file)to make sure they are functioning properly
.m file
import "CJSONDeserializer.h"
import "JSON.h"
#synthesize homeTeam, awayTeam, awayTeam2;
(void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:#"http://yourserver.com/yourdata.json"]; // Modify this to match your url.
NSString *jsonreturn = [[NSString alloc] initWithContentsOfURL:url]; // Pulls the URL
//NSLog(jsonreturn);
// Look at the console and you can see what the restults are
NSData *jsonData = [jsonreturn dataUsingEncoding:NSUTF32BigEndianStringEncoding];
NSError *error = nil;
// In "real" code you should surround this with try and catch
NSDictionary * dict = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:&error];
if (dict)
{
rows = [dict objectForKey:#"users"];
[rows retain];
}
NSLog(#"Array: %#",rows); //making NOTE this NSLOG does print my JSON Data grabbed from the SQL Database
awayTeam2.text =[dict objectForKey:#"awayteam"];
awayTeam.text = [dict objectForKey:#"awayteam"];
[jsonreturn release];
CONSOLE: as you can see it grabs my JSON data just fine....
Array: (
{
awayicon = "Brampton.png";
awayteam = Mississauga;
homeicon = "Mississauga.jpg";
hometeam = Brampton;
hscfinal = 10;
id = 1;
}
)

Filling an NSMutableArray with a Set of Classes and Then Getting them Back

Hopefully I can make this clear, but I am new to Objective-C and to be honest not great with Arrays in the first place.
So, I have a Singleton class (called SingletonState) that I am using to pass variables around my app (please can we leave the whether I should use Singleton classes out of this - I will fix that later). In this class, I have an NSMutableArray (called arrayMyEvents). I have also created a class that I am going to store a list of events (called EventClass). When the user logs in, I call a web service and get back 3 strings. The 3rd string is a comma separated list of value. I parse the data and populate the custom class EventClass. I then add this class to the SingletonState.arrayMyEvents.
I have all of this working. I can go to another ViewController and access the "count" of items in arrayMyEvents.
PROBLEM: I now want to edit one of the ScheduledEventsClass"es" in my array. How do I get access to it and edit some of the properties and update the one in my SingletonState class?
Here is some of the code, that I've tried:
NSString *sWebServiceEvents = [[NSString alloc] initWithFormat:#"%#", [result objectAtIndex:2]];
if ( [ sWebServiceEvents isEqualToString:#"NULL" ] != true ) {
NSArray *arrayEvents = [sWebServiceEvents componentsSeparatedByString:#","];
// If the array has not been initialized they initialize it.
if (sharedState.arrayMyEvents == nil) {
sharedState.arrayMyEvents = [[NSMutableArray alloc ] init ];
}
for (NSString * sEvent in arrayEvents) {
// Set equal to the value of the array (the Event Number) at the same
// position as the row that we are being asked to return a cell/row for.
EventClass *eventClass = [[EventClass alloc] retain];
eventClass.sEvent = sEvent;
[ sharedState.arrayEvents addObject:eventClass ];
}
NSLog(#"LoginView - sharedState.arrayMyEvents Count: %d", [sharedState.arrayMyEvents count]);
}
Here is me trying to access it in another ViewController:
EventClass *eventClass =
[sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"eventClass.sEventNumber: ", eventClass.sEventNumber);
eventClass.sLocation = #"Jason's Big Location";
You're going to have some memory leaks from the sEvent loop. [[EventClass alloc]retain] leaves you an uninitialized EventClass object with a reference count of 2. You'll need to change that to [[[EventClass alloc] init] autorelease] to keep it from leaking. The arrayEvents NSMutableArray will retain it during the addObject: call. (Shouldn't that be [sharedState.arrayMyEvents addObject: eventClass] in the loop?)
After that, all you have to do to edit the EventClass object in the second block of code is edit it. The eventClass variable is a pointer to an object in the array. Anything done to that object doesn't affect the pointer referencing it, it affects data referenced by it. The code you have in the second block should change the sLocation of the selected object as you intend.
You have a few more memory leaks in there, too. Use Cmd-Shift-A to build with the static analyzer and it'll tell you where.
Maybe the problem is that you put them in sharedState.arrayEvents but try to take them out of sharedState.arrayMyEvents. Different variables.
Also, lots of memory leaks.
Thanks John and St3fan, your answers and time are appreciated!!!
I think that I figured out my issue:
Basically, the class that I created (EventClass) had the properties setup like this:
#property (nonatomic, assign) NSString *sStudyNumber;
#property (nonatomic, assign) NSString *sTheater;
but, they should be (or at least I got it to work like this):
#property (nonatomic, retain) NSString *sStudyNumber;
#property (nonatomic, retain) NSString *sTheater;
Then, in my second view I was able to do this:
EventClass *eventClass = [sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"MyEvents: %#", eventClass.sEventNumber);
eventClass.sLocation = #"Jason's Big Location";
I then checked it in another method of the view using this and it was still there:
EventClass *eventClass = [sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"MyEvents: %#", eventClass.sEventNumber);
NSLog(#"MyEvents: %#", eventClass.sLocation);
I also, checked it in yet another view and the value was maintained in the SharedState.arrayMyEvents without issue. :)
In the end, I believe that I boiled down to the difference between "assign" and "retain".
Now, on to the memory leaks :(
Please, let me know if you see any other issues with this.
Thanks,
Jason

replaceObjectAtIndex array problems

Been searching for the answer to this for a while now and I think due to the nature of my array set up, I may be searching for the wrong answer!
I have a class which handles adding items to my array:
// Item.h
#interface Item : NSObject {
NSString *name;
NSNumber *seconds;
}
#property(nonatomic,copy) NSString *name;
#property(nonatomic,copy) NSNumber *seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec;
#end
and...
//item.m
#implementation Item
#synthesize name, seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec {
self.name = n;
self.seconds = sec;
return self;
}
#end
So to add an item, I use
Item *item1 = [[Item alloc] initWithName:#"runnerA" seconds:[NSNumber numberWithInt:780]];
I have some code which allows a user to edit a textfield (runner name) and the time which is a UIdatepicker set to hours and minutes. In the save method, that's working fine. It's the UPDATE that I cannot get to work. I've tried alsorts! Here's the code at the moment...
mainAppDelegate *appDelegate = (mainAppDelegate *)[[UIApplication sharedApplication] delegate];
Item *item = [[Item alloc] initWithName:inputName.text seconds:[NSNumber numberWithInt:secs]];
[appDelegate.arrItems replaceObjectAtIndex:rowBeingEdited withObject:item];
The above is simply adding a new item to the array (which is what I don't want). I'm not sure how to replace values. At the function, I have the row I need to update (rowBeingEdited) and the fields inputName.text and secs are both OK. (NSLog out confirms this).
How do I use the replaceObjectAtIndex to actually replace it with the values?! It's driving me mad now!!
Since you are simply trying to edit a particular row, why not use those property accessors that you already have set up in Item? It would look something like this:
Item *item = (Item *)[appDelegate.arrItems objectAtIndex:rowBeingEdited];
[item setName:inputName.text];
[item setSeconds:[NSNumber numberWithInt:secs]];
An a side note, are you using garbage collection, or do you manually release the Item objects that you create when adding items to the array? If you are doing it manually, it should look like this:
Item *item1 = [[Item alloc] initWithName:#"runnerA"
seconds:[NSNumber numberWithInt:780]];
[appDelegate.arrItems addObject:item1];
[item1 release];
This follows the rule of thumb: if you alloc, copy or retain anything, you must also release it. Note that this works because the array will retain the item when it is added.
Are you using NSArray or NSMutableArray?
Assuming you are using NSMutableArray, how did you initialize and populate the array in the first place?
For example, it's not enough to use -initWithCapacity: or +arrayWithCapacity: which only sets aside space. You have to use -addObject: for the first round of population, before you can use -replaceObjectAtIndex:withObject::
Note that NSArray objects are not like C arrays. That is, even though you specify a size when you create an array, the specified size is regarded as a “hint”; the actual size of the array is still 0. This means that you cannot insert an object at an index greater than the current count of an array. For example, if an array contains two objects, its size is 2, so you can add objects at indices 0, 1, or 2. Index 3 is illegal and out of bounds; if you try to add an object at index 3 (when the size of the array is 2), NSMutableArray raises an exception.