[Débutant] Aide core graphics et mémoire

colas_colas_ Membre

Bonjour à  tous !


 


Je souhaite pouvoir intégrer des PDF dans les UIView.


En m'aidant du web, j'ai créé une classe CBDPDFView, qui crée des UIView à  partir de PDF.


Pour la suite, il est important de savoir que CBDPDFView prend deux arguments :


  • l'URL du PDF
  • un numéro de page

C'est à  partir de ces deux données qu'il crée une UIView.


[code de la classe plus bas]


 


J'ai ensuite testé ma classe. ça s'affiche bien, rapidement, c'est parfait. J'ai utilisé un PDF de 91 pages, qui pèse 95Ko.


 


Là  où le bat blesse, c'est au niveau mémoire !!!


Mon appli bouffe 300Mo de mémoire. Pour un info, si je n'affiche qu'une UIWebView avec ce PDF dedans, la mémoire occupée est de 8Mo.


 


Voici mes questions, qui s'adressent à  ceux qui connaissent Core Graphic (enfin, je veux dire les fonctions CGMachinChose()  ) et/ou qui ont pratiqué le caching. Je me demande si je ne suis pas en train de répliquer pleine fois les mêmes données, vu que pour ouvrir une page d'un PDF, je dois faire appel au PDF tout entier.


 


Je ne m'y connais pas du tout en C, et je ne suis pas très à  l'aise avec toutes ces fonctions CGMachinChose()


 


Ainsi, pour conclure, tout conseil, critique ou aide serait la bienvenue pour m'aider à  m'orienter pour la suite. Dois-je mettre en place un cache ? Dois-je associer une fois pour toute un NSData à  mon PDF, et si oui comment faire (bout de code bienvenu) ? Des conseils généraux et/ou de lecture sur la gestion de la mémoire ? NSCache ? etc.


 


 


Merci !


 


Colas



#import <UIKit/UIKit.h>


@interface CBDPDFView : UIView

@property (nonatomic, readonly)NSURL *URL ;
@property (nonatomic, readonly)NSUInteger page ;




//
//
/**************************************/
#pragma mark - Initialization
/**************************************/

- (id)initWithURL:(NSURL *)URLOfPDF
withPage:(NSUInteger)page
withOrigin:(CGPoint)origin
withScaleFactor:(float)scaleFactor ;



@end

et



//
// CBDPDFView.m
//
// Created by Colas on 23/05/2014.
// Copyright (c) 2014 Colas. All rights reserved.
//

#import "CBDPDFView.h"



@interface CBDPDFView ()

@property (nonatomic, readwrite, strong) NSURL *URL ;
@property (nonatomic, readwrite) NSUInteger page ;
@property (nonatomic, readwrite) CGRect mediaRect ;

@end




@implementation CBDPDFView



//
//
/**************************************/
#pragma mark - Initializer
/**************************************/




- (id)initWithURL:(NSURL *)URLOfPDF
withPage:(NSUInteger)page
withOrigin:(CGPoint)origin
withScaleFactor:(float)scaleFactor
{
// url is a file URL
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)URLOfPDF);
CGPDFPageRef pdfPage = CGPDFDocumentGetPage(pdf, page);

// get the rectangle of the cropped inside
CGRect mediaRect = CGPDFPageGetBoxRect(pdfPage, kCGPDFCropBox);
CGRect newRect = CGRectMake(origin.x, origin.y, mediaRect.size.width*scaleFactor, mediaRect.size.height*scaleFactor) ;


self = [super initWithFrame:newRect] ;

if (self)
{
_page = page ;
_URL = URLOfPDF ;
CGRect newRectBis = newRect ;
newRectBis.origin = CGPointZero ;
_mediaRect = mediaRect;
}

return self ;

}




//
//
/**************************************/
#pragma mark - The drawrect method
/**************************************/


- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();

// PDF might be transparent, assume white paper
CGContextSetRGBFillColor(ctx, 255.0, 255.0, 255.0, 1.0);

CGContextFillRect(ctx, rect);

// Flip coordinates
CGContextGetCTM(ctx);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -rect.size.height);

// url is a file URL
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)self.URL);
CGPDFPageRef pdfPage = CGPDFDocumentGetPage(pdf, self.page);

// get the rectangle of the cropped inside
CGContextScaleCTM(ctx, rect.size.width / self.mediaRect.size.width,
rect.size.height / self.mediaRect.size.height);
CGContextTranslateCTM(ctx, -self.mediaRect.origin.x, -self.mediaRect.origin.y);

// draw it
CGContextDrawPDFPage(ctx, pdfPage);
CGPDFDocumentRelease(pdf);
}

@end


Réponses

  • AliGatorAliGator Membre, Modérateur
    2 remarques simples avant de creuser plus en avant dans ton code :

    1) Quand tu fais du CoreGraphics (ou tout autre framework utilisant du C), prend soin de respecter la "Create Rule". Cette règle est expliquée ici dans la doc sur la gestion mémoire.

    Pour faire simple, quand une fonction des frameworks Apple contient le terme "Create" ou "Copy" dans son nom, par convention ça veut dire qu'il crée (ou fait une copie d') un objet que tu auras la responsabilité de nettoyer ensuite à  l'aide d'un appel à  CFRelease(objet).

    2) Evite de recréer ton objet CGPDFDocumentRef à  chaque fois. Si à  chaque fois que tu as besoin d'en extraire une page pour la présenter, tu reparses ton PDF de zéro tout ça pour extraire la page 1, puis reparses tout juste pour extraire la page 2, etc... c'est sûr que ça prend à  la fois du temps inutile et de la mémoire (qui n'est jamais libérée puisque justement tu n'as pas respecté la "Create Rule" dans ton cas et donc que tu n'arrêtes pas d'allouer de la mémoire pour ce PDF sans jamais la libérer)


    Déjà  si tu suis rien que c'est 2 points là  (créer ton CGPDFDocumentRef une fois pour toute, puis utiliser toujours ce même objet CGPDFDocumentRef pour en extraire chaque page, et faire un CFRelease à  la fin une fois que tu n'as plus besoin du tout du PDF), tu vas vite voir une grande différence.
  • colas_colas_ Membre


    2) Evite de recréer ton objet CGPDFDocumentRef à  chaque fois. 




     


    En fait, tu vas voir que dans mon code, le CGPDFDocumentRef est un variable locale.


    Comment je fais pour la stocker ? (cf. mes lacunes en C)


    Si je la mets dans une @property (nonatomic, readwrite) ça va marcher ?


    Comment va marcher la mémoire sachant que ça n'est pas un pointeur et donc que je ne peux appliquer la règle facile strong/weak ?


     


    Merci !!

  • AliGatorAliGator Membre, Modérateur
    mai 2014 modifié #4
    Le mot clé 'assign' est là  pour quand tu n'as pas un NSObject qui gère le Reference Counting mais un truc non-pointeur ;) (comme quand tu fais des @property de type int ou BOOL etc)

    Ceci dit pour être précis, CGPDFDocumentRef est en fait un pointeur (le terme "Ref" dans le nom le laisse transparaà®tre), si tu regardes la définition de ce type dans les headers c'est sans doute un typedef d'un type genre "struct CGPDFDocument*". Mais bon, en effet ce n'est pas un NSObject* mais un type Core Foundation qui n'est pas géré par ARC
  • AliGatorAliGator Membre, Modérateur
    mai 2014 modifié #5
    Ce qui donnerait donc :
    @interface CBDPDFView : UIView
    ...
    @property(assign) CGPDFDocumentRef pdfDocument;
    @end
    CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)URLOfPDF);
    ...
    CBDPDFView* pdfView1 = [[CBDPDFView alloc] initWithPDF:pdf page:0 origin:CGPointZero scaleFactor:1.0];
    CBDPDFView* pdfView2 = [[CBDPDFView alloc] initWithPDF:pdf page:1 origin:CGPointZero scaleFactor:1.0];
    CBDPDFView* pdfView3 = [[CBDPDFView alloc] initWithPDF:pdf page:2 origin:CGPointZero scaleFactor:1.0];
    ...
    CFRelease(pdf);
    Après perso je serais toi je te ferais un petit "wrapper" pour t'éviter d'avoir ensuite à  trimbaler des objets CoreFoundation genre CGPDFDocumentRef, si ça t'embête et que ça te perturbe. Comme ça au moins avec ce wrapper tu reviendras à  manipuler des NSObject gérés par ARC & tout sans te préoccuper d'avoir à  penser au CFRelease à  l'usage, car ton wrapper s'en chargera.

    Par exemple :

    @interface CBDPDFDocument : NSObject
    @property(strong, readonly) NSURL* url;
    @property(assign, readonly) CGPDFDocumentRef document;
    - (instancetype)initWithURL:(NSURL*)url;
    - (CGPDFPageRef)pageAtIndex:(NSUInteger)page;
    @end

    @implementation CBDPDFDocument
    - (instancetype)initWithURL:(NSURL*)url
    {
    self = [super init];
    if (self)
    {
    _url = url;
    _document = CGPDFDocumentCreateWithURL((CFURLRef)url);
    }
    return self;
    }

    - (CGPDFPageRef)pageAtIndex:(NSUInteger)page
    {
    return CGPDFDocumentGetPage(_document, page);
    }

    - (void)dealloc
    {
    if (_document)
    {
    // Create Rule: on a utilisé une fonction ...Create... pour créer cet objet, à  nous de le releaser
    CFRelease(_document);
    }
    }
    @end
    Et dans ta CBDPDFView ton constructeur deviendra "-(id)initWithPDFPage:(CGPDFPageRef)page origin:(CGPoint)origin scaleFactor:(CGFloat)scaleFactor".

    Du coup à  l'usage, dans la classe appelante, tu pourras créer une instance de document avec CBDPDFDocument* pdfDoc = [[CBDPDFDocument alloc] initWithURL:...] " que tu gardes de côté (dans une @property(strong)) pour pas avoir à  reparser/recréer ce PDF à  chaque fois " et à  chaque fois que tu veux afficher une page de ce document, tu crées un CBDPageView en lui passant comme premier paramètre le CGPDFPageRef retourné par "[pdfDoc pageAtIndex:...]".

    ---

    Le fait de créer un petit wrapper CBDPDFDocument te permet ensuite de manipuler un NSObject plutôt qu'un type CoreFoundation et surtout de faire en sorte que la mémoire de ce CGPDFDocumentRef soit automatiquement gérée puisque le CFRelease est fait dans le dealloc dès que l'objet CBDPDFDocument (qui, lui, est géré par ARC et via des propriétés strong/weak & co) est détruit. Du coup ça va sans doute te simplifier la vie, tu y verras peut-être plus clair à  retomber sur de la manipulation d'objets Objective-C comme tu en as l'habitude ;)
  • colas_colas_ Membre

    Merci @Aligator !


     


    je vais essayer ça et je te tiens au courant.


     


    Je vais dire un grosse bêtise mais pourquoi tu dis de mettre



    @property(assign, readonly) CGPDFDocumentRef document;

    Ne veut-on pas plutôt un analogue de strong ?


     


    Or n'a-t-on pas strong <-> retain et weak <-> assign ?


     


    Merci !


  • AliGatorAliGator Membre, Modérateur
    Pas tout à  fait.


    strong est un exact synonyme de retain (sauf que c'est le nouveau vocabulaire, plus adapté, retain date de pré-ARC et strong est le nouveau mot clé qui fait la même chose).


    En fait on a 3 type de politiques mémoire :

    - strong (anciennement retain) pour les objets dont tu as la propriété, dont tu seras propriétaire (concept de composition en UML). Tant que l'objet vit, il garde en vie les objets vers lesquels il a une property strong.


    - weak pour les objets dont tu ne veux qu'une référence sans obtenir la propriété (concept d'agrégation faible en UML) ; ces propriétés sont automatiquement remises à  nil quand l'objet qu'elles contenaient est détruit.


    - assign pour les trucs non-objets où il n'y aura pas de gestion mémoire, juste une affectation "var=valeur".


    Avant ARC on n'avait que retain (= strong) et assign et du coup on traitait les objets weak comme des variables qu'on "assignait juste à  la propriété avec un =", avec le problème que si ces objets disparaissaient entre temps (étaient dealloués) et n'existaient plus, ça crashait. Avec ARC on fit la différence entre "weak" utilisé pour les objets (= c'est juste une bête assignation... MAIS qui se met à  nil quand l'objet est détruit – c'est pour ça que ça n'a du sens que pour les NSObject) et "assign" (= c'est juste une bête assignation et c'est tout)
  • colas_colas_ Membre
    mai 2014 modifié #8

    OK, je retiens :


     


    objets --> strong ou weak


    autre --> assign et si c'est quelque chose de compliqué, prévoir un release CFRelease()


     


    Petite question : doit-on par exemple CFReleaser les CGPoints, etc. ?


     


    Merci !


     


    PS : je teste ta solution aujourd'hui (j'ai aussi prévu de faire un tuto sur les UICollectionView...)


  • AliGatorAliGator Membre, Modérateur
    CFRelease n'est utile que pour les "objets CoreFoundation", c'est à  dire pour les trucs qui ne sont pas des NSObjects Objective-C, mais sont leur équivalent du moins sémantique/conceptuel CoreFoundation / C, avec des CFMachinRef.

    Ces "CFMachinRef" sont des pointeurs (d'où le "...Ref") qui sont alloués (un peu comme avec un malloc) et donc doivent être libérés avec CFRelease.

    Les CGPoint sont juste des structures, ce ne sont pas des CGPointRef ou des CGPoint* qui ont été "malloc", donc pas besoin de les releaser.


  •  J'ai utilisé un PDF de 91 pages, qui pèse 95Ko. Là  où le bat blesse, c'est au niveau mémoire !!!Mon appli bouffe 300Mo de mémoire. Pour un info, si je n'affiche qu'une UIWebView avec ce PDF dedans, la mémoire occupée est de 8Mo. 




     


     


    Après modification,


    j'obtiens 82Mo de mémoire occupée.

  • Chose intéressante à  remarquer, plus mon scaleFactor (le zoom avec lequel j'affiche le pdf) est faible, moins il y a de mémoire occupée.


     


    Ex :


    avec un scaleFactor de 0.5 : 19,5Mo d'occcupé


    avec un scaleFactor de 2 : 82Mo d'occcupé



    avec un scaleFactor de 4 : 283 d'occcupé


     


     

  • AliGatorAliGator Membre, Modérateur
    Pour reboucler avec ce qu'on a dit dans http://forum.cocoacafe.fr/topic/12455-optimisation/ avant de chercher à  optimiser la mémoire en bidouillant des choses sans trop savoir ce qu'il faut toucher, ou en faisant des suppositions sur ce qui prend de la place alors que ce n'est peut-être pas ton code mais peut-être le code d'Apple ou peut-être pas le code autour du PDF, etc... faut savoir ce qui prend de la place

    Avant donc de faire des suppositions sur ce qui prendre de la place mémoire, fait un Profiling. Lance Instruments et regarde ce qui prend tant de place.
  • AliGatorAliGator Membre, Modérateur
    Ok oui ce qu'il faut juste vérifier, c'est aussi et surtout si la mémoire est libérée une fois que tu n'affiches plus tes pages de ton document PDF. Ce qui semble prendre maintenant de la place, c'est plutôt le rendu de la page que les objets eux-même.

    Ca parle de QuarzCore, de CALayer, etc... donc plutôt lié au rendu, et tout ça (les allocations) ça a l'air d'être dans les frameworks Apple donc tu ne peux pas y faire grand chose. Sauf vérifier que par exemple tu n'oublies pas de supprimer la vue représentant une page de PDF quand tu n'en n'as plus besoin parce que tu afficherais une autre page (vérifier qu'il n'y a pas de memory leak sur tes UIView quoi).

    Mais bon je pense pas, je pense qu'à  ce stade là  faut bien de la mémoire pour faire le rendu et que tu n'y pourras pas grand chose. Du moment qu'une fois que tu en as fini avec ton PDF et tes CBDPDFView, la mémoire soit redescendue, en tout cas qu'elle ne soit pas en augmentation constante à  chaque fois que tu ouvres un nouveau PDF ou une nouvelle page, mais bien que les pages précédentes libèrent la mémoire avant que les nouvelles en utilise, pour pas que tout ça se cumule sans jamais être libéré (leak). Vu l'empreinte mémoire que tu as, je pense que c'est déjà  le cas, sinon tu ne plafonnerais pas à  80Mo mais tu n'arrêterais pas de grimper. Et 80Mo pour une appli de rendu PDF c'est pas non plus affolant.
  • PyrohPyroh Membre
    mai 2014 modifié #15

    CGContextScaleCTM(ctx, rect.size.width / self.mediaRect.size.width,
    rect.size.height / self.mediaRect.size.height);

    Y'a que moi que ça choque ? Ne serait-on pas en train de faire en sorte d'afficher un TRàˆàˆàˆàˆS grand buffer dans une petite fenêtre ? 


    ça apparaà®t pixélisé ? 


     


    Un peu de saine lecture permettrait peut-être d'y voir plus clair  ;) : Apple en fait de la bonne


  • Bonjour Pyroh !


     


    Au risque de vous faire crier tous B)  , je trouve que les Apple Programming Guide sont très bons pour un utilisateur avancé qui veut contrôler sa technique et connaà®tre tous les détails. J'en ai lu avec intérêt. Mais pour qui veut apprendre rapidement une techno, quitte à  approfondir ses connaissances plus tard, je trouve que c'est immangeable.


     


    Donc, le code que tu vois est du copié/collé sur web, et je n'ai même pas essayé de comprendre ce qu'il faisait :-/


    Ensuite, oui, je pense qu'il charge toute l'image PDF, même si on lui demande de n'en afficher qu'un petit bout... Pour le moment, c'est en dehors de mes compétences de gérer un buffer qui va n'aller chercher que ce qu'il faut... Mais si tu as un bout de code à  proposer, je peux l'essayer et te dire comment va la mémoire.


     


    Sinon, non, ce n'est pas du tout pixelisé (bonne nouvelle) !!



  • PyrohPyroh Membre

    On va pas essayer de te faire changer de méthode mais on va essayer de te faire changer de crémerie  :) (au moins pour goûter)


    Dans le lien que je t'ai donné on retrouve 2 bouts de code :



    CGPDFDocumentRef MyGetPDFDocumentRef (const char *filename)
    {
    CFStringRef path;
    CFURLRef url;
    CGPDFDocumentRef document;
    size_t count;

    path = CFStringCreateWithCString (NULL, filename,
    kCFStringEncodingUTF8);
    url = CFURLCreateWithFileSystemPath (NULL, path,
    kCFURLPOSIXPathStyle, 0);
    CFRelease (path);
    document = CGPDFDocumentCreateWithURL (url);
    CFRelease(url);
    count = CGPDFDocumentGetNumberOfPages (document);
    if (count == 0) {
    printf("`%s' needs at least one page!", filename);
    return NULL;
    }
    return document;
    }

    Celui-ci crée un CGPDFDocumentRef qui est une référence vers un fichier PDF depuis une URL. 


     


    Le suivant affiche une page dans un contexte graphique :



    void MyDisplayPDFPage (CGContextRef myContext,
    size_t pageNumber,
    const char *filename)
    {
    CGPDFDocumentRef document;
    CGPDFPageRef page;

    document = MyGetPDFDocumentRef (filename);
    page = CGPDFDocumentGetPage (document, pageNumber);
    CGContextDrawPDFPage (myContext, page);
    CGPDFDocumentRelease (document);
    }

    Normalement en adaptant rien que ça tu devrais pouvoir afficher un truc.


     


    Après d'une manière générale "et il m'a déjà  été donné de le lire ici" l'intérêt n'est pas d'apprendre rapidement mais d'apprendre tout court. Tu peux le faire rapidement si t'en as la capacité mais bien souvent c'est plutôt long et ça demande beaucoup de travail.


    C'est con à  dire mais un programme n'est pas un lego que tu construis avec des briques trouvées de-ci de-là  sur Internet. 


    Rends toi service et apprends, essaye de comprendre ce que tu fais, teste, modifie, re-teste, détruit, reconstruit, teste à  nouveau... Avec ça tu vas comprendre et tu pourras entrevoir non seulement les choses que tu veux créer mais aussi le moyen de les créer.


    Les Guides d'Apple ont ça de fabuleux c'est qu'ils comportent pas mal d'exemples (et des projets d'exemple à  disséquer) et une théorie de fond qui te permet en plus d'en apprendre sur le sujet que tu cherche à  maà®triser.


     


    Alors c'est compliqué, c'est dur de tout comprendre. Mais c'est pas grave si tu ne comprends pas comment marche une matrice de transformation du moment que tu sais l'employer (Core Graphic c'est surtout de la matrice de transformation). Apple t'expliquera sur 2 pages comment fonctionnent ces matrices t'y pigeras peut-être rien mais tu auras 4 pages d'exemples et de mise en pratique qui te permettront de savoir comment les maà®triser. Saute les 2 pages chiantes et lis les 4 autres  ^_^


    Sois curieux c'est comme ça qu'on devient bon.


  • CéroceCéroce Membre, Modérateur
    mai 2014 modifié #18

    Au risque de vous faire crier tous B)  , je trouve que les Apple Programming Guide sont très bons pour un utilisateur avancé qui veut contrôler sa technique et connaà®tre tous les détails. J'en ai lu avec intérêt. Mais pour qui veut apprendre rapidement une techno, quitte à  approfondir ses connaissances plus tard, je trouve que c'est immangeable.

    Non, je suis tout à  fait d'accord avec toi. Tout le monde n'apprend pas de la même manière, mais personnellement j'apprends du général au particulier, et quand on lit la doc d'Apple on est souvent noyé dans la quantité d'informations; la plupart n'étant pas importantes et ne représentant que des cas particuliers.

    Note toutefois que les guides se sont quand même beaucoup amélioriés, notamment en débutant par une vue d'ensemble de la techno.

    Acheter des livres et lire des articles sur le web est judicieux: ça permet de survoler le sujet, et on cherche les détails dans la doc au besoin.
  • Par rapport aux règles non-ARC des objets Core Graphics,


    est-ce que si j'obtiens un objet via la méthode



    CGPDFPageRef myPage = CGPDFDocumentGetPage(_document, page)

    et que je veux le garder dans ma classe, je dois faire



    CFRetain(myPage)

    ?


     


     


    En effet, il n'y a ni "Create" ni "Copy".


     


     


    Par ailleurs, si je veux créer un objet wrapper autour d'une CGPDFPageRef, qui aurait par exemple comme méthode d'init



    - (id)initWithPDFDocument:(CBDPDFDocument *)doc atPage:(NSUInteger)index

    aurais-je intérêt à 


    1) garder une "copie" du CGPDFPageRef ? (via un retain et une @property page). Si je fais cela, je ne vais pas stocker en mémoire deux fois la page, n'est-ce pas (à  savoir une fois dans `(CBDPDFDocument *)doc` et une fois dans `@property page` ? En même temps, je ne suis pas sûr, car peut-être que quand on appelle `CGPDFDocumentGetPage` il crée un objet...


    2) garder une référence vers le `(CBDPDFDocument *)doc` et à  chaque fois que j'ai besoin de la page faire `CGPDFDocumentGetPage(doc, index)`.


     


     


    J'ai l'impression que je suis face à  un choix perf vs. mémoire.


     


    Pour info, je cherche un créer un wrapper autour de CGPDFPageRef. (en plus d'un wrapper autour de CGPDFDocumentRef)


     


    Merci


  • Voilà  les classes que j'ai faites :



    #import <Foundation/Foundation.h>

    @class MyCBDPDFPage ;

    @interface MyCBDPDFDocument : NSObject

    @property(nonatomic, assign, readonly) CGPDFDocumentRef coreObject ;
    @property(nonatomic, readonly) NSUInteger numberOfPages ;

    - (instancetype)initWithURL:(NSURL*)URL ;
    - (MyCBDPDFPage *)pageAtIndex:(NSUInteger)page ;

    @end



    #import "MyCBDPDFDocument.h"
    #import "MyCBDPDFPage.h"


    @interface MyCBDPDFDocument ()

    @property(nonatomic, assign, readwrite) CGPDFDocumentRef coreObject ;
    @property(nonatomic, readwrite) NSUInteger numberOfPages ;

    @end



    @implementation MyCBDPDFDocument

    - (instancetype)initWithURL:(NSURL*)URL
    {
    self = [super init];
    if (self)
    {
    _coreObject = CGPDFDocumentCreateWithURL((CFURLRef)URL);
    _numberOfPages = CGPDFDocumentGetNumberOfPages(_coreObject) ;
    }
    return self;
    }

    - (MyCBDPDFPage *)pageAtIndex:(NSUInteger)page
    {
    return [[MyCBDPDFPage alloc] initWithPDF:self
    atPage:page] ;
    }

    - (void)dealloc
    {
    if (_coreObject)
    {
    /*
    Create Rule: on a utilisé une fonction ...Create... pour créer cet objet, à  nous de le releaser

    cf. http://forum.cocoacafe.fr/topic/12463-débutant-aide-core-graphics-et-mémoire/?hl=pdf
    */

    CFRelease(_coreObject);
    }
    }

    @end




    et



    #import <Foundation/Foundation.h>



    @class MyCBDPDFDocument ;

    @interface MyCBDPDFPage : NSObject

    @property(nonatomic, assign, readonly) CGPDFPageRef coreObject ;

    - (instancetype)initWithPDF:(MyCBDPDFDocument *)document
    atPage:(NSUInteger)page ;

    @end





    #import "MyCBDPDFPage.h"
    #import "MyCBDPDFDocument.h"




    @interface MyCBDPDFPage ()

    @property(nonatomic, assign, readwrite) CGPDFPageRef coreObject ;

    @end





    @implementation MyCBDPDFPage

    - (instancetype)initWithPDF:(MyCBDPDFDocument *)document
    atPage:(NSUInteger)page
    {
    self = [super init] ;

    if (self)
    {
    if (page >= document.numberOfPages)
    {
    return nil ;
    }

    _coreObject = CGPDFDocumentGetPage(document.coreObject, page) ;
    CFRetain(_coreObject) ;
    }

    return self ;
    }


    - (void)dealloc
    {
    if (_coreObject)
    {
    CFRelease(_coreObject) ;
    }
    }


    @end
  • AliGatorAliGator Membre, Modérateur

    Par rapport aux règles non-ARC des objets Core Graphics,
    est-ce que si j'obtiens un objet via la méthode


    CGPDFPageRef myPage = CGPDFDocumentGetPage(_document, page)
    et que je veux le garder dans ma classe, je dois faire

    CFRetain(myPage)
    ?
      
    En effet, il n'y a ni "Create" ni "Copy".

    Tout à  fait. Et évidemment si tu fais un CFRetain il faudra le balancer avec un CFRelease pour équilibrer quand tu n'en aura plus besoin (dans le dealloc de ta classe par exemple)
     

    Par ailleurs, si je veux créer un objet wrapper autour d'une CGPDFPageRef, qui aurait par exemple comme méthode d'init


    - (id)initWithPDFDocument:(CBDPDFDocument *)doc atPage:(NSUInteger)index
    aurais-je intérêt à 
    1) garder une "copie" du CGPDFPageRef ? (via un retain et une @property page). Si je fais cela, je ne vais pas stocker en mémoire deux fois la page, n'est-ce pas (à  savoir une fois dans `(CBDPDFDocument *)doc` et une fois dans `@property page` ?

    CFRetain fait un retain, comme à  l'époque où ARC n'existait pas. Ce n'est pas une copie, mais juste une augmentation du compteur de référence de l'objet, qui va lui indiquer qu'une personne de + est intéressée par lui et qu'il ne faut pas qu'il disparaisse tant que son compteur de référence est tombé à  zéro et que tout le monde l'ait relâché. Donc il n'y a pas 2 instances différentes et risque de duplication, juste une "2ème laisse vers le même chien".

    En même temps, je ne suis pas sûr, car peut-être que quand on appelle `CGPDFDocumentGetPage` il crée un objet...

    Non, d'après les conventions de nommage, c'est une méthode qui retourne un objet existant (Get) et qui ne crée pas un nouvel objet. Si ça créait un nouvel objet il y aurait "Create" dans le nom. Si ça te renvoyait une copie d'un objet existant à  chaque appel, il y aurait "Copy" dans le nom. Comme toujours, mêmes conventions de nommage et même "Create Rule" que depuis le début.

    2) garder une référence vers le `(CBDPDFDocument *)doc` et à  chaque fois que j'ai besoin de la page faire `CGPDFDocumentGetPage(doc, index)`.

    La question à  te poser c'est "est-ce que mon CGPDFPageRef peut exister de manière indépendante, même si son CGPDFDocumentRef qui contient cette page a disparu entre temps et a été supprimée de la mémoire ?"
    Si oui (en gros si tu fais "doc = CGPDFDocumentCreateWithURL(...)" puis un "page = CGPDFDocumentGetPage(doc, 0)" puis "CFRelease(doc)", est ce qu'une fois que tu as passé le CFRelease tu peux continuer d'utiliser d'utiliser "page" tout seul sans crash, ou bien est-ce que l'objet CGPDFPageRef devient implicitement invalide parce que le Document parent n'existe plus en mémoire ? Et si ce n'est pas le cas, est-ce que c'est toujours faux si tu fais un "CFRetain(page)" avant le "CFRelease(doc)" ?)

    Si une page peut exister de manière autonome même si son document parent n'existe plus en mémoire, alors il faut que ton wrapper récupère la CGPDFPageRef puis fasse un CFRetain dessus, et un CFRelease quand il passe dans le dealloc.

    ---

    Au passage cela pourrait être intéressant que ton wrapper MyCBDPDFPage ait une "@property(weak) MyCBDPDFDocument* parentDocument" qui permette d'avoir une référence (weak) vers le document qui contient ladite page, histoire de pouvoir remonter la hiérarchie si besoin. Propriété à  affecter dans ta méthode "initWithPDF:atPath:". Bon ce n'est qu'une idée, pas strictement nécessaire, mais quand on a une relation parent/enfant comme ça on aime bien avoir ce genre de propriété weak pour remonter vers le parent.

    C'est d'ailleurs très exactement l'exemple qui est pris dans le Programming Guide "Advanced Memory Managment" d'Apple sur le paragraphe où ils expliquent les Retain Cycle
  • AliGatorAliGator Membre, Modérateur
    juin 2014 modifié #22


    Voilà  les classes que j'ai faites

    Bah écoute ça me paraà®t bien, et correct côté gestion mémoire.


    Si j'avais des remarques à  faire (mais non liées à  tes questions de gestion mémoire & co) :

    1) tu n'as pas besoin de re-déclarer tes @property en readwrite dans l'interface privée du .m : en effet tu n'utilises jamais le setter de tes propriétés même dans ton code du .m. Tu affectes les variables d'instance dans le init avec "_coreObject = ..." etc donc en utilisant direct la variable d'instance ici (et c'est bien ce qu'il faut faire dans le cas du "init") et après tu n'a jamais besoin de faire "selfcoreObject = ..." donc au final le setter readwrite est inutile ici.


    2) quand dans ton wrapper de page tu fais :

    if (page >= document.numberOfPages)
    {
    return nil ;
    }
    c'est une bonne idée mais du coup autant le faire dès avant l'appel à  "self = [super init]" et le "if (self)" car c'est juste une condition de sortie immédiate qui n'a pas besoin de self (c'est une condition qui n'utilise que les paramètres passés à  la méthode), donc c'est un cas où tu peux sortir au plus tôt possible pour éviter de faire des choses pour rien.
  • Merci de tes remarques @Ali ! Avec les wrappers, la mémoire occupée est un peu plus grande (genre +10Mo) mais je n'ai pas fait de tests précis (je vais essayer de faire ça).


     


    Je suis d'accord avec tes remarques sinon, même si je préfère garder la @property "au cas où".


     


    Pour la gestion des retain, je me suis dit que ça ne coûtait rien de garder une référence "strong".


     


    En tous, merci pour m'avoir conseillé de faire ces wrappers, je pense que c'est une super idée. Comme ça, je gère une bonne fois pour toutes ces questions de mémoire, etc. Je garde l'idée en tête pour mes prochaines excursions vers Core Foundation/Graphics, etc.


     


    Pour info, les pages d'un PDF commencent à  1 !


Connectez-vous ou Inscrivez-vous pour répondre.