Category Archives: Objective-C

Il linguaggio Objective-C

C++, Objective-C, Smalltalk: “estendere” una classe

byte_1981_08

Qualche anno fa (sicuramente meno di 10) scoprii l’Objective-C e mi piacque molto. Ci sono almeno due cose da considerare: ① non sono mai stato un amante del C++ (potendo ho sempre provato ad evitarlo; ho iniziato ad usarlo per me stesso giusto con il C++11); ② per non ricordo quale motivo mi ero imbattuto già nello Smalltalk.

Continua a leggere

Annunci

Compilati VS interpretati passo 2 (The compiler strikes back)

Ciao!!!

Spero che i fans di guerre stellari non me ne vogliano per la citazione del titolo 🙂 Dopo settimane di ritardo e alcune discussioni con Ari (come chi è? … non avete letto i commenti al primo articolo?) sono tornato con ulteriori test e risultati da proporvi.
In realtà questa seconda parte doveva essere diversa, ma viste le giuste osservazioni fatte da Ari ho deciso di cambiarlo per dare ascolto a chi questo blog lo legge e quindi da un senso e uno stimolo a chi qui scrive esclusivamente per passione.
Torniamo a noi! Ari mi ha fatto giustamente notare che le routines di python che leggono i file sono scritte in C. Quindi in realtà non è python che legge velocemente i file, ma il codice C dietro le quinte che fa il lavoro sporco.
Ne è seguita una piccola discussione sulla valenza da dare al mio precedente articolo e su quali sarebbero stati i test giusti da fare… e di seguito troverete i risultati che ho ottenuto 🙂
Ho giocato anche a fare la “Cassandra” di turno (non ditemi che non sapete chi è Cassandra 😦 ) per cui vi riporto le mie sibilline parole:

“Per cui nella seconda parte dell’articolo faremo i test che suggerisci, ma ho il sospetto che vedremo dei risultati interessanti, almeno in base a quella che è stata la mia esperienza con Java.”

Ma andiamo a cominciare…
Il test eseguito è molto semplice. Ho fatto calcolare il fattoriale da 1 a 10.000 con una bella procedura non ricorsiva e poi ho fatto un “ottimo” sort con una procedura non efficente (ditemi quanti di voi saprebbero scrivere a memoria un algoritmo di quicksort 🙂 )

Vediamo i sorgenti e i risultati ottenuti per python, C, Objective C e Java.

#!/usr/bin/env python2.6
# -*- coding: utf-8 -*-

import time
import array

MAXLOOP = 10000
nArray = array.array('d', range(MAXLOOP))

t0 = time.time()
print "Tempo inizio caricamento array: ", time.strftime("%H:%M:%S")
for i in range(MAXLOOP):
  f = float(1)

  for a in range(i):
    f = f * a

  nArray[i] = i + f

t1 = time.time()
print "Tempo fine caricamento array: ", time.strftime("%H:%M:%S")
print "Tempo di caricamento dei dati: ", t1 - t0, " secondi"

t0 = time.time()
print "Tempo inizio ordinamento: ", time.strftime("%H:%M:%S")

# ordinamento in maniera decrescente
for i in range(MAXLOOP):
  dMax = nArray[i];

  for j in range (i + 1, MAXLOOP):
    if nArray[j] > dMax:
      dTemp = nArray[j];
      nArray[j] = dMax;
      nArray[i] = dTemp;
      dMax = dTemp;

t1 = time.time()
print "Tempo fine ordinamento: ", time.strftime("%H:%M:%S")
print "Tempo di caricamento dei dati: ", t1 - t0, " secondi"

I tempi sono stati sulla mia macchina freebsd sono stati:

– 11,06 s per il caricamento dell’array con calcolo del fattoriale
– 42,07 s per il sort

Vediamo il C come si comporta:

#include <stdio.h>
#include <time.h>

const int MAXLOOP = 10000;

int main (int argc, const char * argv[])
{
  time_t startTime;
  time_t endTime;
  struct tm * timeinfo;

  long double nArray[MAXLOOP];
  int i;

  time(&startTime);
  timeinfo = localtime(&startTime);
  printf("Tempo inizio caricamento array: %s", asctime(timeinfo));

  for (i = 0; i < MAXLOOP; i++)
  {
    // Calcolo del fattoriale
    long double f = 1;
    int a;

    for(a = 1; a <= i; a++)
    {
      f *= a;
    }

    nArray[i] = i * f;
  }

  time(&endTime);
  timeinfo = localtime(&endTime);
  printf("Tempo fine caricamento array: %s", asctime(timeinfo));

  time(&startTime);
  timeinfo = localtime(&startTime);
  printf("Tempo inizio ordinamento: %s", asctime(timeinfo));
  // ordinamento in maniera decrescente
  for (i = 0; i < MAXLOOP; i++)
  {
    double dMax = nArray[i];
    int j;
    for (j = i + 1; j < MAXLOOP; j++)     
    {       
      if (nArray[j] > dMax)
      {
        double dTemp = nArray[j];
        nArray[j] = dMax;
        nArray[i] = dTemp;
        dMax = dTemp;
      }
    }
  }
  time(&endTime);
  timeinfo = localtime(&endTime);
  printf("Tempo fine ordinamento: %s", asctime(timeinfo));

  return 0;
}

Tempi:

– 4 s per il caricamento dell’array con calcolo del fattoriale
– meno di 1 s per il sort

Adesso è il turno di objective C.

#import <Foundation/Foundation.h>

const int MAXLOOP = 10000;

int main (int argc, const char * argv[])
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  NSDate *startTime;
  NSDate *endTime;

  NSMutableArray *nArray = [[NSMutableArray alloc] initWithCapacity: MAXLOOP];
  int i;

  startTime = [NSDate date];
  NSLog(@"Tempo inizio caricamento array: %@", startTime);

  for (i = 0; i < MAXLOOP; i++)
  {
    // Calcolo del fattoriale
    long double f = 1;
    int a;

    for(a = 1; a <= i; a++)
    {
      f *= a;
    }

    [nArray addObject: [NSNumber numberWithDouble: i * f]];
  }

  endTime = [NSDate date];
  NSLog(@"Tempo fine caricamento array: %@", endTime);

  startTime = [NSDate date];
  NSLog(@"Tempo inizio sort: %@", startTime);

  // ordinamento in maniera decrescente
  for (i = 0; i < MAXLOOP; i++)
  {
    double dMax = [[nArray objectAtIndex: i] doubleValue];
    int j;
    for (j = i + 1; j < MAXLOOP; j++)     
    {       
      if ([[nArray objectAtIndex: j] doubleValue] > dMax)
      {
        double dTemp = [[nArray objectAtIndex: j] doubleValue];
        [nArray replaceObjectAtIndex: j withObject: [NSNumber numberWithDouble: dMax]];
        [nArray replaceObjectAtIndex: i withObject: [NSNumber numberWithDouble: dTemp]];
        dMax = dTemp;
      }
    }
  }
  endTime = [NSDate date];
  NSLog(@"Tempo fine sort: %@", endTime);

  [pool drain];
  return 0;

}

Tempi:

– 4 s per il caricamento dell’array con calcolo del fattoriale
– 2,13 s per il sort

Per finire passiamo a Java.

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SortTest {

    static final int MAXLOOP = 10000;

    public static void main(String[] args) {
        // TODO code application logic here
        int i;
        double[] nArray = new double[MAXLOOP];

        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date startTime = new Date();
        System.out.printf("Tempo inizio caricamento array: %s\n", dateFormat.format(startTime));
        long start = System.nanoTime();

        for (i = 0; i < MAXLOOP; i++)
        {
            // Calcolo del fattoriale
            double f = 1;
            int a;

            for(a = 1; a <= i; a++)
            {
                f *= a;
            }

            nArray[i] = i * f;
        }
        long end = System.nanoTime();

        Date endTime = new Date();
        System.out.printf("Tempo fine caricamento array: %s\n", dateFormat.format(endTime));

        System.out.printf("Tempo caricamento array: %d ms\n", (end - start) / 1000000);

        startTime = new Date();
        System.out.printf("Tempo inizio ordinamento: %s\n", dateFormat.format(startTime));
        start = System.nanoTime();

        // ordinamento in maniera decrescente
        for (i = 0; i < MAXLOOP; i++)
        {
            double dMax = nArray[i];
            int j;
            for (j = i + 1; j < MAXLOOP; j++)             
            {                 
                if (nArray[j] > dMax)
                {
                    double dTemp = nArray[j];
                    nArray[j] = dMax;
                    nArray[i] = dTemp;
                    dMax = dTemp;
                }
            }
        }
        end = System.nanoTime();

        endTime = new Date();
        System.out.printf("Tempo fine ordinamento: %s\n", dateFormat.format(endTime));

        System.out.printf("Tempo ordinamento array: %d ms\n", (end - start) / 1000000);
    }
}

Tempi:

– 248 ms per il caricamento dell’array con calcolo del fattoriale
– 278 ms per il sort

Quindi riassumiamo il tutto in un formato più leggibile.

Caricamento
+ fattoriale

Sort

Python

11,06 s

42,07 s

C

4 s

< 1 s

Objective-C

4 s

2,13 s

Java

0,25 s

0,28 s

Per cui come diceva Ari, il compilato è più efficente. Possiamo notare la differenza di tempi nel sort tra objective-c e C ma l’array usato in objective-c è un NSMutableArray che vuole oggetti al suo interno, per cui è presente un overhead dovuto alla creazione dell’oggetto… in soldoni non stiamo spostando dei semplici long double come in C.
Infine la sorpresa “cassandriana” di Java… che è un bytecode che viene interpretato da una virtual machine.
Anche python genera del bytecode… evidentemente meno efficente di quello java.

Ma torniamo a noi con le conclusioni di questa seconda parte. Comincio mettendo le mani avanti. Io sono un programamtore “stagionato” o “antico” questo vuol dire che mi piace il codice che funziona velocemente e non mi ammazza un processore con 6 core e GHz di potenza se lancio 2 istanze di un programma. Anche se preferisco il C/C++ o Delphi, non mi faccio trascinare dalle mode e ho capito oramai da anni che il linguaggio “buono” è quello che ti permette di staccare il cliente dalla propria giugulare prima di finire dissanguati 🙂 per cui se il mio software deve leggere file, fare sort e usare le regular expression allora python mi permette con poche righe di codice di fare tutto ciò e con poche modifiche di spostarmi su varie piattaforme.
Se invece il problema da risolvere è in ambiente real time o prevede calcoli a go-go o pochissima RAM come in un sistema embedded allora la scelta giusta è il C (provate a far girare python su una centralina di un’auto 😛 ). Se volete lavorare su MAC e scrivere PDF e vedere le vostre applicazioni volare passate ad objective-C.
La scusa della battaglia compilati VS interpretati era solo un pretesto per scrivere del codice e sfatare preconcetti e come ho detto in più occasioni non voleva essere un test scientifico che avrebbe richiesto uno rigore diverso.

L’importante è “divertirsi” ed imparare. Senza diventare gli hooligans dei chip. Ciao e alla prossima

Compilati VS interpretati passo 1.

Comincio facendo una premessa. Quando si deve sviluppare un progetto informatico non esiste il linguaggio del momento, o preferito, o “più bello”. Esiste un gruppo di lavoro con le sue competenze ed una problematica da risolvere. Le considerazioni che seguono sono basate su dei test che non mirano ad essere esaustivi. Lo scopo di quest’analisi è quello di porre domande e ottenere delle risposte. Per cui chiunque non concordasse con i risultati e fosse in grado di aggiungere nuovi tasselli al mosaico che si sta andando a costruire è ben accetto. Quindi… vado ad incominciare.

Era il 1991 mi venne chiesto di velocizzare un algoritmo che durava su un intel 386 la bellezza di 24 ore. Il software era scritto in quick basic che non brillava certo in performance. La soluzione fu di linkare la routine di calcolo scritta in C al programma quick basic. Magia!!! La procedura di calcolo durava solo più un ora.
Sempre in quegli anni era comune trovare sulle riviste test relativi alle performance dei linguaggi, così si trovavano i tempi di compilazione, quelli di esecuzione di sort, accesso ai dischi in maniera sequenziale e random. Tutte cose utilissime, perché i dischi erano da 20/40 Mb e i processori da 20 MHz e spesso privi di coprocessore matematico non c’era tutto il lusso che abbiamo adesso 🙂

Comunque la regola d’oro era che i programmi compilati sono più veloci di quelli interpretati… adesso però quasi tutti i nuovi linguaggi di programmazione hanno la caratteristica di essere o totalmente interpretati o in pseudo codice. La motivazione è che i processori moderni sopperiscono con la loro velocità al divario di performance tra un programma compilato ed uno interpretato. Ovviamente gli interpretati hanno alcuni vantaggi come la tipizzazione a runtime che possono essere utili nella creazione di framework, inoltre in ambienti WEB dove il collo di bottiglia è la banda passante alcuni tempi diventano trascurabili. Così giusto per curiosità ho deciso di fare un test, assolutamente non esaustivo, ma che mi ha lasciato a bocca aperta (leggete e scoprirete perché).

La mia idea era di vedere i tempi di accesso al disco e di sort in memoria di una “grossa” mole di dati. Mi sono creato un file di 1,29GB (mi sembrava molto grosso) pieno di stringhe ordinate in modo decrescente e ho scritto un programma di lettura e sort in maniera crescente misurando i tempi di esecuzione. Ovviamente sono partito da un bel programma objective-c dicendomi: “è compilato, ha le sue fondamenta nel C, il framework cocoa l’ha scritto Apple, sara una vera bomba” 🙂 vi anticipo che mi è esplosa tra le mani 🙂

Il codice è molto semplice:


#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
 {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSLog(@"Inizio lettura");
    NSString *stringa = [NSString stringWithContentsOfFile:@"NoteTrim.txt" encoding:NSISOLatin1StringEncoding error:nil];
    NSArray *array = [stringa componentsSeparatedByString:@"\n"];
    NSLog(@"Fine lettura");

    NSLog(@"Inizio sort");
    array = [array sortedArrayUsingSelector:@selector(compare:)];
    NSLog(@"Fine sort");

    [pool drain];
    return 0;
}

Ed ecco i risultati:
– objective-c Lettura 32 s
– objective-c Sort 1,5 s

I tempi non mi dicevano un granché per cui ho accettato i 32 s per il caricamento e l’1,5 s per il sort  fiducioso della legge: compilato più veloce dell’interpretato.

Per l’interpretato sono partito usando python, sicuro di un aiuto da parte di Juhan per colmare le mie lacune (l’aiuto c’è stato, oramai vale l’ugualianza Juhan = F1). Il risultato è il sorgente che segue:

#!/usr/bin/ env python
# -*- coding: utf-8 -*-

import time

try:
  # lettura del file
  t0 = time.time()
  print "Tempo inizio: ", time.strftime("%H:%M:%S")
  file_input = open("NoteTrim.txt", "ru")
  lines = file_input.readlines()
  file_input.close()
  t1 = time.time()
  print "Tempo fine: ", time.strftime("%H:%M:%S")
  print "Tempo di caricamento dei dati: ", t1 - t0, "secondi"

  # ordinamento del file
  t0 = time.time()
  print "Tempo inizio: ", time.strftime("%H:%M:%S")
  lines.sort()
  print "Elementi caricati: ", len(lines)
  t1 = time.time()
  print "Tempo fine: ", time.strftime("%H:%M:%S")
  print "Tempo di ordinamento dei dati: ", t1 - t0, "secondi"
except(IOError):
  print "Errore nell'elaborazione del file NoteTrim.txt"
  quit()

Devo dire che ho trovato le istruzioni necessarie in un batter d’occhio e che su internet si trova veramente di tutto riferito a python e questo è sicuramente un vantaggio non indifferente.
Lancio il tutto e… mi cade la mascella per terra! 3,6 s per leggere il file e 0,38 s per il sort. Giuro che mi è caduto in testa un macigno 🙂 e meno male che ho la testa dura!!!

I più accorti mi faranno notare che la lettura di un file dipende molto dal sistema operativo e che quindi avrei risultati diversi a parità di computer ma con S.O. differenti e che il sort di stringhe dipende da come vengono trattate le stringhe sempre a livello di S.O. e di linguaggio, ma il divario è sconcertante!
Per cui decido di approfondire la questione (ci ho impiegato i mesi trascorsi dall’ultimo articolo), molto per curiosità e molto perché la differenza tra compilati e interpretati era un concetto dei tempi delle scuole.
Mi accorgo che la versione di python è la 2.7.1 per cui mi chiedo “chissà che performance ci saranno con la 3.0 così ingenuamente lancio lo stesso sorgente… e chiamo Juhan 🙂 il sorgente che prima funzionava era diventato pieno di errori.

Per uno strano motivo non interpretava correttamente i tab e così ho dovuto ribattere tutto il sorgente. Poi ho scoperto che è cambiata la sintassi della print 😦 adesso ha bisogno delle parentesi e per ultimo nella open dovevo specificare l’encoding dei caratteri altrimenti il tutto non parte. Per cui il nuovo sorgente é diventato:

#!/usr/local/bin/ env pythonw3
# -*- coding: iso-8859-1 -*-

import time

try:
  # lettura del file
  t0 = time.time()
  print("Tempo inizio: ", time.strftime("%H:%M:%S"))
  file_input = open("NoteTrim.txt", "r", encoding='cp1252')
  lines = file_input.readlines()
  file_input.close()
  t1 = time.time()
  print("Tempo fine: ", time.strftime("%H:%M:%S"))
  print("Tempo di caricamento dei dati: ", t1 - t0, "secondi")

  # ordinamento del file
  t0 = time.time()
  print("Tempo inizio: ", time.strftime("%H:%M:%S"))
  lines.sort()
  print("Elementi caricati: ", len(lines))
  t1 = time.time()
  print("Tempo fine: ", time.strftime("%H:%M:%S"))
  print("Tempo di ordinamento dei dati: ", t1 - t0, "secondi")
except(IOError):
  print("Errore nell'elaborazione del file NoteTrim.txt")
  quit()

e i tempi diventano 11,78 s per il caricamento del file e 1,65 per il sort. Le performance erano calate esponenzialmente quasi 4 volte più lento in lettura e nel sort.

Spinto dalla curiosità provo il tutto sotto winzot (non è un errore ma l’incrocio tra windows e lo ZOT dei fumetti di BC) in una macchina virtuale… Primo e unico problema con la 2.7.2 devo modificare la open. Non accetta l’opzione “ru” e devo sostituirla con la sola “r” e i tempi diventano:

– Python 2.7 Lettura 36,66 s
– Python 2.7 Sort 0,88 s

Con la versione 3 non devo fare modifiche e ottengo i seguenti tempi:

– Python 3.2 Lettura 60,70 s
– Python 3.2 Sort 2,58 s

Qui comincio a pormi alcune domande:

– Come mai a parità di S.O. la nuova versione di python è più lenta
– Perché non è stata mantenuta la piena compatibilità tra la 2.7 e la 3.2 visto che in un sorgente di 20 righe ne ho dovute modificare ben 9
– Perché ho delle diversità a livello di S.O. nella scrittura del codice. I parametri della open non vengono accettati a parità di versione.

Sono quei tarli che ti assillano che potrebbero essere lasciati tranquillamente in disparte. Sicuramente i programmi python non sono pieni di print come il mio esempio e sicuramente ci sono state innovazioni in altre parti del linguaggio che non sono ricadute nel mio test… e io non sono un esperto python, ne voglio vantarmi di esserelo. Però bisogna porsi delle domande e non si può lasciare correre tutto, poi arrivando da anni di Turbo Pascal prima e Delphi dopo è la prima volta che ricado in un porting problematico (dove cioè devo modificare un sorgente che non funziona più)… ma questi magari saranno spunti per un nuovo post.

Preso dalla curiosità e invidioso di Juhan che scrive articoli più interessanti dei miei provo anche il newlisp. Quante righe di codice? 4 contando anche la shell.

#!/usr/bin/newlisp

(println "Lettura: " (time (set ‘data (parse (read-file "NoteTrim.txt") "\n" 0))) " ms")
(println "Sort: " (time (sort data)) " ms")

(exit)

Risultati? Impressionanti anche questi. Lettura 4,66 s e sort in 1,7 secondi. Decido di provare la cosa anche sotto windows ma purtroppo durante il caricamento del file ottengo un errore di memoria… indagherò e vi farò sapere.

Quindi riassumiamo:

OSX Lion

Windows 7 64 Bit

Lettura (s)

Sort (s)

Lettura (s)

Sort (s)

Objective-C

32

1,5

Python 2.7

3,6

0,38

36,66

0,88

Python 3.2

11,78

1,65

60,70

2,58

NewLisp

4,66

1,7

A questo punto direi di saltare alle conclusioni di questa prima parte.

– I linguaggi interpretati sono veloci e certe verità del passato vanno riviste.
– I nuovi linguaggi sono compatti e pieni di librerie notevoli, per non dire incredibili (in newlisp solo 4 righe di codice con dei risultati di prima categoria).

A questo punto passiamo tutti a python o al lisp? Ovviamente no. Non tutti i software leggono e sortano file, abbiamo calcoli in virgola mobile, interfacce grafiche, applicazioni real time. Quindi i linguaggi compilati devono essere usati solo per applicazioni specifiche e sono meno performanti? Anche questo non è vero. L’applicativo objective-C si basava sulle librerie ad alto livello, ma la base di objective-C è il C che è tante cose, ma lento proprio no. Per cui è il momento di andare a mettere le mani sotto il cofano e vedere se è possibile migliorare… ma questo lo vedremo nella prossima puntata.

Approfondimento sulle classi Objective-C

Definizione delle classi in Objective-C

La volta scorsa abbiamo definito alcune classi in Objective-C per fare un esempio pratico di oggetti, ereditarietà e polimorfismo. Mi hanno subito beccato con le mani nella nutella 😦 perché ahime i ragni non sono insetti… ma l’ultima volta che ho studiato biologia e affini era tanto tempo fa 🙂 più di 24 anni fa… Comunque capisco perché tutti fanno gli esempi con le figure geometriche!
La volta scorsa non sono sceso volontariamente nel dettaglio della sintassi per non appesantire uno scritto già fin troppo pieno di sorgenti.
Però adesso è il momento di mantenere la promessa fatta a fine articolo, perché ogni promessa è debito!

Allora avevamo la nostra classe Insetto, la ricordate? Il ragno l’ho calpestato per cui non c’è più!

//
// Insetto.h
//

# import <Cocoa/Cocoa.h>

@interface Insetto : NSObject {

NSString *nome;
}

- (NSString*) nome;
- (NSString*) cheInsettoSono;
- (NSString*) evoluzione;

@end

Si vedono subito delle differenze rispetto ad altri linguaggi come Java o il C++. I nomi dei metodi sono al di fuori delle graffe che delimitano l’oggetto. In realtà quello che conta è la @end. Nelle graffe vengono descritti i dati membro. Per cui se noi volessimo definire 2 oggetti nello stesso file basta fare così:

@interface Insetto : NSObject {

NSString *nome;
}

- (NSString*) nome;
- (NSString*) cheInsettoSono;
- (NSString*) evoluzione;

@end

@interface InsettoSauro : Insetto {

NSString *soprannome;
}

- (NSString*) soprannome;
@end

Il simbolo – davanti al metodo implica che il metodo è di istanza, un eventuale + indicherebbe che il metodo è di classe. Tanto per fare un paragone i metodi preceduti da un + sono come i metodi static di java e C++.
Vediamo anche questo con un po’ di codice.

//
// Insetto.m
//

#import "Insetto.h"

@implementation Insetto

- (NSString*) nome
{
return (@"Papà insetto. Che domande? :)");
}

- (NSString*) cheInsettoSono
{
return (@"Sono l'antenato di tutti gli insetti!");
}

- (NSString*) evoluzione;
{
return (@"io avevo ben 7 zampe, ma con l'evoluzione ne ho persa una. Meno male! Avanzavo sempre una scarpa");
}

@end
#import "Insetto.h"

@implementation Insetto

- (NSString*) nome
{
return (@"Papà insetto. Che domande?");
}

- (NSString*) cheInsettoSono
{
return (@"Sono l'antenato di tutti gli insetti!");
}

- (NSString*) evoluzione;
{
return (@"io avevo ben 7 zampe, ma con l'evoluzione ne ho persa una. Meno male! Avanzavo sempre una scarpa");
}

@end

@implementation InsettoSauro

- (id) init
{
self = [super init];

if (self != nil)
{
numOfInsettoSauro++;
}
return self;
}

- (NSString*) nome
{
return (@"Insetto");
}

- (NSString*) soprannome
{
return (@"Sauro");
}

+ (int) contaInsettoSauro
{
return numOfInsettoSauro;
}

@end

Adesso scriviamo il main e vediamo cosa capita 🙂

#import <Foundation/Foundation.h>
#import "Insetto.h"

int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

Insetto *insetto;
InsettoSauro *insettoSauro, *insettoSauro2;

insetto = [Insetto new];
insettoSauro = [InsettoSauro new];

NSLog(@"Questo è l'insetto: %@ ", [insetto nome]);
NSLog(@"Questo è l'insettosauro: %@ ", [insettoSauro soprannome]);
NSLog(@"Questa è la chiamata al metodo di classe: %d ", [InsettoSauro contaInsettoSauro]);
insettoSauro2 = [InsettoSauro new];
NSLog(@"Questa è la chiamata al metodo di classe: %d ", [InsettoSauro contaInsettoSauro]);

[pool drain];
return 0;
}

Come si vede per chiamare contaInsettoSauro usiamo il nome della classe InsettoSauro e non il nome della sua istanza.

Altra cosa che avevamo notato è lo strano modo di chiamare le funzioni membro, cioè la sintassi tra parentesi quadre, in questo caso mi ha messo sulla buona strada il mio amico Juhan che mi ha ricordato Smalltalk. Per cui le chiamate ai metodi di un oggetto sono in realtà l’invio di un messaggio (giù le mani dai cellulari non sono gli sms) ad un particolare oggetto. Nel caso:

NSLog(@"Questo è l'insetto: %@ ", [insetto nome]);

Stiamo mandando il messaggio nome all’oggetto insetto. Per cui l’oggetto insetto eseguirà il metodo richiamato.    In Smalltalk leggeremo la sintassi come [ricevente messaggio] in objective-c sarà [oggetto metodo].
Se invece volessimo richiamare una funzione in stile C scriveremmo:

#import <Foundation/Foundation.h>

NSString* funzione()
{
return (@"Io sono una funzione");
};

int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

NSLog(@"Questa è una chiamata a funzione: %@ ", funzione());

[pool drain];
return 0;
}

Per concludere vediamo un’altra particolarità della definizione dei metodi e della loro chiamata. Aggiungiamo il metodo alla classe insetto che imposta il nome e il cognome dell’insetto. Volevo scrivere il metodo che gli assegnava una moglie insetto, ma poi ho avuto pietà di loro… ve lo immaginate quanto durerebbe il giro al negozio di scarpe con moglie insetto avendo a disposizione 6 piedi… i più sfigati sono i ragni che di zampe ne hanno 8… (e non sono insetti) ma torniamo a noi. In Insetto.h aggiungiamo la definizione.


- (void) setNomeCompleto: (NSString*) extNome cognome: (NSString*) extCognome;
- (NSString*) nomeCompleto;

Ho aggiunto quindi i metodi per impostare e visualizzare il nome completo, composto da un nome e un cognome. Scriviamo ora il codice di implementazione che andrà aggiunto in Insetto.m


- (void) setNomeCompleto: (NSString*) extNome cognome: (NSString*) extCognome
{
nome = extNome;
cognome = extCognome;
}

- (NSString*) nomeCompleto
{
return [[nome stringByAppendingString: @" "] stringByAppendingString: cognome];
}

Nel main mettiamo la chiamata al tutto:

insettoSauro2 = [InsettoSauro new];

[insettoSauro2 setNomeCompleto:@"Insetto" cognome: @"Sauro"];
NSLog(@"Stampiamo nome e cognome: %@ ", [insettoSauro2 nomeCompleto]);

Il risultato sarà qualche cosa di simile:

[Switching to process 313 thread 0x0]
2011-08-09 21:23:24.461 TP_2_OC[313:903] Stampiamo nome e cognome: Insetto Sauro
Program ended with exit code: 0

Per adesso è tutto, spero vi siate divertiti anche più di quello che mi sono divertito io a studiare un nuovo linguaggio. La prossima volta faremo qualche cosa di più appariscente e pratico in fondo abbiamo un mac a disposizione 🙂

Ciao, ciao.

Programmazione ad oggetti in Objective-C

Ai miei tempi i computer erano i PC IBM e soprattutto i compatibili. Il laboratorio d’informatica era pieno di M24, il mondo era pieno di beep e strani rumori che uscivano dai floppy da 5 pollici e un quarto. Da qualche parte si sapevano esistessero degli strani sistemi chiamata Cray e qualcuno nominava l’Archimedes con il suo mitico processore RISC.
Sempre a quei tempi i linguaggi di programmazione seri (per PC) erano fatti da Borland, il Turbo Pascal era talmente diffuso che il compilatore Pascal di Microsoft generava librerie compatibili con quello Borland. Poi gli anni sono passati e le cose sono cambiate come tutti le conosciamo.
Gia allora (più o meno il 1990) si parlava di programmazione orientata agli oggetti. Adesso quando si spiegano le peculiarità della programmazione ad oggetti si fanno esempi con le figure geometriche, le auto.
Nel manuale ufficiale del Turbo Pascal la OOP Guide  si parlava di mele e di entmologi. Il tutto per spiegare l’incapsulazione, l’ereditarietà e il polimorfismo. Insomma tra un ragno e l’altro ti spiegavano che cos’erano questi strani “oggetti”.

Tutti parlano di incapsulazione, ereditarietà e polimorfismo, facciamo un ripasso del loro significato, copiando quello che dicevano sul manuale del Turbo Pascal.

  • Incapsulamento: è la combinazione dei dati con le procedure e funzioni necessari a gestirli in modo da creare un nuovo tipo di dato: un oggetto;
  • Ereditarietà: definire un oggetto e poi usarlo per creare una gerarchia di oggetti discendenti, ogni oggetto discendente eredita l’accesso a tutti i dati e i metodi degli oggetti antenati;
  • Polimorfismo: dare ad un’azione un nome condiviso in tutta la gerarchia di oggetti, ogni oggetto della gerarchia implementa questa azione nel modo più appropriato.

Vediamo ora come creare un oggetto in Objective-C. Dovremo definire un file d’interfaccia ed uno d’implementazione per ogni oggetto. Nel file header (*.h) verranno definiti i dati e i metodi. Nel file body (*.m) invece sarà scritto il codice dei metodi.
Facciamo subito un bel esempio, che ho provato e sviluppato su Mac con Xcode… però dovrebbe funzionare allo stesso modo anche su altre piattaforme.

Questa è la classe Insetto:

//
// Insetto.h
//

# import <Cocoa/Cocoa.h>

@interface Insetto : NSObject {

    NSString *nome;
}

- (NSString*) nome;
- (NSString*) cheInsettoSono;
- (NSString*) evoluzione;

@end

 

//
// Insetto.m
//

#import "Insetto.h"

@implementation Insetto

- (NSString*) nome
{
  return (@"Papà insetto. Che domande?");
}

- (NSString*) cheInsettoSono
{
  return (@"Sono l'antenato di tutti gli insetti!");
}

- (NSString*) evoluzione;
{
  return (@"io avevo ben 9 zampe, ma con l'evoluzione ne ho persa una. Meno male! Avanzavo sempre una scarpa");
}

@end

Questa è la classe Mosca:

//
// Mosca.h
//

@interface Mosca : Insetto {

}

@end

 

//
// Mosca.m
//

#import "Insetto.h"
#import "Mosca.h"

@implementation Mosca

- (NSString*) nome
{
  return (@"Salve sono la Mosca, l'insetto non la città! Non sbagliatevi.");
}

- (NSString*) cheInsettoSono
{
  return (@"Sono una mosca. Non dite a schelob dove sono.");
}

@end

Questa è la classe Ragno:

//
// Ragno.h
//

@interface Ragno : Insetto {

}

@end

 

//
// Ragno.m
//

#import "Insetto.h"
#import "Ragno.h"

@implementation Ragno

- (NSString*) nome
{
  return (@"Sono Shelob (e non ditemi che qualcuno non ha guardato la trilogia del signore degli anelli)");
}

- (NSString*) cheInsettoSono
{
  return (@"Sono un ragno. Qualcuno ha visto la mia mosca?");
}

@end

E questo è il main:

//
// main.m
//

#import <Foundation/Foundation.h>
#import "Insetto.h"
#import "Mosca.h"
#import "Ragno.h"

int main (int argc, const char * argv[])
{

  // Non ragioniam di lor, ma guarda e passa... ah non sono Dante? Non me ne ero accorto...
  // ... ma il suggerimento è valido, questa riga serve per la gestione della memoria...
  // ... ne parliamo un'altra volta!
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  Insetto *insetto;
  Mosca *mosca;
  Ragno *ragno;

  Insetto *insetti[3];

  insetto = [Insetto new];
  mosca = [Mosca new];
  ragno = [Ragno new];

  // Qua facciamo le magie!!

  // Questo è il polimorfismo
  NSLog(@"Come ti chiami? %@", [insetto nome]);
  NSLog(@"Cosa sei? %@", [insetto cheInsettoSono]);
  NSLog(@"Come ti chiami? %@", [mosca nome]);
  NSLog(@"Cosa sei? %@", [mosca cheInsettoSono]);
  NSLog(@"Come ti chiami? %@", [ragno nome]);
  NSLog(@"Cosa sei? %@", [ragno cheInsettoSono]);

  // Questo è ancora il polimorfismo
  NSLog(@"Come ti chiami? %@", [((Insetto *) ragno) nome]);
  NSLog(@"Cosa sei? %@", [((Insetto *) ragno) cheInsettoSono]);

  // Polimorfismo all'ennesima potenza
  insetti[0] = insetto;
  insetti[1] = mosca;
  insetti[2] = ragno;

  for (int i = 0; i < 3; i++)
  {
    NSLog(@"Come ti chiami? %@", [insetti[i] nome]);
    NSLog(@"Cosa sei? %@", [insetti[i] cheInsettoSono]);
  }

  // Questa è l'ereditarietà
  NSLog(@"Evoluzione. %@", [ragno evoluzione]);

  // Anche questa riga è per la memoria, si sempre li all'inferno di Dante
  [pool drain];
  return 0;
}

Se eseguite il programma, avrete più o meno questo risultato.

Dunque… è tutto qua, mi spiace ma devo scappare… ok rimettete a posto le pietre, vi spiego cosa è successo!

Le due righe con i riferimenti danteschi, lasciamole per il momento da parte, servono per gestire la memoria nelle applicazioni mac. Se vi chiedete come mai ho fatto riferimento all’inferno… beh passate voi una settimana a cercare un memory leak in 20.000 righe di codice per scoprire che l’errore che si scatenava nella tua procedura era nel codice scritto da un altro 🙂

Si incomincia con la definizione delle variabili, i puntatori ai nostri oggetti.

Insetto *insetto;
Mosca *mosca;
Ragno *ragno;

Si alloca la memoria…

insetto = [Insetto new];
mosca = [Mosca new];
ragno = [Ragno new];

…e finalmente vediamo i risultati.

NSLog(@"Come ti chiami? %@", [insetto nome]);
NSLog(@"Cosa sei? %@", [insetto cheInsettoSono]);
NSLog(@"Come ti chiami? %@", [mosca nome]);
NSLog(@"Cosa sei? %@", [mosca cheInsettoSono]);
NSLog(@"Come ti chiami? %@", [ragno nome]);
NSLog(@"Cosa sei? %@", [ragno cheInsettoSono]);

Vedete che chiamo le stesse procedure per i 3 oggetti e tutti mi rispondono correttamente chiamando le giuste implementazioni. Vi ricordate che l’interfaccia è stata definita una volta sola in Insetto.h? Voi mi direte che è facile il corpo della procedura era definito in ogni classe… ed è per questo che ho scritto le righe sotto:

// Questo è ancora il polimorfismo
NSLog(@"Come ti chiami? %@", [((Insetto *) ragno) nome]);
NSLog(@"Cosa sei? %@", [((Insetto *) ragno) cheInsettoSono]);

In questo caso ho fatto un cast, quindi ho convertito l’oggetto di tipo ragno in un suo oggetto antenato, tipo “Insetto” (si c’era solo quello, non facciamo i pignoli) ma le funzioni mome e cheInsettoSono mi hanno sempre restituito le implementazioni della classe Ragno. Questo è possibile perché un oggetto è “coscente” del suo tipo.
Infatti subito dopo faccio questo:

// Polimorfismo all'ennesima potenza
insetti[0] = insetto;
insetti[1] = mosca;
insetti[2] = ragno;

for (int i = 0; i < 3; i++)
{

NSLog(@"Come ti chiami? %@", [insetti[i] nome]);
NSLog(@"Cosa sei? %@", [insetti[i] cheInsettoSono]);
}

Cioè carico in un array che contiene dati di tipo Insetto sia puntatori a Insetto che a Mosca che a Ragno e quando vado a stampare le loro proprietà grazie al polimorfismo ottengo sempre le chiamate alle funzioni giuste. Un possibile utilizzo? Immaginate di dover fare un CAD e di tenere in collezioni tutti gli oggetti che fanno parte di uno stesso layer, basterà definire la collezione come un contenitore di un oggetto antenato e riempirlo con i vostri dati sicuri che al richiamo dell’ipotetica funzione “Disegnami” verrà disegnato il giusto oggetto.

Per ultimo vedete un esempio di ereditarietà.

// Questa è l'ereditarietà
NSLog(@"Evoluzione. %@", [ragno evoluzione]);

Ho chiamato la funzione evoluzione che era definita solo nell’oggetto Insetto da un oggetto di tipo Ragno che non l’aveva reimplementata.

Si ci sono un sacco di segni strani e di chiamate bizzarre in questi file Objective-C ma la prossima volta li spiego tutti. Giurin giuretta (tanto nessuno ha visto che avevo le dita incrociate) 8)

Objective-C

Oggi, inizio del nuovo anno (cinese) un post che dischiude nuovi orizzonti*

Un nuovo collaboratore per il blog, Walter, un giovane webmaster vecchio di esperienza, che lavora anche con il Mac e FreeBSD come vedrete. E, a differenza di me, è una persona seria: usa vi.

Leggere l’articolo di Juhan su Smalltalk ha acceso in me la scintilla di scrivere qualche cosa che parlasse di Apple (26 anni fa mi veniva regalato il mio primo computer, che ho ancora, un mitico Apple ][ compatibile). Mi sono quindi detto perché non parlare di Objective-C?

Devo ammettere che fino al 2002 non sapevo neanche che esistesse, ma oramai dopo il successo di iPhone & Co. in libreria si trova di tutto sia in italiano che in inglese.
Leggendo su Wikipedia si scopre che:

L’Objective-C fu creato principalmente da Brad Cox e Tom Love all’inizio degli anni ’80 alla Stepstone.

Objective-C è perfettamente compatibile con il C standard con estensioni per la gestione degli oggetti. Sì lo so oramai quasi tutti i linguaggi di programmazione sono orientati agli oggetti (Smalltalk-80, Ruby, Python, C++, Java, Delphi…) e molti di loro sono linguaggi riflessivi.
A volte ci si chiede perché se ne inventi uno nuovo ogni mese 🙂

Comunque per installare Objective-c su una macchina FreeBSD basta dare il comando:

[root@enkibsd ~]# pkg_add -r gnustep

Questo comando installerà non solo il compilatore ma anche il framework gnustep

Per il nostro esempio useremo l’ambiente a riga di comando, ma invece del classico “Hello world!” alla K&R (The C Programming Language di Kernighan and Ritchie) faremo il nuovo “Hello okpanico!” 😉

Il sorgente è molto semplice ed è il seguente:

#import

int main (int argc, const char * argv[])

{
        NSLog (@"Hello okpanico!");
        return 0;
}

Salviamo il nostro file con il nome hello.m. Prima di passare alla compilazione diamo una lettura veloce del codice.

Prima differenza rilevante con il C è che per includere gli header file si usa la parola riservata #import che rispetto alla clausola #include del c standard non richiede l’utilizzo di blocchi:

#ifndef ...
#define ...
...
...
#endif

per evitare l’inclusione multipla degli header.

Altra particolarità rispetto al C classico è il segno di @ dinnanzi alla stringa “Hello okpanico!”. La @ indica che stiamo passando alla funzione NSlog un oggetto e non una stringa.

Passiamo finalmente alla compilazione. Sempre su una macchina FreeBSD diamo il comando:

gcc -o hello.app hello.m -I /usr/local/GNUstep/System/Library/Headers \
-L /usr/local/GNUstep/System/Library/Libraries/ -lobjc -lgnustep-base \
-fconstant-string-class=NSConstantString

Sono molto importanti i parametri -I e -L che indicano al compilatore dove trovare i file di include e le librerie ovviamente una distribuzione Linux li installerà in altre cartelle.

Il parametro -fconstant-string-class=NSConstantString indica che il compilatore dovrà usare le stringhe di tipo NSConstantString e non quelle di default cioè le NXConstantString (senza questo parametro in compilazione si otterrà l’errore: cannot find interface declaration for ‘NXConstantString’).

Lanciamo il nostro applicativo con:

./hello.app

E godiamoci il risultato:

2011-01-31 19:50:43.807 hello.app[83800] Hello okpanico!


Si è tutto qua… ma è dalle piccole cose che nasce sempre tutto quanto 8)

________________
* No non è come sembra Vostro Onore! È vero che in fondo al post compaio come autore anche se sono il primo a dire che non è farina del mio sacco. Posso spiegare tutto: ho dei testimoni. Davvero. Lo giuro sulla testa dei figli del Nano Pelato e del Maestro Apicella.