Simple utility for only executing code every so often.

Overview

Rate Limit

Version Build Status Swift Version Carthage compatible CocoaPods compatible

Simple utility for only executing code every so often.

This will only execute the block passed for a given name if the last time it was called is greater than limit or it has never been called.

This is really handy for refreshing stuff in viewDidAppear: but preventing it from happening a ton if it was just refreshed.

Rate Limit is fully thread-safe. Released under the MIT license.

Usage

We’ll start out with a TimedLimiter:

// Initialize with a limit of 5, so you can only use this once every 5 seconds.
let refreshTimeline = TimedLimiter(limit: 5)

// Call the work you want to limit by passing a block to the execute method.
refreshTimeline.execute {
    // Do some work that runs a maximum of once per 5 seconds.
}

Limiters aren’t persisted across application launches.

Synchronous Limiters

TimedLimiter conforms to the SyncLimiter protocol. This means that the block you pass to execute will be called synchronously on the queue you called it from if it should fire. TimedLimiter uses time to limit.

CountedLimiter is also included. This works by taking a limit as a UInt for the maximum number of times to run the block.

The SyncLimiter protocol has a really neat extension that let’s you do things like this:

let funFactLimiter = CountedLimiter(limit: 2)
let funFact = funFactLimiter.execute { () -> String in
    // Do real things to get a fun fact from a list
    return "Hi"
}

Now funFact is a String?. It’s just an optional of whatever you return from the block. The returned value will be nil if the block didn’t run.

You can of course make your own SyncLimiters too!

Asynchronous Limiter

One AsyncLimiter is included. You can make your own too. The included async limiter is DebouncedLimiter. This is perfect for making network requests as a user types or other tasks that respond to very frequent events.

The interface is slightly different:

let searchLimiter = DebouncedLimiter(limit: 1, block: performSearch)

func textDidChange() {
  searchLimiter.execute()
}

You would have to setup the limiter in an initializer since it references an instance method, but you get the idea. The block will be called at most once per second in this configuration.

Pretty easy!

Open up the included Xcode project for an example app and tests.

Installation

Carthage

Carthage is the recommended way to install Rate Limit. Add the following to your Cartfile:

github "soffes/RateLimit"

CocoaPods

Add the following to your Podfile:

pod "RateLimit"

Then run pod install.

Comments
  • Multiple limiters

    Multiple limiters

    Instead of a single, global timed rate limiter, make single limiters per thing. This will be version 2.0.0.

    Upgrading

    Here's a comparison of the current API and the new API:

    // Current
    RateLimit.execute(name: "timeline", limit: 5) {
        refresh()
    }
    
    // Proposed
    let timeline = TimedLimiter(limit: 5)
    timeline.execute {
        refresh()
    }
    

    I'm also adding a wrapper for backwards compatibility that will be in there until 2.1.0.

    New Limiters

    In addition to the old style timed limiter, new limiters will be much easier due to the new Limiter protocol:

    public protocol Limiter {
        func execute(_ block: () -> Void)
        func reset()
    }
    

    Here's one for saying something can run a set number of times:

    let tutorialPopOver = CountedLimiter(limit: 3)
    tutorialPopOver.execute {
        showSomeBigAnnoyingAnimation()
    }
    

    I'd also love to make one that debounces. This would be especially useful for stuff like search as you type and other applications.

    opened by soffes 16
  • RateLimit is not Bitcode compatible

    RateLimit is not Bitcode compatible

    This one is hard to explain. If I upload a build that both contains RateLimit and has Bitcode enabled I get this:

    screen shot 2015-11-25 at 11 31 07 pm

    Thanks to @mattbischoff, I found the following message in this developer forum thread:

    To see the error, achive/export/developer enable bitcode gen. Then when it errors, click the 'view logs' button to see the problem.

    And indeed there it was:

    error: Ld: cd /var/folders/1g/fssqm74d49qb4m09k14h_8780000gn/T/tempdVAWUo
    "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld" "-arch" "armv7" "-dylib" "-compatibility_version" "1.0.0" "-current_version" "1.0.0.0.0" "-install_name" "@rpath/RateLimit.framework/RateLimit" "-ios_version_min" "8.0.0" "-rpath" "@executable_path/Frameworks" "-rpath" "@loader_path/Frameworks" "-dead_strip" "-application_extension" "-syslibroot" "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.1.sdk" "-sdk_version" "9.1.0" "-ignore_auto_link" "-allow_dead_duplicates" "1.o" "2.o" "3.o" "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.1.sdk/usr/lib/libSystem.B.tbd" "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.1.sdk/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation.tbd" "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.1.sdk/System/Library/Frameworks/Foundation.framework/Foundation.tbd" "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.1.sdk/usr/lib/libobjc.A.tbd" "/var/folders/1g/fssqm74d49qb4m09k14h_8780000gn/T/ipatool20151125-21309-1yedmou/MachOs/iphoneos/armv7/(dylibs)/libswiftCore.dylib" "/var/folders/1g/fssqm74d49qb4m09k14h_8780000gn/T/ipatool20151125-21309-1yedmou/MachOs/iphoneos/armv7/(dylibs)/libswiftDarwin.dylib" "/var/folders/1g/fssqm74d49qb4m09k14h_8780000gn/T/ipatool20151125-21309-1yedmou/MachOs/iphoneos/armv7/(dylibs)/libswiftDispatch.dylib" "/var/folders/1g/fssqm74d49qb4m09k14h_8780000gn/T/ipatool20151125-21309-1yedmou/MachOs/iphoneos/armv7/(dylibs)/libswiftFoundation.dylib" "/var/folders/1g/fssqm74d49qb4m09k14h_8780000gn/T/ipatool20151125-21309-1yedmou/MachOs/iphoneos/armv7/(dylibs)/libswiftObjectiveC.dylib" "-L" "/var/folders/1g/fssqm74d49qb4m09k14h_8780000gn/T/ipatool20151125-21309-1yedmou/MachOs/iphoneos/armv7/(dylibs)" "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/7.0.0/lib/darwin/libclang_rt.ios.a" "-o" "/private/var/folders/1g/fssqm74d49qb4m09k14h_8780000gn/T/RateLimitkGMPU9/RateLimit.armv7.out" 
    -= Output =-
    Undefined symbols for architecture armv7:
      "_llvm_gcov_init", referenced from:
          ___hidden#311_ in 3.o
      "_llvm_gcda_start_file", referenced from:
          ___hidden#309_ in 3.o
      "_llvm_gcda_summary_info", referenced from:
          ___hidden#309_ in 3.o
      "_llvm_gcda_end_file", referenced from:
          ___hidden#309_ in 3.o
    ld: symbol(s) not found for architecture armv7
    Exited with 1
    
    
    error: Failed to compile bundle: /var/folders/1g/fssqm74d49qb4m09k14h_8780000gn/T/RateLimitkGMPU9/RateLimit.armv7.xar}
    

    I'm now trying to figure out exactly what these symbols are and why they are allegedly referenced from RateLimit. I will post back here when I know more.

    opened by calebd 15
  • Defer execution until end of limit

    Defer execution until end of limit

    I think this library is a really great idea, but I have one question about how this would work.

    I would like to use RateLimit within updateSearchResultsForSearchController(), as is shown in Little Bytes Of Cocoa 81. http://i.imgur.com/srdLoLr.png

    My problem is, though I don't want to call the search request too often, if the user STOPS typing then the request should be made no matter what. I don't want to have the user type in My Search Term but the last network request was with the query My Sea. Their results would be pretty off in that case.

    Does RateLimit already implement anything to handle this scenario? I'm thinking of maybe having an option where, if at any point DURING the limited time execute is called again, then the call will be deferred until the end of the limit time. It would only execute once at the end of the limit time, no matter how many times execute was called during that time.

    Is this already implemented? If not, is it a viable addition?

    opened by AnthonyMDev 9
  • Possible bug?

    Possible bug?

    Line 67 in RateLimit.swift:

    // Record execution
    dictionary[name] = NSDate()
    

    You record new execution time even if decide to not execute? Is that right?

    opened by dmrev 9
  • Fixed bug where limit was being reset when the block was not executed

    Fixed bug where limit was being reset when the block was not executed

    When the execute method is called, but the block was not called, because the rate limit had not been reached, the execution time was still being recorded. This would cause the block to never be called if the execute method was continually called too quickly. The execution time should only be recorded on a successful execution.

    opened by AnthonyMDev 2
  • Thoughts on adding a simple macro based solution (for non-persisted)?

    Thoughts on adding a simple macro based solution (for non-persisted)?

    Macro

    #define RateLimit(SECS) \
    ({ \
        static NSDate *lastCall = nil; \
        ^(dispatch_block_t block) { \
            @synchronized(lastCall) { \
                if (!lastCall || fabs(lastCall.timeIntervalSinceNow) > SECS) { \
                    lastCall = NSDate.date; \
                    block(); \
                } \
            } \
        }; \
    })
    

    Usage

    RateLimit(60.0)(^{
        // Do some work that runs a maximum of once per minute
    });
    
    opened by mhuusko5 2
  • README: explain how to install using cocoapods

    README: explain how to install using cocoapods

    When I'm checking out a new OS project for Xcode, one of the first things I want to know is if it's set up as a Pod. So I made the "installation" section I little more explanatory.

    opened by jjulian 1
  • TVOS Support?

    TVOS Support?

    Any plan to add it?

      spec.osx.deployment_target = '10.9'
      spec.ios.deployment_target = '8.0'
      spec.watchos.deployment_target = '2.0'
    

    Of course not having access to store we can't just add it there, maybe add it as key-value storage in iCloud or something similar?

    opened by valeIT 1
  • Update Lookup date only when ShouldExecute is true

    Update Lookup date only when ShouldExecute is true

    I was reading the class that implements the validation logic to run the rate limit. While reading the class method below:

    private class func shouldExecute(name name: String, limit: NSTimeInterval) -> Bool

    I've notice the lookup date is updated regardless if 'should' is true or false. I would expect the last executed date to be updated when the limit has passed. That way, subsequent request could refer to the original date instead of the last checked date.

    Sorry for not making a pull request.

    opened by xr1337 1
  • Not sure this be useful, but...

    Not sure this be useful, but...

    What about CFTimeInterval and CACurrentMediaTime() instead of NSDate?

    private static var executionTimeByName = [String: CFTimeInterval]()
    ...
    if let lastExecutedTime = executionTimeByName[name] {
                let elapsed = CACurrentMediaTime() - lastExecutedTime
                shouldExecute = elapsed > limit
    }
    ...
    
    opened by dmrev 1
  • Refixed bug that was a bug and now has tests to show it

    Refixed bug that was a bug and now has tests to show it

    I was using the latest version of the library and it wasn't working as expected. Calling RateLimit.execute() several times, and most notably, just before the time would normally expire, resets the execution time stored in the dictionary and, thus, the block is not executed. I've updated the test so it passes with this change and fails with the previous code.

    opened by troya2 0
  • Adding block parameter to DebouncedLimiter.execute?

    Adding block parameter to DebouncedLimiter.execute?

    Great library btw, thanks for sharing this!

    I was wondering what the thoughts were behind not providing a block parameter to the execute function of the DebouncedLimiter like TimedLimiter? I wanted to do something like this:

    let refreshTimeline = DebouncedLimiter(limit: 5)
    
    refreshTimeline.execute {
        // Do some work that only runs last block per 5 seconds.
    }
    

    I have extended this in my fork to do this, but wanted to get your thoughts before creating a PR in case there's something I'm missing: https://github.com/basememara/RateLimit/commit/763a1b60fdc5884b70540cbb1c58aff6874a68b9

    opened by basememara 1
  • Expose execute function to Objective-C API's

    Expose execute function to Objective-C API's

    Allows encapsulation of debounce logic in lazy loaded properties and feed into legacy Objective-C API's like NotificationCenter, see #selector(refreshProgress.execute):

    class ViewController: UIViewController {
    	
        lazy var refreshProgress: DebouncedLimiter = {
            return DebouncedLimiter(limit: 3) { [weak self] in
                self?.debouncedExecutionCount += 1
            }
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            NotificationCenter.default.addObserver(refreshProgress,
                selector: #selector(refreshProgress.execute),
                name: .UIApplicationDidBecomeActive,
                object: nil
            )
        }
    }
    
    opened by basememara 0
Releases(v2.1.1)
Owner
Sam Soffes
Building things
Sam Soffes
Protocols for your every day iOS needs

Standard Template Protocols Essential protocols for your every day iOS needs Example UIGestureRecognizerProtocols About Swift 2.0 opens a world of opp

Chris 382 Sep 23, 2022
A declarative, thread safe, and reentrant way to define code that should only execute at most once over the lifetime of an object.

SwiftRunOnce SwiftRunOnce allows a developer to mark a block of logic as "one-time" code – code that will execute at most once over the lifetime of an

Thumbtack 8 Aug 17, 2022
OwO.swift Badges go here in one line for the master branch ONLY.

OwO.swift Badges go here in one line for the master branch ONLY. Badges can also go in the header line. Short description describing the application/l

Spotlight 2 May 28, 2022
Simple utility to change macOS Big Sur menu bar color by appending a solid color or gradient rectangle to a wallpaper image

Change menu bar color in macOS Big Sur Simple utility to change macOS Big Sur menu bar color by appending a solid color or gradient rectangle to a wal

Igor Kulman 876 Jan 5, 2023
A simple Swift utility for producing pseudolocalized strings.

Build your App UI to adapt and respond to translations, and find localization bugs!

Reece Como 2 Sep 28, 2021
A simple macOS utility that can be used to control the behaviour of Bose QC35 Headphones straight from the menu bar.

bose-macos-utility A simple macOS utility that can be used to control the behaviour of Bose QC35 Headphones straight from the menu bar. Why Have you e

Łukasz Zalewski 11 Aug 26, 2022
A simple utility allowing to detect Swift version at runtime.

SwiftVersionDetector SwiftVersionDetector allows you to detect Swift version at runtime. Note that detecting the Swift version of the machine on which

Alessandro Venturini 2 Dec 3, 2022
A utility that reminds your iPhone app's users to review the app written in pure Swift.

SwiftRater SwiftRater is a class that you can drop into any iPhone app that will help remind your users to review your app on the App Store/in your ap

Takeshi Fujiki 289 Dec 12, 2022
A Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and other native frameworks.

ZamzamKit ZamzamKit is a Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and othe

Zamzam Inc. 261 Dec 15, 2022
macOS utility for converting fat-frameworks to SPM-compatible XCFramework with arm64-simulator support

xcframework-maker macOS utility for converting fat-frameworks to SPM-compatible XCFramework with arm64-simulator support. ?? Description make-xcframew

Dariusz Rybicki 312 Dec 22, 2022
A utility to generate PreviewDevice presets from the available devices

SwiftUIGen A utility to generate PreviewDevice presets from the available devices Installation Manual Go to the GitHub page for the latest release Dow

Timberlane Labs 29 Nov 14, 2022
Utility to run the SPI-Server tests as a benchmark

spi-benchmark This package comprises a simple tool to run the SwiftPackageIndex-Server tests in a loop, logging the run times. The purpose is to colle

Swift Package Index 2 Mar 13, 2022
A tiny macOS utility to foster sustainable OSS

SustainableOSS SustainableOSS is a status bar app for macOS that indexes the third-party dependencies your projects depend on, sorts them by usage, an

Tuist 0 Dec 14, 2021
Utility functions for validating IBOutlet and IBAction connections

Outlets Utility functions for validating IBOutlet and IBAction connections. About Outlets provides a set of functions which validate that IBOutlets ar

Ben Chatelain 129 May 2, 2022
Super powerful remote config utility written in Swift (iOS, watchOS, tvOS, OSX)

Mission Control Super powerful remote config utility written in Swift (iOS, watchOS, tvOS, OSX) Brought to you by Have you ever wished you could chang

appculture 113 Sep 9, 2022
Async+ for Swift provides a simple chainable interface for your async and throwing code, similar to promises and futures

Async+ for Swift provides a simple chainable interface for your async and throwing code, similar to promises and futures. Have the best of both worlds

async_plus 132 Jan 6, 2023
Generate Markdown documentation from source code

SourceDocs SourceDocs is a command line tool that generates markdown documentation files from inline source code comments. Similar to Sphinx or Jazzy,

Eneko Alonso 349 Dec 10, 2022
All the reusable code that we need in each project

SwiftyUtils SwiftyUtils groups all the reusable code that we need to ship in each project. This framework contains: Extensions Protocols Structs Subcl

Tom Baranes 529 Dec 25, 2022
Swift code to programmatically execute local or hosted JXA payloads without using the on-disk osascript binary

Swift code to programmatically execute local or hosted JXA payloads without using the on-disk osascript binary. This is helpful when you have Terminal access to a macOS host and want to launch a JXA .js payload without using on-disk osascript commands.

Cedric Owens 20 Sep 27, 2022