ComponentTextKit/TextKit/CKTextKitRendererCache.h (90 lines of code) (raw):

/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import <ComponentKit/CKDefines.h> #if CK_NOT_SWIFT #import <Foundation/Foundation.h> #import <ComponentTextKit/CKCacheImpl.h> #import <ComponentTextKit/CKTextKitAttributes.h> @class CKTextKitRenderer; namespace CK { namespace TextKit { /* These are wrapper functions for CFNotificationCenter to call into the cache with low memory and backgrounding notifications. */ void lowMemoryNotificationHandler(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo); void enteredBackgroundNotificationHandler(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo); struct ApplicationObserver { std::function<void (void)>onLowMemory; std::function<void (void)>onEnterBackground; ApplicationObserver(std::function<void (void)> lowMem, std::function<void (void)>bg) : onLowMemory(lowMem), onEnterBackground(bg) { // We use CFNotificationCenter here so that we can avoid creating an NSObject and registering it *just* to // receive low memory and backgrounding notifications. CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, &lowMemoryNotificationHandler, (__bridge CFStringRef)UIApplicationDidReceiveMemoryWarningNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately); CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, &enteredBackgroundNotificationHandler, (__bridge CFStringRef)UIApplicationDidEnterBackgroundNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately); }; ApplicationObserver() { CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), this, (__bridge CFStringRef)UIApplicationDidReceiveMemoryWarningNotification, NULL); CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), this, (__bridge CFStringRef)UIApplicationDidEnterBackgroundNotification, NULL); }; }; namespace Renderer { /** This cache key is conceptually different from the TextComponent Attributes. It must contain everything related to the actual drawing of the text, which may include additional parameters. */ struct Key { CKTextKitAttributes attributes; CGSize constrainedSize; Key(CKTextKitAttributes a, CGSize cs); size_t hash; bool operator==(const Key &other) const { // These comparisons are in a specific order to reduce the overall cost of this function. return hash == other.hash && CGSizeEqualToSize(constrainedSize, other.constrainedSize) && attributes == other.attributes; } }; struct KeyHasher { size_t operator()(const Key &k) const { return k.hash; } }; /* This is a thin wrapper around a c++ cache and a mutex. It wraps the bare minimum of calls we need for this case with a simple mutex, and observes for memory warnings and backgrounding notifications so that we compact or evict the cache. These caches are very useful for: 1. Performance of text layout. You can cache CKTextKitRenderer objects which store very expensive to compute TextKit layout artifacts. The cache is in C++ so we can use stack-allocated keys to query the cache and avoid additional mallocs just to fetch a pre-built renderer or raster buffers. 2. Performance of text rendering. You can cache the raster CGImageRefs or UIImages generated by a renderer object using a cache and not redraw commonly used text (think "Like" button text...). 3. Memory profile. Text artifacts and raster buffers are LARGE. The best practice to reduce overall memory load is actually not to hold onto your renderer objects directly, but instead just hold onto CKTextKitAttributes struct and query a renderer or raster cache when the results are needed. Since these caches can maintain a central, threadsafe data structure of all artifacts it can use a LRU policy to evict less active artifacts over the lifetime of the application. What this means is that you get a small, stable memory footprint of your text in your application, no matter how many different text elements you may be drawing. The maximum cost factor should be tuned based on which artifacts you're storing in this cache. If you are storing raster buffers then it should likely be a couple MB. If you are storing renderers it's a good idea to have it related to the visible length of the string (as a proxy for number of glyph artifacts). For an example of usage please see CKTextComponent. */ struct Cache { private: CK::ConcurrentCacheImpl<const Key, id, KeyHasher> cache; ApplicationObserver *applicationObserver; public: Cache(const std::string cacheName, const NSUInteger maxCost, const CGFloat compactionFactor) : cache(cacheName, maxCost, compactionFactor) { applicationObserver = new ApplicationObserver([this] { compact(0.95); }, [this] { removeAllObjects(); }); }; ~Cache() { delete applicationObserver; } void cacheObject(const Key &key, id object, size_t cost) { cache.insert(key, object, cost); } id objectForKey(const Key &key) { return cache.find(key); } void compact(double compactionFactor) { cache.compact(compactionFactor); } void removeAllObjects() { cache.removeAllObjects(); } }; }; }; }; #endif