From 170de19d47e247fb933d826691b7575ec5839f8f Mon Sep 17 00:00:00 2001 From: Nikita Vasilev Date: Tue, 13 Feb 2024 15:47:01 +0100 Subject: [PATCH] Implement `ServiceLocator` framework --- .clang-format | 61 ++++++ .github/ISSUE_TEMPLATE/bug_report.md | 41 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 11 + .../PULL_REQUEST_TEMPLATE/bug_template.yml | 9 + .../feature_template.yml | 12 ++ .github/workflows/ci.yml | 201 ++++++++++++++++++ .github/workflows/danger.yml | 29 +++ .gitignore | 5 + Brewfile | 2 + Brewfile.lock.json | 101 +++++++++ CHANGELOG.md | 2 + Dangerfile | 1 + Gemfile | 3 + Makefile | 16 ++ ServiceLocator.podspec | 14 ++ ServiceLocator/Classes/Model/NVScope.h | 11 + .../Registry/Model/Registered/NVRegistered.h | 39 ++++ .../Registry/Model/Registered/NVRegistered.m | 30 +++ ServiceLocator/Classes/Registry/NVRegistry.h | 40 ++++ ServiceLocator/Classes/Registry/NVRegistry.m | 90 ++++++++ ServiceLocator/ServiceLocator.h | 18 ++ ServiceLocatorTests/Helpers/DummyProtocol.h | 13 ++ .../UnitTests/NVRegistryTests.m | 140 ++++++++++++ codecov.yml | 48 +++++ hooks/pre-commit | 12 ++ project.yml | 58 +++++ 26 files changed, 1007 insertions(+) create mode 100644 .clang-format create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/bug_template.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE/feature_template.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/danger.yml create mode 100644 Brewfile create mode 100644 Brewfile.lock.json create mode 100644 CHANGELOG.md create mode 100644 Dangerfile create mode 100644 Gemfile create mode 100644 Makefile create mode 100644 ServiceLocator.podspec create mode 100644 ServiceLocator/Classes/Model/NVScope.h create mode 100644 ServiceLocator/Classes/Registry/Model/Registered/NVRegistered.h create mode 100644 ServiceLocator/Classes/Registry/Model/Registered/NVRegistered.m create mode 100644 ServiceLocator/Classes/Registry/NVRegistry.h create mode 100644 ServiceLocator/Classes/Registry/NVRegistry.m create mode 100644 ServiceLocator/ServiceLocator.h create mode 100644 ServiceLocatorTests/Helpers/DummyProtocol.h create mode 100644 ServiceLocatorTests/UnitTests/NVRegistryTests.m create mode 100644 codecov.yml create mode 100755 hooks/pre-commit create mode 100644 project.yml diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ce4be89 --- /dev/null +++ b/.clang-format @@ -0,0 +1,61 @@ +# For options, see http://clang.llvm.org/docs/ClangFormatStyleOptions.html + +# A style complying with Google’s C++ style guide +BasedOnStyle: Google + +# If true, horizontally aligns arguments after an open bracket. +# This applies to round brackets (parentheses), angle brackets and square brackets. +AlignAfterOpenBracket: true + +# If true, horizontally align operands of binary and ternary expressions. +AlignOperands: false + +# Allow putting all parameters of a function declaration onto the next line even if BinPackParameters is false. +AllowAllParametersOfDeclarationOnNextLine: true + +# If true, always break after the template<...> of a template declaration. +AlwaysBreakTemplateDeclarations: true + +# If false, a function call’s arguments will either be all on the same line or will have one line each. +BinPackArguments: false + +# If false, a function call’s or function definition’s parameters will either all be on the same line or will have one line each. +BinPackParameters: false + +# If true, ternary operators will be placed after line breaks. +BreakBeforeTernaryOperators: true + +# Column limit for Obj-C at Google is 100 +ColumnLimit: 100 + +# Indent width for line continuations. +ContinuationIndentWidth: 4 + +# If true, analyze the formatted file for the most common alignment of & and *. PointerAlignment is then used only as fallback. +DerivePointerAlignment: false + +# The number of characters to use for indentation of ObjC blocks. +ObjCBlockIndentWidth: 2 + +# Add a space after @property in Objective-C. +# i.e. use \@property (readonly) instead of \@property(readonly). +ObjCSpaceAfterProperty: false + +# Add a space in front of an Objective-C protocol list +# i.e. use Foo instead of Foo. +ObjCSpaceBeforeProtocolList: true + +# Pointer and reference alignment style. +# ie. Right means * and & align to the variable, not the type. +# (PointerBindsToType looks like it's been deprecated and replaced by PointerAlignment) +PointerBindsToType: false +PointerAlignment: Right + +# If true, spaces are inserted inside container literals (e.g. ObjC array and dict literals). +SpacesInContainerLiterals: true + +# The number of columns used for tab stops. +TabWidth: 2 + +# The way to use tab characters in the resulting file. +UseTab: Never \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..8dc7e75 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,41 @@ +--- +name: "🐛 Bug Report" +about: Report a reproducible bug or regression. +title: 'Bug: ' +labels: 'bug' + +--- + + + +Application version: + +## Steps To Reproduce + +1. +2. + + + +Link to code example: + + + +## The current behavior + + +## The expected behavior \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..7268caf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,11 @@ +--- +name: 🛠 Feature request +about: If you have a feature request for the service-locator, file it here. +labels: 'type: enhancement' +--- + +**Feature description** +Clearly and concisely describe the feature. + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/bug_template.yml b/.github/PULL_REQUEST_TEMPLATE/bug_template.yml new file mode 100644 index 0000000..7d6a149 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/bug_template.yml @@ -0,0 +1,9 @@ +## Bug description +Clearly and concisely describe the problem. + +## Solution description +Describe your code changes in detail for reviewers. Explain the technical solution you have provided and how it fixes the issue case. + +## Covered unit test cases +- [x] yes +- [x] no \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/feature_template.yml b/.github/PULL_REQUEST_TEMPLATE/feature_template.yml new file mode 100644 index 0000000..ab3978b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/feature_template.yml @@ -0,0 +1,12 @@ +## Feature description +Clearly and concisely describe the feature. + +## Solution description +Describe your code changes in detail for reviewers. + +## Areas affected and ensured +List out the areas affected by your code changes. + +## Covered unit test cases +- [x] yes +- [x] no \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8159031 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,201 @@ +name: service-locator + +on: + push: + branches: + - main + - dev + pull_request: + paths: + - ".github/workflows/**" + - "ServiceLocator/**" + - "ServiceLocatorTests/**" +jobs: + macOS: + name: ${{ matrix.name }} + runs-on: ${{ matrix.runsOn }} + env: + DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer" + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - xcode: "Xcode_15.0" + runsOn: macos-13 + name: "macOS 13, Xcode 15.0" + - xcode: "Xcode_14.3.1" + runsOn: macos-13 + name: "macOS 13, Xcode 14.3.1" + - xcode: "Xcode_14.2" + runsOn: macOS-12 + name: "macOS 12, Xcode 14.2" + - xcode: "Xcode_14.1" + runsOn: macOS-12 + name: "macOS 12, Xcode 14.1" + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Dependencies + run: brew bundle + - name: Generate project + run: xcodegen generate + - name: ${{ matrix.name }} + run: xcodebuild test -scheme "ServiceLocator" -destination "platform=macOS" clean -enableCodeCoverage YES -resultBundlePath "test_output/${{ matrix.name }}.xcresult" || exit 1 + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3.1.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + xcode: true + xcode_archive_path: test_output/${{ matrix.name }}.xcresult + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.name }} + path: test_output + + iOS: + name: ${{ matrix.name }} + runs-on: ${{ matrix.runsOn }} + env: + DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer" + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - destination: "OS=17.0.1,name=iPhone 14 Pro" + name: "iOS 17.0.1" + xcode: "Xcode_15.0" + runsOn: macos-13 + - destination: "OS=16.4,name=iPhone 14 Pro" + name: "iOS 16.4" + xcode: "Xcode_14.3.1" + runsOn: macos-13 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Dependencies + run: brew bundle + - name: Generate project + run: xcodegen generate + - name: ${{ matrix.name }} + run: xcodebuild test -scheme "ServiceLocator" -destination "${{ matrix.destination }}" clean -enableCodeCoverage YES -resultBundlePath "test_output/${{ matrix.name }}.xcresult" || exit 1 + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3.1.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + xcode: true + xcode_archive_path: test_output/${{ matrix.name }}.xcresult + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.name }} + path: test_output + + tvOS: + name: ${{ matrix.name }} + runs-on: ${{ matrix.runsOn }} + env: + DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer" + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - destination: "OS=17.0,name=Apple TV" + name: "tvOS 17.0" + xcode: "Xcode_15.0" + runsOn: macos-13 + - destination: "OS=16.4,name=Apple TV" + name: "tvOS 16.4" + xcode: "Xcode_14.3.1" + runsOn: macos-13 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Dependencies + run: brew bundle + - name: Generate project + run: xcodegen generate + - name: ${{ matrix.name }} + run: xcodebuild test -scheme "ServiceLocator" -destination "${{ matrix.destination }}" clean -enableCodeCoverage YES -resultBundlePath "test_output/${{ matrix.name }}.xcresult" || exit 1 + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3.1.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + xcode: true + xcode_archive_path: test_output/${{ matrix.name }}.xcresult + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.name }} + path: test_output + + watchOS: + name: ${{ matrix.name }} + runs-on: ${{ matrix.runsOn }} + env: + DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer" + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - destination: "OS=10.0,name=Apple Watch Series 9 (45mm)" + name: "watchOS 10.0" + xcode: "Xcode_15.0" + runsOn: macos-13 + - destination: "OS=9.4,name=Apple Watch Series 8 (45mm)" + name: "watchOS 9.4" + xcode: "Xcode_14.3.1" + runsOn: macos-13 + - destination: "OS=8.5,name=Apple Watch Series 7 (45mm)" + name: "watchOS 8.5" + xcode: "Xcode_14.3.1" + runsOn: macos-13 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Dependencies + run: brew bundle + - name: Generate project + run: xcodegen generate + - name: ${{ matrix.name }} + run: xcodebuild test -scheme "ServiceLocator" -destination "${{ matrix.destination }}" clean -enableCodeCoverage YES -resultBundlePath "test_output/${{ matrix.name }}.xcresult" || exit 1 + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3.1.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + xcode: true + xcode_archive_path: test_output/${{ matrix.name }}.xcresult + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.name }} + path: test_output + + merge-test-reports: + needs: [iOS, macOS, watchOS, tvOS] + runs-on: macos-13 + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: test_output + - run: xcrun xcresulttool merge test_output/**/*.xcresult --output-path test_output/final/final.xcresult + - name: Upload Merged Artifact + uses: actions/upload-artifact@v4 + with: + name: MergedResult + path: test_output/final + + discover-typos: + name: Discover Typos + runs-on: macOS-12 + env: + DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer + steps: + - uses: actions/checkout@v2 + - name: Discover typos + run: | + export PATH="$PATH:/Library/Frameworks/Python.framework/Versions/3.11/bin" + python3 -m pip install --upgrade pip + python3 -m pip install codespell + codespell --ignore-words-list="hart,inout,msdos,sur" --skip="./.build/*,./.git/*" \ No newline at end of file diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml new file mode 100644 index 0000000..1a0ab52 --- /dev/null +++ b/.github/workflows/danger.yml @@ -0,0 +1,29 @@ +name: Danger + +on: + pull_request: + types: [synchronize, opened, reopened, labeled, unlabeled, edited] + +env: + LC_CTYPE: en_US.UTF-8 + LANG: en_US.UTF-8 + +jobs: + run-danger: + runs-on: ubuntu-latest + steps: + - name: ruby setup + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + bundler-cache: true + - name: Checkout code + uses: actions/checkout@v2 + - name: Setup gems + run: | + gem install bundler + bundle install --clean --path vendor/bundle + - name: danger + env: + DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }} + run: bundle exec danger --verbose \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7801c93..ba0fa40 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,8 @@ fastlane/test_output # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ + +# Other + +*.xcodeproj +*.workspace \ No newline at end of file diff --git a/Brewfile b/Brewfile new file mode 100644 index 0000000..13a4bba --- /dev/null +++ b/Brewfile @@ -0,0 +1,2 @@ +brew "clang-format" +brew "xcodegen" \ No newline at end of file diff --git a/Brewfile.lock.json b/Brewfile.lock.json new file mode 100644 index 0000000..869a27a --- /dev/null +++ b/Brewfile.lock.json @@ -0,0 +1,101 @@ +{ + "entries": { + "brew": { + "clang-format": { + "version": "17.0.6", + "bottle": { + "rebuild": 0, + "root_url": "https://ghcr.io/v2/homebrew/core", + "files": { + "arm64_sonoma": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/clang-format/blobs/sha256:1bb881df31b9f0dd6a85ef97572b31bf8292aa7d05d8f35d49bc830424b3011b", + "sha256": "1bb881df31b9f0dd6a85ef97572b31bf8292aa7d05d8f35d49bc830424b3011b" + }, + "arm64_ventura": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/clang-format/blobs/sha256:7835985d5e6edfb05205883c484135120789c78bc3b5eeeddc39d7b5170c6b16", + "sha256": "7835985d5e6edfb05205883c484135120789c78bc3b5eeeddc39d7b5170c6b16" + }, + "arm64_monterey": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/clang-format/blobs/sha256:67fbefb432b2cc11d08c14ffb89cb71b1026a83b81c2e7fac089663a053b64c2", + "sha256": "67fbefb432b2cc11d08c14ffb89cb71b1026a83b81c2e7fac089663a053b64c2" + }, + "sonoma": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/clang-format/blobs/sha256:e11bb2ee8e4012e08afeb1c2109af21feba56e7225ff6e473e69c8ad2aed36ea", + "sha256": "e11bb2ee8e4012e08afeb1c2109af21feba56e7225ff6e473e69c8ad2aed36ea" + }, + "ventura": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/clang-format/blobs/sha256:fcb5fe00f5fca01bbe9aae794a6d4c3459effce8f9906445f44d2991fece69ae", + "sha256": "fcb5fe00f5fca01bbe9aae794a6d4c3459effce8f9906445f44d2991fece69ae" + }, + "monterey": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/clang-format/blobs/sha256:1ea4441a6fc772efe6eed7a3e64ca74229753eb2b0d66f5b81ead8eed3ae973e", + "sha256": "1ea4441a6fc772efe6eed7a3e64ca74229753eb2b0d66f5b81ead8eed3ae973e" + }, + "x86_64_linux": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/clang-format/blobs/sha256:35e9c5cc360ace20a6eaa5ee6c1956ba93e32faf67834e4c931f60277f590724", + "sha256": "35e9c5cc360ace20a6eaa5ee6c1956ba93e32faf67834e4c931f60277f590724" + } + } + } + }, + "xcodegen": { + "version": "2.38.0", + "bottle": { + "rebuild": 0, + "root_url": "https://ghcr.io/v2/homebrew/core", + "files": { + "arm64_sonoma": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/xcodegen/blobs/sha256:422fb8dfbc7e2ed59125d22b4687bb54a1ab3f0ddef044a3875b624121f9be47", + "sha256": "422fb8dfbc7e2ed59125d22b4687bb54a1ab3f0ddef044a3875b624121f9be47" + }, + "arm64_ventura": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/xcodegen/blobs/sha256:5b2d9dfdf8bc9912ecef48ecc4a03cfb4ba68f35f03c4ab4fc9e893b077f8796", + "sha256": "5b2d9dfdf8bc9912ecef48ecc4a03cfb4ba68f35f03c4ab4fc9e893b077f8796" + }, + "arm64_monterey": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/xcodegen/blobs/sha256:7a239feca86c46f78ae91d631858d957cb2e7e63ea7230b30f3d618097774bff", + "sha256": "7a239feca86c46f78ae91d631858d957cb2e7e63ea7230b30f3d618097774bff" + }, + "sonoma": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/xcodegen/blobs/sha256:346164300a7e835f8516c70b25793702bab2437d7e9fb606b5394ab757dab4f5", + "sha256": "346164300a7e835f8516c70b25793702bab2437d7e9fb606b5394ab757dab4f5" + }, + "ventura": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/xcodegen/blobs/sha256:2bca799f6fee1e679a3f826a9a977449a23f81f02896b22a525056f6cd4a07dd", + "sha256": "2bca799f6fee1e679a3f826a9a977449a23f81f02896b22a525056f6cd4a07dd" + }, + "monterey": { + "cellar": ":any_skip_relocation", + "url": "https://ghcr.io/v2/homebrew/core/xcodegen/blobs/sha256:3e306a4b9ad078c77b61d93090c224304c7dac35ca119808db87792edb983be8", + "sha256": "3e306a4b9ad078c77b61d93090c224304c7dac35ca119808db87792edb983be8" + } + } + } + } + } + }, + "system": { + "macos": { + "ventura": { + "HOMEBREW_VERSION": "4.2.7", + "HOMEBREW_PREFIX": "/usr/local", + "Homebrew/homebrew-core": "api", + "CLT": "", + "Xcode": "15.2", + "macOS": "13.6.3" + } + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..954c14b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +#Change Log +All notable changes to this project will be documented in this file. diff --git a/Dangerfile b/Dangerfile new file mode 100644 index 0000000..b266982 --- /dev/null +++ b/Dangerfile @@ -0,0 +1 @@ +danger.import_dangerfile(github: 'space-code/dangerfile') \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..20dff64 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem 'danger' \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eac1917 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +all: bootstrap + +bootstrap: hook brew + +brew: + brew bundle check || brew bundle + +hook: + ln -sf ../../hooks/pre-commit .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit + +fmt: + find ServiceLocator -iname '*.h' -o -iname '*.m' | xargs clang-format -i + find ServiceLocatorTests -iname '*.h' -o -iname '*.m' | xargs clang-format -i + +.PHONY: all bootstrap hook brew mint lint fmt \ No newline at end of file diff --git a/ServiceLocator.podspec b/ServiceLocator.podspec new file mode 100644 index 0000000..685b44d --- /dev/null +++ b/ServiceLocator.podspec @@ -0,0 +1,14 @@ +Pod::Spec.new do |s| + s.name = 'ServiceLocator' + s.version = '1.0.0' + s.license = 'MIT' + s.summary = 'A screen capture framework' + s.homepage = 'https://github.com/space-code/service-locator' + s.authors = { 'Nikita Vasilev' => 'nv3212@gmail.com' } + s.source = { :git => 'https://github.com/space-code/service-locator.git', :tag => s.version } + s.documentation_url = 'https://github.com/space-code/service-locator' + + s.osx.deployment_target = '11.0' + + s.source_files = 'CaptureKit/**/*.{h,m}' +end \ No newline at end of file diff --git a/ServiceLocator/Classes/Model/NVScope.h b/ServiceLocator/Classes/Model/NVScope.h new file mode 100644 index 0000000..bc66adb --- /dev/null +++ b/ServiceLocator/Classes/Model/NVScope.h @@ -0,0 +1,11 @@ +// +// ServiceLocator +// Copyright © 2024 Space Code. All rights reserved. +// + +#ifndef NVScope_h +#define NVScope_h + +typedef NS_ENUM(NSUInteger, NVScope) { NVRegistryScopeSignleton, NVRegistryScopePrototype }; + +#endif /* NVScope_h */ diff --git a/ServiceLocator/Classes/Registry/Model/Registered/NVRegistered.h b/ServiceLocator/Classes/Registry/Model/Registered/NVRegistered.h new file mode 100644 index 0000000..8d3c15b --- /dev/null +++ b/ServiceLocator/Classes/Registry/Model/Registered/NVRegistered.h @@ -0,0 +1,39 @@ +// +// ServiceLocator +// Copyright © 2024 Space Code. All rights reserved. +// + +#import +#import "NVScope.h" + +NS_ASSUME_NONNULL_BEGIN + +/// An object representing a registered dependency. +@interface NVRegistered : NSObject + +/// The scope type of the registered dependency. +@property(nonatomic, readonly) NVScope type; + +/// The singleton object registered, if applicable. +@property(nonatomic, readonly) id singletonObject; + +/// The block to create a prototype object, if applicable. +@property(nonatomic, readonly) id (^prototypeBlock)(void); + +/// Initializes the instance with a singleton object. +/// +/// @param object The singleton object to initialize with. +/// +/// @return An instance of NVRegistered. +- (instancetype)initWithSingleton:(id)object; + +/// Initializes the instance with a prototype block. +/// +/// @param block The block to create a prototype object. +/// +/// @return An instance of NVRegistered. +- (instancetype)initWithPrototypeBlock:(id (^)(void))block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ServiceLocator/Classes/Registry/Model/Registered/NVRegistered.m b/ServiceLocator/Classes/Registry/Model/Registered/NVRegistered.m new file mode 100644 index 0000000..70e2d87 --- /dev/null +++ b/ServiceLocator/Classes/Registry/Model/Registered/NVRegistered.m @@ -0,0 +1,30 @@ +// +// ServiceLocator +// Copyright © 2024 Space Code. All rights reserved. +// + +#import "NVRegistered.h" + +@implementation NVRegistered + +#pragma mark - Initialization + +- (instancetype)initWithSingleton:(id)object { + self = [super init]; + if (self) { + _type = NVRegistryScopeSignleton; + _singletonObject = object; + } + return self; +} + +- (instancetype)initWithPrototypeBlock:(id _Nonnull (^)(void))block { + self = [super init]; + if (self) { + _type = NVRegistryScopePrototype; + _prototypeBlock = [block copy]; + } + return self; +} + +@end diff --git a/ServiceLocator/Classes/Registry/NVRegistry.h b/ServiceLocator/Classes/Registry/NVRegistry.h new file mode 100644 index 0000000..bd6db3c --- /dev/null +++ b/ServiceLocator/Classes/Registry/NVRegistry.h @@ -0,0 +1,40 @@ +// +// ServiceLocator +// Copyright © 2024 Space Code. All rights reserved. +// + +#import +#import "NVScope.h" + +NS_ASSUME_NONNULL_BEGIN + +/// A class for managing protocol-based dependencies. +@interface NVRegistry : NSObject + +/// Returns the shared instance of NVRegistry. ++ (instancetype)sharedInstance; + +/// Registers a factory block for the given protocol and scope. +/// +/// @param aProtocol The protocol to register. +/// @param scope The scope of the dependency. +/// @param factory A block that returns an instance of the dependency. +- (void)register:(Protocol *_Null_unspecified)aProtocol + scope:(NVScope)scope + factory:(id (^)(void))factory; + +/// Resolves and returns an instance for the given protocol. +/// +/// @param aProtocol The protocol to resolve. +/// +/// @return An instance conforming to the given protocol. +- (id)resolve:(Protocol *_Null_unspecified)aProtocol; + +/// Removes a dependency with a given protocol. +/// +/// @param aProtocol The protocol to delete. +- (void)remove:(Protocol *_Null_unspecified)aProtocol; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ServiceLocator/Classes/Registry/NVRegistry.m b/ServiceLocator/Classes/Registry/NVRegistry.m new file mode 100644 index 0000000..8bf8ca5 --- /dev/null +++ b/ServiceLocator/Classes/Registry/NVRegistry.m @@ -0,0 +1,90 @@ +// +// ServiceLocator +// Copyright © 2024 Space Code. All rights reserved. +// + +#import "NVRegistry.h" +#import "NVRegistered.h" + +@interface NVRegistry () + +@property(nonatomic, strong) NSMutableDictionary *registry; + +@end + +@implementation NVRegistry + +#pragma mark - Initialization + ++ (instancetype)sharedInstance { + static dispatch_once_t token; + static id sharedInstance = nil; + dispatch_once(&token, ^{ + sharedInstance = [[[self class] alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + if (self = [super init]) { + _registry = [[NSMutableDictionary alloc] init]; + } + return self; +} + +#pragma mark - Internal + +- (void)register:(Protocol *_Null_unspecified)aProtocol + scope:(NVScope)scope + factory:(nonnull id _Nonnull (^)(void))factory { + switch (scope) { + case NVRegistryScopeSignleton: + [self registerSingleton:aProtocol factory:factory]; + break; + case NVRegistryScopePrototype: + [self registerPrototype:aProtocol factory:factory]; + break; + } +} + +- (nonnull id)resolve:(Protocol *_Null_unspecified)aProtocol { + NSString *key = [self makeKey:aProtocol]; + NVRegistered *registered = _registry[key]; + + if (registered != nil) { + switch (registered.type) { + case NVRegistryScopeSignleton: + return registered.singletonObject; + case NVRegistryScopePrototype: + return registered.prototypeBlock(); + } + } + + return nil; +} + +- (void)remove:(Protocol *)aProtocol { + NSString *key = [self makeKey:aProtocol]; + [_registry removeObjectForKey:key]; +} + +#pragma mark - Private + +- (nonnull NSString *)makeKey:(Protocol *_Null_unspecified)aProtocol { + NSString *key = NSStringFromProtocol(aProtocol); + return key; +} + +- (void)registerSingleton:(Protocol *_Null_unspecified)aProtocol + factory:(nonnull id _Nonnull (^)(void))factory { + NSString *key = [self makeKey:aProtocol]; + _registry[key] = [[NVRegistered alloc] initWithSingleton:factory()]; +} + +- (void)registerPrototype:(Protocol *_Null_unspecified)aProtocol + factory:(nonnull id _Nonnull (^)(void))factory { + NSString *key = [self makeKey:aProtocol]; + _registry[key] = [[NVRegistered alloc] initWithPrototypeBlock:factory]; +} + +@end diff --git a/ServiceLocator/ServiceLocator.h b/ServiceLocator/ServiceLocator.h new file mode 100644 index 0000000..8fab7b0 --- /dev/null +++ b/ServiceLocator/ServiceLocator.h @@ -0,0 +1,18 @@ +// +// ServiceLocator +// Copyright © 2024 Space Code. All rights reserved. +// + +#import + +//! Project version number for ServiceLocator. +FOUNDATION_EXPORT double ServiceLocatorVersionNumber; + +//! Project version string for ServiceLocator. +FOUNDATION_EXPORT const unsigned char ServiceLocatorVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like +// #import + +#import +#import diff --git a/ServiceLocatorTests/Helpers/DummyProtocol.h b/ServiceLocatorTests/Helpers/DummyProtocol.h new file mode 100644 index 0000000..3667b31 --- /dev/null +++ b/ServiceLocatorTests/Helpers/DummyProtocol.h @@ -0,0 +1,13 @@ +// +// ServiceLocatorTests +// Copyright © 2024 Space Code. All rights reserved. +// + +#ifndef DummyProtocol_h +#define DummyProtocol_h + +@protocol DummyProtocol +@end +; + +#endif /* DummyProtocol_h */ diff --git a/ServiceLocatorTests/UnitTests/NVRegistryTests.m b/ServiceLocatorTests/UnitTests/NVRegistryTests.m new file mode 100644 index 0000000..263929b --- /dev/null +++ b/ServiceLocatorTests/UnitTests/NVRegistryTests.m @@ -0,0 +1,140 @@ +// +// ServiceLocatorTests +// Copyright © 2024 Space Code. All rights reserved. +// + +#import +#import +#import "DummyProtocol.h" +#import "NVRegistry.h" + +@interface NVRegistryTests : XCTestCase + +@property(nonatomic, strong) NVRegistry *sut; + +@end + +@implementation NVRegistryTests + +#pragma mark - XCTestCase + +- (void)setUp { + [super setUp]; + _sut = [NVRegistry sharedInstance]; +} + +- (void)tearDown { + _sut = nil; + [super tearDown]; +} + +#pragma mark - Tests + +- (void)test_thatRegistryResolvesSingletonObjects { + // given + [_sut register:@protocol(DummyProtocol) + scope:NVRegistryScopeSignleton + factory:^id _Nonnull { + return [self makeClass:@"Classs"]; + }]; + + // when + id obj1 = [_sut resolve:@protocol(DummyProtocol)]; + id obj2 = [_sut resolve:@protocol(DummyProtocol)]; + + // when + XCTAssertEqual(obj1, obj2); +} + +- (void)test_thatRegistryResolvesPrototypeObjects { + // given + [_sut register:@protocol(DummyProtocol) + scope:NVRegistryScopePrototype + factory:^id _Nonnull { + return [self makeClass:@"ClassName"]; + }]; + + // when + id obj1 = [_sut resolve:@protocol(DummyProtocol)]; + id obj2 = [_sut resolve:@protocol(DummyProtocol)]; + + // when + XCTAssertNotEqual(obj1, obj2); +} + +- (void)test_thatRegistryResolvesAFewObjectsWithDifferentProtocols { + // given + [_sut register:[self makeProtocol:@"ProtocolName1"] + scope:NVRegistryScopePrototype + factory:^id _Nonnull { + return [self makeClass:@"ClassName1"]; + }]; + [_sut register:[self makeProtocol:@"ProtocolName2"] + scope:NVRegistryScopePrototype + factory:^id _Nonnull { + return [self makeClass:@"ClassName2"]; + }]; + + // when + id obj1 = [_sut resolve:[self makeProtocol:@"ProtocolName1"]]; + id obj2 = [_sut resolve:[self makeProtocol:@"ProtocolName2"]]; + + // when + XCTAssertNotEqual(obj1, obj2); + XCTAssertEqualObjects(NSStringFromClass([obj1 class]), @"ClassName1"); + XCTAssertEqualObjects(NSStringFromClass([obj2 class]), @"ClassName2"); +} + +- (void)test_thatRegistryResolvesLastRegisteredObject { + // given + [_sut register:@protocol(DummyProtocol) + scope:NVRegistryScopePrototype + factory:^id _Nonnull { + return [self makeClass:@"ClassName1"]; + }]; + [_sut register:@protocol(DummyProtocol) + scope:NVRegistryScopeSignleton + factory:^id _Nonnull { + return [self makeClass:@"ClassName2"]; + }]; + + // when + id obj1 = [_sut resolve:@protocol(DummyProtocol)]; + id obj2 = [_sut resolve:@protocol(DummyProtocol)]; + + // when + XCTAssertEqual(obj1, obj2); + XCTAssertEqualObjects(NSStringFromClass([obj1 class]), @"ClassName2"); +} + +- (void)test_thatRegistryRemovesRegisteredObject { + // given + [_sut register:@protocol(DummyProtocol) + scope:NVRegistryScopePrototype + factory:^id _Nonnull { + return [self makeClass:@"ClassName1"]; + }]; + + // when + [_sut remove:@protocol(DummyProtocol)]; + id obj = [_sut resolve:@protocol(DummyProtocol)]; + + // then + XCTAssertNil(obj); +} + +#pragma mark - Helpers + +- (Protocol *)makeProtocol:(NSString *)name { + Protocol *aProtocol = + objc_allocateProtocol([name cStringUsingEncoding:NSMacOSRomanStringEncoding]); + return aProtocol; +} + +- (Class)makeClass:(NSString *)name { + Class aClass = objc_allocateClassPair( + [NSObject class], [name cStringUsingEncoding:NSMacOSRomanStringEncoding], 0); + return aClass; +} + +@end diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..b415604 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,48 @@ +codecov: + # Require CI to pass to show coverage, default yes + require_ci_to_pass: yes + notify: + # Codecov should wait for all CI statuses to complete, default yes + wait_for_ci: yes + +coverage: + # Coverage precision range 0-5, default 2 + precision: 2 + + # Direction to round the coverage value - up, down, nearest, default down + round: nearest + + # Value range for red...green, default 70...100 + range: "70...90" + + status: + # Overall project coverage, compare against pull request base + project: + default: + # The required coverage value + target: 50% + + # The leniency in hitting the target. Allow coverage to drop by X% + threshold: 5% + + # Only measure lines adjusted in the pull request or single commit, if the commit in not in the pr + patch: + default: + # The required coverage value + target: 85% + + # Allow coverage to drop by X% + threshold: 5% + changes: no + +comment: + # Pull request Codecov comment format. + # diff: coverage diff of the pull request + # files: a list of files impacted by the pull request (coverage changes, file is new or removed) + layout: "diff, files" + + # Update Codecov comment, if exists. Otherwise post new + behavior: default + + # If true, only post the Codecov comment if coverage changes + require_changes: false \ No newline at end of file diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 0000000..abdac63 --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,12 @@ +#!/bin/bash + +git diff --diff-filter=d --staged --name-only | grep -e '\.\(h\|m\)' | while read line; do + if [[ $line == *"/Generated"* ]]; then + echo "IGNORING GENERATED FILE: " "$line"; + else + clang-format -style=file -i "${line}"; + git add "$line"; + fi +done + +xcodegen \ No newline at end of file diff --git a/project.yml b/project.yml new file mode 100644 index 0000000..08d21fc --- /dev/null +++ b/project.yml @@ -0,0 +1,58 @@ +name: ServiceLocator +options: + developmentLanguage: en + createIntermediateGroups: true + deploymentTarget: + iOS: 12.0 + macOS: 10.14 + tvOS: 12.0 + watchOS: 6.0 + xcodeVersion: 15.0 +settings: + base: + SWIFT_VERSION: "5.7" + PRODUCT_BUNDLE_IDENTIFIER: com.nikitavasilev.capture-kit + CODE_SIGN_STYLE: automatic + GENERATE_INFOPLIST_FILE: YES +attributes: + ORGANIZATIONNAME: space-code +schemes: + ServiceLocator: + build: + targets: + ServiceLocator: all + run: + config: Debug + test: + gatherCoverageData: true + targets: + - ServiceLocatorTests + coverageTargets: + - ServiceLocator +targets: + ServiceLocator: + type: framework + supportedDestinations: [iOS, tvOS, macOS] + sources: + - path: ServiceLocator + settings: + base: + MARKETING_VERSION: 1.0.0 + CURRENT_PROJECT_VERSION: 1 + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: NO + GENERATE_INFOPLIST_FILE: YES + TARGETED_DEVICE_FAMILY: "1,2,3,4" + SUPPORTED_PLATFORMS: "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator" + ServiceLocatorTests: + type: bundle.unit-test + supportedDestinations: [iOS, tvOS, macOS] + sources: + - ServiceLocatorTests + dependencies: + - target: ServiceLocator + settings: + base: + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: NO + GENERATE_INFOPLIST_FILE: YES + TARGETED_DEVICE_FAMILY: "1,2,3,4" + SUPPORTED_PLATFORMS: "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator" \ No newline at end of file