Kärndata är en ram som Apple tillhandahåller utvecklare som beskrivs som en "schemaledsad objektgrafhantering och persistensramverk". Vad betyder det egentligen? Ramverket hanterar var data lagras, hur det lagras, datakachning och minneshantering. Den portades till iPhone från Mac OS X med 3.0 iPhone SDK-utgåvan.
I denna handledning kommer jag att vägleda dig genom processen att skapa ett projekt med Core Data och visa hur du använder det med en enkel UITableViewController. Det kommer att bli en enkel en tabelldatabas som kommer att lagra omgångstider och visa tiderna och deras delningsskillnader i en UITableView.
Innan vi börjar, är det viktigt att förstå varför du kanske vill använda Core Data med SQLite för lagring över fastighetslistor, ett anpassat XML-format eller direkt SQLite-databasåtkomst. Core Data API tillåter utvecklare att skapa och använda en relationsdatabas, utföra registreringsvalidering och utföra frågor med hjälp av SQL-mindre villkor. Det tillåter dig i huvudsak att interagera med SQLite i Objective-C och behöver inte oroa sig för anslutningar eller hantera databasschemat, och de flesta av dessa funktioner är bekanta för personer som har använt objektrelationell kartläggning (ORM) -teknik som de som implementerats i Ruby på Rails, CakePHP, LINQ eller andra bibliotek och ramverk som abstrakt tillgång till databasen. Den största fördelen med detta tillvägagångssätt är att det eliminerar den utvecklingstiden som annars är nödvändig för att skriva komplexa SQL-frågor och manuellt hantera SQL och utdata från dessa operationer.
Exemplet ansökan vi kommer att bygga idag är en enkel varvtimer. Det kommer att skapa en ny post i Core Data Store för varje varv som vi gör. UITableView visar då skillnaden mellan nuvarande och sista varv.
För att komma igång öppnar vi XCode och skapar ett nytt projekt. Namnge det du önskar, jag har kallat det "LapTimer". Vi kommer att skapa en "Window-based application" från det nya projektet mallvalet. Se till att "Använd kärndata för lagring" är markerad.
Projektet ska vara bekant med vad du tidigare har sett om du tidigare har utvecklat iPhone-applikationer.
Det finns inte mycket att göra eftersom alternativet "Använd kärndata för lagring" i "Window-based Application" -mallen automatiskt har ställt in några viktiga variabler och skapade filer i projektet för oss.
LapTimer.xcdatamodel-filen är där vi ska definiera schemat för vår SQLite-databas. Kärndataramen läggs också till i projektet för att inkludera filerna för API-åtkomst. De andra ändringarna görs inom standardprogramfilerna. I synnerhet har applikationsdelegeringsfilerna metoderna att konfigurera Core Data-butiken i vår ansökan och hänvisa den till barn UIViewControllers.
Vad vi är intresserade av för tillfället är filen LapTimer.xcdatamodel under "Resources". Den här filen ger dig en visuell karta över ditt schema som visar enheter och attribut.
Det finns några olika termer och fraser som används i Core Data som kan vara löst relaterade till vanliga databasnamn, men de är inte identiska.
En "enhet", även känd som ett "hanterat objekt", liknar ett bord. Det är en definition av ett objekt som kommer att innehålla en samling av data. Ett objektobjekt innehåller "attribut". Dessa kan löst associeras med kolumner, men dessa attribut är inte bara begränsade till datalagring. Attribut kan definiera ett förhållande mellan två enheter, en dynamiskt hämtade egenskap eller definiera en egenskap för datalagring.
Från diagrammet ovan kan du få en känsla för hur flexibla objekt som härrör från Core Data-enheter är. För det här exemplet måste vi ha en väldigt enkel enhet. Vi kommer att ringa enheten "Event" som lagrar register över våra varv.
Vad vi ska göra för att skapa denna enhet klickar du på knappen [+] i den första (från vänster) övre kolumnen. Detta skapar en ny enhet i listan och visuellt på schema-kartan under kolumnerna.
Detta är en fin visuell redaktör för din modell. Core Data gör verkligen den tunga lyften när det gäller "M" (Model) delen av MVC. Den visuella redaktören visar relationerna, egenskaperna och enheterna i butiken medan schemat dynamiskt skapas och hanteras allt för dig. Detta liknar gränssnittsbyggare eftersom det tar hand om att allokera, hantera och placera objekt för dig på en UIView utan en enda kodrad.
Med den nya Event-enheten på plats vill vi skapa en "egenskap". Eftersom denna egenskap kommer att lagra data kommer vi att ange det som en "attribut". Så det här nya attributet lagrar bara det aktuella datumet för när posten skapades. I vår exempelapplikation använder vi detta för att referera till våra varvtider.
Nästa kolumn till höger är där vi definierar våra egenskaper (se till att Event-enheten är vald). Så skapa en ny egendom med knappen [+] i kolumnen, välj "Lägg till attribut".
Vi kommer att kalla attributet "timeStamp" och ställa in typ till "Date". I listrutan typval finns det liknande kolumndatatyper som de som finns i relationsdatabasystem som PostgreSQL eller MySQL, inklusive datatyper som heltal, floats, strängar, booleaner, datum och binära data (blobs).
För det här attributet behöver vi inte några andra alternativ som valts eller ändrats.
Det är det för xcdatamodel-filen, och vi kan flytta på att integrera det i vår app. Nu är det bra att spara ditt jobb.
Om du har använt ett MVC-ramverk som har databasdefinitioner som definierar en tabells struktur eller beteenden, så kommer det att bli en välkänd uppgift.
Vi måste börja med att skapa en definition av enheten. Vi gör det genom att skapa en NSManagedObject-klass i enheten och definiera variablerna som butiken har.
Detta är en enkel process. Välj Event-enheten i filen LapTimer.xcdatamodel och gå till Arkiv> Ny fil. Du kommer att se att det finns en ny filmall i avsnittet "Cocoa Touch Class" som heter "Managed Object Class".
Välj mallen och klicka på "Nästa". Nästa skärm definierar bara var vi sparar filen och målet att inkludera det med, det här är alla korrekta som standard så tryck på "Nästa" igen. Nästa skärm är där du definierar vilka enheter du vill skapa NSManagedObject-klasser för. Om det inte är markerat väljer du Event och ser till att kryssrutorna "Generate Accessors" och "Generate Obj-C 2.0 properties" är markerade, vi behöver inte valideringar just nu. Hit Finish.
Så nu har vi 2 nya filer i vår ansökan. Event.m och Event.h. De definierar klassen NSManagedObject för Event-enheten som vi skapade i vår Core Data-butik. De definierar tidstämplingsfältet så när vi vill använda händelseklassen kan vi komma åt attributet.
Event.h
#importera@interface Event: NSManagedObject @property (nonatomic, behåll) NSDate * timeStamp; @slutet
Event.m
#import "Event.h" @implementation Event @dynamic timeStamp; @slutet
Precis som modelldefinitioner i andra ramar och språk kan du lägga till anpassade metoder för alla poster i Event-enheten. Du kommer att märka att timeStamp-attributet har lagts till och tilldelats som ett NSDate-objekt.
Med Core Data setup är det dags att arbeta på en del av controllerns logik i applikationen. Vi använder en UITableViewController som huvudgränssnittet för appen för att visa omgångstiderna samt logga in en ny tid.
Så ska vi skapa den nya UITableViewController med File> New File. Välj sedan "UIViewController subclass" under avsnittet iPhone och kolla "UITableViewController subclass" men inte kryssrutor som relaterar till att använda XIB eller för inriktning på en iPad. Vi använder inte en XIB för den här kontrollen. Ring den nya filen "TimeTableController" och avsluta filguiden.
I denna kontroller behöver vi 2 egenskaper, en referens till NSManagedObjectContext och en array för att lagra poster i för UITableView. Förutom att definiera dessa egenskaper importerar vi Event.h-filen så att vi kan använda klassen.
TimeTableController.h
#importera#import "Event.h" @interface TimeTableController: UITableViewController NSManagedObjectContext * managedObjectContext; NSMutableArray * eventArray; @property (nonatomic, behåll) NSManagedObjectContext * managedObjectContext; @property (nonatomic, behåll) NSMutableArray * eventArray; - (void) fetchRecords; - (void) addTime: (id) avsändare; @slutet
Vad är en NSManagedObjectContext? Det kallas "scratch pad" för Core Data i programmet för hantering av hämtning, uppdatering och skapande av poster i butiken. Det hanterar också några grundläggande funktioner i Core Data, inklusive validering och ångra / redo hantering för poster.
Kontexten för hanterad objekt är kopplingen mellan din kod och datalagret. Alla operationer som du ska utföra för Core Data gör det mot kontexten för hanterad objekt. När en förfrågan utförs talar det hanterade objektets sammanhang med den ihållande butikskoordinatorn som ansvarar för att kartlägga objekten till data för datalagret. Detta gör det möjligt för Core Data att vara flexibel mellan olika datalagringsformat. Här är ett diagram över hur det här ser ut.
Med den definierade headerfilen behöver vi sprida de tilldelade egenskaperna och metoderna i implementeringsfilen.
TimeTableController.m
#import "TimeTableController.h" @implementation TimeTableController @synthesize managedObjectContext, eventArray; // ... // ... standard kommenterad kod från filmallen // ... - (void) viewDidLoad [super viewDidLoad]; self.title = @ "Lap Times"; UIBarButtonItem * addButton = [[UIBarButtonItem alloker] initWithBarButtonSystemItem: UIBarButtonSystemItemAdd mål: self action: @selector (addTime :)]; self.navigationItem.rightBarButtonItem = addButton; [addButton release]; [självfetchRecords]; - (void) addTime: (id) avsändare Event * event = (Event *) [NSEntityDescription insertNewObjectForEntityForName: @ "Event" inManagedObjectContext: managedObjectContext]; [event setTimeStamp: [NSDate date]]; NSError * fel; om (! [managedObjectContext save: & error]) // Detta är ett allvarligt fel som säger att posten // inte kunde sparas. Advisera användaren till // försök igen eller starta om programmet. [eventArray insertObject: event atIndex: 0]; [self.tableView reloadData]; - (void) fetchRecords // Definiera vårt bord / enhet för att använda NSEntityDescription * entity = [NSEntityDescription entityForName: @ "Event" inManagedObjectContext: managedObjectContext]; // Ställ in hämtningsförfrågan NSFetchRequest * request = [[NSFetchRequest alloc] init]; [förfrågan setEntity: entity]; // Definiera hur vi ska sortera posterna NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey: @ "timeStamp" stigande: NEJ]; NSArray * sortDescriptors = [NSArray arrayWithObject: sortDescriptor]; [begär setSortDescriptors: sortDescriptors]; [sortDescriptor release]; // Hämta posterna och hantera ett fel NSError * -fel; NSMutableArray * mutableFetchResults = [[managedObjectContext executeFetchRequest: begäran fel: & error] mutableCopy]; om (! mutableFetchResults) // Hantera felet. // Detta är ett allvarligt fel och bör rekommendera användaren att starta om programmet // Spara vår hämtade data till en array [self setEventArray: mutableFetchResults]; [mutableFetchResults release]; [förfrågan release]; // ... // ... fler mallkommentarer och standardmetoddefinitioner // ... - (void) dealloc [managedObjectContext release]; [eventArray release]; [super dealloc]; @slutet
Det är en rättvis kod, så vi kan gå igenom det med varje metod individuellt. Det finns kod från när du skapar filen från mallen, kommenterade metoder som viewDidUnload, som jag just har lämnat ut från ovanstående.
Vi börjar med det vanliga samtalet till superklassen. Då definierar vi UINavigationBars titel. Därefter måste vi definiera knappen vi ska använda för att lägga till en post i Core Data Store. När du trycker på knappen ber vi dig att ringa väljaren addTime: vilken kommer då att interagera med Core Data. Efter de nödvändiga utgåvorna får vi ringa fetchRecords-funktionen som förklaras nedan.
Detta avfyras när UIBarButtonItem längst upp till höger på UINavigationBar trycks in. Vi måste skapa en ny händelsepost med nuvarande NSDate och spara den i databasen.
Vi skapar en ny händelsepost som heter NSEntityDescription. Detta är din rad i databasen för den nya posten. För att göra detta definierar vi den enhetens namn som posten tillhör och tillhandahåller NSManagedObjectContext. Det aktuella datumet ställs sedan in mot timeStamp-attributet.
Lagringsoperationen utförs sedan, men det finns en bestämmelse att hantera ett fel om insatsen misslyckas. Du brukar slänga en UIAlertView som säger att posten inte skapade, och kanske instruera användaren att försöka igen eller stänga och öppna programmet igen.
Posten måste läggas till i den matris som UITableView matar in från. Då måste UITableView få veta att ladda om data. Du kan göra det mer grafiskt med en animering men för tutorialets skull, låt oss hålla det enkelt.
Den här metoden kommer att hämta data från butiken och lägga till den i matrisen som vi har i regulatorn. För mer information om att få poster, så titta på NSFetchRequest.
Core Data har ett annat sätt att hämta data från sin databas. Det är en NoSQL datalagring, vilket innebär att alla villkor för en fråga är baserade på metoder. Det här är bra eftersom basaffären, som är SQLite, kan ändras till någon annan databassteknik och det enda som ska ändras skulle vara anslutningen och drivrutinerna för databasen.
Så, för att skapa en förfrågan skapar vi ett NSFetchRequest-objekt. Detta är basobjektet som frågarvillkoren kommer att ställas in mot. Vi kan definiera villkor för att matcha en viss egendom baserat på hur poster beställs. Du kan läsa mer om Core Data och dess villkor för NSFetchRequests i deras dokumentation.
Att skapa en ny NSFetch-förfrågan är enkel. Du behöver bara definiera den enhet du vill ha poster från och NSManagedObjectContext.
NSEntityDescription * entity = [NSEntityDescription entityForName: @ "Event" inManagedObjectContext: managedObjectContext]; NSFetchRequest * request = [[NSFetchRequest tilldela] init]; [förfrågan setEntity: entity];
Enheten definieras med ett NSEntityDescription-objekt som kräver enhetens namn och NSManagedObjectContext. Hämtningsförfrågan skapas sedan passerar enhetens beskrivning. Detta skulle motsvara den första delen av ett SQL-uttalande:
VÄLJ * FRÅN "händelser"
I vårt exempelapplikation sorterar vi data vid tidstämpeln på fallande sätt. För att göra detta använder vi en NSSortDescriptor.
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey: @ "timeStamp" stigande: NEJ]; NSArray * sortDescriptors = [NSArray arrayWithObject: sortDescriptor]; [begär setSortDescriptors: sortDescriptors]; [sortDescriptor release];
NSSortDescriptor är skapad och vi definierar attributet som vi vill sortera och huruvida det stiger, i det här fallet vill vi att den ska sjunka så att den är inställd på NEJ. Hämtningsförfrågan kan ta många sorteringsskrivare så att den accepterar en array när du ställer in sortdeskriptorerna. Eftersom vi bara vill ha en, behöver vi bara skapa en matris med ett objekt i det. Vi ställer beskrivaren array mot hämtningsförfrågan och det är det.
För att definiera ett villkor för att matcha ett dokumentinnehåll kommer NSPredicate-klassen till spel. Det låter hämtningsförfrågan matcha eller definiera ett intervall som innehållet i en post måste uppfylla. Detta motsvarar dina lika, större och mindre än matchningar i SQL. Det har mer än dina grundläggande SQL-funktioner, som du kan se här.
Att ställa in ett predikat kan vara mycket enkelt.
NSPredicate * predicate = [NSPredicate predicateWithFormat: @ "(förnamn som% @) OCH (födelsedag>% @)", lastNameSearchString, birthdaySearchDate];
Använda NSPredicate predicateWithFormat: är en enkel och bekant metod som låter dig definiera villkoren för frågan. För en fördjupad förklaring om NSPredicates har Apple Dokumentationen några bra guider.
När du har definierat villkoren i din hämtningsförfrågan kan du sedan utföra det.
NSMutableArray * mutableFetchResults = [[managedObjectContext executeFetchRequest: begäran fel: & error] mutableCopy];
Det kommer att returnera en rad objekt objekt, NSManagedObjects, att använda i din datautgång.
Med de data som hämtats från Core Data och lagras i händelsenArray kan vi nu mata ut dessa poster i UITableView.
Första är att berätta för bordet att vi bara behöver 1 avsnitt och hur många rader vi måste använda.
Utdrag från TimeTableController.m
-(NSInteger) numberOfSectionsInTableView: (UITableView *) tableView return 1; - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) avsnitt returnera [eventArray count];
Om du har använt en UITableViewController före, ska nästa funktion vara rakt framåt.
-(UITableViewCell *) tableView: (UITableView *) tableVisa cellForRowAtIndexPath: (NSIndexPath *) indexPath static NSString * CellIdentifier = @ "Cell"; statisk NSDateFormatter * dateFormatter = nil; om (dateFormatter == nil) dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat: @ "h: mm.ss a"]; UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier]; om (cell == nil) cell = [[[UITableViewCell-allokering] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier: CellIdentifier] autorelease]; Händelse * händelse = [eventArray objectAtIndex: [indexPath row]]; Händelse * previousEvent = nil; om ([eventArray count]> ([indexPath row] + 1)) previousEvent = [eventArray objectAtIndex: ([indexPath row] + 1)]; [cell.textLabel setText: [dateFormatter stringFromDate: [event timeStamp]]]; om (tidigareEvent) NSTimeInterval timeDifference = [[händelsestidStämpel] timeIntervalSinceDate: [previousEvent timeStamp]]; [cell.detailTextLabel setText: [NSString stringWithFormat: @ "+%. 02f sec", timeDifference]]; annars [cell.detailTextLabel setText: @ "---"]; returcell;
Cellen kommer att visa 2 värden, från att använda formatet UITableViewCellStyleValue1. Vänster blir klockan och höger blir skillnaden i sekunder från föregående rekord.
Eftersom den här metoden är iterates måste vi vara extra försiktiga med den belastning som den kan lägga på enheten om den inte hanteras korrekt. Därför lagras NSDatFormatter som en statisk variabel så att den kan återanvändas i varje iteration utan att allokera och släppa den varje gång.
Lazy Loading är en teknik där du försenar begäran eller tilldelningen av en egendom så mycket som möjligt. Detta hjälper till att hålla minnet nere och under iterativa funktioner är detta avgörande. Att veta när och hur man fördelar data är avgörande för att hålla en mobilapp snabb. Att fördela objektet är också lika viktigt, ju tidigare desto bättre.
cellForRowAtIndexPath: är en iterativ metod och alla data som behandlas eller allokeras i denna metod måste speciellt hållas på ett minimum. Denna metod körs när en cell kommer fram så när en användare bläddrar snabbt kan den här metoden, beroende på rekorduppsättningen, kallas mycket ofta i följd.
Nästa uppgift är att få händelseobjektet associerat med tabellraden som måste göras. Eftersom vi behöver hämta den tidigare posten för tidsjämförelsen finns det en enkel kontroll för att se om det finns en tidigare post och att lagra den i tidigareEvent. Om föregåendeEvent existerar beräknar vi delningen med [NSDate timeIntervalSinceDate: (NSDate)]. TextLabel och DetailedTextLabel ställs sedan in med de värden som vi har beräknat.
Med inställningen UITableViewController och tabelldatakällan som arbetar med Core Data-butiken är allt som behövs för att ladda reglaget när programmet startas.
I programkontrollen måste en egenskap för UINavigationController definieras. Då behöver ApplicationDidFinishLaunching-metoden bara tilldela styrenheten och vi är färdiga.
LapTimerAppDelegate.h
@interface LapTimerAppDelegate: NSObjectNSManagedObjectModel * managedObjectModel; NSManagedObjectContext * managedObjectContext; NSPersistentStoreCoordinator * persistentStoreCoordinator; UIWindow * fönster; UINavigationController * navigationController; @property (nonatomic, behåll, readonly) NSManagedObjectModel * managedObjectModel; @property (nonatomic, behåll, readonly) NSManagedObjectContext * managedObjectContext; @property (nonatomic, behåll, readonly) NSPersistentStoreCoordinator * persistentStoreCoordinator; @property (nonatomic, behåll) IBOutlet UIWindow * fönster; @property (nonatomic, behåll) UINavigationController * navigationController; - (NSString *) applicationDocumentsDirectory; @slutet
Utdrag från LapTimerAppDelegate.m
#import "LapTimerAppDelegate.h" #import "TimeTableController.h" @implementation LapTimerAppDelegate @synthesize fönster, navigationController; - (void) applicationDidFinishLaunching: (UIApplication *) ansökan TimeTableController * tableController = [[TimeTableController allokerar initWithStyle: UITableViewStylePlain]; tableController.managedObjectContext = [self managedObjectContext]; self.navigationController = [[UINavigationController alloc] initWithRootViewController: tableController]; [tableController release]; [fönster addSubview: [self.navigationController view]]; [fönster makeKeyAndVisible]; // ... // ... andra mallmetoder // ... - (void) dealloc [managedObjectContext release]; [managedObjectModel release]; [persistentStoreCoordinator release]; [fönsterfrisättning]; [navigationController release]; [super dealloc];
Filen TimeTableController.h ingår i programdelegationen och tilldelas sedan med en UINavigationController.
Det borde vara det. Bygg programmet för att kontrollera om det finns fel. Några av kodexemplen har bara utdragits, ingen kod som skapas när en fil har tagits bort, fylls bara in. Om du stöter på fel som du inte kan spricka kan du ladda ner projektfilen som bifogas denna handledning som du då kan sammanställa och jämföra.
Kör ansökan. Du kommer att se navigeringskontrollen och tilläggsknappen. Tryck på add-knappen och du kommer att få en ny tid i tabellen.
I den här handledningen har vi skapat ett exempelprogram för att lagra enkla data i en Core Data Store. Applikationen gick igenom den ursprungliga installationsproceduren när du skapade en applikation med Core Data, definierar datastrukturen och hämtar poster från datalagret.
Förhoppningsvis har du fått en introduktion till Core Data och kan se hur lätt det är att använda och hur det kan förbättra programmets prestanda och funktionalitet.
Om du vill veta mer om Core Data eller vill ha en detaljerad titt på rammens struktur är Apple Developer Documentation det perfekta stället att gå.
Apple Developer Documentation:
Introduktion till Core Data Programming
Core Data Migration och Versioning
NSPredicate Programmeringsguide: