Last in-First Out Stack z GCD?

Mam UITableView, który wyświetla obrazy powiązane z kontaktami w każdym wierszu. W niektórych przypadkach obrazy te są odczytywane na pierwszym ekranie z obrazu kontaktu książki adresowej, a jeśli nie ma takiego, są to awatary renderowane na podstawie przechowywanych danych. Obecnie mam te obrazy są aktualizowane w tle wątku za pomocą GCD. Jednak ładuje obrazy w żądanej kolejności, co oznacza, że podczas szybkiego przewijania Kolejka staje się długa i gdy użytkownik przestaje przewijać aktualne komórki to Ostatnia, która zostanie zaktualizowana. Na iPhonie 4 problem nie jest tak naprawdę zauważalny, ale chętnie obsługuję starszy sprzęt i testuję na iPhonie 3G. opóźnienie jest znośne, ale dość zauważalne.

Wydaje mi się, że ostatni stos In-First Out prawdopodobnie rozwiąże ten problem, ponieważ za każdym razem, gdy użytkownik przestanie przewijać te komórki, będzie Następna aktualizacja, a następnie inne, które są obecnie poza ekranem, zostaną zaktualizowane. Is such a thing możliwe z centralą? Czy nie zbyt uciążliwe, aby wdrożyć w inny sposób?

Zauważ przy okazji, że używam Core Data ze sklepem SQLite i nie używam kontrolera NSFetchedResultsController z powodu relacji wiele do wielu, która musi zostać przejechana, aby załadować dane dla tego widoku. (O ile mi wiadomo, wyklucza to użycie kontrolera NSFetchedResultsController.) [odkryłem, że kontroler NSFetchedResultsController może być używany z wieloma relacjami, wbrew oficjalnej dokumentacji. Ale nie używam go jeszcze w tym kontekście.]

Addition: aby zauważyć, że chociaż tematem jest "jak stworzyć stos Last in-First Out z GCD", w rzeczywistości chcę tylko rozwiązać problem opisany powyżej i może być lepszy sposób, aby to zrobić. Jestem bardziej niż otwarty na sugestie takie jak timthetoolman, który rozwiązuje problem przedstawiony w inny sposób; jeśli taka sugestia jest w końcu tym, czego używam, rozpoznam oba najlepsza odpowiedź na oryginalne pytanie, a także najlepsze rozwiązanie, które udało mi się wdrożyć... :)

Author: Duncan Babbage, 2011-09-27

8 answers

Ze względu na ograniczenia pamięci urządzenia, należy załadować obrazy na żądanie i na kolejce GCD w tle. W cellForRowAtIndexPath: metoda sprawdź, czy obraz Twojego kontaktu jest zerowy lub został zbuforowany. Jeśli obraz jest nil lub nie w pamięci podręcznej, użyj zagnieżdżonego dispatch_async, aby załadować obraz z bazy danych i zaktualizować komórkę tableView.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
   {
       static NSString *CellIdentifier = @"Cell";
       UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
       if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
       }
       // If the contact object's image has not been loaded, 
       // Use a place holder image, then use dispatch_async on a background queue to retrieve it.

       if (contact.image!=nil){
           [[cell imageView] setImage: contact.image];
       }else{
           // Set a temporary placeholder
           [[cell imageView] setImage:  placeHolderImage];

           // Retrieve the image from the database on a background queue
           dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
           dispatch_async(queue, ^{
               UIImage *image = // render image;
               contact.image=image;

               // use an index path to get at the cell we want to use because
               // the original may be reused by the OS.
               UITableViewCell *theCell=[tableView cellForRowAtIndexPath:indexPath];

               // check to see if the cell is visible
               if ([tableView visibleCells] containsObject: theCell]){
                  // put the image into the cell's imageView on the main queue
                  dispatch_async(dispatch_get_main_queue(), ^{
                     [[theCell imageView] setImage:contact.image];
                     [theCell setNeedsLayout];
                  });
               }
           }); 
       }
       return cell;
}

Wideo z konferencji WWDC2010 "wprowadzenie bloków i Grand Central Dispatch" pokazuje przykład użycia zagnieżdżonego dispatch_async również.

Kolejną możliwą optymalizacją może być rozpoczęcie pobierania obrazów w kolejce tła o niskim priorytecie po uruchomieniu aplikacji. tj.

 // in the ApplicationDidFinishLaunchingWithOptions method
 // dispatch in on the main queue to get it working as soon
 // as the main queue comes "online".  A trick mentioned by
 // Apple at WWDC

 dispatch_async(dispatch_get_main_queue(), ^{
        // dispatch to background priority queue as soon as we
        // get onto the main queue so as not to block the main
        // queue and therefore the UI
        dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)
        dispatch_apply(contactsCount,lowPriorityQueue ,^(size_t idx){
               // skip the first 25 because they will be called
               // almost immediately by the tableView
               if (idx>24){
                  UIImage *renderedImage =/// render image
                  [[contactsArray objectAtIndex: idx] setImage: renderedImage];
               }

        });
 });

Z tą zagnieżdżoną wysyłką renderujemy obrazy w kolejce o bardzo niskim priorytecie. Umieszczenie renderowania obrazu w kolejce priorytetów tła pozwoli na renderowanie obrazów z powyższej metody cellForRowAtIndexPath z wyższym priorytetem. Tak więc, ze względu na różnicę w priorytety kolejek, będziesz miał" biednych ludzi " LIFO.

Powodzenia.
 16
Author: timthetoolman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-28 18:41:39

Poniższy kod tworzy elastyczny stos last in-first out, który jest przetwarzany w tle za pomocą Grand Central Dispatch. Klasa SYNStackController jest generyczna i wielokrotnego użytku, ale ten przykład zawiera również kod dla przypadku użycia określonego w pytaniu, renderując obrazy komórek tabeli asynchronicznie i zapewniając, że po zatrzymaniu szybkiego przewijania, aktualnie wyświetlane komórki są następne do zaktualizowania.

Wyrazy uznania dla Bena M. którego odpowiedź na to pytanie dostarczyła początkowy kod, na którym to było oparte. (Jego odpowiedź zawiera również kod, którego możesz użyć do przetestowania stosu.) Implementacja przedstawiona tutaj nie wymaga ARC i wykorzystuje wyłącznie centralną dyspozytornię, a nie wykonuje zadania. Poniższy kod przechowuje również odniesienie do bieżącej komórki za pomocą objc_setassocjatedobject, które umożliwi skojarzenie renderowanego obrazu z właściwą komórką, gdy obraz zostanie następnie załadowany asynchronicznie. Bez tego kodu obrazy renderowane dla poprzednie kontakty zostaną nieprawidłowo wstawione do ponownie używanych komórek, nawet jeśli teraz wyświetlają inny kontakt.

Przyznałem nagrodę Benowi M. Ale zaznaczam to jako akceptowaną odpowiedź, ponieważ ten kod jest w pełni opracowany.

SYNStackController.h

//
//  SYNStackController.h
//  Last-in-first-out stack controller class.
//

@interface SYNStackController : NSObject {
    NSMutableArray *stack;
}

- (void) addBlock:(void (^)())block;
- (void) startNextBlock;
+ (void) performBlock:(void (^)())block;

@end

SYNStackController.m

//
//  SYNStackController.m
//  Last-in-first-out stack controller class.
//

#import "SYNStackController.h"

@implementation SYNStackController

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

    if (self != nil) 
    {
        stack = [[NSMutableArray alloc] init];
    }

    return self;
}

- (void)addBlock:(void (^)())block
{
    @synchronized(stack)
    {
        [stack addObject:[[block copy] autorelease]];
    }

    if (stack.count == 1) 
    {
        // If the stack was empty before this block was added, processing has ceased, so start processing.
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
        dispatch_async(queue, ^{
            [self startNextBlock];
        });
    }
}

- (void)startNextBlock
{
    if (stack.count > 0)
    {
        @synchronized(stack)
        {
            id blockToPerform = [stack lastObject];
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
            dispatch_async(queue, ^{
                [SYNStackController performBlock:[[blockToPerform copy] autorelease]];
            });

            [stack removeObject:blockToPerform];
        }

        [self startNextBlock];
    }
}

+ (void)performBlock:(void (^)())block
{
    @autoreleasepool {
        block();
    }
}

- (void)dealloc {
    [stack release];
    [super dealloc];
}

@end

W widoku.h, przed interfejsem@:

@class SYNStackController;

W widoku.sekcja interfejsu h@:

SYNStackController *stackController;

W widoku.h, po interfejsie @ Sekcja:

@property (nonatomic, retain) SYNStackController *stackController;

W widoku.m, przed @ implementacją:

#import "SYNStackController.h"

W widoku.m viewDidLoad:

// Initialise Stack Controller.
self.stackController = [[[SYNStackController alloc] init] autorelease];

W widoku.m:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // Set up the cell.
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    else 
    {
        // If an existing cell is being reused, reset the image to the default until it is populated.
        // Without this code, previous images are displayed against the new people during rapid scrolling.
        [cell setImage:[UIImage imageNamed:@"DefaultPicture.jpg"]];
    }

    // Set up other aspects of the cell content.
    ...

    // Store a reference to the current cell that will enable the image to be associated with the correct
    // cell, when the image subsequently loaded asynchronously. 
    objc_setAssociatedObject(cell,
                             personIndexPathAssociationKey,
                             indexPath,
                             OBJC_ASSOCIATION_RETAIN);

    // Queue a block that obtains/creates the image and then loads it into the cell.
    // The code block will be run asynchronously in a last-in-first-out queue, so that when
    // rapid scrolling finishes, the current cells being displayed will be the next to be updated.
    [self.stackController addBlock:^{
        UIImage *avatarImage = [self createAvatar]; // The code to achieve this is not implemented in this example.

        // The block will be processed on a background Grand Central Dispatch queue.
        // Therefore, ensure that this code that updates the UI will run on the main queue.
        dispatch_async(dispatch_get_main_queue(), ^{
            NSIndexPath *cellIndexPath = (NSIndexPath *)objc_getAssociatedObject(cell, personIndexPathAssociationKey);
            if ([indexPath isEqual:cellIndexPath]) {
            // Only set cell image if the cell currently being displayed is the one that actually required this image.
            // Prevents reused cells from receiving images back from rendering that were requested for that cell in a previous life.
                [cell setImage:avatarImage];
            }
        });
    }];

    return cell;
}
 11
Author: Duncan Babbage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:45:39

Ok, przetestowałem to i działa. Obiekt po prostu ściąga następny blok ze stosu i wykonuje go asynchronicznie. Obecnie działa tylko z blokami zwrotnymi void, ale możesz zrobić coś wymyślnego, na przykład dodać obiekt, który będzie miał blok i delegata, aby przekazać Typ powrotu do bloku.

Uwaga: użyłem ARC w tym więc trzeba XCode 4.2 lub większy, dla tych z Was na późniejszych wersjach, po prostu zmienić silny zachować i powinno być w porządku, ale będzie pamięć wyciekaj wszystko, jeśli nie dodasz w wydaniach.

EDIT: aby uzyskać bardziej konkretny przypadek użycia, jeśli tableviewcell ma obrazek, użyłbym mojej klasy stosu w następujący sposób, aby uzyskać żądaną wydajność, proszę daj mi znać, jeśli działa dobrze dla Ciebie.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    // Configure the cell...

    UIImage *avatar = [self getAvatarIfItExists]; 
    // I you have a method to check for the avatar

    if (!avatar) 
    {
        [self.blockStack addBlock:^{

            // do the heavy lifting with your creation logic    
            UIImage *avatarImage = [self createAvatar];

            dispatch_async(dispatch_get_main_queue(), ^{
                //return the created image to the main thread.
                cell.avatarImageView.image = avatarImage;
            });

        }];
    }
    else
    {
         cell.avatarImageView.image = avatar;
    }

    return cell;
}

Oto kod testowy, który pokazuje, że działa jako stos:

WaschyBlockStack *stack = [[WaschyBlockStack alloc] init];

for (int i = 0; i < 100; i ++)
{
    [stack addBlock:^{

        NSLog(@"Block operation %i", i);

        sleep(1);

    }];
}

Oto .h:

#import <Foundation/Foundation.h>

@interface WaschyBlockStack : NSObject
{
    NSMutableArray *_blockStackArray;
    id _currentBlock;
}

- (id)init;
- (void)addBlock:(void (^)())block;

@end

I ... m:

#import "WaschyBlockStack.h"

@interface WaschyBlockStack()

@property (atomic, strong) NSMutableArray *blockStackArray;

- (void)startNextBlock;
+ (void)performBlock:(void (^)())block;

@end

@implementation WaschyBlockStack

@synthesize blockStackArray = _blockStackArray;

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

    if (self) 
    {
        self.blockStackArray = [NSMutableArray array];
    }

    return self;
}

- (void)addBlock:(void (^)())block
{

    @synchronized(self.blockStackArray)
    {
        [self.blockStackArray addObject:block];
    }
    if (self.blockStackArray.count == 1) 
    {
        [self startNextBlock];
    }
}

- (void)startNextBlock
{
    if (self.blockStackArray.count > 0) 
    {
        @synchronized(self.blockStackArray)
        {
            id blockToPerform = [self.blockStackArray lastObject];

            [WaschyBlockStack performSelectorInBackground:@selector(performBlock:) withObject:[blockToPerform copy]];

            [self.blockStackArray removeObject:blockToPerform];
        }

        [self startNextBlock];
    }
}

+ (void)performBlock:(void (^)())block
{
    block();
}

@end
 6
Author: Ben M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-26 23:52:16

Prosta metoda, która może być wystarczająco dobra dla Twojego zadania: użyj NSOperation S ' dependencies feature.

Gdy musisz przesłać operację, Pobierz operacje kolejki i wyszukaj ostatnio przesłaną operację (np. wyszukiwanie od końca tablicy), która nie została jeszcze uruchomiona. Jeśli taka istnieje, ustaw ją na zależną od nowej operacji za pomocą addDependency:. Następnie dodaj nową operację.

To buduje odwrotny łańcuch zależności poprzez Nie rozpoczęte operacje, które wymusią je uruchomić seryjnie, last-in-first-out, Jak dostępne. Jeśli chcesz, aby operacje N (> 1) były uruchamiane jednocześnie: znajdź ostatnio dodaną operację N i dodaj do niej zależność. (i oczywiście ustawić maxConcurrentOperationCount na n .) Są skrajne przypadki, w których nie będzie to 100% LIFO, ale powinno być wystarczająco dobre dla jazzu.

(Nie dotyczy to operacji re-priorytetyzacji, jeśli (np.) użytkownik przewija listę w dół, a następnie tworzy kopię zapasową szybciej niż kolejka może wypełnić obrazy. Jeśli chcesz zająć się tą sprawą i dałeś sobie sposób na zlokalizowanie odpowiedniej już-zapytanej-ale-nie-rozpoczętej operacji, możesz usunąć zależności od tej operacji. To skutecznie uderza go z powrotem do "głowy linii". Ale ponieważ czysty "pierwszy-w-pierwszy-out" jest prawie wystarczająco dobry, możesz nie potrzebować tego fantazji.)

[Edytuj aby dodać:]

Zaimplementowałem coś takiego-tabelę użytkowników, ich avatary pobierane z gravatar.com w tle - i ta sztuczka zadziałała świetnie. Poprzedni kod brzmiał:

[avatarQueue addOperationWithBlock:^{
  // slow code
}]; // avatarQueue is limited to 1 concurrent op

Który stał się:

NSBlockOperation *fetch = [NSBlockOperation blockOperationWithBlock:^{
  // same slow code
}];
NSArray *pendingOps = [avatarQueue operations];
for (int i = pendingOps.count - 1; i >= 0; i--)
{
  NSOperation *op = [pendingOps objectAtIndex:i];
  if (![op isExecuting])
  {
    [op addDependency:fetch];
    break;
  }
}
[avatarQueue addOperation:fetch];

Ikony widocznie wypełniają się od góry do dołu w pierwszym przypadku. W drugim ładuje się górny, następnie reszta ładuje się od dołu do góry, a szybkie przewijanie w dół powoduje sporadyczne Ładowanie, a następnie natychmiastowe ładowanie (od dołu) ikon ekranu, na którym się zatrzymujesz. Bardzo slick, o wiele "snappier" czuć do aplikacji.

 4
Author: rgeorge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-26 17:04:23

Nie próbowałem tego-tylko rzucam tam pomysły.

Możesz utrzymać swój własny stos. Dodaj do stosu i kolejkuj do GCD w wątku pierwszoplanowym. Blok kodu, który ustawiasz w kolejce do GCD, po prostu ściąga następny blok ze stosu (sam stos wymaga wewnętrznej synchronizacji dla push & pop) i uruchamia go.

Inną opcją może być po prostu pominięcie pracy, jeśli w kolejce jest więcej niż n pozycji. Oznaczałoby to, że jeśli szybko masz kopię zapasową kolejki, to jeśli przewiń do góry, komórka ponownie użyje kolejki, otrzyma inną komórkę, a następnie ponownie ustawi ją w kolejce, aby załadować obraz. To zawsze będzie priorytetem n ostatnio w kolejce. Nie jestem pewien, w jaki sposób blok w kolejce będzie wiedział o liczbie elementów w kolejce. Może jest na to jakiś sposób? Jeśli nie, możesz mieć licznik threadsafe do zwiększania/zmniejszania. Przyrost przy kolejce, przyrost przy przetwarzam. Jeśli to zrobisz, będę zwiększał i zmniejszał jako pierwsza linia kodu po obu stronach.

Mam nadzieję, że to wywołało jakieś pomysły ... Mogę się nim później pobawić.

 3
Author: bryanmac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-27 11:54:03

Robię coś takiego, ale tylko iPad, i wydawało się, że wystarczająco szybko. NSOperationQueue (lub raw GCD) wydaje się najprostszym podejściem, w którym wszystko może być samodzielne i nie trzeba się martwić o synchronizację. Możesz również zapisać ostatnią operację i użyć setQueuePriority:, aby ją obniżyć. Następnie najnowszy zostanie wyciągnięty z kolejki jako pierwszy. Lub przejść przez wszystkie -operations w kolejce i obniżyć ich priorytet. (Prawdopodobnie można to zrobić po zakończeniu każdego z nich, zakładam, że to nadal być znacznie szybsze niż wykonywanie samej pracy.)

 1
Author: David Dunham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-21 22:35:24

Utwórz Bezpieczny stos wątków, używając czegoś takiego jako punktu wyjścia:

@interface MONStack : NSObject <NSLocking> // << expose object's lock so you
                                           // can easily perform many pushes
                                           // at once, keeping everything current.
{
@private
    NSMutableArray * objects;
    NSRecursiveLock * lock;
}

/**
  @brief pushes @a object onto the stack.
  if you have to do many pushes at once, consider adding `addObjects:(NSArray *)`
*/
- (void)addObject:(id)object;

/** @brief removes and returns the top object from the stack */
- (id)popTopObject;

/**
  @return YES if the stack contains zero objects.
*/
- (BOOL)isEmpty;

@end

@implementation MONStack

- (id)init {
    self = [super init];
    if (0 != self) {
        objects = [NSMutableArray new];
        lock = [NSRecursiveLock new];
        if (0 == objects || 0 == lock) {
            [self release];
            return 0;
        }
    }
    return self;
}

- (void)lock
{
    [lock lock];
}

- (void)unlock
{
    [lock unlock];
}

- (void)dealloc
{
    [lock release], lock = 0;
    [objects release], objects = 0;
    [super dealloc];
}

- (void)addObject:(id)object
{
    [self lock];
    [objects addObject:object];
    [self unlock];
}

- (id)popTopObject
{
    [self lock];
    id last = 0;
    if ([objects count]) {
        last = [[[objects lastObject] retain] autorelease];
    }
    [self unlock];
    return last;
}

- (BOOL)isEmpty
{
  [self lock];
  BOOL ret = 0 == [objects count];
  [self unlock];
  return ret;
}

@end

Następnie użyj NSOperation podklasy (lub GCD, jeśli wolisz). możesz udostępnić stos między operacją a klientami.

Więc pusty bit i główny NSOperation są nieco skomplikowanymi sekcjami.

Zacznijmy od pustego bitu. jest to trudne, ponieważ musi być threadsafe:
// adding a request and creating the operation if needed:
{
    MONStack * stack = self.stack;
    [stack lock];

    BOOL wasEmptyBeforePush = [stack isEmpty];
    [stack addObject:thing];

    if (wasEmptyBeforePush) {
        [self.operationQueue addOperation:[MONOperation operationWithStack:stack]];
    }

    [stack unlock];
// ...
}

Główny nsoperation powinien po prostu przejść i wyczerpać stos, tworząc Pula autorelease dla każdego zadania i sprawdzanie anulowania. gdy stos jest pusty lub operacja jest anulowana, cleanup I exit main. w razie potrzeby Klient utworzy nową operację.

Obsługa anulowania wolniejszych żądań (np. sieci lub dysku) może mieć ogromne znaczenie. anulowanie w przypadku operacji, która wyczerpała kolejkę, wymagałoby, aby żądany widok mógł usunąć swoje żądanie, gdy zostanie odebrane (np. do ponownego użycia podczas przewijanie).

Kolejna częsta pułapka: natychmiastowe ładowanie asynchroniczne (np. dodanie operacji do kolejki operacji) obrazu może łatwo obniżyć wydajność. miara.

Jeśli zadanie korzysta z równoległości, Zezwól na wiele zadań w kolejce operacji.

Powinieneś również zidentyfikować zbędne żądania (wyobraź sobie, że użytkownik przewija dwukierunkowo) w kolejce zadań, Jeśli twój program jest w stanie je wyprodukować.

 1
Author: justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-25 04:43:33

Jestem wielkim fanem interfejsu i łatwości obsługi NSOperationQueue, ale potrzebowałem również wersji LIFO. Skończyło się na wdrożeniu wersji LIFO NSOperationQueue tutaj {[5] } to się dla mnie całkiem dobrze trzyma. Naśladuje interfejs NSOperationQueue, ale wykonuje rzeczy w (mniej więcej) kolejności LIFO.

 1
Author: cbrauchli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-03 20:34:39