     /// items from memory.
     public class Backend<T: CacheCostCalculable> {
         let storage = NSCache<NSString, StorageObject<T>>()
+
+        // Keys trackes the objects once inside the storage. For object removing triggered by user, the corresponding
+        // key would be also removed. However, for the object removing triggered by cache rule/policy of system, the
+        // key will be remained there until next `removeExpired` happens.
+        //
+        // Breaking the strict tracking could save additional locking behaviors.
+        // See https://github.com/onevcat/Kingfisher/issues/1233
         var keys = Set<String>()
 
-        var cleanTimer: Timer? = nil
-        let lock = NSLock()
-
-        let cacheDelegate = CacheDelegate<StorageObject<T>>()
+        private var cleanTimer: Timer? = nil
+        private let lock = NSLock()
 
         /// The config used in this storage. It is a value you can set and
         /// use to config the storage in air.

            storage.removeAllObjects()
            keys.removeAll()
        }

        class CacheDelegate<T>: NSObject, NSCacheDelegate {
            let onObjectRemoved = Delegate<T, Void>()
            func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject obj: Any) {
                if let obj = obj as? T {
                    onObjectRemoved.call(obj)
                }
            }
        }
    }
}


            for key in keys {
                let nsKey = key as NSString
                guard let object = storage.object(forKey: nsKey) else {
                    // This could happen if the object is moved by cache `totalCostLimit` or `countLimit` rule.
                    // We didn't remove the key yet until now, since we do not want to introduce additonal lock.
                    // See https://github.com/onevcat/Kingfisher/issues/1233
                    keys.remove(key)
                    continue
                }
