Giter VIP home page Giter VIP logo

configcat / swift-sdk Goto Github PK

View Code? Open in Web Editor NEW
19.0 5.0 11.0 711 KB

ConfigCat SDK for Swift. ConfigCat is a hosted feature flag service: https://configcat.com. Manage feature toggles across frontend, backend, mobile, desktop apps. Alternative to LaunchDarkly. Management app + feature flag SDKs.

Home Page: https://configcat.com/docs/sdk-reference/ios

License: Other

Swift 99.57% Ruby 0.38% C 0.06%
configcat feature-toggles feature-flags swift ios macos watchos tvos featureflags feature-toggle configuration configuration-management remote-config feature-flag

swift-sdk's Introduction

ConfigCat SDK for Swift

Build Status CocoaPods Supported Platforms Coverage Status Quality Gate Status

ConfigCat SDK for Swift provides easy integration for your application to ConfigCat.

The following device platform versions are supported:

Platform Version
iOS >= 12.0
watchOS >= 4.0
tvOS >= 12.0
macOS >= 10.13
visionOS >= 1.0

Getting started

1. Install the package

  • CocoaPods

    Add the following to your Podfile:

    target '<YOUR TARGET>' do
    pod 'ConfigCat'
    end

    Then, run the following command to install your dependencies:

    pod install
  • Swift Package Manager

    You can add ConfigCat to an Xcode project by adding it as a package dependency.

    https://github.com/configcat/swift-sdk

    If you want to use ConfigCat in a SwiftPM project, it's as simple as adding a dependencies clause to your Package.swift:

    dependencies: [
      .package(url: "https://github.com/configcat/swift-sdk", from: "11.0.3")
    ]
  • Carthage

    Add the following to your Cartfile:

    github "configcat/swift-sdk"
    

    Then, run the carthage update command and then follow the Carthage integration steps to link the framework with your project.

2. Go to the ConfigCat Dashboard to get your SDK Key:

SDK-KEY

3. Import the ConfigCat module to your application

import ConfigCat

4. Create a ConfigCat client instance

let client = ConfigCatClient.get(sdkKey: "#YOUR-SDK-KEY#")

5. Get your setting value

client.getValue(for: "isMyAwesomeFeatureEnabled", defaultValue: false) { isMyAwesomeFeatureEnabled in
    if isMyAwesomeFeatureEnabled {
        doTheNewThing()
    } else {
        doTheOldThing()
    }
}

// or with async/await
let isMyAwesomeFeatureEnabled = await client.getValue(for: "isMyAwesomeFeatureEnabled", defaultValue: false)
if isMyAwesomeFeatureEnabled {
    doTheNewThing()
} else {
    doTheOldThing()
}

6. Close the client on application exit

client.close();

Getting user specific setting values with Targeting

Using this feature, you will be able to get different setting values for different users in your application by passing a User Object to the getValue() function.

Read more about Targeting here.

let user = ConfigCatUser(identifier: "#USER-IDENTIFIER#")
client.getValue(for: "isMyAwesomeFeatureEnabled", defaultValue: false, user: user) { isMyAwesomeFeatureEnabled in
    if isMyAwesomeFeatureEnabled {
        doTheNewThing()
    } else {
        doTheOldThing()
    }
}

Sample/Demo app

Sample iOS app

Polling Modes

The ConfigCat SDK supports 3 different polling mechanisms to acquire the setting values from ConfigCat. After latest setting values are downloaded, they are stored in the internal cache then all requests are served from there. Read more about Polling Modes and how to use them at ConfigCat Docs.

Sensitive information handling

The frontend/mobile SDKs are running in your users' browsers/devices. The SDK is downloading a config.json file from ConfigCat's CDN servers. The URL path for this config.json file contains your SDK key, so the SDK key and the content of your config.json file (feature flag keys, feature flag values, targeting rules, % rules) can be visible to your users. This SDK key is read-only, it only allows downloading your config.json file, but nobody can make any changes with it in your ConfigCat account.

If you do not want to expose the SDK key or the content of the config.json file, we recommend using the SDK in your backend components only. You can always create a backend endpoint using the ConfigCat SDK that can evaluate feature flags for a specific user, and call that backend endpoint from your frontend/mobile applications.

Also, we recommend using confidential targeting comparators in the targeting rules of those feature flags that are used in the frontend/mobile SDKs.

Need help?

https://configcat.com/support

Contributing

Contributions are welcome. For more info please read the Contribution Guideline.

About ConfigCat

ConfigCat is a feature flag and configuration management service that lets you separate releases from deployments. You can turn your features ON/OFF using ConfigCat Dashboard even after they are deployed. ConfigCat lets you target specific groups of users based on region, email or any other custom user attribute.

ConfigCat is a hosted feature flag service. Manage feature toggles across frontend, backend, mobile, desktop apps. Alternative to LaunchDarkly. Management app + feature flag SDKs.

swift-sdk's People

Contributors

adams85 avatar adrianensan avatar barkoded avatar configcat-developer avatar craigsiemens avatar dependabot[bot] avatar hakonk avatar kp-cat avatar lajos88 avatar laliconfigcat avatar mr-sige avatar sigewuzhere avatar z4kn4fein avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

swift-sdk's Issues

Xcode 12 different definitions in different modules build error

Hi,

One of our dependency (CometChatPro) has a namespace conflict in the compile run time. This happens in XCode 12.

The affected class object is

'User' has different definitions in different modules; first difference is definition in module 'CometChatPro.Swift' found super class with type 'AppEntity' User: App

Additionally, there are many developers facing this issue with other pods as well.
https://developer.apple.com/forums/thread/658012

Could you look into this issue as it does not give us confidence in integrating ConfigCat SDK with our project.

Thanks

XCFramework built with Carthage is missing CFBundleIdentifier

Describe the bug

When using carthage to build ConfigCat as an XCFramework, the simulator framework's Info.plist is missing a CFBundleIdentifier.

This causes Xcode to fail installing an app to the simulator with the following error.

Bundle at path ~/Users/neo~/Library/Developer/CoreSimulator/Devices/9DDC0094-839D-45A5-86FC-3E1A6AFFC881/data/Library/Caches/com.apple.mobile.installd.staging/temp.MKcv1j/extracted/Payload/App/Frameworks/ConfigCat.framework did not have a CFBundleIdentifier in its Info.plist
Domain: MIInstallerErrorDomain
Code: 12
User Info: {
    FunctionName = "-[MIBundle _validateWithError:]";
    LegacyErrorString = MissingBundleIdentifier;
    SourceFileLine = 54;
}

To reproduce

carthage build swift-sdk --platform ios --use-xcframeworks
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" Carthage/Build/ConfigCat.xcframework/ios-arm64_armv7/ConfigCat.framework/Info.plist
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" Carthage/Build/ConfigCat.xcframework/ios-arm64_i386_x86_64-simulator/ConfigCat.framework/Info.plist

Expected behavior

After building the XCFramework with carthage, the following two lines should print out the CFBundleIdentifier from the iOS and iOS Simulator framework. They should both have a value but the second one fails with

Print: Entry, ":CFBundleIdentifier", Does Not Exist

Compilation error with Xcode 14.0.1 into Version.swift

Describe the bug

Using Xcode 14.0.1 (14A400) it does appear this compilation error
'let' property 'major' may not be initialized directly; use "self.init(...)" or "self = ..." instead onto Version.swift.

To reproduce

Use ConfigCat as Pod in a project and try to build with Xcode Version 14.0.1 (14A400)

Expected behavior

Build successfully without any errors.

Screenshots

Captura de Tela 2022-10-27 às 15 27 50

How can I disable ConfigCat logging to Xcode debug console?

I see it is using OSLog() for most of the logging, which is good. But it logs too many things like Evaluating rule: or Returning User object: {...}, which clutters up our console log. Is there a way to disable logging to Xcode debug console?

Data race when using Async or AsyncResult

Hi! I think I have found a potential issue related to Async and AsyncResult in the code base. As Async executes submitted completion handlers on its own private queue, there is a potential for data races when manipulating shared resources.

To reproduce

Consider the following test:

import XCTest
@testable import ConfigCat

class CrashAsyncTests: XCTestCase {
    
    func testNotCrashing() {
        let numberOfAttempts = 10
        let expectation = self.expectation(description: "wait for all attempts to mutate dictionary")
        expectation.assertForOverFulfill = true
        expectation.expectedFulfillmentCount = numberOfAttempts
        for i in 0..<numberOfAttempts {
            var sharedResource = ["": ""]
            let async = Async()
            async.apply {
                sharedResource["\(i)"] = "blargh"
                expectation.fulfill()
            }
            async.complete()
        }
        wait(for: [expectation], timeout: .pi)
    }
    
    func testCrashing() {
        let numberOfAttempts = 10
        let expectation = self.expectation(description: "wait for all attempts to mutate dictionary")
        expectation.assertForOverFulfill = true
        expectation.expectedFulfillmentCount = numberOfAttempts
        var sharedResource = ["": ""]
        for i in 0..<numberOfAttempts {
            let async = Async()
            async.apply {
                sharedResource["\(i)"] = "blargh"
                expectation.fulfill()
            }
            async.complete()
        }
        wait(for: [expectation], timeout: .pi)
    }

}

When running the test above with thread sanitizer enabled, one can indeed observe a data race:

Skjermbilde 2022-07-14 kl  15 32 43

In and of itself, one can stipulate that Async inherently is not thread safe, which is not necessarily problematic. The issue arises when one is manipulating a shared resources via different instances of Async or AsyncResult.

One example of this, can be found in the way AutoPollingPolicy.writeConfigCache|readConfigCache are called in closures passed to instances of AsyncResult. See AutoPollingPolicy:59:

timer.setEventHandler(handler: { [weak self] in
            guard let `self` = self else {
                return
            }

            if self.fetcher.isFetching() {
                self.log.debug(message: "Config fetching is skipped because there is an ongoing fetch request")
                return;
            }

            self.fetcher.getConfiguration()
                .apply(completion: { response in
                    let cached = self.readConfigCache()
                    if let config = response.config, response.isFetched() && config.jsonString != cached.jsonString {
                        self.writeConfigCache(value: config) // <- writing to property on Async's private queue
                        self.onConfigChanged?()
                    }
                    
                    if !self.initialized.getAndSet(new: true) {
                        self.initResult.complete()
                    }
                })
        })

and in AutoPollingPolicy:95:

public override func getConfiguration() -> AsyncResult<Config> {
        if self.initResult.completed {
            return self.readCacheAsync()
        }
        
        return self.initResult.apply(completion: {
            return self.readConfigCache() // <- reading from property on Async's private queue
        })
    }

While access to properties are enforced some places in the code base with Synced, in the said examples the code is inherently not thread safe. It is perhaps unfortunate that a by-effect of Async's implementation leads to dispatching code on otherwise inaccessible queues to code from the outside, such that synchronization is needed.

Another issue is that AsyncResult itself is not thread safe. In AsyncResult:96:

public func complete(result: Value) {
        self.result = result // <- assignment on the caller's queue
        super.complete()
    }

we can see that self.result is assigned the argument of complete(result:). As it is not private, the method will be called on the caller's queue. In AsyncResult:122:

@discardableResult
    public func apply(completion: @escaping (Value) -> Void) -> AsyncResult<Value> {
        self.queue.async {
            if self.state.get().isCompleted() {
                guard let result = self.result else { // <- reading self.result on private queue
                    assert(false, "completion handlers executed on an incomplete AsyncResult")
                    return
                }
                
                completion(result)
            } else {
                self.completions.append({
                    guard let result = self.result else {
                        assert(false, "completion handlers executed on an incomplete AsyncResult")
                        return
                    }
                    
                    completion(result)
                })
            }
        }
        
        return self
    }

Thus, it is evident there is a potential for accessing self.result on different queues.

Conversely, the property Async.state is wrapped in a thread safe container / actor. However, since this is a private property, and it is seemingly only accessed on Async.queue, it never had to be wrapped in synchronization code in the first place.

Hank Risk when building with Xcode 14.3

Describe the bug

Xcode 14.3 started warning about this possible hang risk:

Thread running at QOS_CLASS_USER_INTERACTIVE waiting on a lower QoS thread running at QOS_CLASS_DEFAULT. Investigate ways to avoid priority inversions.

It happens in ConfigCatClient.getValueSync(for:defaultValue:user:):167, in the semaphore.wait() call.

To reproduce

Compile the app using Xcode 14.3 and run it.

Expected behavior

No warnings about possible thread hangs.

Screenshots

image

Apple M1 arm64-simulator support

Describe the bug

Run app on simulator with ConfigCat pod and building fails. Missing architecture arm64-simulator

To reproduce

Using the latest version of Xcode (12.5.1) on most recent's Apple MacBook Pro (processor Apple M1), install ConfigCat (7.2.0) install using Cocoapods (1.11.0)

  1. Attempt to Build
  2. Observe error: Framework not found ConfigCat

Expected behavior

Builds successfully with no errors or warnings

SDK version

ConfigCat (7.2.0)

Platform

MacBook Pro (13-inch, M1, 2020)
macOS Big Sur 11.5.2 (20G95)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.