Inici de sessió d'usuari


Mutabilitat d'Objectes


Els objectes Cocoa són mutables o immutables. No pots canviar els valors encapsulats dels objectes immutables; els altres un cop s'ha creat un objecte, el valor que el representa es manté igual en tota la seva vida. Però pots canviar els valors encapsulats d'un objecte mutable en qualsevol moment. Les següents seccions expliquen les raons per tenir les variants mutables i immutables d'uns tipus d'objectes, explica les característiques i els efectes col·laterals de la mutabilitat d'objectes i recomana com tractar millor els objectes quan la seva mutabilitat en una edició.

Perquè la Variant d'Objectes Mutables i Immutables

Els objectes per defecte són mutables. La majoria d'objectes et permeten canviar els seus valors encapsulats a través dels mètodes "set". Per exemple, pots canviar la mida, la posició, el títol i altres característiques d'un objecte NSWindow. Tal com diu el model de disseny d'objecte, un objecte representa una gravació del client -- requereix mètodes set per canviar-los-hi les seves dades d'instància.

La framework Foundation afegeix algunes matisos a aquesta situació introduint classs que tenen variants mutables i immutables. Les sub-classes mutables són normalment sub-classes de les seves super-classes immutables i tenen incrustat un "Mutable" en el nom de la classe. Aquestes classes inclouen els següents:

NSMutableArray

NSMutableDictionary

NSMutableSet

NSMutableIndexSet

NSMutableCharacterSet

NSMutableData

NSMutableString

NSMutableAttributedString

NSMutableURLRequest

Nota: Excepte per l'NSMutableParagraphStyle de l'Application Kit, la framework Foundation actualment defineix explícitament tots els nomes de classes mutables. Tanmateix, qualsevol framework Cocoa pot potencialment tenir les seves pròpies variants de classes mutables i immutables.

Encara que aquestes classes tenen noms atípics, estan dins la norma mutable com a contrari de les seves immutables. Perquè aquesta complexitat? Quin és el propòsit de tenir una variant immutable d'un objecte mutable que fa el servei?

Considereu l'escenari en que tots els objectes són capaços de mutar. En les teves aplicacions invoques un mètode i retornen una referència a un objecte representant un string. Utilitzes aquest string en la teva interfície d'usuari per identificar un tros de dades en particular. Ara un altres sub-sistema de la teva aplicació agafa aquesta mateixa referència del mateix string i decideix mutar-lo. De cop i volta la teva etiqueta ha canviat sense tu voler-ho. Aquestes coses poden ser pitjors si, per exemple, utilitzes una referència a una referència a una matriu que ha estat esborrada per algun codi d'una altra part del programa -- problema assegurat. La immutabilitat és una garantia que un objecte no canviarà inesperadament de valor mentre l'utilitzes.

Els objectes que són bons candidats per la immutabilitat són els que encapsulen col·leccions de valors discrets o contenen valors que estan guardats en buffers (els quals són ells mateixos de classe de col·leccions, de caràcters o bytes). Però no tots els objectes "valor" es beneficien necessàriament per tenir versions mutables. Els objectes que contenen un únic valor simple, com les instàncies de NSNumber o NSDate, no són bons candidats per la mutabilitat. Quan en aquests casos, el valor representat varia, té més sentit canviar la instància antiga per una nova instància.

El rendiment també és una raó per les versions immutables dels objectes representant coses com strings i diccionaris. Els objectes mutables com aquests duen unes despeses indirectes. Doncs han de gestionar dinàmicament un magatzem canviant -- assignant i des-assignant trossos de memòria a mida que es necessita -- els objectes mutables poden ser menys eficients que les seves parts immutables.

Encara que en teoria la immutabilitat garanteix que un valor d'objecte és estable, en la pràctica aquesta garantia no sempre està assegurada. Un mètode pot escollir retornar un objecte mutable de la seva variant immutable; més tard, aquest pot decidir mutar l'objecte, possiblement violant les assumpcions i opcions del recipient. La mutabilitat d'un objecte pròpi pot canviar experimentant varies tranformacions. Per exemple, serialitzant una llista de propietat (utilitzant la classe NSPropertyListSerialization) fa que no es preservi la mutabilitat de l'objecte, només les seves classes general -- un diccionari, una matriu, etc. Aquests, quan deserialitzes aquesta llista de propietats, els objectes resultats poden no ser de la mateixa classe que els objectes originals. Per exemple, que el que era un objecte NSMutableDictionary, ara podria ser un objecte NSDictionary.

Programant Amb Objectes Mutables

Quan la mutabilitat d'objectes en una modificació, és millor adoptar algunes pràctica de programació defensives. Aquí hi ha unes quantes regles generals a seguir:

  • Utilitzar una variant mutable d'un objecte quan necessitis modificar el seu contingut freqüentment i incrementalment després de crear-lo.
  • Algunes vegades és preferible reemplaçar un objecte immutable amb un altre; per exemple, la majoria de variables d'instància que contenen valors string haurien de ser assignats objectes NSString immutables que són reemplaçats amb els mètodes "set".
  • Confiar en el tipus de retorn per les indicacions de mutabilitat.
  • Si tens qualsevol dubte sobre si un objecte és, o hauria de ser, mutable, fes-lo immutable.

Aquesta secció explora les àrees grises d'aquestes regles a seguir, discutint les eleccions normals que s'ha de prendre quan es programa amb objectes mutables. També dona una descripció dels mètodes en la framework Foundation per crear objectes mutables i per convertir-los entre les variants mutables i immutables.

Creant i Convertint Objectes Mutables

Pots crear un objecte mutable a través del missatge niuat estàndard alloc-init, per exemple:

NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] init];

Tanmateix, moltes classes mutables ofereixen inicialitzadors i mètodes de factoria que et permeten especificar la capacitat inicial o probable de l'objecte, com el mètode de classe arrayWithCapacity: de l'NSMutableArray:

NSMutableArray *mutArray = [NSMutableArray arrayWithCapacity:[timeZones count]];

Aquest indirectament activa un emmagatzemament més eficient de les dades dels objectes mutables. (Docs la convenció dels mètodes de factoria de classes retorna instàncies auto-alliberables, assegureu-vos de retenir l'objecte si voleu mantenir-lo viable en el vostre codi).

També pots crear objectes mutables fent una copia mutable d'un objecte existent de tipus general. Per fer-ho, invoqueu el mètode mutableCopy que implementa cada super-classe immutable d'una classe Foundation mutable:

NSMutableSet *mutSet = [aSet mutableCopy];

Per altra banda, pots enviar un copy a un objecte mutable per fer una còpia immutable d'ell.

Moltes classes Foundation, amb variants mutables i immutables inclouen mètodes per convertir entre les variants, incloent-hi:

  • typeWithType: -- per exemple, arrayWithArray:
  • setType: -- per exemple, setString: (només classes mutables)
  • initWithType: -- per exemple, initWithDictionary:copyItems

Guardant i Retornant Variables d'Instància Mutables

Una decisió normal en el desenvolupament en Cocoa és si fer una variable d'instància mutable o immutable. Per una variable d'instància en que pot canviar el valor, com un diccionari o un string, quan és apropiat fer un objecte mutable? I quan és millor fer l'objecte immutable i reemplaçar-lo amb un altre objecte quan el valor representat canvia?

Normalment, quan tens un objecte que els continguts canviar en grans quantitats, és millor utilitzar un objecte immutable. Els strings (NSString) i objecte de dades (NSData) normalment entren dins aquesta categoria. Si un objecte és fàcilment canviable, és una bona decisió fer-lo mutable. Els col·leccions com són els arrays i els diccionaris entren dins aquesta categoria. Tanmateix, la freqüència dels canvis i la mida de la col·lecció serien factors d'aquesta decisió. Per exemple, si tens un petit array que rarament canvia, és millor fer-lo immutable.

Hi ha un parell d'altres consideracions quan es decideix sobre la mutabilitat d'una col·lecció continguda com una variable d'instància:

  • Si tens una col·lecció mutable que canvia freqüentment i que freqüentment deixes als clients (es a dir, retornes directament en un mètode "get") corres el risc que canviïn algunes coses que els teus clients en tinguin una referència. Si aquest risc és probable, la variable d'instància hauria de ser immutable.
  • Si el valor de la variable d'instància canvia freqüentment però rarament la retornaràs als clients mitjançant mètodes "get", pots fer la variable d'instància mutable però retornant una copia immutable auto-alliberable d'ell en el teu mètode d'accés (mireu el Llistat 1 per tenir-ne un exemple).

Llistat 1: Retornant una copia immutable d'una variable d'instància mutable

@interface MyClass : NSObject {
    // ...
    NSMutableSet *widgets;
}
// ...
@end

@implementation MyClass
- (NSSet *)widgets {
    return (NSSet *)[[widgets copy] autorelease];
}

Una aproximació sofisticada per entregar col·leccions mutables que són retornades a clients és mantenir una bandera que guardi si l'objecte és actualment mutable o immutable. Si hi ha un canvi, fer l'objecte mutable i aplicar el canvi. Quan entreguem la col·lecció, fem l'objecte immutable (si cal) abans de retornar-lo.

Revent Objectes Mutables

L'invocador d'un mètode està interessat en la mutabilitat d'un objecte retornat per dues raons:

  • Vol conèixer si pot canviar els valors de l'objecte.
  • Vol conèixer si el valor de l'objecte canviarà inesperadament mentre aquest en té una referència.
Utilitzar el Tipus de Retorn, No Introspecció

Per determinar si es pot canviar un objecte rebut, el receptor ha de confiar en el tipus formal del valor retornat. Si rep, per exemple, un objecte array definit com a immutable, no intentarà mutar-lo. No és una pràctica de programació acceptable determinar si un objecte està basat en un objecte mutable mirant si és membre d'una classe, per exemple:

if ( [unArray isKindOfClass:[NSMutableArray class]] ) {
    // afegeix, elimina objectes des de unArray
}

Per raons d'implementació, que retorna isKindOfClass: en aquest cas pot no ser acurat. Però, per altres raons, no hauries de prendre decisions sobre si un objecte està basat en mutable sobre si pertany a una classe. Aquesta decisió seria guiada només per quina signatura del mètode de l'objecte diu sobre la seva mutabilitat. Si no estàs segur si un objecte és mutable o immutable, assumeix-lo immutable.

Un parell d'exemple podrien ajudar a clarificar perquè aquestes pautes són importants:

  • Lleigeixes una llista de propietats des d'un fitxer. Quan la framework Foundation processa la llista notifica que varis sub-conjunts de la llista de propietats són idèntiques, així crea un conjunt d'objectes que comparteix entre tots aquells sub-conjunts. Llavors mires els objectes de la llista de propietats creada i decideixes mutar un sub-conjunt. De cop i volta, i sense adonar-te'n han canviat l'arbre en múltiples llocs.
  • Preguntes a un NSView per les seves sub-vistes (mètode subviews) i retorna un objecte que és declarat a ser un NSArray però que podria ser internament un NSMutableArray. Llavors passes aquest array a algun altre codi que, a través de la introspecció, determina que aquest és mutable i el canvia. Al canviar aquest array, el codi està modificant les estructures de dades internes de l'NSView.

Així no preneu decisions sobre la mutabilitat d'un objecte basant-vos en el que us diu la introspecció sobre un objecte. Si necessites inequívocament marcar un objecte com a mutable o immutable quan el passes als clients, passa aquesta informació com una bandera amb l'objecte.

Fer Fotos dels Objectes Rebuts

Si vols assegurar que un objecte rebut suposadament immutable des d'un mètode no canvia sense que te n'assabentis, pots fer "fotos" de l'objecte copiant-lo localment. Després compara la versió guardada de l'objecte amb la versió més recent. Si l'objecte a canviat, pots fer les ajustos necessaris en el teu programa que depèn sobre la versió prèvia de l'objecte. El Llistat 2 mostra una possible implementació d'aquesta tècnica.

Llistat 2: Fent una foto d'un objecte potencialment mutable

static NSArray *foto = nil;
- (void)laMevaFunció {
    NSArray *cosesArray = [unAltreObj coses];
    if (foto) {
        if ( ![cosesArray isEqualToArray:foto] ) {
            [self actualitzaEstatAmb: cosesArray];
        }
    }
    foto = [cosesArray copy];
}

Unn problema amb fer fotos d'objectes per comparar-los posteriorment és que és car. Requereix fer múltiples còpies del mateix objecte. Una alternativa més eficient és utilitzar l'observació dels valors clau.

Objectes Mutables en Col·leccions

Guardant objectes mutables en objectes col·lecció pot causar problemes. Certes col·leccions poden arribar a ser invàlides o fins i tot corruptes si conté objectes mutables, doncs, amb la mutació, aquells objectes poden afectar la forma en que són situats en la col·lecció. En el primer cas, les propietats dels objectes que són claus en les col·leccions hash (clau) com són els objectes NSDictionary o els objectes NSSet faran, si canvien, corrupta la col·lecció si les propietats canviades afecten als mètodes hash o isEqual: de l'objecte. (Si el mètode hash dels objectes en la col·lecció no depén del seu estat intern, la corrupció és una probabilitat menor). Segons, si un objecte en una col·lecció ordenada com és un array ordenat ha canviat les seves propietats, això pot afectar com els objectes es comparen amb altres objectes en l'array, així mostrant una ordenació invàlida.