{"id":8957,"date":"2012-05-22T12:24:11","date_gmt":"2012-05-22T10:24:11","guid":{"rendered":"http:\/\/www.devapp.it\/wordpress\/?p=8957"},"modified":"2012-05-24T11:55:13","modified_gmt":"2012-05-24T09:55:13","slug":"t109-key-value-observing-costruiamo-un-semplice-lettore-multimediale","status":"publish","type":"post","link":"https:\/\/www.devapp.it\/wordpress\/t109-key-value-observing-costruiamo-un-semplice-lettore-multimediale\/","title":{"rendered":"T#109 &#8211; Key-Value Observing: Costruiamo un semplice lettore multimediale"},"content":{"rendered":"<p>Se si potesse stilare una classifica dei meccanismi meno conosciuti di Cocoa, il <strong>Key-Value Observing pattern<\/strong> o <strong>KVO<\/strong> sicuramente ricoprirebbe uno dei primi posti. KVO \u00e8 un meccanismo molto utile che permette agli oggetti di essere notificati rispetto a cambiamenti di specifiche propriet\u00e0 di altri oggetti. Come \u00e8 facile immaginare, questo genere di meccanismi si prestano molto bene al pattern di programmazione <strong>Model-View-Controller<\/strong>. In quest&#8217;articolo cercheremo di capire meglio come usare questa tecnica affiancando la teoria ad un esempio pratico che ne dimostri appieno le funzionalit\u00e0.<!--more--><\/p>\n<h4>Cosa vogliamo realizzare.<\/h4>\n<p>Visto che una delle cose che mi viene chiesta pi\u00f9 spesso \u00e8 la costruzione di un lettore MP3 personalizzato, per l&#8217;esempio di quest&#8217;articolo ho pensato di sfruttare KVO per costruire un semplice e snello lettore di stream audio per web radio.<br \/>\nIn letteratura e sul web esistono tanti esempi di lettori streaming per iOS. Il pi\u00f9 famoso di essi \u00e8 sicuramente quello descritto da <strong>Matt Gallagher<\/strong> nel suo articolo &#8220;<em>Streaming and<br \/>\nplaying an MP3 stream<\/em>&#8221; [<a href=\"http:\/\/cocoawithlove.com\/2008\/09\/streaming-and-playinglive- mp3-stream.html\" target=\"_blank\">http:\/\/cocoawithlove.com\/2008\/09\/streaming-and-playinglive-<br \/>\nmp3-stream.html<\/a>]. Il metodo descritto da Matt nel suo articolo si basa su un complesso meccanismo di download e caching gestito da <strong>CFSocket<\/strong>. Sebbene il risultato ottenuto sia egregio, l&#8217;inclusione o l&#8217;adattamento del progetto a particolari casi d&#8217;uso pu\u00f2 risultare molto laborioso per via della natura stessa dell&#8217;approccio. Nel nostro articolo, invece, sfrutteremo un oggetto nativo di Cocoa: <strong>AVPlayer<\/strong>. Il risultato sar\u00e0 una classe facilmente trasportabile che potremo includere in qualunque progetto, sia esso destinato al mondo iOS o al mondo Mac. Per trarre il massimo profitto da quest&#8217;approccio, faremo uso del meccanismo KVO unitamente alla costruzione di un protocollo adhoc.<\/p>\n<p>Prima di andare avanti, proviamo a semplificare con una grafica quello che vogliamo ottenere.<\/p>\n<p><center><\/p>\n<div id=\"attachment_8964\" style=\"width: 560px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-01.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8964\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-01.jpg\" alt=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-01\" title=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-01\" width=\"550\" height=\"589\" class=\"size-full wp-image-8964\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-01.jpg 550w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-01-280x300.jpg 280w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/a><\/p>\n<p id=\"caption-attachment-8964\" class=\"wp-caption-text\">Fig. 1 &#8211; Diagramma di massima del nostro oggetto<\/p>\n<\/div>\n<p><\/center><\/p>\n<p><center><\/p>\n<div id=\"attachment_8965\" style=\"width: 560px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-02.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8965\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-02.jpg\" alt=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-02\" title=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-02\" width=\"550\" height=\"230\" class=\"size-full wp-image-8965\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-02.jpg 550w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-02-300x125.jpg 300w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/a><\/p>\n<p id=\"caption-attachment-8965\" class=\"wp-caption-text\">Fig.2 &#8211; Spaccato del Model e sue interazioni con il mondo esterno<\/p>\n<\/div>\n<p><\/center><\/p>\n<p>Il nostro oggetto, CPStreamPlayer, dovr\u00e0 essere in grado di notificare i cambiamento di stato (titolo del brano in riproduzione, buffer in esaurimento, buffer riempito, etc.) all&#8217;interfaccia grafica o a qualunque altro oggetto ne faccia richiesta. Per ottenere questo scopo utilizzeremo un pattern di programmazione molto interessante esposto dal framework Cocoa, denominato <strong>Key-Value Coding<\/strong>. <\/p>\n<p><strong>KVC<\/strong> \u00e8 un meccanismo che permette di impostare o leggere un valore di variabile usando il nome stesso. Il nome \u00e8 una semplice stringa e ci riferiremo ad essa con il termine chiave o key. Ogni oggetto che eredita da NSObject pu\u00f2 fare accesso alle sue propriet\u00e0 attraverso coppie chiave-valore.<\/p>\n<p>Cosa significa? <\/p>\n<p>Immaginate di avere un oggetto che espone una propriet\u00e0 denominata &#8216;title&#8217;. Per poter configurare a dovere questa propriet\u00e0 \u00e8 possibile usare il tradizionale costrutto:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n[myObject setTitle: @\"qualcosa\"];\r\n<\/pre>\n<p>oppure, seguendo la notazione dotted introdotta da Objective-C 2.0:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\nmyObject.title = @\"qualcosa\";\r\n<\/pre>\n<p>esiste un&#8217;altro metodo che sfrutta il concetto di Key-Value Coding:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n[myObject setValue:@\"qualcosa\" forKey:@\"title\"];\r\n<\/pre>\n<p>Allo stesso modo, se vorremo recuperare il valore della propriet\u00e0 title potremo usare i costrutti noti di Objective-C, oppure fare ricorso al meccanismo KVC:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\nNSString *myTitle = [myObject valueForKey:@\"title\"];\r\n<\/pre>\n<p>A questo punto dell&#8217;articolo potrebbe venire naturale una domanda: perch\u00e9 stiamo introducendo un nuovo modo per impostare e recuperare il valore delle propriet\u00e0? Non bastano i metodi gi\u00e0 noti?<\/p>\n<p>Per poter rispondere a questa domanda \u00e8 necessario introdurre un altro concetto che snoccioleremo nel corso dell&#8217;articolo: Il meccanismo di Key-Value Observing o KVO. Ogni oggetto che configura le sue propriet\u00e0 attraverso il meccanismo sopra esposto di KVC, pu\u00f2 fare uso del meccanismo di KVO. Questo meccanismo permette di osservare gli eventuali cambiamenti delle propriet\u00e0 di un oggetto da un&#8217;altro oggetto, nell&#8217;ipotesi di alterare i valori delle propriet\u00e0 attraverso il meccanismo di KVC.<\/p>\n<h4>NSNotificationCenter e Key-Value Observing<\/h4>\n<p>Alcuni di voi avranno gi\u00e0 avuto modo di dialogare ed interagire con le notifiche. Questa soluzione coinvolge il centro di notifica o <strong>NSNotificationCenter<\/strong>. In questo approccio, il nostro oggetto CPStreamPlayer invier\u00e0 dei messaggi al centro di notifica che, a sua volta, girer\u00e0 queste informazioni a tutti gli oggetti che si sono registrati per riceverle.<\/p>\n<p>Ritornando al nostro esempio, sfruttando questo approccio, le entit\u00e0 coinvolte saranno tre: NSNotificationCenter, CPStreamPlayer e gli eventuali Observer.<\/p>\n<p>Nell&#8217;approccio con KVO, invece, quello che faremo sar\u00e0 aggiungere un observer sull&#8217;oggetto player, in<br \/>\nmaniera da essere notificati direttamente in base a quello che succede dietro le quinte dell&#8217;oggetto CPStreamPlayer. In quest&#8217;ultimo caso, le entit\u00e0 coinvolte saranno due: CPStreamPlayer ed Observer.<\/p>\n<p><center><\/p>\n<div id=\"attachment_8966\" style=\"width: 560px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-03.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8966\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-03.jpg\" alt=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-03\" title=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-03\" width=\"550\" height=\"427\" class=\"size-full wp-image-8966\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-03.jpg 550w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-03-300x232.jpg 300w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/a><\/p>\n<p id=\"caption-attachment-8966\" class=\"wp-caption-text\">Fig.3 &#8211; Pattern di notifica basato su NSNotificationCenter.<\/p>\n<\/div>\n<p><\/center><\/p>\n<p><center><\/p>\n<div id=\"attachment_8967\" style=\"width: 560px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-04.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8967\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-04.jpg\" alt=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-04\" title=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-04\" width=\"550\" height=\"428\" class=\"size-full wp-image-8967\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-04.jpg 550w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-04-300x233.jpg 300w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/a><\/p>\n<p id=\"caption-attachment-8967\" class=\"wp-caption-text\">Fig.4- Pattern di notifica basato su Key-Value Observing.<\/p>\n<\/div>\n<p><\/center><\/p>\n<p>Sebbene entrambi i pattern di programmazione sembrano essere simili, il loro funzionamento a basso livello \u00e8 molto diverso. Come regola generale di programmazione il pattern con Notification Center dovrebbe essere impiegato solo quando sussiste l&#8217;esigenza di notificare uno stesso messaggio a pi\u00f9 osservatori assolutamente slegati tra loro. Questi messaggi dovrebbero essere di tipo allarme e tutto l&#8217;overhead di trasferire informazioni aggiuntive sar\u00e0 a carico nostro, attraverso il concetto di NSNotification e dizionario di userInfo (vedremo questo pattern di programmazione in dettaglio in un altro articolo). <\/p>\n<p>Inoltre, utilizzando un&#8217;entit\u00e0 esterna (NSNotificationCenter) le comunicazioni tra gli oggetti potrebbero essere ritardate, causando dei piccoli glitch sulla nostra user interface. La comunicazione KVO, invece, non coinvolge altri attori ed \u00e8 diretta tra i due oggetti che la instaurano. Diremo che due oggetti che fanno KVO tra loro sono legati o &#8220;bindati&#8221;.<\/p>\n<p>Un ultima considerazione relativamente all&#8217;oggetto che cercheremo di costruire nel nostro articolo. Per rendere il nostro esempio il pi\u00f9 portabile possibile, costruiremo un oggetto wrapper a pi\u00f9 alto livello, che si preoccuper\u00e0 per noi di dialogare con AVPlayer.<\/p>\n<p>Nella grafica sotto \u00e8 mostrata una semplificazione di quello che sar\u00e0 la classe CPStreamPlayer insieme al meccanismo di Key-Value Observing:<\/p>\n<p><center><\/p>\n<div id=\"attachment_8968\" style=\"width: 560px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-05.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8968\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-05.jpg\" alt=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-05\" title=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-05\" width=\"550\" height=\"225\" class=\"size-full wp-image-8968\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-05.jpg 550w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-05-300x122.jpg 300w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/a><\/p>\n<p id=\"caption-attachment-8968\" class=\"wp-caption-text\">Fig.5 &#8211; Semplificazione classe CPStreamPlayer e suoi oggetti interni.<\/p>\n<\/div>\n<p><\/center><\/p>\n<p><center><\/p>\n<div id=\"attachment_8969\" style=\"width: 560px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-06.jpg\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8969\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-06.jpg\" alt=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-06\" title=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-06\" width=\"550\" height=\"404\" class=\"size-full wp-image-8969\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-06.jpg 550w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-06-300x220.jpg 300w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/a><\/p>\n<p id=\"caption-attachment-8969\" class=\"wp-caption-text\">Fig. 6 &#8211; KVO tra la classe CPStreamPlayer e le sue istanze interne.<\/p>\n<\/div>\n<p><\/center><\/p>\n<h4>KVO in pratica<\/h4>\n<p>Ci sono tre passi da effettuare per configurare correttamente un osservatore.<\/p>\n<ol>\n<li>Prima di tutto, bisogna accertarsi di essere davanti ad uno scenario in cui l&#8217;approccio KVO risulti utile. Ad esempio, un oggetto che ha bisogno di essere notificato quando viene effettuata una modifica ad una propriet\u00e0 di un altro oggetto. Nel nostro caso, l&#8217;oggetto CPStreamPlayer dovr\u00e0 essere a conoscenza dei cambiamenti che avvengono sullo stream attualmente in corso gestito dagli oggetti AVPlayer ed AVPlayerItem.<\/li>\n<li>L&#8217;oggetto CPStreamPlayer deve registrarsi come osservatore sugli oggetti che intende monitorare, inviando un messaggio <strong>addObserver:forKeyPath:options:context:<\/strong><br \/>\nIl messaggio addObserver:forKeyPath:options:context: stabilisce una interconnessione tra gli oggetti che specifichiamo. La connessione non viene specificata tra le due classi, bens\u00ec tra le istanza degli oggetti a runtime. Tornando al nostro esempio, l&#8217;oggetto wrapper CPStreamPlayer sar\u00e0 osservatore dei suoi oggetti privati in maniera da poterne monitorare lo stato.<\/li>\n<li>Per rispondere alle notifiche di cambiamento, l&#8217;osservatore deve implementare il metodo <strong>observeValueForKeyPath:ofObject:change:context:<\/strong><br \/>\nQuesto metodo definisce come l&#8217;osservatore risponde alle notifiche di cambiamento. In pratica, \u00e8 il metodo su cui dobbiamo lavorare per reagire ai cambiamenti che arriveranno.<br \/>\nIl metodo observeValueForKeyPath:ofObject:change:context: viene invocato automaticamente quando una propriet\u00e0 risulta modificata seguendo lo standard KVO.<\/li>\n<\/ol>\n<p>Il primo beneficio di quest&#8217;approccio consiste sicuramente nel non (re)implementare un nostro personale schema per l&#8217;invio di notifiche ogni qualvolta una propriet\u00e0 dei nostri oggetti verr\u00e0 cambiata. Tipicamente non ci sono altre azioni oltre a quelle elencate nei quattro passi sopra per adottare il meccanismo di notifica basato su KVO. A differenza del meccanismo di notifica centrale attraverso NSNotificationCenter, inoltre, le notifiche vengono inviate direttamente agli osservatori registrati. NSObject fornisce un&#8217;implementazione base per questo meccanismo e, raramente, si avr\u00e0 l&#8217;esigenza di<br \/>\nmodicarne l&#8217;implementazione.<\/p>\n<h4>AVPlayer ed AVItem &#8211; Come funzionano gli stream MP3<\/h4>\n<p>Dalla versione 4.0, iOS mette a disposizione un oggetto utilissimo per la fruizione di contenuti multimediali, compresi quelli che arrivano da un flusso MP3 trasmesso via http.<br \/>\nL&#8217;oggetto in questione si chiama <strong>AVPlayer<\/strong>. Esso funziona equamente bene sia con oggetti<br \/>\nlocali che con oggetti remoti e\/o in stream. E&#8217; possibile anche creare una playlist con oggetti multipli ed avere controllo sui singoli elementi.<\/p>\n<p>L&#8217;organizzazione degli oggetti relativamente ad uno stream musicale pu\u00f2 essere semplificata nel seguente diagramma:<\/p>\n<p><center><br \/>\n<a href=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-07.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-07.jpg\" alt=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-07\" title=\"Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-07\" width=\"452\" height=\"274\" class=\"aligncenter size-full wp-image-8970\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-07.jpg 452w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2012\/05\/Key-Value-Observing-kvo-costruire-un-lettore-mp3-ios-07-300x181.jpg 300w\" sizes=\"auto, (max-width: 452px) 100vw, 452px\" \/><\/a><br \/>\n<\/center><\/p>\n<p>La prima cosa da fare per poter sfruttare l&#8217;oggetto AVPlayer \u00e8 allocarne un&#8217;istanza generica in memoria:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\nplayer = [[AVPlayer alloc] init];\r\n<\/pre>\n<p>Il prossimo passo \u00e8 quello di creare un&#8217;istanza di AVPlayerItem, ovvero un&#8217;istanza che conosca cosa vogliamo suonare (file locale o stream remoto). Nel nostro caso, passeremo ad AVPlayerItem un oggetto NSURL, contenente la stringa dell&#8217;endpoint http remoto da suonare.<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\nNSURL *streamUrl = [NSURL URLWithString:streamAddress];\r\nAVPlayerItem *anItem = [AVPlayerItem playerItemWithURL:streamUrl];\r\n<\/pre>\n<p>Una volta ottenuta un&#8217;istanza valida di AVPlayerItem possiamo rimpiazzare il currentItem della nostra istanza di AVPlayer, come segue:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n[player replaceCurrentItemWithPlayerItem:anItem];\r\n<\/pre>\n<p>Infine, lanciamo un messaggio di play all&#8217;istanza di AVPlayer, per iniziare la riproduzione:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n[player play];\r\n<\/pre>\n<p>E&#8217; necessario spendere qualche altra parola relativamente alle modalit\u00e0 di streaming MP3.<br \/>\nI server di stream MP3, tipicamente, seguono una falsa riga incentrata su un sistema di unicast per ogni singolo client connesso. In pratica, ogni cliente che richiede lo stream avr\u00e0 riservato un suo spazio per lo streaming ma non potr\u00e0 comunicare con il server. <\/p>\n<p>Per poter costruire un lettore da zero, sarebbe necessario scendere in dettaglio, relativamente alla codifica dei dati ricevuti e come vengono generati i blocchi (chunks) di informazione. Questa \u00e8 una della parti pi\u00f9 corpose dell&#8217;articolo di Matt Gallagher a cui mi riferivo sopra. Visto che noi useremo un oggetto a pi\u00f9 alto livello, disponibile nel framework Cocoa\/CocoaTouch, non avremo bisogno di scendere a questo livello di dettaglio. Ci baster\u00e0 conoscere l&#8217;indirizzo http dello stream che vogliamo usare. <\/p>\n<p>Risolto questo problema, se ne presenta un&#8217;altro non di meno conto. Uno stream di dati ha alcune difficolt\u00e0 concettuali da risolvere. I dati, infatti, vengono inseriti in un buffer tampone che serve per evitare un&#8217;interruzione dell&#8217;ascolto in caso di un&#8217;improvviso calo di banda nel nostro dispositivo. Questo buffer viene aggiustato dinamicamente in funzione delle condizioni di banda ed il lettore deve adattarsi a queste condizioni dinamiche.<\/p>\n<p>Un altro aspetto che merita attenzione riguarda l&#8217;aggiornamento del brano attualmente in ascolto.<br \/>\nGli attuali sistemi di stream permettono l&#8217;invio di un pacchetto ad inizio brano, unitamente ai dati dell&#8217;audio, contenente le informazioni relative al brano attualmente in riproduzione.<\/p>\n<p>L&#8217;idea \u00e8 quella di aggiornare la nostra vista soltanto quando se ne verifica una reale esigenza (ad esempio, quando cambia il brano del nostro stream o quando stiamo effettuando il buffering dello stream). E&#8217; in questo contesto che trova spazio quanto esposto relativamente a KVO e KVC. Senza un meccanismo di questo tipo, infatti, l&#8217;aggiornamento del brano attualmente riprodotto diventerebbe una sfida senza soluzioni. Se aggiornassimo il titolo ad intervalli regolari, ci troveremmo quasi sempre in una condizione di inconsistenza tra il brano riprodotto e quello visualizzato, stessa cosa per quanto riguarda i messaggi di sistema relativi al buffering. Ancora, utilizzando un paradigma basato sul polling ad intervalli regolari, tutta la user interface ne risentirebbe a livello di fluidit\u00e0 e reattivit\u00e0.<\/p>\n<p>Supponendo che l&#8217;istanza di AVPlayerItem da suonare sia denominata anItem, potremo cos\u00ec procedere:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n[anItem addObserver:self\r\n         forKeyPath:@\"timedMetadata\"\r\n            options:NSKeyValueObservingOptionNew\r\n            context:NULL];\r\n<\/pre>\n<p>In questo modo, la classe CPStreamPlayer (self), sar\u00e0 notificata relativamente a nuovi cambiamenti (NSKeyValueObservingOptionNew) che intervengono per le propriet\u00e0 elencate sopra. I pi\u00f9 attenti di voi avranno notato che il metodo utilizzato non \u00e8 quello precedentemente illustrato ma una sua variante denominata <strong>addObserver:forKeyPath:options:context:<\/strong> che prende come parametro un keypath<br \/>\ne non una chiave.<\/p>\n<p>Cosa \u00e8 un Keypath?<\/p>\n<p>Semplicemente \u00e8 un percorso di chiavi, separate da punti, che esprimono un percorso sino ad arrivare ad una particolare propriet\u00e0. Visto che le propriet\u00e0 che stiamo cercando sono direttamente esposte dall&#8217;istanza dell&#8217;oggetto AVPlayerItem, nel nostro caso il keypath corrisponder\u00e0 con la key.<\/p>\n<p>A questo punto l&#8217;istanza di CPStreamPlayer sar\u00e0 legate all&#8217;istanza di AVPlayerItem denominata anItem. Questo significa che qualunque cambiamento delle propriet\u00e0 sopra elencate, scatener\u00e0 un messaggio <strong>observeValueForKeyPath:ofObject:change:context:<\/strong> sull&#8217;oggetto observer CPStreamPlayer.<\/p>\n<p>Ci resta dunque da implementare questo metodo e la relativa logica per reagire ai singoli eventi:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n- (void)observeValueForKeyPath:(NSString *)keyPath\r\n                      ofObject:(id)object\r\n                        change:(NSDictionary *)change\r\n                       context:(void *)context\r\n{\r\n    if([keyPath rangeOfString:@\"playbackBufferEmpty\"].location != NSNotFound && self.isPlaying)\r\n    {\r\n        [player pause];\r\n    }\r\n    if([keyPath rangeOfString:@\"playbackLikelyToKeepUp\"].location != NSNotFound)\r\n    {\r\n        [player play];\r\n    }\r\n    else if([keyPath rangeOfString:@\"timedMetadata\"].location != NSNotFound)\r\n    {\r\n        if([[change objectForKey:@\"new\"] isKindOfClass:[NSArray class]])\r\n        {\r\n            \/\/update songTitle and Artist\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>Nel caso di playbackBufferEmpty, metteremo in pausa l&#8217;istanza del player e proveremo a lanciare un messaggio di CPStreamPlayerDidPaused al delegato designato. Nel caso di playbackLikelyToKeeup, ossia quando il buffer \u00e8 quasi pieno, ripristineremo l&#8217;ascolto.<br \/>\nInfine, nel caso di una variazione alla propriet\u00e0 timedMetadata, ossia quando variano i metadati associati allo stream, reagiremo popolando correttamente i corrispondenti campi del brano: titolo ed artista.<\/p>\n<h4>UIViewController e gestione del player<\/h4>\n<p>Ci resta da gestire il controllore di vista, responsabile del player. Anche in questo caso, si tratter\u00e0 di un&#8217;operazione davvero semplice. La classe CPStreamPlayer, implementa un protocollo informale:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n@protocol CPStreamPlayerDelegate\r\n@optional\r\n- (void)CPStreamPlayerDidStarted:(CPStreamPlayer *)actPlayer;\r\n- (void)CPStreamPlayerDidPaused:(CPStreamPlayer *)actPlayer;\r\n- (void)CPStreamPlayerMetadataDidUpdated:(CPStreamPlayer *)actPlayer;\r\n@end\r\n<\/pre>\n<p>La nostra istanza di UIViewController dovr\u00e0 implementarlo se desidera ricevere notifiche relativamente a start e stop dello stream e cambiamento dei metadati. Nel nostro esempio base, ci baster\u00e0 aggiornare il titolo del pulsante di play ed i campi che indicano il brano in riproduzione:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n#pragma mark -\r\n#pragma mark CPStreamPlayerDelegate\r\n\r\n- (void)CPStreamPlayerDidStarted:(CPStreamPlayer *)actPlayer {\r\n    [aButton setTitle:@\"Buffering\" forState:UIControlStateNormal];\r\n}\r\n- (void)CPStreamPlayerDidPaused:(CPStreamPlayer *)actPlayer {\r\n    [aButton setTitle:@\"Play\" forState:UIControlStateNormal];\r\n}\r\n- (void)CPStreamPlayerMetadataDidUpdated:(CPStreamPlayer *)actPlayer {\r\n    [aButton setTitle:@\"Stop\" forState:UIControlStateNormal];\r\n    songTitle.text = actPlayer.songTitle;\r\n    songArtist.text = actPlayer.artistTitle;\r\n    channel.text = actPlayer.channelTitle;\r\n}\r\n<\/pre>\n<h4>Un ultimo tocco: il controllo remoto<\/h4>\n<p>iOS ci permette di sfruttare i controlli multimediali disponibili sulla cuffia o quelli che compaiono quando il telefono \u00e8 bloccato, premendo rapidamente due volte il tasto home.<br \/>\nPer fare questo \u00e8 necessario impostare il nostro UIViewController come primo risponditore implementando il seguente metodo:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n\/* required for remote control *\/\r\n- (BOOL)canBecomeFirstResponder {\r\nreturn YES;\r\n}\r\n<\/pre>\n<p>A questo punto, la nostra classe UIViewController \u00e8 in grado di rispondere agli eventi inviati dal controllore multimediale remoto. Il metodo da implementare per reagire correttamente a questi eventi \u00e8 remoteControlReceivedWithEvent:. Ci baster\u00e0 controllare l&#8217;oggetto UIEvent passato come parametro e reagire di conseguenza:<\/p>\n<pre lang=\"objc\" line=\"1\" escaped=\"true\">\r\n#pragma mark -\r\n#pragma mark Remote Control Event Handling\r\n\r\n- (void)remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {\r\n    if (receivedEvent.type == UIEventTypeRemoteControl) {\r\n        NSLog(@\"subtype: %d\", receivedEvent.subtype);\r\n        switch (receivedEvent.subtype) {\r\n            case UIEventSubtypeRemoteControlPlay:\r\n                [streamPlayer startPlay];\r\n                break;\r\n            case UIEventSubtypeRemoteControlPause:\r\n                [streamPlayer startPlay];\r\n                break;\r\n            case UIEventSubtypeRemoteControlStop:\r\n                [streamPlayer startPlay];\r\n                break;\r\n            case UIEventSubtypeRemoteControlTogglePlayPause:\r\n                [streamPlayer startPlay];\r\n                break;\r\n            default:\r\n            break;\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<h4>Github: Dove trovate l&#8217;esempio<\/h4>\n<p>L&#8217;esempio completo, citato in quest&#8217;articolo \u00e8 disponibile su github all&#8217;indirizzo: <a href=\"https:\/\/github.com\/valvoline\/CPStreamPlayer\" target=\"_blank\">https:\/\/github.com\/valvoline\/CPStreamPlayer<\/a><\/p>\n<p><center><strong>Vi \u00e8 piaciuto l&#8217;articolo? Ringraziate l&#8217;autore Costantino Pistagna scaricando la sua applicazione &#8220;<a href=\"http:\/\/itunes.apple.com\/it\/app\/strips\/id463424148?mt=8\" target=\"_blank\">Strips<\/a>&#8221; (GRATIS in App Store) e <a href=\"http:\/\/www.iphoneitalia.com\/al-via-il-secondo-apps-day-della-storia-in-aquafan-vota-la-migliore-applicazione-italiana-e-vinci-un-soggiorno-a-riccione-358925.html\" target=\"_blank\">VOTATELA<\/a> come migliore applicazione italiana.<\/strong><\/center><\/p>\n<p><center><br \/>\n<a href=\"http:\/\/itunes.apple.com\/it\/app\/strips\/id463424148?mt=8\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2011\/09\/Strips.jpg\" alt=\"Strips-iPhone\" title=\"Strips-iPhone\" width=\"468\" height=\"234\" class=\"aligncenter size-full wp-image-7720\" srcset=\"https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2011\/09\/Strips.jpg 468w, https:\/\/www.devapp.it\/wordpress\/wp-content\/uploads\/2011\/09\/Strips-300x150.jpg 300w\" sizes=\"auto, (max-width: 468px) 100vw, 468px\" \/><\/a><br \/>\n<\/center><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Se si potesse stilare una classifica dei meccanismi meno conosciuti di Cocoa, il Key-Value Observing pattern o&#8230;<\/p>\n","protected":false},"author":476,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[1105,1050,1103,1101,1104,1102,934,835],"class_list":["post-8957","post","type-post","status-publish","format-standard","hentry","category-tutorial-pratici","tag-avplayer","tag-design-pattern-ios","tag-key-value-coding","tag-key-value-observing","tag-kvc","tag-kvo-ios","tag-model-view-controller","tag-nsnotificationcenter"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/8957","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/users\/476"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/comments?post=8957"}],"version-history":[{"count":16,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/8957\/revisions"}],"predecessor-version":[{"id":9000,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/posts\/8957\/revisions\/9000"}],"wp:attachment":[{"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/media?parent=8957"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/categories?post=8957"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devapp.it\/wordpress\/wp-json\/wp\/v2\/tags?post=8957"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}