Category Archives: iOS

Sijaintipalvelut iOS-applikaatioissa 2 – Geokoodaus

Edellinen kirjoitukseni oli pintaraapaisu iOS SDK:n CoreLocation-kirjastoon. Kävin läpi kuinka laitteen sijainti saatiin haettua CLLocationManager-luokan avulla leveys- ja pituuspiirin koordinaatit sisältävän CLLocation-olion muodossa. Kuitenkaan pelkät koordinaatit eivät sellaisenaan ole kovin kiinnostavaa tai hyödynnettävää tietoa ja sen takia  koordinaatteihin liittyy usein kysymyksiä kuten “missä tämä paikka on?” ja “mitä täällä paikassa on?”. Vastauksen näihin tarjoaa geokoodaus.

Geokoodaus on osoitteen muuttamista koordinaateiksi, ja käänteinen geokoodaus koordinaattien muuttamista osoitteeksi. CoreLocation tarjoaa kummankin palvelun CLGeocoder-luokassa. Geokoodauksen käyttö ei edellytä paikkatietojen käytön hyväksyntää käyttäjältä. Luokka sisältää kolme metodia geokoodaukseen ja yhden metodin käänteiseen geokoodaukseen.

  • geocodeAddressString:completionHandler: Ottaa parametrinä vapaamuotoisen osoitteen, esimerkiksi “Urho Kekkosen katu 5, 00100 Helsinki”
  • geocodeAddressString:inRegion:completionHandler: Tekee samaa kuin edellinen, mutta toisena parametrina voi antaa aluetta kuvaavan CLRegion-olion jonka sisällä olevia kohteita suositaan tuloksia järjestellessä.
  • geocodeAddressDictionary:completionHandler: Parametrina annetaan NSDictionary johon voi määritellä tarkemmin haettavan alueen esim. kaupungin tai maan perusteella. Esimerkiksi parametrinä annettu NSDictionary @{(id)kABPersonAddressCityKey : @”St. Petersburg”, (id)kABPersonAddressCountryKey : @”United States”} palauttaisi Floridassa olevan St. Petersburgin kaupungin. Dictionaryn kABPersonAddressCountryKey-avaimen arvon ollessa @”Russia“, palauttaa geokoodaaja itänaapurin entistä pääkaupunkia kuvaavan vastauksen.
  • reverseGeocodeLocation:completionHandler: palauttaa listan koordinaatteja vastaavia CLPlacemark-olioita. Parametrina metodi ottaa CLLocation-olion. CLLocation-olion voi luoda ja alustaa CLLocation-oliolla.
Kaikilla neljällä metodilla on samanlainen completion handler. Se sisältää listan CLPlacemark-olioita (joita normaalisti on yksi kappale listassa) ja NSError-olion. CLGeocoding-oliolla on myös metodit isGeocoding ja cancelGeocode. Geokoodauksen kumoaminen saa aikaan kCLErrorGeocodeCanceled-tyyppisen virheilmoituksen.

Mitä täällä on?
CLPlacemark on luokka paikkatietojen esitystä varten. Oliosta löytyy alueesta riippuen mm. seuraavia tietoja:
  • paikan koordinaatit (CLLocation-olion muodossa)
  • Listan alueen maamerkeistä (esim. Golden Gate -silta San Franciscossa), listana NSString-olioita
  • paikan osoitetiodot Address Book -yhteensopivina avain-arvopareina
  • paikan nimi
  • kaupunki ja kaupunginosa
  • mahdollistet läänit ja maakunnat joiden osa paikka on
  • maa, johon paikka kuuluu, sekä maakoodi
  • vesistö tai valtameri jossa paikka sijaitsee

 Vaihtoehto Applen geokoodauspalvelulle
Applen tarjoaman geokoodauspalvelun lisäksi Googlella on Geokoodaus-API. Googlen paikkatietokanta on epäilemättä paljon kattavampi ja tarkempi kuin Applen oma, mutta Applen valmiit CLGeocoding-luokat tarjoavat hyvin nopean tavan muuttaa koordinaatteja informaatioksi ja toisin päin. Yleensä Applen palvelu riittää katuosoitteiden ja kaupunkien nimien muuttamisen koordinaateiksi ja toisin päin. Googlen palvelussa kehittäjä joutuisi itse parsimaan JSON- tai XML-muotoisen vastauksen itse. Mielestäni Applen geokoodauspalvelun suurin puute on maamerkkien, kauppojen, ravintoloiden yms. kiinnostavien kohteiden täydellinen puute. Siitä huolimatta CLGeocoder on riittänyt useimpiin kehittämiini applikaatioihin.

Sijaintipalvelut iOS-applikaatioissa

Nykyaikaisista älypuhelimista ja tableteista löytyy välineet käyttäjän sijainnin selvittämiseen. Sijaintia voi käyttää mobiiliapplikaatioissa monella tavalla: käyttäjälle voidaan esimerkiksi tarjota lähiympäristöön liittyvää tietoa – esimerkiksi paikallisuutisia, tai käyttäjää voi muistuttaa hänen saapuessaan tärkeään paikkaan. Liikuntasuorituksia kirjaava applikaatio voi muodostaa juoksureitistä kartan tallennettujen GPS-päivitysten perusteella.
iOS SDK tarjoaa monipuolisen paikannuskirjaston nimeltään CoreLocation. Kirjasto toimii rajapintana koodin ja mobiililaitteen fyysisten mittalaitteiden välillä mahdollistaen mm. seuraavat asiat:
  • laitteen sijainnin paikantaminen, parhaimmillaan alle kymmenen metrin tarkkuudella
  • viestittäminen laitteen sijainnin muuttumisesta
  • määritellyille alueille liikkumisen tarkkailu
  • sijainnin tarkkailu ja siitä tiedottaminen applikaation ollessa taustalla
  • applikaation käynnistäminen sijainnin muuttuessa merkittävästi
Paikannuksen musta laatikko
Mobiililaitteet hoitavat laitteen paikannuksen monella tavalla: GPS-paikantimella, Wifi-tukiasemien avulla ja kolmiomittaamalla puhelinmastoja. CoreLocation valitsee automaattisesti sopivimman paikannusmenetelmän, ja tarvittaessa yhdistelmää useasta menetelmästä. Syy automaattiseen paikannusmenetelmän valintaan on laitteen virrankulutuksen minimointi ja paikannusnopeuden maksimointi.
Vaikka paikannusmenetelmän valinnan läpinäkymättömyys kuulostaa kehittämistä rajoittavalta, helpottaa se kehittäjän työtä. Käytännössä kehittäjän tarvitsee valita haluttu sijainnin tarkkuus ja kirjasto hoitaa kaiken muun.
Paikannusprosessi tapahtuu kahden luokan avulla jotka ovat CLLocationManager ja CLLocation.
  • CLLocationManager paikantaa sijiannin ja lähettää tietoa sijainnin muutoksista CLLocationManagerDelegate-protokollan kautta päivityksiä kuuntelevalle delegaatille. Kaikki paikannukseen liittyvät muutettavat asetukset löytyy tästä luokasta
  • CLLocation kuvaa yhtä paikkatietopäivitystä. Olio sisältää aikaleiman, koordinaatit ja päivityksen tarkkuuden metreinä. Mukana on myös tieto liikkeen suunnasta ja nopeudesta.
Kertakäyttöinen vai jatkuva paikannus?
Moni applikaatio tarvitsee sijaintitietoa vain kerran, esimerkiksi käynnistyessään. Yleensä kerran haettu paikkatieto riittää useimpiin tarpeisin. Silloin on järkevää että CLLocationManager hakee käyttäjän sijaintia vain kun paikkatietoa ei ole haettu, tai viimeisin paikkatietopäivitys on liian vanha tai epätarkka. Yleensä applikaatioihin riittää yksi jaettu CLLocationManager-olio jonka location-propertyä muu applikaatio käyttää. Jaettu CLLocationManager laitetaan silloin hakemaan käyttäjän sijaintia, ja haku päätetään kun tarpeeksi tarkka sijainti on löydetty. Tämän jälkeen jaetussa CLLocationManager-oliossa on tallessa viimeisin haettu sijainti tallennettuna location-propertyyn josta sijaintia tarvitsevat applikaation osat sitä voivat kysyä. On syytä tarkastaa location-propertyn aikaleima, sillä applikaation käynnistyessä tai palatessa taustalta viimeisin sijaintipäivitys voi olla liian vanha kertomaan käyttäjän nykyisen sijainnin.

Paikannus applikaation ollessa taustalla
Sijainnin paikantamista voi jättää päälle kun applikaatio siirtyy taustalle ajoon. Paikannusmenetelmiä on kolme erilaista:
  1. startUpdatingLocation – jatkuva ja tarkka sijainnin päivitys
  2. startMonitoringSignificantLocationChanges – epätarkka ja harvoin päivittyvä sijainnin seuraaminen
  3. startMonitoringForRegion: – tarkkailee liikkuuko laite määritellyn alueen sisälle tai pois alueelta. Alueita voi olla seurannassa yhtäaikaisesti 20 kappaletta.

Kolmesta edellämainitusta menetelmästä ensimmäinen on tarkin, ja sen ollessa käynnissä applikaatio pysyy koko ajan taustalla ajossa. Tämä on paras vaihtoehto jos paikannusta käyttää esimerkiksi liikuntasovellukseen tai reittioppaaseen. Haittapuolena on jatkuva akun kuluminen.

Kaksi jälkimmäistä seurantatapaa kuluttaa vähemmän akkua kuin ensimmäinen, eikä applikaatio jää päälle taustalle. Nämä menetelmät kuitenkin käynnistävät applikaation, ja sijaintipaikannus pysyy päällä vaikka laitteen sammuttaisi ja käynnistäisi uudestaan.

Sijaintipalveluiden käyttö taustalla XCode-projektiss
a
  1. lisää CoreLocation.framework projektiin.
  2. Ota käyttöön Background modes Capabilities-näkymästä. Se löytyy projektin targetista.
  3. Valitse Location updates. Applikaatio saa nyt päivityksiä sijainnista ollessaan taustalla.
CoreLocationin haasteet ja ongelmat
Laitteen sijainnin hakeminen yksittäistä käyttökertaa varten ei ole niin suoraviivaista kuin se voisi olla. Sijainti toimitetaan asynkronisesti CLLocationManagerDelegate-protokollan kautta asynkronisesti. Ei siis ole olemassa suoraa myLocation = getLocation() -tyylistä tapaa hakea sijaintia. Helpoin tapa hakea laitteen sijainti kerra on kutsua CLLocationManager-luokan startUpdatingLocation-metodia. Kutsu käynnistää paikannuksen, ja lähettää normaalisti 3-4 locationManager:didUpdateLocations: -kutsua delegaatille sijainnin päivittymisestä. Paikannus kannattaa lopettaa silloin kun kutsujen mukana tulevien CLLocation-olioiden horizontalAccuracy-attribuutti on tarpeeksi pieni, eli paikannettu sijainti on riittävän tarkka applikaation tarpeisiin.
Haettavan sijainnin halutun tarkkuuden voi määritellä CLLocationManager-olioon, desiredAccuracy-propertyyn. Halutulla sijainnin tarkkuudella on suuri merkitys paikannuksen nopeuteen ja laitteen virran kulutukseen, joten on suositeltavaa että haettavan sijainnin tarkkuus on niin pieni kuin se on välttämätöntä applikaatiolle. Esimerkiksi paikallissäätä tai paikallisuutisia näyttävä applikaatio voi hyvin käyttää kCLLocationAccuracyThreeKilometers-tarkkuutta, kun taas juoksureittiä tallentava applikaatio vaatii tarkimman mahdollisen tarkkuuden, eli kCLLocationAccuracyBest-tarkkuuden.

Lopuksi
Tämä kirjoitus esitteli pintapuolisesti CoreLocation-kirjaston mahdollisuuksia. Moni mielenkiintoinen ja hyödyllinen kirjaston osa, kuten iBeacon-majakat ja CLGeocoder-geokoodauspalvelu, jäivät tässä kirjoituksessa esittelemättä.

Sijaintipalveluiden hyödyntäminen tekee applikaatiosta tilannetietoisen, mutta sijaintipalveluita käyttäessä pitää punnita sijainnista saadut hyödyt ja nopeammin tyhjenevän akun haitat tarkkaan.

Porttautuva koodi mobiilissa

“Write once, run anywhere” oli Javan silver bullet -mantra aikanaan. Eli kirjoitat koodin kerran, käännät sen kerran ja ajat missä vaan tuetussa ympäristössä ilman että ohjelmasi tarvitsee liiemmin tietää ajonaikaisesti mikä käyttöjärjestelmä / rauta ympäristössä on käytössä. No, vaikkei tuohon ole vieläkään – 18 vuotta myöhemmin – ole ihan päästy, on Javan ja vaikkapa Pythonin kaltaisten tulkattujen/scriptauskielten kanssa useimmiten paljon helpompaa elää monialustaympäristössä kuin natiivikoodin. Mutta entä jos halutaankin kehittää natiivisovellus jossa uudelleenkäytetään mahdollisimman suuri osa koodista sen sijaan että N alustaa varten tehdään N toteutusta? Vaikka nykyisten mobiililaitteiden rautaspeksejä (kotitehtävä: montako 25MHz i386 CPU:ta tarvitaan tuottamaan Samsung Galaxy S4:n CPU-teho?) ihmetellessä ei parhaalla omallatunnollakaan voi sanoa resurssien puutteen pakottavan natiivikoodiin, on sen käyttö esim. Android-alustallakin perusteltua sovelluksissa joiden on puristettava se viimeinenkin pisara suorituskykyä irti laitteesta.

Luonnollisesti kaikenlaisiin sovellustyyppeihin porttautuvaa koodia on turha yrittää ujuttaa; jos sovellus on 80%:sti käyttöliittymää eikä ole tarjolla/ei haluta käyttää kaikilla kohdealustoilla toimivaa porttautuvaa UI-kirjastoa, ei kannata väkisin lyödä päätään seinään vaan kirjoittaa toteutukset kiltisti alustan natiivi-API:a käyttäen – tai valita teknologiaksi HTML5 joka sopii webmäisiin lomakesovelluksiin kuin nyrkki silmään. Ym. UI-kirjastoa käyttämällä saatetaan myös – sen toteutuksesta toki riippuen – menettää alustan oma look-and-feel. Ihan hihasta ravistettuna porttautuvan arkkitehtuurin puolesta / vastaan listat voisivat näyttää vaikkapa tältä:

Vastaan:

  • Väkisin yritetty porttautuvuus tekee arkkitehtuurista kömpelön ja lisää turhaa kompleksisuutta
  • Alustan natiiveja ominaisuuksia saatetaan menettää (hyvässä ja pahassa)
  • Käytettäväksi valittu yhteinen ohjelmointikieli ei välttämättä ole tuttu kaikille projektin jäsenille

Puolesta:

  • Koodin joutuu kirjoittamaan vain kerran eli kehityskustannus pienenee
  • Bugit joutuu korjaamaan vain kerran
  • Koodipohjasta tulee merkittävästi pienempi jolloin sen ylläpidettävyys paranee ja siihen on nopeampaa perehdyttää uusia tekijöitä
  • Ohjelmiston luonteen salliessa paljon koodia kierrättävä arkkitehtuuri on hyvin elegantti

Esimerkkiprojekti: MMark13 – mobiilia suorituskykytestausta

Käytän esimerkkinä kuluneen vuoden aikana hiljalleen syntynyttä harrasteprojektiani MMark13, joka – hyvin tiivistetysti – on mobiililaitteiden CPU/GPU suorituskyvyn mittaamiseen tarkoitettu työkalu. Vastaavalla idealla (joskin merkittävästi isommalla tiimillä & budjetilla :)) rakennettu sovellus löytyy Desktop-puolelta, monelle tuttu suomalaista tekoa oleva Futuremark:in 3DMark. Ja ei, en ole suoraan rinnastamassa harrastelijatuotostani PC-maailman (Ja Futuremark:han puuhaa parasta aikaa myös mobiilibenchmarkingia, Androidille se jo ilmestyikin! -toim.) uraauurtavaan tuotteeseen.. no joka tapauksessa, projekti lähti liikkeelle halusta hioa/päivittää OpenGL ES 2.0 taitoja ja havaittuani ettei tuossa vaiheessa (alkuvuosi 2012) juuri vastaavia sovelluksia ollut tarjolla (paitsi kaupallinen ja maksullinen GLBenchmark 2.5 – joskin tuolloin vain iOS:lle), valitsin kyseisen sovellusalueen jo siksikin että porttautuvan arkkitehtuurin hyväksikäyttäminen laajalti tarjosi ainutlaatuisen edun: kun jokaisella alustalla ajettava koodi on 100% sama niiltä osin jotka grafiikan piirtoon / mittauksiin vaikuttavat, voidaan tuloksia verrata 1:1 toisiinsa alustojen välillä ilman että tarvitsee napista Javan tehottomuudesta Androidilla.

 

MMark13 koodin rakenne

 

Koodin uudelleenkäytön suunnittelussa on hyvä lähteä liikkeelle siitä että kartoittaa tarkasti kohdealustansa ja niiden tarjoamat asiat ja sitten hahmottelee niiden pohjalta suurimman yhteisen nimittäjän. Valitsin kohdealustoikseni iOS (Applen laitteet) ja Qt (Android, kaikki merkittävät desktop-käyttöjärjestelmät, Meego, Blackberry10, Symbian, Sailfish OS, Ubuntu Phone, luultavasti myös Tizen ja Firefox OS). Kun alustat ovat tiedossa, aloittaa voi vaikka ohjelmointikielestä; se on valinta jota alemmalle tasolle ei juuri pääse. C olisi aina varma valinta, mutta puhtaalla C:llä syntyy tulosta kovin hitaasti ja dynaamisten tietorakenteiden / merkkijonojen / jne käsittely on melko kömpelöä. Valitsinkin siksi pykälää korkeamman abstraktiotason eli C++:n. Ohjelmointikielen version valintaan kannattaa paneutua myös hetken eikä sännätä käyttämään heti uusinta; itse en uskaltanut vielä tähän projektiin valita tuoreinta C++11 versiota vaikka se oliskin tarjonnut muutamat elämää helpottavat asiat kuten “auto” tyypin muuttujille, sisäänrakennetun säiekirjaston ja paljon muuta. Sittemmin olen perehtynyt tarkemmin C++11 feature support matriisiin merkittävillä kääntäjillä (GCC, clang, MSVC++) ja vaikuttaa että aika on kypsä sen käyttöönotolle.

Ohjelmointikielen valinnan jälkeen seuraava looginen askel on kartoittaa APIen/ulkoisten kirjastojen tarve ja koettaa vaikkapa proof-of-concept -hengessä varmistaa että kirjastovalinnat täyttävät tarpeet. MMark13 -projektissa tarvitsin säikeistystä, joten käytin hyväkseni tietoa että kaikki kohdealustani ovat UNIX-perillisiä ja siten tarjoavat POSIX threads (pthreads) paketit. Tarvittiin myös JSON kirjastoa; pienellä googlauksella löysin “jsoncpp” paketin jonka koodi/dokumentaatio oli poikkeuksellisen laadukkaan oloista ja joka istui suoraan tarpeisiini. Kenties tuosta löydöksestä riemuitessani kirjoitin seuraavan askeleen eli tuloslaskelma-JSON lähetyksen palvelimelle abstraktion läpi siten että kirjoitin alustoille omat toteutuksensa sen sijaan että olisin käyttänyt ilmiselvää ratkaisua libcurlia. Tajusin mokani vasta projektin loppumetreillä enkä rupea yleensä korjaamaan jotain joka toimii, joten saivat jäädä. Lisäksi otin mukaan simppelin C++ MD5-toteutuksen. Halusin toteuttaa softani käyttöliittymän alustariippumattomasti siten että se käyttäytyy kaikilla laitteilla samalla tavalla ja myös näyttää samalta. Etsinkin melko laajalti OpenGL-pohjaista Widget-kirjastoa mutta löytämäni paketit olivat joko aivan järjettömiä bloatteja tai sitten erittäin huonolaatuisen näköistä koodia (usein molempia) että päädyin kirjoittamaan hyvin lightweight toteutuksen aivan alusta erään intensiivisen viikonlopun aikana. Eli ainakin yksi pyörä tuli keksittyä uusiksi – toisaalta aikaa ei tuohon uponnut paljoa, lopputulos miellyttää silmää, homma oli erittäin hauskaa ja lisäksi vieläpä opin paljon.

Lisäksi tarvittiin seuraavat asiat alustariippuvasti: softan bootstrapping, softa/hardisinformaation penkomista laitteelta, joitain sekalaisia pikkujuttuja sekä resurssifilejen lukeminen systeemistä – tähän olisi voinut käyttää ihan fread():ia mutta halusin toisaalta käyttää Qt:n mainiota resource systeemiä jonka kanssa tuo taas ei toimi. Alla muutama konkreettinen koodiesimerkki alustariippuvasta koodin jaosta:

CommonFunctionsQT.cpp:

std::string RandomUuid()
{
    // .mid(1,36) strips the curly braces added by toString(), duh
    return std::string(QUuid::createUuid().toString().mid(1,36).toUtf8());
}

CommonFunctionsIOS.cpp:

std::string RandomUuid()
{
    CFUUIDRef theUUID = CFUUIDCreate(NULL);
    CFStringRef cfuuid = CFUUIDCreateString(NULL, theUUID);
    CFRelease(theUUID);
    NSString* uuid = (NSString*)cfuuid;
    [uuid autorelease];

    return std::string([uuid UTF8String]);
}

Kurkistetaan vielä korkealla tasolla ohjelmiston toimintaa ja sitä miten alustariippuva ja porttautuva osa koodia pelaavat yhteen. iOS-buildissa tarvittava wrapperkoodi on luonnollisesti Objective-C(++):aa; kokemus osoitti ettei sen ja C++ -maailman sekoittaminen keskenään ole triviaalia tai kovin eleganttia, ja kehotankin eristämään kyseisen rajapinnan mahdollisimman pienelle alalle. Käytännössä toimiva pattern on antaa Objective-C++ koodille yksisuuntainen has-a -patternin mukainen suhde porttautuvaan C++ koodiin ja rakentaa iOS-specific toteutus C++ luokalle joka hoitaa callbackien tarvitseman alustariippuvan koodin. Qt maailma on natiivisti C++:aa, joten siellä ei tarvittu taikatemppuja. Ohjelman toimintalogiikka pääpiirteittäin:

  1. Alustariippuvainen osa ottaa vastaan inputit, muuttaa niiden datatyypit alustariippumattomaan muotoon ja välittää ne eteenpäin allaolevalle porttautuvalle osalle. Käytännössä inputit ovat:
    • Redraw timer. iOS:ssa CADisplayLink, Qt:ssä QTimer.
    • Käyttäjän kosketukset. iOS:ssa UITouch*, Qt:ssa QTouchEvent.
  2. Alustariippumaton osa päivittää tilan / menussa ollessa UI:n tilan touchien / kuluneen ajan avulla
  3. Uuden tilan perusteella piirretään uusi frame sekä mm. aloitetaan fysiikkalaskenta seuraavaa framea varten
  4. Tarvittaessa kutsutaan abstraktoituja callbackeja kun tarvitaan alustariippuvaa toiminnallisuutta; esimerkiksi kun ladataan testin vaatimia tekstuureita levyltä tai halutaan lähettää tulokset serverille

Jollekulle on saattanut tätä lukiessa tulla mieleen että mitenkäs se Windows Phone sitten? WP8 pois jättämiseen kohdealustojen joukosta on oikeastaan vain yksi syy – OpenGL APIn puute. Vaikka ymmärtääkseni WP8 hardis on OpenGL ES 2.0 yhteensopivaa, Microsoft ei tapansa mukaan tarjoa standardia rajapintaa vaan pakottaa kehittäjät porttaamaan koodinsa Direct3D:lle. Ja koska softani tärkeimpiä ominaisuuksia on olla suoraan vertailukelpoinen ympäristöjen välillä, ei tuo oikein ollut optio. Jos Googlen ANGLE projektista tulee ikina luotettava port WP:lle *tai* jostain taivaasta putoaa OpenGL ajuri, teen ilomielin myös WP-buildin. Tämän vuoden aikana on tarkoitus suoltaa ulos buildit ainakin Blackberry10:lle, Sailfish OS:lle sekä Ubuntu Phonelle riippuen siitä saadaanko moisia laitteita käsiimme.

Hyvää kevättä kaikille!

Linkkejä

Lisää luettavaa:

Push-viestit ja push-palvelin – mitä ne ovat?

Viimeisen kahden vuoden aikana push-viestien osuus mobiiliviestinnässä on kasvanut räjähdysmäisesti uusien älykkäämpien palvelujen myötä. Seuraavassa pyrimme avaamaan mitä push-viestit ovat sekä niiden hyötyjä ja mahdollisuuksia. Tulemme julkaisemaan myöhemmin myös push-viestintäteknologiaa ja -arkkitehtuuria tarkemmin käsittelevän artikkelin.

Push-viestejä voitaisiin kuvata esimerkiksi seuraavalla virkkeellä:

Push-viestit ovat viestejä, jotka laitteeseen saapuessaan lähes poikkeuksetta herättävät käyttäjän huomion. Käyttäjä voi viestin otsikosta riippuen joko unohtaa sen tai perehtyä siihen ja saada mahdollisesti lisää tietoa asiasta.

Kuvauksen mukaisesti push-viestejä voitaisiin verrata tekstiviesteihin, sillä lisäyksellä, että ne aina liittyvät puhelimelle ladattuun sovellukseen.

Mainonnan ja markkinoinnin näkökulmasta push-viestit eroavat merkittävästi tekstiviesteistä tai muista digitaalisista viestintämuodoista. Digitaalinen mainonta tekstiviestien avulla saattaa monien mielestä olla tunkeilevaa ja sähköpostin välityksellä tapahtuvana persoonatonta sekä harvoin tunteita herättävää. Push-viestit sen sijaan saapuvat aina käyttäjän sallimana, näkyvät reaaliaikaisesti laitteessa ja voivat sisältää monipuolista interaktiivista sisältöä.

Henkilökohtainen merkityksellisyys

Yksilön profilointi on perinteisessä mainonnassa usein pulmallista ja sen toteutus sekä järkevästi että käytännöllisesti on hankalaa, sillä käyttäjien taustat pitää kartoittaa ja sen perusteella jakaa käyttäjät lokeroihin. Käyttäjien profiilitietojen muuttuminen puolestaan ei välttämättä koskaan saavuta palveluiden tarjoaa. Tällainen tilanne saattaa olla kaikille vahingollista. Mainostaja tekee turhaa työtä ja tuhlaa resurssejaan. Viestin saajan osalta roskaviestinnäksi muuttuneen yhteydenottojen käsittely voidaan kokea jopa ärsyttäväksi, mikä saattaa herättää negatiivisia tunteita palvelun tarjoajaa kohtaan.

Push-viestien vastaanottamisen sallimisen mahdollisuus mobiilisovelluksessa estää turhien viestien saapumisen loppukäyttäjille. Viestien lähettämisen salliminen myös ennakoi vastaanottavampaa suhtautumista asiasisältöön. Mobiilisovellus, joka on yhteydessä push-palveluun, voi kerätä ja pitää ajantasalla käyttäjien palvelutietoja, listaa kiinnostuksen kohteista ja sekä esimerkiksi paikkatietoja – käyttäjän sen salliessa. Erityisesti paikkatietojen hyväksikäyttö on vielä vähän käytetty “se suuri” mahdollisuus; paikkatietoisuus yhdistettynä käyttäjien kiinnostuksien kohteisiin tuo merkityksellisyyttä ja korostaa palvelun tarjoajan kunnioitusta yksilöä kohtaan.

Push-viestit saavuttavat kohdeyleisön välittömästi

Ihmisillä on nykyään hyvin usein monia sähköpostitilejä: todennäköisesti tietty tili varattuna henkilökohtaiseen viestintään ja jokin toinen tai toiset tilit yleisempään viestintään, kuten rekisteröitymisiin ja muihin harvemmin käytettyihin palveluihin. Viestin ja loppukäyttäjän kohtaaminen saattaa tapahtua vasta päivien tai viikkojen kuluttua itse lähetystapahtumasta, koska käyttäjät eivät välttämättä seuraa aktiivisesti kaikkia tilejään.

Push-viestit saavuttavat vastaanottajan viimeistään muutaman minuutin sisällä lähetyksestä. Käyttäjien voidaan olettaa saaneen ja huomioineen viestin lähes välittömästi, sillä puhelin värisee, soittaa mahdollisesti merkkiäänen ja ruudulla tapahtuu visuaalinen päivitys.

Pushin avulla sisältöä mobiiliviestintään

Niin massaviestit kuin käyttäjien profiloinnin kautta yksilöidyt viestit kannattaa muotoilla huolellisesti ja sisällyttää niihin jotain, joka oikeuttaa välittömän yhteydenoton käyttäjiin.

Pelkkä tekstitieto,esimerkiksi vaikkapa “Liikkeemme on auki tänäänkin klo 9-18” voi olla paikallaan sillöin tällöin muistuttamassa palvelun olemassaolosta. Suositeltavampaa olisi kuitenkin antaa käyttäjille arvokkaampaa informaatiota, jota epush-viestipalvelua käyttämättömät eivät tietäisi. Esimerkiksi “Muotisuunnittelijamme on tänään liikkeessämme klo 18 asti antamassa henkilökohtaisia vinkkejä. Skumppaa 50 ensimmäiselle!” -tyyppinen viesti tuo lisäarvoa viestiin ja on myös käyttäjille syy olla liittyneenä viestipalveluun. Varsinkin lyhytaikaiset kaikille tarjottavat tiedotteet ja edut houkuttelevat palvelusta kiinnostuneet liittymään palveluun saadakseen tiedon tapahtumista ensimmäisten joukossa.

Push-viestivaihtoehdot

Push-viestit ovat perusidealtaan samankaltaisia kaikissa laitteissa: viesti saapuu, se mahdollisesti avataan ja siihen sidottu mobiilisovellus käynnistyy ja näyttää lisätietoa asiasta.

Applen iOS-laitteilla push-viestit tulevat laitteen ruudulle ja käyttäjä voi reagoida siihen välittömästi tai palata asiaan myöhemmin uudelleen viestikeskuksen kautta. Näkyvän viestisisällön lisäksi viestiin liitetään usein myös käyttäjälle näkymätöntä tietoa, kuten mikä merkkiääni soitetaan viestin saapuessa tai mitä dataa palveluun halutaan välittää sisällönpäivittämisen mahdollistamiseksi.

Windows Phone -laitteille on tarjolla puolestaan usean tyyppisiä push-viestejä, joista perusvaihtoehto (“toast”) on samankaltaisin muiden alustojen push-viesteihin verrattuna. Windows Phone -laitteelle on mahdollisuus lähettää myös raw- ja tile-viestejä. Raw-viestit tarjoavat mahdollisuuden palvelulle syöttää tietoa (esimerkiksi kuponki) mobiilisovellukseen taustalla, ilman että sovelluksen pitää hakea tietoa erikseen.

Tile-viesteissä puolestaan palvelu voi syöttää sovellustiileen (vastaa muilla alustoilla suurin piirtein ikonia työpöydällä) taustakuvia, tekstejä ja numerotietoa. Tämän avulla voidaan toteuttaa esimerkiksi käyttäjän työpöydän uutissovelluksen ikonin sisällönmuutos tai vastaava tilapäivitys.

Android-laitteilla mahdollisuuksia on useita. Käytännössä kehittäjä voi päättää mitä tehdään push-viestin tullessa. Yleisin käytäntö kuitenkin on, että käyttäjälle näytetään notifikaatti, jonka hän voi avata silloin kun on siihen sopiva hetki. Voidaan kuitenkin tehdä monimutkaisempiakin toteutuksia — esimerkiksi käsketään laite lataamaan jokin iso tiedosto ja vasta sen jälkeen ilmoitetaan käyttäjälle notifikaatilla. Käyttäjälle ei ole pakko ilmoittaa push-viestistä mitään, vaan se voi pelkästään olla taustalla tapahtuvaa logiikkaa.

Toimintaympäristö

Push-viestipalvelun käynnistämisen jälkeineen tapahtuva viestien lähetys voi parhaimmillaan olla varsin yksinkertaista ja suoraviivaista. Palvelun tarjoaja luo uuden push-viestin syöttämällä lähettävässä palvelussa lomakkeeseen viestin tiedot. Lähetysnapin painamisen jälkeen tiedot siirtyvät push-viestipalvelimelle, joka lähettää viestin edelleen Applen, Googlen ja Microsoftin kautta mobiililaitteille. Edellä kuvattu toimintatapa vaatii kuitenkin sen, että palvelun kehittäjä on joko itse kehittänyt push-palvelimen viestien välittämiseen tai käyttää tarjolla olevia kaupallisia vaihtoehtoja.

Qvik on toteuttanut push-palvelimen ja tarjoaa sitä mielellään kaikkien asiakkaiden käyttöön. Jatkamme Push-teknologiasta ja viestintäarkkitehtuurista tarkemmin seuraavassa artikkelissa.

 

Kuva 1. Push -viestipalvelin lähettää viestit mobiilikäyttäjille

 

iPad Mini -arvostelu

Ensimmäinen iPad julkaistiin tammikuussa 2010. Aikaa on siis kulunut vasta vähän vajaat kolme vuotta, mutta julkaisusta alkaneen tablettivallankumouksen takia aika tuntuu huomattavasti pidemmältä ja paljon on vettä virrannut julkaisun jälkeen. Suurin muutos tablettimarkkinoilla iPadin julkaisun jälkeen ovat olleet halvat minitabletit Amazon Kindle Fire ja Google Nexus 7. Nyt Apple lähtee mukaan minitablettitaistoon ei-niin-halvalla iPad  Minillä.

Otin viikonlopun ajaksi testiin iPad Minin 16-gigaisen WiFi-version ja testasin, onko uutukaisesta mihin ja mihinkään.

Onko minun käteni todella iso, vai onko iPad Mini?

Ensi tuntuma

Ensi tuntuma iPad Miniin on melkein maaginen: se on kevyt, erittäin ohut ja tuntuma on “arvokas”. Samanlaista tunnetta ei välitä iPad Minin Android vastine Google Nexus 7, joka on pienestä koostaan huolimatta varsin pulska ja painava. Myös sen ulkokuori natisee kun laitetta käsittelee. Tuo arvokkuuden tuntuma on todennäköisesti syy, miksi Apple on hinnoitellut iPad Minin melkein 100 euroa kalliimmaksi kuin mitä Google on laittanut hintalapuksi Google Nexus 7:lle.

Kannettavuus on iPad Minissä huippuluokkaa: se sujahtaa talvitakin povitaskuun näppärästi, eikä sitä edes huomaa kantavansa mukanaan.

Näyttö

Retina iPadiin tottuneelle iPad Minin pienempi näytöntarkkuus on heti huomattavissa, ja teksti näyttää hiukan sumuiselta. Latasin iBooksista testin vuoksi pari kirjaa ja yhden sarjakuvan. Kirjoissa näyttö ei varsinaisesti haitannut, mutta sarjakuvissa tekstien näkemin vaati huomattavaa keskittymistä. Zoomailuun ei kuitenkaan tarvinnut turvautua. Verkkosivuilla huomasin zoomailevani huomattavasti enemmän kuin Retina iPadilla.

Käyttö

Kolmannen sukupolven Retina iPad (eli se ensimmäinen Retina), kärsii hiukan nopeusongelmista, johtuen näytön suuresta koosta. Nuo nopeusongelmat ovat poissa iPad Minissä: kuten myös samalla suunnilleen samalla raudalla toimivassa iPad 2:ssa, applikaatiot avautuvat sukkelaan ja isotkin pdf-tiedostot ovat selattavissa nopeasti iBooksissa.

Peleistä testasin Suomalaisen Remedyn Death Rally -peliä, jota olen aiemmin testannut iPhone 4:lla ja kaikilla aiemmilla iPadeilla. iPad Minillä pelattuna pelikokemus oli tähän astisista laitteita paras. Näyttö oli tarpeeksi iso, mutta laite on niin kevyt, että sitä jaksaa kannatella pidemmänkin aikaan. Voidaan siis sanoa, että homma pelittää.

Pähkinänkuoressa

Olin ennen testirupeaa hiukan skeptinen minitablettien tarpeellisuudesta, testi ei aivan kokonaan saannut mieltäni muutettua, mutta ei minitabletit aivan turhia ole. iPad Mini on rahansa arvoinen laite ja varsinkin sisäisellä verkkoyhteydellä varustettuna laite olisi varsin kätevä paljon tienpäällä oleville.

Retina iPadin omistajille iPad Mini ei tuota mainittavaa lisäarvoa, mutta esimerkiksi iPod Touch tai iPad 1 olisi jo varsin perusteltua korvata iPad Minillä. Muihin minitabletteihin verrattuna iPad Minin iso näyttö, mutta pieni koko antaa mielesätni sellaisia etuja, joista kannattaa maksaa muutama euro enemmän.

 

iPad Mini kehittäjän näkökulmasta

Apple julkaisi eilen tukun uusia tuotteita, joiden mukana oli myös kokonaan uusi iOS-laite, iPad Mini. Tässä hiukan mietteitä kehittäjän näkulmasta.

iPad Mini on raudaltaan ja pikseleiltään sama kuin iPad2, eli sovellukset jotka toimivat iPad2:ssa toimivat myös iPad Minissä. Ainoat isommat erot ovat iPad Minin LTE-tuki, Siri ja luonnollisesti koko.

Internet-huhut olivat yhdessä vaiheessa sitä mieltä, että iPad Mini olisi kuvasuhteeltaan 16:9, joka olisi ollut sovelluksille huomattavasti isompi remontti, kuin iPhone 5 yhteensopivuuden tekeminen iPhone-sovelluksissa. Näin ollen kehittäjät voivat huokaista helpotuksesta, kun nykyiset sovellukset rullaavat enemmän tai vähemmän samalla tavalla kuin iPad 2:ssa.

Tervetuloa iPad Mini.

 

Ehta modaalidialogi iOS:lla


Muista kehitysympäristöistä iOS-maailmaan tulevasta voi tuntua oudolta Cocoa Touchin modaalidialogien puute. Vaikkakin on mahdollista rakentaa kaiken muun päälle piirtyvä dialogi joka oletukselta (ja tämä on ohitettavissa) saa kaiken syötteen, dialogin näyttävä kutsu ei blokkaa vaan palaa välittömästi ja dialogin sulkeutuminen on otettava kiinni asynkronisella callbackilla (‘delegate’). Edes perustyökalu UIAlertView ei käyttäydy kuin muiden alustojen – vaikkapa MFC, Javascript, Symbian Series60/Series80, Java AWT/Swing, Qt, Android, GTK, jne.. – stock komponentit MessageBoxit ja InputDialogit.

Tämänkaltaista UI paradigmaa voi kuitenkin paitsi kaivata, myös tarvita; oletetaan etta ollaan rakentamassa iOS -toteutusta cross-platform sovellukseen jossa täytyy toteuttaa metodi vaikkapa seuraavasti:

std::string AskUserInput()
{
  // Semantiikka: kysy käyttäjältä syöte popup dialogilla 
  //   ja palauta annettu teksti
}

Miten tämä toteutetaan iOS:lla? Nostetaan kädet pystyyn, otetaan projektin arkkitehti Skypen päähän ja anotaan API:in muutosta joka mahdollistaisi syötteen kyselyn asynkronisen käsittelyn? Miksei, mutta jos yo. mekanismi on jo toteutettu viidelle muulle alustalle, on edessa iso muutostyö ja paljon hampaita kiristeleviä ihmisiä. Turha asynkronia näin yksinkertaisessa asiassa hankaloittaa sekä pirstaloittaa arkkitehtuuria.

Ongelman ratkaisu lähtee UI frameworkin toiminnan ymmärtämisestä. Käytännössä kaikissa moderneissa käyttöliittymäkirjastoissa on 1-N UI säiettä jotka palvelevat käyttöliittymää ns. event loopin [tapahtumakieriö?! -toim.] kautta. Event loop ottaa inputteja komponenteilta, timereilta tai vaikkapa muilta säikeiltä alustakohtaisten viestinvälitysmekanismien välityksellä ja tarjoilee niitä kiinnostuneille kuuntelijoille. Käytännössä jokaisella säikeellä on tasan yksi (tai nolla, jos kyseessä ei ole UI:ta palveleva säie) event loop, ja yleisesti mobiilikäyttöjärjestelmissä UI:ta palvelee vain yksi säie kerrallaan. Käyttöliittymä kumminkin toimii jouhevasti niin kauan kuin tätä säiettä ei varata kerralla liian pitkäksi aikaa; pitkäkestoinen laskenta täytyy joka jakaa pieniin osiin tai tehdä muissa säikeissä.

Event loopiin perustuvat järjestelmät sallivat yleensä event looppien sisäkkäisen ajamisen (nesting); tällöin vanha event loop aktivaatio pysähtyy eikä jatka ennenkuin sisempi loop aktivaatio loppuu. Tätä voidaan hyväksikäyttää asynkronian poistamiseksi; API-metodin sisällä ajetaan uusi aktivaatio, kuunnellaan asynkronista vastausta ja lopetataan aktivaatio kun vastaus on saatu. Tällöin APIa kutsuva metodi blokkaa koko suorituksen ajan. On kuitenkin erotettava tämä semaforin odottamisesta; säiehän ei ole idlena vaan suorittaa sisempää event loop aktivaatiota.

Rakennetaan esimerkkitoteutus iOS:lle. Halutaan tehda seuraavanlainen kutsu:

-(void) requestAndLogInput {
  InputDialog* dlg = [InputDialog dialog];
  [dlg showModal];
  NSLog(@"Dialog input was = %@", [dlg getInput]);
}

Aloitetaan dialogin luomisesta. Tähän käytetään normaalia Cocoa Touch boilerplatea:

+(InputDialog*) dialog {
    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"InputDialog"
                                                      owner:nil
                                                    options:nil];
    InputDialog* dlg = [[[nibViews objectAtIndex:0] retain] autorelease];

    return dlg;
}

Määritellään metodi jolla dialogi näytetään modaalisti – se asettuu päällimmäiseksi ja blokkaa kunnes dialog on suljettu. iOS:lla event loop tunnetaan nimellä Run loop, ja sitä vastaavat APIt ovat NSRunLoop sekä Core Foundationin CFRunLoop*:

-(void) showModal {
    // Lisätään dialogimme keywindow'n ylimmän lapsen ylimmäksi lapseksi,
    // jolloin dialogi päätyy päällimmäiseksi view stackiin
    UIWindow* keyWindow = [[UIApplication sharedApplication] keyWindow];
    UIView* topmostView = [keyWindow.subviews objectAtIndex:0];
    [topmostView addSubview:self];

    // Sijoitetaan dialogi suunnilleen keskelle ruutua    
    CGSize s = topmostView.bounds.size;
    self.center = CGPointMake(s.width / 2, (s.height / 2) - 70);

    // Ajetaan nested run loop aktivaatio jolloin showModal kutsu blokkaa
    CFRunLoopRun();
}

Lopuksi määritellään dialogin sulkeva IBAction:

-(IBAction) okPressed {
    // Poistetaan dialogi näkyvistä
    [self removeFromSuperview];

    // Pysäytetään sisempi run loop aktivaatio; kontrolli palaa 
    // ulommalle ja showModal -kutsu palaa
    CFRunLoopStop(CFRunLoopGetCurrent());
}

Valmista! Ja koska kyseessä  ovat erilliset run loop aktivaatiot eivätkä erilliset run loopit, esimerkiksi timerit jatkavat eloaan ja autorelease pool on jaettu aktivaatioiden välillä.

 Esimerkkiprojekti Xcode 4.5 -projektina. 40kB zip

 

Lua mobiilisoftassa

Käyttämällä skriptikieliä mobiiliohjelmoinnissa voidaan mm. edistää koodin jakamista eri alustojen kesken. Varsinkin peliohjelmoinnissa skriptien käyttäminen on suosittua: Pelilogiikkaa on helposti paljon ja sen kaiken kirjoittaminen erikseen eri ohjelmointikielillä eri alustoille olisi aikaavievää ja virhealtista. Eräs varsin suosittu skriptikieli on Lua. Lua on suhteellisen helppo yhdistää toiseen projektiin ja siihen on helppo luoda applikaatiospesifisiä laajennuksia. Lua on lähtökohtaisestikin suuniteltu laajennuskieleksi ja verrattuna esim. Pythoniin Lua on hyvin kevyt. Tässä artikkelissa opastetaan, miten Luaa voi käyttää iOS- ja Android-projekteissa.

Vaihe 0 — Luan lähdekoodi

Hae Luan lähdekoodi http://www.lua.org/ftp/.

Huomaa, että Lua on muuttunut jonkin verran versioiden välillä. Tämä ohje on kirjoitettu versiolle 5.2.1. Jotkin aiemmalle versiolle kirjoitetut Lua-ohjelmat eivät välttämättä toimi suoraan uusimmalla versiolla. Näitä ohjeita voi soveltaa myös muihin versioihin, mutta ne eivät välttämättä toimi sellaisenaan.


iOS

Vaihe 1 — Lua-kirjasto

  1. Luo uusi kirjastoprojekti. Tässä esimerkissä olen antanut projektille nimeksi Lua521 Luan versionumeron mukaan.Luo uusi kirjastoprojekti
  2. Poista projektiin automaattisesti luodut ProjektinNimi.m ja .h -tiedostot.
  3. Lisää projektiin Luan lähdekoodipaketista src-hakemiston sisältö lukuunottamatta tiedostoja lua.c, luac.c ja Makefile.
  4. Mene projektin Build Phases -ikkunaan ja lisää Copy Headers -vaihe.
    Build Phases
    Lisää vaihe
  5. Lisää Copy Headers -vaiheeseen projektin tiedostot lauxlib.h, lua.h, lua.hpp, luaconf.h ja lualib.h ja siirrä ne Public-osaan.
    Copy Headers -vaihe
  6. Lua-kirjasto on nyt valmis käytettäväksi.

Vaihe 2 — Lua-kirjaston käyttäminen toisessa projektissa

Esimerkkinä käytän kokonaan uutta projektia, mutta Luan lisääminen olemassaolevaan projektiin tapahtuu samoin.

  1. Lisää projektiin viittaus Lua-kirjastoprojektiin. Tämä tapahtuu kuten minkä tahansa muunkin olemassaolevan tiedoston lisääminen. Tässä tapauksessa lisättävä tiedosto on kirjastoprojektin .xcodeproj-projektitiedosto.
    Lisätään viittaus kirjastoon
    Lisättävä tiedosto on kirjastoprojektin .xcodeproj-tiedosto
  2. Aseta projektin targetti riippuvaiseksi Lua-kirjaston targetista.
  3. Lisää Lua-kirjasto linkitettäviin kirjastoihin.
    Appin Build Phase konffit
  4. Lisää Header Search Pathsiin polku $(BUILT_PRODUCTS_DIR)/usr/local/include.
    Header Search Paths
  5. Nyt projektissa voidaan käyttää Luaa.

Android

Vaihe 1 — Lua-kirjasto

Lua on kirjoitettu C-kielellä, joten Androidissa tarvitsemme NDK:ta sen kääntämiseen. Kätevintä on luoda siitä NDK-moduli. Android NDK:n ja NDK-modulien käytön opastaminen tässä olisi hieman liian työlästä, ja NDK:ssa on varsin hyvät dokumentaatiot ja esimerkit. Mainittakoon joka tapauksessa, että jos et ole aiemmin käyttänyt ja/tai luonut NDK-moduleita, helpointa on luoda yksi hakemisto jonka alle sijoitat kaikki NDK-modulit. Tämän hakemiston polku pitää olla NDK_MODULE_PATH-ympäristömuuttujassa.

  1. Luo uusi hakemisto NDK-modulihakemiston alle (tämän hakemiston nimi on luotavan modulin nimi).
  2. Kopioi tähän hakemistoon Lua-lähdekoodipaketin src-hakemiston sisältö lukuunottamatta tiedostoja lua.c, luac.c ja Makefile.
  3. Luo tähän hakemistoon seuraavansisältöinen Android.mk-tiedosto:
    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE := liblua521
    LOCAL_CFLAGS := -D"getlocaledecpoint()='.'"
    LOCAL_SRC_FILES := \
    	lapi.c \
    	lauxlib.c \
    	lbaselib.c \
    	lbitlib.c \
    	lcode.c \
    	lcorolib.c \
    	lctype.c \
    	ldblib.c \
    	ldebug.c \
    	ldo.c \
    	ldump.c \
    	lfunc.c \
    	lgc.c \
    	linit.c \
    	liolib.c \
    	llex.c \
    	lmathlib.c \
    	lmem.c \
    	loadlib.c \
    	lobject.c \
    	lopcodes.c \
    	loslib.c \
    	lparser.c \
    	lstate.c \
    	lstring.c \
    	lstrlib.c \
    	ltable.c \
    	ltablib.c \
    	ltm.c \
    	lundump.c \
    	lvm.c \
    	lzio.c
    
    LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
    include $(BUILD_STATIC_LIBRARY)

    Android.mk-tiedoston rivi LOCAL_MODULE := liblua521 määrää luotavan kirjaston nimen — sen voi muokata haluamakseen.

  4. Lua-kirjastomoduli on valmis käytettäväksi

Vaihe 2 — Lua-kirjaston käyttäminen toisessa projektissa

Koska itse Lua-kirjasto on NDK-moduli, pitää sitä myös käyttää NDK:lla käännettävän koodin kautta. Ohjeita NDK:n käyttöön saa muualta. Lua-kirjastomodulia käytetään kuten mitä tahansa NDK-modulia: Projektin Android.mk-tiedostoon lisätään tieto linkitettävästä kirjastosta, ja tiedoston lopussa kutsutaan import-module-makroa. Esimerkkiprojektini Android.mk-tiedosto näyttää seuraavanlaiselta:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE  := luatest
LOCAL_CFLAGS := -std=c99 -Wall -Werror
LOCAL_SRC_FILES := luatest.c
LOCAL_STATIC_LIBRARIES := lua521
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

$(call import-module,lua521)

Kiinnostavat rivit ovat siis LOCAL_STATIC_LIBRARIES := lua521, jossa linkitetään Lua-modulin luoma liblua521-kirjasto, sekä $(call import-module,lua521), jolla sisällytetään projektiin lua521-niminen Lua-moduli.


Vaihe 3 — Lua-koodin ajaminen

Jokaiselle sopivia tyhjentäviä ohjeita on hankala kirjoittaa, joten valaisen asiaa muutamalla yksinkertaisella esimerkillä. Näitä esimerkkejä soveltamalla päässee yllättävän pitkälle. Kaikki esimerkkikoodi on C:tä (ei Objective- tai -++); koodiesimerkkien yhdistäminen muuhun koodiin, oli kyse sitten Android- tai iOS-koodista, jätetään harjoitustehtäväksi.

Yksinkertaisen skriptin ajaminen

Lua-kirjaston funktiot on esitelty kolmessa .h-tiedostossa: lua.h, lualib.h sekä lauxlib.h. Koodiesimerkit on kirjoitettu olettaen että kaikki kolme otsikkotiedostoa on sisällytetty lähdekooditiedostoon. Huom! Nämä ovat nimeomaan C-headereita; jos Luaa käytetään C++-koodissa, sisällytettävä tiedosto on lua.hpp (ei muita).

Seuraava koodinpätkä ajaa lyhyen Lua-skriptin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
static void luaExample() {
     lua_State* lua = luaL_newstate();
     luaL_openlibs(lua);
     const char* script = “print(\”Printing from Lua!\”)\n”;
     luaL_loadstring(lua, script);
     int err = lua_pcall(lua, 0, 0, 0);
     if(err != LUA_OK) {
          const char* errMsg = lua_tostring(lua, -1);
          printf(“Error while calling Lua function: %s”, errMsg);
     }
     lua_close(lua);
}

Rivillä 2 luodaan uusi lua_State-olio. Tämä olio on käytännössä kokonainen Lua-tulkki. Tapauksesta riippuen appissa voisi olla yksi lua_State koko sen elinkaaren ajan, tai siinä voidaan vaikka luoda ja tuhota lua_Stateja tarpeen mukaan. Kutsun palauttama olio on periaatteessa käytettävissä suoraan, mutta käytännössä aina pitää tehdä myös rivillä 3 oleva kutsu, joka lataa Lua-tulkkiin Luan standardikirjastot.

Rivillä 5 ladataan merkkijonosta Lua-funktio. Tässä kohtaa lienee parasta paneutua hieman Lua-tulkin C-APIin. Lua-tulkilla on oma operandipino. Suurin osa C-API-kutsuista käsittelee tätä pinoa. Esimerkiksi Lua-funktion kutsuminen tapahtuu siten, että pinoon työnnetään ensiksi kutsuttava funktio ja sitten funktion kaikki argumentit ensimmäisestä aloittaen. Tämän jälkeen kutsutaan lua_call()- tai lua_pcall()-funktiota. Tämä kutsu poistaa funktion ja argumentit pinosta, kutsuu funktiota ja funktion palattua työntää funktion mahdolliset paluuarvot pinoon ensimmäisestä aloittaen. Kun uusi Lua-tulkki luodaan, pino on tyhjä. Kutsu luaL_loadstring()-funktioon lukee annetusta merkkijonosta Lua-funktion ja työntää sen pinon päällimäiseksi. Tämän kutsun jälkeen siis pinon päällimmäinen (ja ainoa) olio on tuo Lua-funktio (tai virheilmoitus, jos funktion lataaminen epäonnistui).

Rivillä 6 kutsutaan edellisellä rivillä ladattua funktiota. Käytettävä C-funktio on lua_pcall(). Ero lua_call()- ja lua_pcall()-funktioiden välillä on, että lua_pcall() (p = protected) palauttaa virhekoodin jos kutsu epäonnistuu. Funktio lua_call() sen sijaan käyttää Lua-tulkin virheenhallintajärjestelmää, joka lähtökohtaisesti tulostaa virheilmoituksen ja kaataa koko appin. Kannattaa siis lähes poikkeuksetta käyttää lua_pcall():ia. Lua_pcall():lle annettavat argumentit ovat: lua_State, argumenttien määrä, paluuarvojen enimmäismäärä, ja message handlerin indeksi pinossa (0 ei ole validi indeksi, vaan se merkitsee, että message handleria ei ole; pinon indeksit alkavat 1:stä). Tässä tapauksesssa emme välitä yhtään argumenttia funktiollemme, emmekä ole kiinnostuneita sen mahdollisista paluuarvoista.

Rivillä 7 tarkistetaan, onnistuiko funktiokutsu. Jos paluuarvo on LUA_OK (0), funktiokutsu onnistui, ja pinossa on nyt funktion mahdolliset paluuarvot, kuitenkin enintään niin monta arvoa kuin lua_pcall()-kutsussa oli määrätty. Tässä tapauksessa pino on siis tyhjä. Muu paluuarvo merkitsee, että funktiota suoritettaessa tapahtui virhe. Paluuarvo kertoo virheen laadusta jotain (katso tarkempaa tietoa Lua Reference Manualista), mutta tässä tapauksessa tarkastelemme vain virheilmoitusta. Jos kutsu epäonnistuu, pinoon työnnetään (funktion paluuarvojen sijaan) virheilmoitus. Rivillä 8 haemme virheilmoituksen pinosta. Indeksi -1 tarkoittaa pinon päällimmäisintä alkiota (negatiiviset indeksit lasketaan pinon päältä alkaen). Tässä esimerkissä tänne ei koskaan tulla, mutta voit kokeilla virheenhallintaa muokkaamalla Lua-skriptin virheelliseksi.

Rivillä 11 lua_State tuhotaan. Lua_close()-kutsun jälkeen lua_Statea ei voi enää käyttää. Jos appissa luodaan Lua-tulkkeja tarpeen mukaan, on tärkeää muistaa tuhota ne lua_close()-kutsulla kun niitä ei enää tarvita, etteivät ne jää varaamaan muistia.

Argumentit ja paluuarvot

Seuraavassa on esimerkki Lua-funktiosta joka ottaa argumentteja ja palauttaa arvoja:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static void argsAndRetsExample() {
    lua_State* lua = luaL_newstate();
    luaL_openlibs(lua);

    const char* script = "\
local arg1, arg2 = ...\n\
print(\"Lua function was called with \" .. arg1 .. \" and \" .. arg2)\n\
local ret1 = arg1 + arg2\n\
local ret2 = arg1 * arg2\n\
return ret1, ret2\n\
";
    luaL_loadstring(lua, script);

    lua_Number arg1 = 3.7, arg2 = -.1;
    printf("calling Lua function with %f and %f\n", arg1, arg2);
    lua_pushnumber(lua, arg1);
    lua_pushnumber(lua, arg2);
    int err = lua_pcall(lua, 2, 2, 0);
    if(err == LUA_OK) {
        lua_Number ret1 = lua_tonumber(lua, 1);
        lua_Number ret2 = lua_tonumber(lua, 2);
        printf("Lua function returned %f and %f\n", ret1, ret2);
    } else {
        const char* errMsg = lua_tostring(lua, -1);
        printf("Error while calling Lua function: %s\n", errMsg);
    }

    lua_close(lua);
}

Ajettaessa tämä tulostaa seuraavaa:

calling Lua function with 3.700000 and -0.100000
Lua function was called with 3.7 and -0.1
Lua function returned 3.600000 and -0.370000

Tarkastellaan ensin Lua-koodia. LuaL_loadstring()-funktiolla ladattu Lua-funktio on tyypiltään nk. vararg-funktio. Kaikki sen saamat argumentit on luettavissa vararg-lausekkeesta joka on muodoltaan kolme peräkkäistä pistettä (...). Lause local arg1, arg2 = ... rivillä 6 asettaa (paikallisen) muuttujan arg1 arvoksi funktion ensimmäisen argumentin ja muuttujan arg2 arvoksi toisen argumentin. Jos argumentteja ei ole tarpeeksi, muuttujan arvoksi tulee nil. Rivillä 10 Lua-funktio palauttaa arvoja. Lua-funktio voi palauttaa useita arvoja, tässä tapauksessa palautetaan kaksi arvoa.

Rivillä 12 ladataan funktio kuten edellä. Funktion argumentit työnnetään pinoon funktion jälkeen. Tässä kutsumme funktiota kahdella argumentilla. Riveillä 16 ja 17 työnnetään argumentit pinoon. Rivillä 18 suoritetaan kutsu. Argumenttien määrä on 2, ja haluamme enintään 2 paluuarvoa. Kutsun onnistuttua lukemme riveillä 20 ja 21 paluuarvot pinon pohjalta (funktio ja argumentit ovat poistuneet pinosta).

C-funktioiden kutsuminen Lua-koodista

Esimerkki C-funktion kutsumisesta:

1
2
3
4
static int myCFunction(lua_State* lua) {
    int numArgs = lua_gettop(lua);
    printf("myCFunction called with %d args\n", numArgs);
    for(int i = 1; i

Ajettaessa tämä tulostaa seuraavaa:

myCFunction called with 2 args
arg 1: 5
arg 2: bacon
myCFuntion returned 3.141592 and second retvalue

Lua-koodista kutsuttava C-funktio on muuten samankaltainen kuin mikä tahansa C-funktio, mutta se saa argumenttinsa ja palauttaa arvonsa Lua-pinon kautta. Kaikkien Luasta kutsuttavien funktioiden on otettava yksi argumentti tyypiltään lua_State* ja palautettava int. Kun funktiota kutsutaan, annetussa lua_Statessa on pinossa funktion saamat argumentit (ensimmäinen pinon pohjimmaisena jne.), eikä mitään muuta. Argumenttien määrä saadaan tietää pinon korkeudesta. Argumenttien tyypit voidaan myös selvittää. Funktio palauttaa arvoja siten, että kaikki paluuarvot työnnetään pinoon ensimmäisestä aloittaen ja funktion varsinainen paluuarvo (C-mielessä) kertoo Lua-paluuarvojen määrän. Pinoa ei tarvitse erikseen “puhdistaa” ennen palaamista.

Rivillä 2 selvitetään argumenttien määrä. Riveillä 4-6 oleva silmukka tulostaa kaikki funktion saamat argumentit (muutettuaan ne ensin merkkijonomuotoon). Funktio palauttaa kolme arvoa; ne työnnetään järjestyksessä pinoon riveillä 8-10. Rivillä 11 palautetaan paluuarvojen määärä.

Riveillä 18-21 on taulukko, jonka avulla C-funktio liitetään Luaan. Taulukossa on tietueita, joissa ensimmäinen arvo on funktion nimi Luassa ja toinen arvo on pointteri varsinaiseen funktioon. Viimeisenä on on tietue, jossa molemmat arvot ovat NULL. Tässä liitetään vain yksi funktio, mutta samalla voisi liittää useammankin funktion. Rivi 23 lukee em. taulukon ja luo Lua-taulukon, jossa on annetuilla nimillä viittaukset annettuihin C-funktioihin. Tässä tapauksessa siis (Lua-)taulukko, jossa on arvolla "myCFunction" viittaus rivin 1 funktioon. Rivillä 24 tämä taulukko annetaan globaalin muuttujan "myCFunctions"-arvoksi.

Lua-koodissa rivillä 27 kutsutaan "myCFunctions"-taulukon "myCFunction"-jäsentä, ts. edellä määriteltyä C-funktiota. Funktiokutsu tehdään kuten muutkin Lua-funktiokutsut. Tässä funktiolle annetaan 2 argumenttia. Funktion 2 ensimmäistä paluuarvoa asetetaan paikallisten muuttujien a ja b arvoiksi. Kuten tulosteesta huomataan, funktion kolmas paluuarvo jää huomiotta.

Voit kokeilla panna breakpointin myCFunction()-funktioon. Ohjelman pysähtyessä ko. breakpointiin, voit tarkastella kutsupinoa ja huomata, että kutsu on tullut Lua-tulkin sisältä.

Apuohjelmia iOS-designereille

Xcode on asennettu, mitä nyt? Tässä kokoelma apuohjelmia ja muita resursseja, jotka helpottavat iOS-sovellusten kehitystä ja erityisesti käyttöliittymäsuunnittelua.

Templaattikokoelmat

Omnigraffle on suosikkityökaluni rautalankojen laatimiseen. Da5id:n kokoelma on paras tietämäni iOS-templaatti OmniGrafflelle.

Tuotantokelpoista jälkeä tarvittaessa Teehan & Lax:n Photoshop-kokoelma on mainio. Kannattaa malttaa nähdä sen verran vaivaa, että tekee 1x-grafiikat sille tarkoitetun tyylipohjan avulla, niin kuvat pysyvät mahdollisimman tarkkoina.

Liveview (ilmainen)

Liveview näyttää iPhone tai iPadin ruudulla valitun alueen tietokoneen työpöydältä. Tämä on hyödyllistä, jos haluaa tarkistaa, miltä grafiikat näyttävät retina-näytöllä ja että tekstit ovat varmasti luettavissa.

Sovelluksen avulla voi toteuttaa myös yksinkertaisen wizard of oz -demon operoimalla näkymiä tietokoneelta ja antamalla puhelimen käyttäjän käteen.

iExplorer (ilmainen)

 iOS-laitteissa ei ole tiedostojenhallintaa. Joskus on kuitenkin hyödyllistä nähdä, mitä tiedostoja minnekin kertyy. iExplorer on Mac-ohjelma, jolla pääsee selaamaan puhelimensa tiedostorakennetta.

xScope (29,99 $)

xScope tarjoaa vastaavan toiminnallisuuden kuin Liveview. Lisäksi sen avulla voi mittailla eri elementtien kokoja tietokoneen näytöltä ja lisätä apuviivoja. Tuleepa mukana virtuaalinen viivoitinkin. Apuviivoista on apua esim. Xcodessa, jonka omat viivat toimivat vähän kankeasti.

Ostin sovelluksen aikoinaan alennusmyynistä. Täysi hinta on hieman tyyris. Jos tarvitset vain viivoitinta, katso Free Ruler. Se on ilmainen, mutta toimii vähän bugisesti uusilla käyttöjärjestelmillä.

Mittaamisesta puheen ollen: itse käytän jatkuvasti kuvakaappausnäppäinkomentoa eri asioiden koon mittaamiseen. Painamalla komento + shift + 4 voi ottaa kuvakaappauksen halutusta näytön alueesta. Ominaisuutta voi käyttää myös elementtien kokojen selvittämiseen – ja kun painaa esciä ennen kuin päästää hiireen irti, säästää maailman turhalta kuvaruutukaappaukselta.

Hex color picker (ilmainen)

 Kuvakaapparin lisäksi Mac OS X:ssä on mainio pipettityökalu värien poimimiseen. Sen käyttöliittymä on peruja Next-ajoilta, ja pelkään pahoin, että moni ei edes tiedä sen olemassaolosta. Suurennuslasia klikkaamalla voi valita haluamansa värin kuin Photoshopin pipetillä. Ja mikä parasta, tämä toimii myös ohjelmien välillä.

Kätevää on myös siirtää värejä sovellusten välillä raahaamalla värin valitsimen alalaitaan.

Mac OS X:n värivalitsinta voi tehostaa asentamalla Hex color picker -laajennuksen. Sen avulla saa näkyviin suoraan värin heksa-arvon, mikä on hyödyksi myös web-suunnittelussa.

Skitch (ilmainen)

 Vakavamielisempään kuvakaappausten ottamiseen kannattaa hankkia Skitch. Se on yksi ohjelmista, joita ei voi suositella kylliksi. Kuvakaappausten ottamisen lisäksi Skitchillä voi tehdä niihin kätevästi merkintöjä: tekstiä, nuolia, vapaita tuherruksia. Paras puoli on kuvien jakaminen. Jos kuvan raahaa pois Skitchistä, siitä luodaan automaattisesti tiedosto kovalevylle ilman että kuvaa joutuisi itse erikseen tallentamaan.

Jos haluaa vaikkapa lisätä Jiraan kuvakaappauksen löytämästään bugista, ei tarvitse ensin tallentaa tiedostoa ennen kuvan lataamista selaimeen ja sitten etsiä uudelleen avaa-dialogissa, vaan riittää, että raahaa sen selaimeen suoraan Skitchistä. Kuvien lataaminen kaikkien nähtäville skitch.comiin käy sekin yhdellä klikkauksella.

Screenshot Journal (1,59 €)

Tietokoneen lisäksi kuvakaappauksia tulee helposti ottaneeksi valtavat määrät puhelimelta. Valitettavasti Apple on katsonut viisaaksi näyttää kuvakaappaukset samassa läjässä puhelimella otettujen valokuvien kanssa. Apuun tulee Screenshot Journal, joka poimii kuvakaappaukset erilleen kuvavirrasta ja tarjoaa keinoja kuvien hallintaan.

Jos tykkää nahkaefekteistä ja tikkauksista, sovellukseen kannattaa tutustua jo niiden vuoksi. Ovat keskimääräistä hienommat.

Kuvansiirtäjä

Mac OS X:n mukana tuleva kuvansiirtäjä ei ole tainnut saada ainuttakaan päivitystä sitten ensimmäisen Mac OS X -version, ja käyttöliittymä on muutamilta osin rasittava. Jos puhelimelta haluaa noutaa kuvakaappauksia, se on kuitenkin kevyempi vaihtoehto kuin iPhoto. Jos tiedät Kuvansiirtäjää paremman vaihtoehdon, kerro toki kommenteissa!

iOS-Simulator Cropper (ilmainen)

Laitteella kuvakaappauksien ottaminen on kaikesta huolimatta vähän jähmeää. Käyttöohjeita, promokuvia yms. varten on usein kätevämpää  ottaa kuvakaappaukset suoraan Xcoden Simulator-ohjelmasta. iOS-Simulator Cropper ottaa kuvakaappauksen pelkästään simulaattorissa pyörivästä ohjelmasta, nimeää sen halutulla tavalla ja lisää haluttaessa kuvan ympärille iPhone-kuoret kiiltoineen kaikkineen.

Myös status barin poistaminen kuvan ylälaidasta onnistuu helposti, jos vaikka haluaa lisätä kuvakaappaukset App Storeen Applen ohjeiden mukaan ilman palkkia.

iPhone Configuration Utility (ilmainen)

Sovellusten ja provisiointiprofiilien asentaminen onnistuu periaatteessa iTunesilla, mutta sillä on paha tapa synkronoida valtavia datamassoja joka välissä heti kun se löytää koneeseen kytketyn puhelimen. Kätevämpi työkalu on Applen tarjoama iPhone Configuration Utilyty. Sen avulla voi mm. asentaa .IPA-muotoisia sovelluksia laitteelleen, kun sellainen tarve tulee. Saatavilla myös Windowsille.

Quicklook plugin for provisioning profile files (ilmainen)

Provisiointiprofiilien kanssa tappelu on iOS-kehityksessä väistämättä vastaantuleva hupi. Tuskaa vähentää, jos voi kätevästi varmistua, että sovelluksen pitäisi teoriassa asentua halutulle laitteelle. Tämä kätevä lisäpalikka antaa Finderille kyvyn näyttää suoraan, mitkä testilaitteet sovelluksen provisiointiprofiiliin on lisätty.

UDID (ilmainen)

Jotta voisi varmistua, että oma puhelin on tuettu, pitäisi tietää puhelimen UDID-koodi. Sen saa selvitettyä iTunesilla, mutta Apple on tehnyt sen siinä määrin hankalaksi, että on usein helpompaa neuvoa ihmisiä asentamaan puhelimeen sovellus tätä varten. UDID on yksi näistä. Parempiakin saattaa olla.

Picturesque (11,99 €)

Törmäsin Picturesquehun vasta, mutta se näyttää lupaavalta. iPhone-sovelluksia tykätään usein esitellä heijastuksin je perspektiivitempuin kuorrutettuina. Picturesque tekee nämä yleisimmät efektit helposti ja antaa tallentaa kuvat myös läpinäkyvällä taustalla. Kätevä, jos kaipaat vapautusta Photoshopista.