Komunikacja po stronie aplikacji mobilnej

Podobnie jak w języku Java, do komunikacji pomiędzy klientem i serwerem będziemy potrzebować obiektów reprezentujących strumienie danych. Framework Do Cocoa definiuje nadklasę NSStream, która definiuje wirtualne funkcjonalności takie jak open, close i delegate, oraz klasy NSInputStream i NSOutputStream, służące odpowiednio do czytania i wysyłania danych. Zacznij od napisania deklaracji obiektów NSInputStream i NSOutputStream w pliku nagłówkowym kontrolera widoku.

@interface TANGViewController : UIViewController <NSStreamDelegate>
{
   NSInputStream *inp;
   NSOutputStream *outp;
}

@property (strong, nonatomic) IBOutlet UITextField *display;
- (IBAction)sendPressed:(id)sender;

@end

W pliku implementacyjnym kontrolera widoku dodaj funkcję, która będzie inicjować komunikację.

- (void)initCommunication
{
   CFReadStreamRef readStream;
   CFWriteStreamRef writeStream;

   CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)@"localhost", 7000,
                                                                   &readStream, &writeStream);
   inp = (__bridge NSInputStream *)readStream;
   outp = (__bridge NSOutputStream *)writeStream;!
}

Oczywiście nagłówek tej funkcji musi się także znaleźć w pliku nagłówkowym. Funkcja CFStreamCreatePairWithSocketToHost (bind) wiąże strumień wejściowy i strumień wyjściowy z adresem i numerem portu serwera.

Ponieważ użyjemy klasy kontrolera widoku jako delegate, do metody initCommunication dodaj

[inp setDelegate:self];
[outp setDelegate:self];

oraz dostosuj odpowiednio jej nagłówek klasy kontrolera widoku w pliku nagłówkowym

@interface TANGViewController : UIViewController <NSStreamDelegate>

Ponieważ zarówno strumień wejściowy, jak i strumień wyjściowy muszą być zawsze w stanie przyjąć i wysyłać dane, dodaj run-loop scheduling, który zapewni obsługę strumieni bez blokowania wykonania dalszej części funkcji. Po wykonaniu tej czynności musimy jeszcze na rzecz strumienia wejściowego i strumienia wyjściowego wywołać metodę open.

[inp scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outp scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inp open];
[outp open];

W naszym przypadku chcemy żeby komunikacja była możliwa od momentu załadowania widoku. W tym celu dodaj wywołanie metody initCommunication w metodzie viewDidLoad.

- (void)viewDidLoad
{
   [super viewDidLoad];
   [self initCommunication];
}

Kolejnym krokiem będzie zaimplentowanie funkcji obsługi przycisku Send. Po naciśnięciu tego przycisku chcemy wykonać trzy czynności - przeczytać tekst z pola tekstowego (obiekt display ) i utworzyć odpowiedni obiekt NSString który będzie przechowywał ten tekst, utworzyć obiekt NSData zawierający dane w postaci przygotowanej do wysłania i wreszcie wysłać dane do strumienia (wywołanie metody write na rzecz obiektu outp i przekazanie do tej metody obiektu NSData zawierającego dane oraz informacji o ilości danych do wysłania).

- (IBAction)sendPressed:(id)sender
{
   NSString *response = [NSString stringWithFormat:@"%@",_display.text];

   NSData *data = [[NSData alloc] initWithData:[response
                                                dataUsingEncoding:NSASCIIStringEncoding]];
   [outp write:[data bytes] maxLength:[data length]];
}

Pozostało jeszcze odbieranie danych. O ile wysyłanie komunikatu ma miejsce w przypadku wystąpienia zdarzenia polegającego na naciśnięciu przycisku, odbieranie danych musi być realizowane wtedy kiedy te dane nadejdą. Co za tym idzie, zamiast obsłużyć zdarzenie polegające na naciśnięciu przycisku musimy obsłużyć zdarzenie polegające na pojawieniu się danych do pobrania ze strumienia. Żeby przetestować połączenie, możesz wydrukować do standardowego wyjścia informacje dotyczące rodzaju zdarzenia.

- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)evt
{
   NSLog(@"stream event id %i", evt);
}

W momencie zajścia takiego zdarzenia, funkcja stream zostanie wywołana automatycznie i zostanie do niej przekazany obiekt klasy NSStreamEvent przechowujący informację o tym zdarzeniu. Klasa ta definiuje szereg stałych, odpowiadających szczegółom zdarzenia. Z naszego punktu widzenia najważniejsza będzie wartość NSStreamEventHasBytesAvailable. W przypadku tej wartości, aplikacja powinna odczytać dane ze strumienia i wstawić przesłany z serwera tekst do odpowiedniego komponentu interfejsu graficznego. Odpowiednia definicja metody obsługi tego zdarzenia będzie miała więc postać na przykład taką jak poniżej.

- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)evt
{

   //NSLog(@"stream event id %i", evt);

   switch (evt)
   {

      case NSStreamEventOpenCompleted:

         NSLog(@"Stream opened");
         break;

      case NSStreamEventHasBytesAvailable:

         if (theStream == inp)
         {
            uint8_t buffer[1024];
            int len;

            while ([inp hasBytesAvailable])
            {
               len = [inp read:buffer maxLength:sizeof(buffer)];
               if (len > 0)
               {

                  NSString *msg = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];

                  if (nil != msg)
                  {
                     NSLog(@"server said: %@", msg);
                     _display.text=msg;
                  }
               }
            }
         }
         break;

      case NSStreamEventErrorOccurred:
            NSLog(@"Connection failure");
            break;

      case NSStreamEventEndEncountered:
            break;

      default:
            NSLog(@"Unknown event");

   }

}

Twoja aplikacja pozwala teraz na wymianę komunikatów tekstowych z serwerem. Ponieważ zarówno teksty przeznaczone do wysłania, jak i teksty odebrane z serwera są wypisywane w tym samym polu tekstowym, zaprojektuj bardziej wyrafinowany interfejs graficzny z użyciem dodatkowych komponentów.

Następna część - Implementacja serwera