Kuidas luua jagatud C ++ teeki iOS-i ja Androidi jaoks

SafetyCulture soovib saata asju kiiresti, nii et meie kliendid saaksid kiirelt parandada töökoha ohutust ja kvaliteeti. Selleks otsib meie mobiilne insenerimeeskond uusi võimalusi meie rakenduste arendamiseks, ilma et peaksite sama koodi mitu korda erinevatele platvormidele kirjutama.

Seal on päris palju võimalusi plusside ja miinustega. Selle valimine pole aga lihtne. Nende võimaluste valideerimisel peame silmas järgmisi juhiseid:

  1. Kasutajakogemuse osas pole kompromisse. Sõltumata sellest, millise lahenduse me valime, peaksid kasutajad ikkagi saama kasutada kvaliteetset rakendust, mis tundub loomulik ja sujuv.
  2. Arendaja kogemusest vähe või üldse mitte kompromisse. Kindlasti teeme midagi muud kui loomulik arendamine, kuid me ei taha tunda, et peame lihtsalt koodi jagamiseks uue korstna jaoks suure kohustuse võtma. See peaks olema piisavalt paindlik, et saaksime tulevikus liikuda ükskõik millisesse suunda, ilma et peaksime kogu koodi minema viskama.

Olles veetnud mõned päevad uurimistöös ja aruteludes, vaatasime oma pilku Djinni juurde - Dropboxi jagamiskoodilahendusse, mis võimaldab teil jagada peamist äriloogikat C ++ -s ja omada endiselt kasutajaliidese käsitsi meisterdatud, et saavutada parim kasutajakogemus. Samuti saate C ++ kasutamiseks silla kaudu paljastada mõned platvormile spetsiifilised API-liidesed ja vajadusel tagasi kasutajaliidese juurde. See on tõesti võimas, kuna me saame kirjutada puhta, õhuke kasutajaliidese kihi, kasutades mõlemal platvormil samu API-sid.

Kuidas Djinni häälestada

Seadistamine on lihtne:

Lisage Djinni oma alamprojektiks alamkoodina:

git submodule add https://github.com/dropbox/djinni.git deps / djinni
git alammooduli värskendus --init --rekursiivne

Looge näiteks .djinni liidese kirjeldusfail:

küsimus = rekord {
  id: string;
  pealkiri: string;
  järjekord: i32;
}
leht = rekord {
  id: string;
  pealkiri: string;
  järjekord: i32;
  küsimused: nimekiri ;
}
vorm = kirje {
  id: string;
  nimi: string;
  lehed: loend ;
}
shared_core = liides + c {
  staatiline loomine (): jagatud_core;
  genereeri vorm (lehtede arv_lehti: i32, küsimuste_leht: i32): vorm;
  prefiks_string (sisend: string): string;
}

Liidese kirjeldus on üsna sirgjooneline: meil on kolm kirjet, mis koosnevad ühest pesastatud andmestruktuurist: vorm sisaldab mitut küsimust mitmel lehel.

Jagatud tulemus on selle liidese nimi, mida me C ++ -s rakendame. Samuti saate määratleda liidese, mida saab rakendada Objective-C-s ja Java-s. Näitame teile selle artikli hiljem näidet. Keskendume nüüd ainult sellele lihtsale näitele. väärib märkimist, et määratlesime jagatud tulemuse jaoks staatilise loomismeetodi, millel puuduvad parameetrid. See on mõistlikum, kui peate oma platvormis rakendatud objekte edasi andma, kuna enamikul juhtudel vajate neid.

Järgmine samm on kesta skripti loomine, mis võtab sisendina ülaltoodud .djinni faili ja genereerib meile kogu sildavat koodi. .Sh-fail näeb välja selline:

#! / usr / bin / env bash
### seadistamine
# Djinni IDL-faili asukoht
djinni_file = "demo.djinni"
# C ++ nimeruum loodud src-le
nimeruum = "demo"
# Objektiiv-C klassi nime eesliide loodud src-le
objc_prefix = "SC"
# Java paketi nimi loodud src-le
java_package = "com.safetyculture.demo"
### Skript
# hangi baaskataloog
base_dir = $ (cd "` dirname "0" `" && pwd)
# hankige java kataloog paketi nimest
java_dir = $ (echo $ java_package | tr. /)
# genereeritud src väljundkataloogid
cpp_out = "$ base_dir / generated-src / cpp"
objc_out = "$ base_dir / generated-src / objc"
jni_out = "$ base_dir / generated-src / jni"
java_out = "$ base_dir / generated-src / java / $ java_dir"
# puhas genereeritud src dirs
rm -rf $ cpp_out
rm -rf $ jni_out
rm -rf $ objc_out
rm -rf $ java_out
# käivita käsk djinni
deps / djinni / src / run \
   --java-out $ java_out \
   --java-pack $ java_package \
   - mFooBar - alaline java-väli \
   --cpp-out $ cpp_out \
   --cpp-namespace $ nimeruum \
   - jni-out $ jni_out \
   - elanike-jni-klass NativeFooBar \
   - alaline-jni-fail NativeFooBar \
   --objc-out $ objc_out \
   --objc-type-prefix $ objc_prefix \
   --objcpp-out $ objc_out \
   --idl $ djinni_fail

See skript sõltub Djinni alammoodulist, mille alguses lisasime, seega veenduge, et olete selle juba teinud. Kui olete selle skripti käivitanud, näete uut kataloogi nimega generated-src. See sisaldab nelja alamkataloogi: cpp, objc, jni, java, millel on kogu kood, mida vajate oma iOS-i või Androidi projekti jaoks.

Dinjini loodud koodi kompileerimine iOS-i projekti jaoks:

  1. Looge kataloog nimega platvormid / ios ja looge selle sees oma iOS-i projekt.
  2. Lisage oma iOS-i projekti failid deps / djinni / support-lib / objc, generated-src / objc ja generated-src / cpp. ÄRGE kasutage kopeerimise võimalust.
  3. Nimetage iOS-i projekt main.m ümber main.mm; see muudab selle Objective-C ++ failiks.

Kõik tehtud, peaksite saama oma projekti vigadeta üles ehitada.

Alustame nüüd mõne koodi kirjutamist. Looge kataloog jagatud ja looge kaks faili nimega shared_core_impl.hpp ja shared_core_impl.cpp. Sellele kohta kirjutame oma jagatud koodi C ++. Ja ärge unustage lisada need kaks faili ka oma iOS-i projekti.

Päisefail shared_core_impl.hpp näeb välja selline:

#include "shared_core.hpp"
nimeruumi demo {
  klass SharedCoreImpl: avalik demo :: SharedCore {
  avalik:
    SharedCoreImpl ();
    Vorm genereerimavorm (int32_tarv_lehtede arv, int32_t küsimused_lehel);
    std :: string prefix_string (const std: string ja sisend);
  };
}

Mis see tähendab, loome uue klassi SharedCoreImpl, mis rakendab liidese SharedCore. Ma ei saada siia täielikku rakenduskoodi, kuid loogika on üsna lihtne. Meetod gener_form võtab 2 täisarvu parameetrit, genereerib ja tagastab vormi objekti koos parameetrite kaudu pakutavate lehtede ja küsimuste arvuga. Prefiks_string paneb lihtsalt sisestusstringi ette tere ja tagastab uue stringi.

COS-i koodi on iOS-i projektis tõesti lihtne kasutada; siin on lühike näide:

#import "ViewController.h"
#import "SCSharedCore.h"
@ liidese ViewController ()
@omadus (mitteatomaatiline, tugev) SCSharedCore * coreAPI;
@ realiseerimine ViewController
- (tühine) viewDidLoad {
    [super viewDidLoad];
    _coreAPI = [SCSharedCore loomine];
}
- (tühine) generatorForm () {
    // genereerige vorm sisaldab 5000000 küsimust
    SCForm * vorm = [_coreAPI generatorForm: 500 küsimusiPerPage: 1000];
}
@lõpp

See selleks! Pole liiga raske, eks? Seadistusprotsess on Androidi puhul pisut erinev, peate importima nii genereeritud jni kui ka Java-koodi. Kuid kontseptsioon on sarnane: importige automaatselt genereeritud päised ja lisage projektile C ++ rakenduskood. Seetõttu peame need C ++ -failid paigutama ühisesse kataloogi väljaspool iOS-i või Androidi projekti.

Arhitektuur

See on arhitektuuriskeem, mille lõpuks me Djinni kasutame.

Laenasime kontseptsiooni Reduxilt ja Fluxilt, kus andmed voolavad ainult ühes suunas. Vaade saadab toimingud ja renderdab seejärel uue oleku. Iga ekraani või kasutajaliidese voo jaoks on konkreetsed haldurid kirjutatud C ++ keeles. Vaate käivitamisel vastutab vaatekontroller halduri objekti kiirendamise ja seadistamise eest. Vaatel on vaja muretseda ainult selle pärast, millal toiminguid saata ja kuidas olekut renderdada. Kogu äriloogika on halduris rakendatud ja kasutajaliidese kihi eest peidetud, mis aitab meil kirjutada tõesti puhta kasutajaliidese koodi.

See teeb ka ühikatsete kirjutamise tõesti lihtsaks; kuna kasutajaliidese kiht renderdab ja saadab toiminguid nüüd eraldi, saame luua mudelihalduri, mis toidab renderdusloogikat vaid erinevate olekute kuvamiseks ja testimiseks. Saame luua ka teise mudelihalduri, et kontrollida, kas kasutaja UI-ga suheldes saadetakse õigeid toiminguid.

Platvormi koostoime

Mõnel juhul võib kasutajaliides olla vajalik platvormi API-dega suhtlemiseks. Vältime platvormi API-de kutsumist UI-kihist otse, selle asemel paljastame natiivselt rakendatud API-d haldurile ja pinna tagasi UI-kihile. Seda lähenemist kutsume U-kujuks.

Siin on hea näide platvormi liidese kohta:

ui_platform_support = liides + o + j {
  post_task_in_background_thread (ülesanne: ülesanne);
  post_task_in_main_thread (ülesanne: ülesanne);
}

Siin kasutame ära iga platvormi olemasolevaid samaaegsuse / keermestamise API-sid. Allolev diagramm näitab, kuidas kasutajaliides töötab platvormi API-dega:

Võrgustiku loomine

Nagu on näidatud esimeses arhitektuuriskeemis, vastutab püsiv kiht, kuhu andmed suunata. Oleme seisukohal, et taustaprogrammi API-liides on sarnane kettaruumiga, mis on meile lihtsalt veel üks allikas andmete lugemiseks ja kirjutamiseks. Kasutame taustaprogrammiga suhtlemiseks oma võrguprotokollina gRPC. GRPC kasutamisel mobiilirakenduste jaoks on palju eeliseid. Selle kohta on meil tulevikus veel üks blogipostitus. Kuid kui te ei soovi kõiki neid HTTP-kõnesid kirjutada, kasutades C ++, on endiselt võimalik oma platvormivõrgu loomine püsivale kihile üle anda.

Mida me oleme õppinud

C ++ pole nii hirmutav

Kui teil pole varasemaid C ++-alaseid kogemusi nagu meie, soovitaksin teil vähemalt proovida. See keel on viimastel aastatel palju edasi arenenud, pakkudes palju tänapäevaseid funktsioone. Nutikas osuti sarnaneb iOS-is ARC-iga, nii et te ei pea mälu eraldamist käsitsi vabastama, kuid tsükli säilitamise vältimiseks peate kasutama nõrka viidet.

Olge teadlik jõudlusest pea kohal

IOS-i kohal on silla kohal vähe või mitte ühtegi, sest C ++ andmestruktuur pole Objective-C (Objective-C ++) võõras. Kuid Djinni, kes kasutab JNI-d kapoti all, mõjutab Androidi jõudlust märkimisväärselt, seega on C ++ ja Java vahelise andmestruktuuri sorteerimine ja sorteerimise tühistamine kulukad toimingud. Kui teie rakendus peab saatma silla kaudu suuri struktureeritud andmeid ja kiirus on oluline, peate uurima võimalusi, nagu näiteks platvormideülese binaarse andmevormingu (nt Flatbuffers) saatmine struktureeritud andmete asemel.

Seadistamiseks on vaja täiendavaid samme

Kui te pole tuttav Makefile'iga oma tugiteekide loomiseks, investeerite tõenäoliselt esialgu lisaaega, et kõik paika saada. Kuna osa teie koodist kirjutatakse C ++ keeles, peavad C ++ kasutamiseks mõned teie kolmanda osapoole teegid olema õigesti seadistatud. Meie puhul ehitame gRPC, ProtoBuf ja Flatbuffereid erinevates arhitektuurides staatilisteks raamatukogudeks, ühendame need lipo abil iOS-i jaoks rasvakahenditeks ja lisame need meie rakendusse. Kuid kõik need asjad on ühekordne investeering. Kui olete kõik valmis ja käima saanud, on ebatõenäoline, et kavatsete sama aega ikka ja jälle veeta.

See selleks! Me jätkame selle uue arhitektuuri kasutuselevõttu mobiilimeeskonnas. Jagame pidevalt oma kogemusi.

Kas teil on kogemusi platvormiüleste mobiilirakenduste loomisega? Liituge meie insenerimeeskonnaga ja aidake meil luua tooteid, mis mõjutavad elu.