iOS/Obj-C : utilisation de GCD


28 septembre 2013

Le but de GCD est de permettre de programmer en "multi-thread" de façon simple et en adéquation avec les capacités de l'appareil qui exécute le code (Un Mac Pro est un poil plus performant qu'un iPhone 3GS...).

J'écris multi-thread entre guillemets parce que GCD utilise le concept de Queue qui est une abstraction d'une thread : parfois c'est une thread, parfois c'en est plusieurs, parfois c'est la thread principale.

Ainsi, pour illustrer de manière concise une utilisation de GCD, imaginons la situation suivante : des petites tuiles d'une grosse image doivent être chargées de manière asynchrone et sans ralentir la thread principale de l'application. On va donc créer une Queue pour le décodage qui se chargera de mettre l'UI à jour avec ce qu'elle a décodée.

self.filesList = [self.archive filesList];
self.fileIdx = 0;
self->Queue_Test1 = dispatch_queue_create("tileLoadingQueue", DISPATCH_QUEUE_CONCURRENT);       //DISPATCH_QUEUE_CONCURRENT / DISPATCH_QUEUE_SERIAL
TestArchive*    Self = self;
double delayInSeconds = 0.02;       //Si on met un délai nul (0.0) la mémoire occupée par les NSData et UIImage (autorelease pour les deux) n'aura jamais l'occasion de se libérer !
//
self->Test1_block = [^{
    NSString*   FileName = [Self.filesList objectAtIndex:Self.fileIdx];
    //On charge et on décode l'image sur la thread auxiliaire
    NSData* ImageDatas = nil;
    if ((ImageDatas = [Self.archive extractFileByName:FileName]) != nil)        {
        UIImage* TargetImage = [UIImage imageWithData:ImageDatas];
        CGImageRef  TargetCGImage = [TargetImage CGImage];
        //On affecte l'image à son layer cible sur la thread UI
        dispatch_sync(dispatch_get_main_queue(), ^()        {
            Self.imageView.layer.contents = (id) TargetCGImage;
        });
        [self fpsStats];
    }
    //On planifie le chargement de l'image suivante (après un certain délai qui permet de libérer la mémoire allouée juste avant)
    Self.fileIdx++;
    if (Self.fileIdx < [Self.filesList count])      {
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, Self->Queue_Test1, Self->Test1_block);
    }
} copy];        //Le block est alloué sur la pile au départ. "copy" est la seule manière de le mettre dans la heap et qu'il ne soit pas désalloué.
//
dispatch_async(self->Queue_Test1, self->Test1_block);

On a donc utilisé beaucoup de fonctions de GCD :

  • dispatch_queue_create : pour créer la queue de décodage.
  • dispatch_async : pour démarrer l'exécution du processus complet de décodage.
  • dispatch_sync : pour affecter de façon SYNCHRONE l'image décodée à sa view dans l'interface utilisateur.
  • dispatch_get_main_queue : permet de cibler la queue principale, qui est celle utilisée par l'UI.
  • dispatch_after : pour continuer l'exécution du processus de décodage. Le délai permet de flusher la mémoire placer dans les pools autorelease.

Schéma interne des classes Objective-C


23 mars 2013

Un aide-mémoire sur les relations entre instances et classes en objective-C, du blog très intéressant de Greg Parker :

Le post correspondant à cette image est également très instructif et explique clairement et rapidement le concept de meta-classes.

On peut noter que CocoaWithLove possède une page sur ce même sujet.

Obj-C : NSArray or plain C-Array ?


28 octobre 2012

Le débat est permanent entre l'utilisation de NSArray et l'utilisation de id* MonTableau pour stocker des objets ne serait-ce que pour des problèmes de performances mais également pour des questions d'écriture (Encore que la notation littérale des NSArray améliore l'apparence des sources). Ainsi, le code suivant devrait (?) être plus rapide qu'avec des NSArray :

for (int i=0; i<10; i++) {
    things[i] = [[Thing alloc] init];
}

//And then later in the code doing something like:

for (UITouch *touch in touches) {
    for (int i=0; i<10; i++) {
        if ([things[i] containsTouch:touch]) { 
            // thing was touched
        }
    }
}

Mais un post de Remy Desmarest sur la mailing-list Objective-C contient des informations très intéressantes sur le fait que ARC peut être utilisé avec des tableaux C. Ainsi même la nouvelle notation littérale des tableaux NSArray ne pousse pas les tableaux C dans les oubliettes.

iOS : détection de la zone de touche


21 octobre 2012

Comment détecter la zone (circulaire) d'une touche sur les iBidules ? Il faut obtenir un UITouch depuis un UIEvent et à partir de là :

float vf = 10.0;
    id valFloat = [thisTouch valueForKey:"pathMajorRadius"];
    if (valFloat != nil)    { vf = [valFloat floatValue]; }

Source : http://stackoverflow.com/questions/5179426/tap-pressure-strength-detection-using-accelerometer

iOS, relation entre CALayer et UIView


21 décembre 2011
When a layer needs to be displayed and has no valid backing store (perhaps because the layer received a setNeedsDisplay message), the system sends the display message to the layer. The -[CALayer display] method looks roughly like this:
- (void)display {
    if ([self.delegate respondsToSelector:@selector(displayLayer:)]) {
        [[self.delegate retain] displayLayer:self];
        [self.delegate release];
        return;
    }

    CABackingStoreRef backing = _backingStore;
    if (!backing) {
        backing = _backingStore = ... code here to create and configure
            the CABackingStore properly, given the layer size, isOpaque,
            contentScale, etc.
    }

    CGContextRef gc = ... code here to create a CGContext that draws into backing,
        with the proper clip region
    ... also code to set up a bitmap in memory shared with the WindowServer process

    [self drawInContext:gc];
    self.contents = backing;
}

So, if you override display, none of that happens unless you call [super display]. And if you implement displayLayer: in FooView, you have to create your own CGImage somehow and store it in the layer's contents property. The -[CALayer drawInContext:] method looks roughly like this:
- (void)drawInContext:(CGContextRef)gc {
    if ([self.delegate respondsToSelector:@selector(drawLayer:inContext:)]) {
        [[self.delegate retain] drawLayer:self inContext:gc];
        [self.delegate release];
        return;
    } else {
        CAAction *action = [self actionForKey:@"onDraw"];
        if (action) {
            NSDictionary *args = [NSDictionary dictionaryWithObject:gc forKey:@"context"];
            [action runActionForKey:@"onDraw" object:self arguments:args];
        }
    }
}

The onDraw action is not documented as far as I know. The -[UIView drawLayer:inContext:] method looks roughly like this:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)gc {
    set gc's stroke and fill color spaces to device RGB;
    UIGraphicsPushContext(gc);
    fill gc with the view's background color;
    if ([self respondsToSelector:@selector(drawRect:)]) {
        [self drawRect:CGContextGetClipBoundingBox(gc)];
    }
    UIGraphicsPopContext(gc);
}

...I used Hopper to disassemble the IOS simulator libraries...
Accueil