Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Helps you define secure storages for your properties using Swift property wrappers.

License

NotificationsYou must be signed in to change notification settings

alexruperez/SecurePropertyStorage

Repository files navigation

Helps you define secure storages for your properties using Swiftproperty wrappers.

TwitterSwiftLicenseSwift Package ManagerCarthageBitriseMaintainabilitycodecovQuality

🌟 Features

All keys are hashed usingSHA512 and all values are encrypted usingAES-GCM to keep user information safe, automagically.Symmetric key is stored in Keychain in a totally secure way.

🐒 Basic usage

@UserDefault

This property wrapper will store your property inUserDefaults usingStoreKey (anyString but i recommend you a String typed enum).Optionally, you can assign a default value to the property that will be secure stored at initialization.

@UserDefault(<#StoreKey#>)varyourProperty:YourType?= yourDefaultValueIfNeeded

UserDefaultsStorage is also available, a subclass ofUserDefaults with all the security provided by this library, where you can customize suite name.

@Keychain

This property wrapper will store your property inKeychain usingStoreKey.

@Keychain(<#StoreKey#>)varyourProperty:YourType?= yourDefaultValueIfNeeded

AsUserDefaultsStorage,KeychainStorage is also available, where you can customize access, group and synchronize it with iCloud.

@Singleton

This property wrapper will store your property in a memorysingleton, every property with the same wrapper and key can access or modify the value from wherever it is.

@Singleton(<#StoreKey#>)varyourProperty:YourType?= yourDefaultValueIfNeeded

AsKeychainStorage,SingletonStorage is also available.

@Inject

This property wrapper is similar to@Singleton but, together with@Register, will inject your dependencies. More details inDependency Injection usage guide.

@InjectvaryourDependency:YourProtocol?

AsSingletonStorage,InjectStorage is also available.

@Store

This is a custom wrapper, you can define your ownStorage protocol implementation.

@Store(<#YourStorage#>,<#StoreKey#>)varyourProperty:YourType?= yourDefaultValueIfNeeded

AsInjectStorage,DelegatedStorage is also available with all the magic of this library.

🧙‍♂️ Codable usage

If your property conformsCodable protocol, just addCodable keyword as prefix of your property wrapper.

  • @CodableUserDefault
  • @CodableKeychain
  • @CodableSingleton
  • @CodableStore

🥡 Unwrapped usage

To avoid continually unwrapping your property, just addUnwrapped keyword as prefix of your property wrapper, assign a default value (mandatory except for@UnwrappedInject), and it will return stored value or default value, but your property will always be there for you.

  • @UnwrappedUserDefault
  • @UnwrappedKeychain
  • @UnwrappedSingleton
  • @UnwrappedInject
  • @UnwrappedStore

🥡 + 🧙‍♂️ Combo usage

You can also combine previous cases in case you need it, unwrapped first please.

  • @UnwrappedCodableUserDefault
  • @UnwrappedCodableKeychain
  • @UnwrappedCodableSingleton
  • @UnwrappedCodableStore

💉 Dependency Injection usage

@Register (click to expand)

This property wrapper will register the implementations of your dependencies.Register them wherever you want before inject it, but be sure to do it only once (except if you use qualifiers), for example, in anInjector class.You can register through a protocol or directly using your class implementation.

@RegistervaryourDependency:YourProtocol=YourImplementation()@RegistervaryourDependency=YourImplementation()

You can also define a closure that builds your dependency.Just remember cast your dependency if you are going to inject it through a protocol.

@RegistervaryourDependency={YourImplementation()asYourProtocol}@RegistervaryourDependency={YourImplementation()}

You can also register your dependencies only after the moment someone tries to inject them and you haven't registered them yet,for this you can use the error closure.

InjectStorage.standard.errorClosure={ errorinif caseInjectError.notFound= error{YourImplementation.register()}}

You can get this syntactic sugar because you can now use property wrappers in function parameters.

staticfunc register(@Register yourDependency:YourProtocol=YourImplementation()){}
@Inject and@UnwrappedInject (click to expand)

These property wrappers injects your dependencies@Register implementations.

@InjectvaryourDependency:YourProtocol?@InjectvaryourDependency:YourImplementation?@UnwrappedInjectvaryourUnwrappedDependency:YourProtocol@UnwrappedInjectvaryourUnwrappedDependency:YourImplementation

Scope

Because these property wrappers works similarly to@Singleton, the default scope is.singleton, but if you use builder closures on@Register, you can modify them to inject a single instance.

@Inject(.instance)varyourDependency:YourProtocol?@UnwrappedInject(.instance)varyourUnwrappedDependency:YourProtocol
@InjectWith and@UnwrappedInjectWith (click to expand)

Your dependency may need parameters when injecting, you can pass them with these property wrappers.Simply define a model with your dependency parameters and pass it.It will inject a new instance built with these parameters.

@RegistervaryourDependency={ parametersinYourImplementation(parameters)asYourProtocol}@Inject(YourParameters())varyourDependency:YourProtocol?@UnwrappedInject(YourParameters())varyourUnwrappedDependency:YourProtocol
Qualifiers (click to expand)

You can usequalifiers to provide various implementations of a particular dependency. A qualifier is just a@objc protocol that you apply to aclass.

For example, you could declareDog andCat qualifier protocols and apply it to another class that conformsAnimal protocol. To declare this qualifier, use the following code:

protocolAnimal{func sound()}@objcprotocolDog{}@objcprotocolCat{}

You can then define multiple classes that conformsAnimal protocol and uses this qualifiers:

classDogImplementation:Animal,Dog{func sound(){print("Woof!")}}classCatImplementation:Animal,Cat{func sound(){print("Meow!")}}

Both implementations of the class can now be@Register:

@RegistervarregisterDog:Animal=DogImplementation()@RegistervarregisterCat:Animal=CatImplementation()

To inject one or the other implementation, simply add the qualifier(s) to your@Inject:

@UnwrappedInject(Dog.self)vardog:Animal@UnwrappedInject(Cat.self)varcat:Animaldog.sound() // prints Woof!cat.sound() // prints Meow!
Testing (click to expand)

One of the advantages of dependency injection is that the code can be easily testable with mock implementation.That is why there is aMock qualifier that has priority over all, so you can have your dependencies defined in the app and create your mock in the test target simply by adding this qualifier.

// App targetclassYourImplementation:YourProtocol{}@RegistervaryourDependency:YourProtocol=YourImplementation()@InjectvaryourDependency:YourProtocol?
// Test targetclassYourMock:YourProtocol,Mock{}@RegistervaryourDependency:YourProtocol=YourMock()
Groups (click to expand)

When you havea lot of dependencies in your app, you may want to optimize dependency resolution.

You can group them using@Register(group:) and aDependencyGroupKey:

@Register(group:<#DependencyGroupKey#>)varyourDependency:YourProtocol=YourImplementation()

@Inject(group:) will look for those dependencies only in that group:

@Inject(group:<#DependencyGroupKey#>)varyourDependency:YourProtocol?

🔒 Concurrency andSendable

This library is designed with Swift's modern concurrency features in mind. Access to the underlying storage mechanisms (UserDefaults, Keychain, etc.) is serialized through a global actor (StorageActor), preventing data races during read and write operations performed by the library itself.

However, for your application to be fully concurrency-safe when using this library, it is crucial thatthe types you store and retrieve areSendable.

  • Generic Types: When using generic property wrappers or methods like@Store var item: MyType?,storage.value(forKey: "key") as? MyType, orstorage.decodable(forKey: "key") as? MyDecodableType, ensure thatMyType andMyDecodableType conform to theSendable protocol.
  • Object Archiving: If you use methods that rely onNSKeyedArchiver (such asstorage.set(object: myNSObject, forKey: "key")), the classmyNSObject should conform toNSSecureCoding for security and also beSendable for concurrency safety.
  • Why isSendable important?: While the library ensures that the act of storing or retrieving data is safe, if the data itself (e.g., a class instance) is notSendable, then passing it across different concurrent tasks or actors after retrieval can lead to data races if the data is mutable. Conforming toSendable helps Swift enforce that such types can be safely shared.

Enabling strict concurrency checking in your project's build settings (e.g., "Strict Concurrency Checking" set to "Complete" in Xcode) is highly recommended to help identify potential concurrency issues at compile time.

👀 Examples

Talk is cheap. Show me the code.

    // Securely stored in UserDefaults.@UserDefault("username")varusername:String?    // Securely stored in Keychain.@Keychain("password")varpassword:String?    // Securely stored in a Singleton storage.@Singleton("sessionToken")varsessionToken:String?    // Securely stored in a Singleton storage.    // Always has a value, the stored or the default.@UnwrappedSingleton("refreshToken")varrefreshToken:String="B0610306-A33F"structUser:Codable{letusername:Stringletpassword:String?letsessionToken:String?}    // Codable model securely stored in UserDefaults.@CodableUserDefault("user")varuser:User?

🛠 Compatibility

  • macOS 11.5+
  • iOS 13.0+
  • iPadOS 13.0+
  • tvOS 13.0+
  • watchOS 6.0+
  • visionOS 1.0+

⚙️ Installation

You can use theSwift Package Manager by declaring SecurePropertyStorage as a dependency in yourPackage.swift file:

.package(url:"https://github.com/alexruperez/SecurePropertyStorage", from:"0.7.1")

By default, all property wrappers are installed and you canimport them, but if you want, you can install only some of them:

  • UserDefault: @*UserDefault property wrappers.
  • Keychain: @*Keychain property wrappers.
  • Singleton: @*Singleton property wrappers.
  • Storage: @*Store property wrappers.
  • Inject: @*Inject property wrappers.

For more information, seethe Swift Package Manager documentation.

Or you can useCarthage:

github "alexruperez/SecurePropertyStorage"

🍻 Etc.

👨‍💻 Author

Alex Rupérez –@alexruperezme@alexruperez.com

👮‍♂️ License

SecurePropertyStorage is available under the MIT license. See theLICENSE file for more info.

About

Helps you define secure storages for your properties using Swift property wrappers.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

No packages published

Contributors5


[8]ページ先頭

©2009-2025 Movatter.jp