sobota, 6 grudnia 2014

iOS, a co to jest - odcinek drugi: Jeszcze o Objective-C

Dziś kolejna ucieczka do alternatywnej mocno rzeczywistości Objective-C, do czasów wskaźników czy tworów nie spotykanych czasami nigdzie indziej.  Tym razem z elementami na żywo, bo tak zawsze lepiej. W aktualnym XCode mamy w projektach do wyboru także Swift, ale dziś gramy klasykę. Chociaż i Objective-C trochę zdaje się ucywilizowało,  wraz z upływem czasu trzeba pisać w nim coraz mniej, chociaż nadal w wielu przypadkach trzeba się opisać bardzo dużo w stosunku np. do JS i Angulara.  Ale mamy za to wszystko natywne, oryginalnie pochodzące od twórców Mac czy iOS i jest to na swój sposób piękne.  Z drugiej strony wiele rozwiązań  jest jednak ideowo znajomych z innych języków czy platform i mimo innej formy nie czuje się, że to coś naprawdę innego.

Zrobię podsumowanie przez kilka spostrzeżeń

  • Łączenie kodu z deklaratywnym UI - oryginalne dość łączenie strzałkami elementów UI z kodem, jak się coś później namiesza, to całość może się skompilować, a dopiero po uruchomieniu może nam sypnąć wyjątkami o jakichś osieroconych odwołaniach. Nie wydaje się to najmocniejszą stroną XCode.
  • Kontrolery - mimo nazwy wydają się tu być takim code-behind, do nich przecież trafiają referencje do wizualnych elementów.
  • Kilka dodatkowych rzeczy w Objective-C - odpowiedniki metod anonimowych,  rozszerzeń metod (własne rozszerzenia interfejsów w plikach z implementacją), interpretacja pustego obiektu w if podobnie jak w JS
  • Bogactwo klas NS* i ich metod - wszystko z dobrych podstaw mamy, a więc NSString, NSData, NSFormatter, NSArray, NSDictionary, a nawet takie rzeczy jak NSNumber, NSNull czy NSSet. To tylko kropla w morzu klas NS*.  Zapewniają one także m.in operacje na systemie plików, komunikację sieciową, parsowanie JSON.  Jak czegoś nie wiemy, to pewnie będzie to obiekt NS*.  Metody klas *NS są liczne, mają różne warianty.  Przykładowo obsługa napisów jest tak bogata jak w najnowszych współczesnych platformach, miejscami zdaje się nawet je przewyższać.
  • Asynchroniczna obsługa połączenia sieciowego nienajgorsza.  Pewna toporność czy nadmiar formy wyraża się w podawaniu kolejki, tworzenie kilku obiektów do wywołania połączenia czy uzupełnianie czasem wymaganych parametrów nil-ami. Niestety, widziałem w Objective-C  synchroniczne API do operacji mogących potencjalnie trwać dłużej tj. operacje na systemie plików czy właśnie komunikacja sieciowa.
  • Obsługa SQLLite całkiem prosta, kopiowanie pliku bazy z zasobów do folderu aplikacji skojarzył mi się z podobnymi zabiegami w Windows Phone 7.5 z bazą SQL Server
  • Parsowanie JSON do listy obiektów nienajgorsze, całkiem logiczne, choć szału nie ma, nie ma takiej wygodnej automatyzacji jak w .NET czy bezobsługowości jak w Java Script
  • Podpięcie pobranych z sieci danych do TableView to jednak XIX wiek, przynajmniej to, z czym się zaznajomiłem. Ten kod zadziałał także w aplikacji targetowanej pod iOS 8.1, może jednak powstało coś nowocześniejszego?  A może nie jest najgorzej, parę metod do oprogramowania by zasilić pola w komórkach, referencja na TableView, trochę zabawy z łączeniem TableView strzałkami ?  Nieeeeee, w XAML mam kontrolkę, binduję do kolekcji, ewentualnie definiuję łatwo dowolny szablon elementu i tyle, w Angularze jest jeszcze prościej, nie ma nawet często kontrolki i nikt się bawi w rzutowanie i opisywanie obiektów o jakichś typach…
  • Możliwość dość prostego definiowania animacji przybliża nas do świata współczesnego

Moje notatki poniżej.

 

Intro

Najnowszy XCode 6.1.1 (Mac OS X 10.10)

Capture1a

łączenie UI z kodem:  zaznaczamy element + CTRL + przeciągamy z designera na interfejs w kodzie…

Capture3a

Use Automatic Reference Counting

“nib” - plik xib, “NextStep Interface Builder”, NS (od “NextStep”) w Objective-C

XCode:  

  • Można przeciągnąć połączenie od przycisku w designerze do kodu z interfejsem. W pojawiającym się oknie wybieramy Action i wpisujemy nazwę.
  • Można przeciągnąć labelkę z designera do kodu. Zostawiamy outlet i podajemy nazwę.

#import <UIKit/UIKit.h>

@interface XViewController:  UIViewController

@property (retain, nonatomic) IBOutlet UILabel  *xLabel;

-  (IBAction) didTapButton:  (id) sender;

@end

 

@implementation XViewController

@synthesize xLabel;

- (void) viewDidLoad

{

          [super viewDidLoad];

}

-  (IBAction) didTapButton:  (id) sender  {

          xLabel.text = @”xxx”;

          [UIView animateWithDuration: 1.0  animations: ^{

                   self.view.backgroundColor = [UIColor redColor];

                   xLabel.frame = CGRectMake(0, 0, 200, 50);

          }];

}

@end

 

Stringi

%@ - taka podmiana w tekście zadziała dla każdego typu wymuszając jego konwersję do string

NSString *str = @”Kazik”;

str = [str stringByReplacingOccurrencesOfString:  @“K”  withString:  @”G” ];

 

[str stringByReplacingCharactersInRange: NSMakeRange(0,1)  withString: @”G”];

[str uppercaseString];

[str capitalizedString];

 

NSString *str = @”Polski mam paszport na sercu”;

NSRange range = [str rangeOfString: @”paszport”];

if (range.length > 0)  {

}

NSString *subStr = [str substringFromIndex:7];

[str substringWithRange: NSMakeRange(12, 20)];

 

NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: @”Polski mam ( .*)$”

             options: NSRegularExpressionCaseInsensitive  error: nil];

NSArray *matches = [regex matchesInString: str options:0  range: NSMakeRange(0, [str.length])]

for (NSTextCheckingResult *result in matches)  {

            NSLog(@”%@”, [str substringWithRange: [result rangeAtIndex:1]]);

}

 

Liczby

NSNumber *x = [NSNumber numberWithInt:5];

[x intValue]

 

NSInteger  - makro, w zależności od platformy odpowiada int lub long

CGFloat - float lub double

 

Daty

NSDate *today = [NSDate date];

NSDate *tomorrow = [today dateByAddingTimeInterval: 60 * 60 * 24];

 

NSDateFormatter *formatter = [[NSDateFormatter alloc]  init];

[formatter setDateFormat: @”dd/mm/yyyy”];

NSDate *date1 = [formatter dateFromString: @”06/12/2014”];

 

[formatter setDateStyle: NSDateFormatterLongStyle];

[formatter setTimeZone: [NSTimeZone localTimeZone ]];

 

NSLog(@”%@”, [formatter stringFromDate: today];

 

Własne rozszerzenia do zdefiniowanych już interfejsów (coś jak method extensions):

@interface NSDate (InaDiffrentStylee)

+ (NSDate *) tomorrow;

@end

@implementation NSDate (InaDifferentStylee)

+ (NSDate *) tomorrow {

}

@end

Użycie:

[NSDate tomorrow]

 

Pętle i tablice

NSArray *songs = [NSArray arrayWithObjects: @”Młody Junak”, @”Miś”, @”Czerwony autobus”, nil];

for (int i = 0; i < [songs count];  i++)  {

         NSLog(@”%@”, [songs objectAtIndex: i]);

};

for (NSString *song in songs)  {

         int idx = [songs indexOfObject: song];

};

[songs enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx,  BOOL *stop) {         

} ];

 

NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:

                                                           @ “Zuch”,  @“Doktor Yry”, nil ];

for (NSString *key in [dictionary allKeys]) {

         NSLog(@”%@ :   %@”, key, [dictionary objectForKey: key]);

}

 

[NSNull null]

 

NSSet *albums = [NSSet setWithObjects: @”Melassa”, @“Hurra”,  @“Prosto”,  nil];

NSString *album = [albums anyObject];

for (NSString *album in albums) {

}

 

Zapytania i dane

XCode: Build phases

libsqlite3.dylib  - dodajemy bibliotekę do linkowania

#import “FMDatabase.h”   //wrapper Objective-C na sqllite

@implementation XViewController

- (void) viewDidLoad

{

           [super viewDidLoad];

 

           NSString *path = [[NSBundle mainBundle]  pathForResource: @”baza”  ofType: @”db” ];

           FMDatabase *db = [FMDatabase databaseWithPath: path];

           @try {

                    if (![db open])

                            return;

                    [db executeUpdate: @”INSERT into albums(id,name) VALUES(?,?)”  withArgumentsInArray:

                             [NSArray arrayWithObjects: [NSNumber numberWithInt:1],  @”Hurra”, nil ] ];

                    FMResultSet  *result = [db executeQuery: @”select * from albums”]; 

                    while ([result next])  {

                            NSLog(@”%@”, [result stringForColumn: @”name”]);

                    }

           }@catch(NSException *ex) {

                                      

           }@finally {

                   [db close];

           }          

}

Na fizycznym urządzeniu należy dodatkowo w didFinishLaunchingWithOptions (AppDelegate.m), przed innymi operacjami:

NSString *directory = nil;

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

directory = [paths objectAtIndex:0];

NSString *path = [directory stringByAppendingPathComponent:  @”baza.db”];

if (! [[NSFileManager defaultManager] fileExistsAtPath: path]) {

          NSString *sourcePath = [[NSBundle mainBundle]  pathForResource: @”baza”  ofType: @”db”];

          [[NSFileManager defaultManager]  copyItemAtPath: sourcePath  toPath: path  error:nil];

}

+ w widoku zaczytywanie ze ścieżki z katalogu dokumentów

 

Zdalne dane w JSON

@implementation XViewController

- (void) showMessage: (NSString *)message {

         UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @”” message: message

           delegate:nil  cancelButtonTitle: @”OK”  otherButtonTitles:nil];

         [alert show];

}

- (void) viewDidLoad

{

         [super viewDidLoad];

 

          NSURL *url = [NSURL URLWithString: @”http://localhost/XXX/api/albums”];

          NSURLRequest *req = [NSURLRequest  requestWithURL: url ];

 

          NSError *err = nil;

          NSURLResponse *response = nil;

 

          //synchronicznie

         // NSData *data = [NSRLConnection sendSynchronousRequest: req  returningResponse: &response  error: &err ];

         // NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse  *) response;

         // NSLog(@”%d”, [httpResponse statusCode]);

 

          //asynchronicznie

          [NSURLConnection sendAsynchronousRequest: req  queue: [NSOperationQueue mainQueue] 

                       completionHandler: ^(NSURLResponse *response, NSData *data, NSError *err) {

                                  NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse  *) response;

                                  // NSString *str = [[NSString alloc]  initWithData:data  encoding: NSUTF8StringEncoding];

 

                                 NSDictionary *dict = [NSJSONSerialization  JSONObjectWithData:data  options:0  error:nil ];

 

                                  // NSLog(@”%d”, [httpResponse statusCode]);

                                  [self showMessage: [dict objectForKey: @”title”] ];

                        }];

}

@end

Parsowanie JSON do kolekcji obiektów do wyświetlenia w TableView:

@interface MKAlbum: NSObject

  -  (id) initWithDictionary:  (NSDictionary *) dictionary;

@property (nonatomic, copy)  NSString *title;

@end

 

@implementation MKAlbum

@synthesize title = _title;

- (id) initWithDictionary: (NSDictionary *)dictionary  {

         self = [super init];

         if  (self)  {

                  _title = [dictionary objectForKey: @”title”];

         }

         return self;

}

@end

Widok TableView

@interface XViewController:  UIViewController <UITableViewDelegate,  UITableViewDataSource>

@property (nonatomic, strong)  IBOutlet UITableView *tableView;

@end

 

@interface XViewController ()

@property (nonatomic, strong)  NSArray *albums;

@end

 

@implementation XViewController

@synthesize albums = _albums;

@synthesize tableView = _tableView;

- (void) viewDidLoad

{

         [super viewDidLoad];

         [self  loadData];

}

- (NSInteger)tableView:  (UITableView *) tableView   numberOfRowsInSection: (NSInteger) section {

         return self.albums.count;

}

- (UITableViewCell *) tableView:  (UITableView *) tableView  cellForRowAtIndexPath:  (NSIndexPath *) indexPath {

         static NSString *cellIdentifier = @”cell”;

         UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellIdentifier];

         if (!cell)  {   //podobnie jak w Javascript null jest false

                 cell = [[UITableViewCell alloc]  initWithStyle: UITableViewCellStyleSubtitle  reuseIdentifier: cellIdentifier];

         }

         MKAlbum *album = [self.albums objectAtIndex: indexPath.row];

         cell.textLabel.text = album.title;

         cell.detailTextLabel.text = @”ddd”;

         return cell;

}

- (void) loadData  {       

}

- (void) parseResponse:  (NSData *) jsonData {

          NSError *error = nil;

          NSDictionary *dictionary = [NSJSONSerialization JSONObjectWihData: jsonData options:NSJSONReadingAllowFragments  error:&error];

 

          NSMutableArray *albums = [NSMutableArray array];

          for (NSDictionary *albumDictionary in [dictionary objectForKey: @”albums”])  {

                    MKAlbum *album = [[MKAlbum alloc]  initWithDictionary: albumDictionary];

                    [albums  addObject: album];

          }

          self.albums = albums;

          [self.tableView reloadData];

}

@end

Łączymy TableView połączeniami w designerze z View Controller Scene w XCode (datasource i delegate). Przez przeciąganie łączymy też referencję do TableView z interfejsem.

Brak komentarzy: