├── .swift-version
├── .travis.yml
├── Demo
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Demo.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── maksim.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── Info.plist
├── LNAppDelegate.swift
├── LNApplication.swift
├── LNViewController.swift
└── main.swift
├── LICENSE
├── Linker.podspec
├── Linker.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ ├── Linker.xcscheme
│ │ └── LinkerTests.xcscheme
└── xcuserdata
│ └── maksim.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── Linker
├── Configs
│ ├── Linker.plist
│ ├── LinkerTests.plist
│ └── logo.png
├── LinkerTests
│ ├── LinkerMocks.swift
│ ├── LinkerTests.swift
│ └── Spectre
│ │ ├── Case.swift
│ │ ├── Context.swift
│ │ ├── Expectation.swift
│ │ ├── Failure.swift
│ │ ├── Global.swift
│ │ ├── GlobalContext.swift
│ │ ├── Reporter.swift
│ │ └── Reporters.swift
└── Sources
│ └── Linker
│ ├── Linker.swift
│ ├── LinkerExtensions.swift
│ ├── LinkerModel.swift
│ └── LinkerTypes.swift
└── README.md
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os:
2 | - osx
3 | language: swift
4 | osx_image: xcode9.2
5 |
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Demo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Demo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Demo/Demo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B55918732035CE2200AFB31A /* Linker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559186F2035CE2200AFB31A /* Linker.swift */; };
11 | B55918742035CE2200AFB31A /* LinkerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918702035CE2200AFB31A /* LinkerModel.swift */; };
12 | B55918752035CE2200AFB31A /* LinkerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918712035CE2200AFB31A /* LinkerExtensions.swift */; };
13 | B55918762035CE2200AFB31A /* LinkerTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918722035CE2200AFB31A /* LinkerTypes.swift */; };
14 | B5A43E65203489B9004D27CA /* LNViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A43E4A203489B9004D27CA /* LNViewController.swift */; };
15 | B5A43E66203489B9004D27CA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5A43E4B203489B9004D27CA /* Assets.xcassets */; };
16 | B5A43E67203489B9004D27CA /* LNAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A43E4C203489B9004D27CA /* LNAppDelegate.swift */; };
17 | B5A43E68203489B9004D27CA /* LNApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A43E4D203489B9004D27CA /* LNApplication.swift */; };
18 | B5A43E69203489B9004D27CA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5A43E4E203489B9004D27CA /* LaunchScreen.storyboard */; };
19 | B5A43E6A203489B9004D27CA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5A43E50203489B9004D27CA /* Main.storyboard */; };
20 | B5A43E6B203489B9004D27CA /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A43E52203489B9004D27CA /* main.swift */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXFileReference section */
24 | B559186F2035CE2200AFB31A /* Linker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Linker.swift; sourceTree = ""; };
25 | B55918702035CE2200AFB31A /* LinkerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerModel.swift; sourceTree = ""; };
26 | B55918712035CE2200AFB31A /* LinkerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerExtensions.swift; sourceTree = ""; };
27 | B55918722035CE2200AFB31A /* LinkerTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerTypes.swift; sourceTree = ""; };
28 | B58BF1872032133C006921B2 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
29 | B5A43E4A203489B9004D27CA /* LNViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LNViewController.swift; sourceTree = ""; };
30 | B5A43E4B203489B9004D27CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
31 | B5A43E4C203489B9004D27CA /* LNAppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LNAppDelegate.swift; sourceTree = ""; };
32 | B5A43E4D203489B9004D27CA /* LNApplication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LNApplication.swift; sourceTree = ""; };
33 | B5A43E4F203489B9004D27CA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
34 | B5A43E51203489B9004D27CA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
35 | B5A43E52203489B9004D27CA /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
36 | B5A43E53203489B9004D27CA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
37 | /* End PBXFileReference section */
38 |
39 | /* Begin PBXFrameworksBuildPhase section */
40 | B58BF1842032133C006921B2 /* Frameworks */ = {
41 | isa = PBXFrameworksBuildPhase;
42 | buildActionMask = 2147483647;
43 | files = (
44 | );
45 | runOnlyForDeploymentPostprocessing = 0;
46 | };
47 | /* End PBXFrameworksBuildPhase section */
48 |
49 | /* Begin PBXGroup section */
50 | B559186D2035CE2200AFB31A /* Sources */ = {
51 | isa = PBXGroup;
52 | children = (
53 | B559186E2035CE2200AFB31A /* Linker */,
54 | );
55 | name = Sources;
56 | path = ../Linker/Sources;
57 | sourceTree = "";
58 | };
59 | B559186E2035CE2200AFB31A /* Linker */ = {
60 | isa = PBXGroup;
61 | children = (
62 | B559186F2035CE2200AFB31A /* Linker.swift */,
63 | B55918702035CE2200AFB31A /* LinkerModel.swift */,
64 | B55918712035CE2200AFB31A /* LinkerExtensions.swift */,
65 | B55918722035CE2200AFB31A /* LinkerTypes.swift */,
66 | );
67 | path = Linker;
68 | sourceTree = "";
69 | };
70 | B58BF17E2032133C006921B2 = {
71 | isa = PBXGroup;
72 | children = (
73 | B559186D2035CE2200AFB31A /* Sources */,
74 | B5A43E46203489B9004D27CA /* Demo */,
75 | B58BF1882032133C006921B2 /* Products */,
76 | );
77 | sourceTree = "";
78 | };
79 | B58BF1882032133C006921B2 /* Products */ = {
80 | isa = PBXGroup;
81 | children = (
82 | B58BF1872032133C006921B2 /* Demo.app */,
83 | );
84 | name = Products;
85 | sourceTree = "";
86 | };
87 | B5A43E46203489B9004D27CA /* Demo */ = {
88 | isa = PBXGroup;
89 | children = (
90 | B5A43E4B203489B9004D27CA /* Assets.xcassets */,
91 | B5A43E4A203489B9004D27CA /* LNViewController.swift */,
92 | B5A43E4C203489B9004D27CA /* LNAppDelegate.swift */,
93 | B5A43E4D203489B9004D27CA /* LNApplication.swift */,
94 | B5A43E4E203489B9004D27CA /* LaunchScreen.storyboard */,
95 | B5A43E50203489B9004D27CA /* Main.storyboard */,
96 | B5A43E52203489B9004D27CA /* main.swift */,
97 | B5A43E53203489B9004D27CA /* Info.plist */,
98 | );
99 | name = Demo;
100 | sourceTree = "";
101 | };
102 | /* End PBXGroup section */
103 |
104 | /* Begin PBXNativeTarget section */
105 | B58BF1862032133C006921B2 /* Demo */ = {
106 | isa = PBXNativeTarget;
107 | buildConfigurationList = B58BF1A42032133C006921B2 /* Build configuration list for PBXNativeTarget "Demo" */;
108 | buildPhases = (
109 | B58BF1832032133C006921B2 /* Sources */,
110 | B58BF1842032133C006921B2 /* Frameworks */,
111 | B58BF1852032133C006921B2 /* Resources */,
112 | );
113 | buildRules = (
114 | );
115 | dependencies = (
116 | );
117 | name = Demo;
118 | productName = Linker;
119 | productReference = B58BF1872032133C006921B2 /* Demo.app */;
120 | productType = "com.apple.product-type.application";
121 | };
122 | /* End PBXNativeTarget section */
123 |
124 | /* Begin PBXProject section */
125 | B58BF17F2032133C006921B2 /* Project object */ = {
126 | isa = PBXProject;
127 | attributes = {
128 | LastSwiftUpdateCheck = 0920;
129 | LastUpgradeCheck = 0920;
130 | ORGANIZATIONNAME = "Maksim Kurpa";
131 | TargetAttributes = {
132 | B58BF1862032133C006921B2 = {
133 | CreatedOnToolsVersion = 9.2;
134 | LastSwiftMigration = 0920;
135 | ProvisioningStyle = Automatic;
136 | };
137 | };
138 | };
139 | buildConfigurationList = B58BF1822032133C006921B2 /* Build configuration list for PBXProject "Demo" */;
140 | compatibilityVersion = "Xcode 8.0";
141 | developmentRegion = en;
142 | hasScannedForEncodings = 0;
143 | knownRegions = (
144 | en,
145 | Base,
146 | );
147 | mainGroup = B58BF17E2032133C006921B2;
148 | productRefGroup = B58BF1882032133C006921B2 /* Products */;
149 | projectDirPath = "";
150 | projectRoot = "";
151 | targets = (
152 | B58BF1862032133C006921B2 /* Demo */,
153 | );
154 | };
155 | /* End PBXProject section */
156 |
157 | /* Begin PBXResourcesBuildPhase section */
158 | B58BF1852032133C006921B2 /* Resources */ = {
159 | isa = PBXResourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | B5A43E69203489B9004D27CA /* LaunchScreen.storyboard in Resources */,
163 | B5A43E66203489B9004D27CA /* Assets.xcassets in Resources */,
164 | B5A43E6A203489B9004D27CA /* Main.storyboard in Resources */,
165 | );
166 | runOnlyForDeploymentPostprocessing = 0;
167 | };
168 | /* End PBXResourcesBuildPhase section */
169 |
170 | /* Begin PBXSourcesBuildPhase section */
171 | B58BF1832032133C006921B2 /* Sources */ = {
172 | isa = PBXSourcesBuildPhase;
173 | buildActionMask = 2147483647;
174 | files = (
175 | B5A43E68203489B9004D27CA /* LNApplication.swift in Sources */,
176 | B5A43E67203489B9004D27CA /* LNAppDelegate.swift in Sources */,
177 | B55918732035CE2200AFB31A /* Linker.swift in Sources */,
178 | B5A43E65203489B9004D27CA /* LNViewController.swift in Sources */,
179 | B55918752035CE2200AFB31A /* LinkerExtensions.swift in Sources */,
180 | B55918742035CE2200AFB31A /* LinkerModel.swift in Sources */,
181 | B55918762035CE2200AFB31A /* LinkerTypes.swift in Sources */,
182 | B5A43E6B203489B9004D27CA /* main.swift in Sources */,
183 | );
184 | runOnlyForDeploymentPostprocessing = 0;
185 | };
186 | /* End PBXSourcesBuildPhase section */
187 |
188 | /* Begin PBXVariantGroup section */
189 | B5A43E4E203489B9004D27CA /* LaunchScreen.storyboard */ = {
190 | isa = PBXVariantGroup;
191 | children = (
192 | B5A43E4F203489B9004D27CA /* Base */,
193 | );
194 | name = LaunchScreen.storyboard;
195 | sourceTree = "";
196 | };
197 | B5A43E50203489B9004D27CA /* Main.storyboard */ = {
198 | isa = PBXVariantGroup;
199 | children = (
200 | B5A43E51203489B9004D27CA /* Base */,
201 | );
202 | name = Main.storyboard;
203 | sourceTree = "";
204 | };
205 | /* End PBXVariantGroup section */
206 |
207 | /* Begin XCBuildConfiguration section */
208 | B58BF1A22032133C006921B2 /* Debug */ = {
209 | isa = XCBuildConfiguration;
210 | buildSettings = {
211 | ALWAYS_SEARCH_USER_PATHS = NO;
212 | CLANG_ANALYZER_NONNULL = YES;
213 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
214 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
215 | CLANG_CXX_LIBRARY = "libc++";
216 | CLANG_ENABLE_MODULES = YES;
217 | CLANG_ENABLE_OBJC_ARC = YES;
218 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
219 | CLANG_WARN_BOOL_CONVERSION = YES;
220 | CLANG_WARN_COMMA = YES;
221 | CLANG_WARN_CONSTANT_CONVERSION = YES;
222 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
223 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
224 | CLANG_WARN_EMPTY_BODY = YES;
225 | CLANG_WARN_ENUM_CONVERSION = YES;
226 | CLANG_WARN_INFINITE_RECURSION = YES;
227 | CLANG_WARN_INT_CONVERSION = YES;
228 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
229 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
230 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
231 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
232 | CLANG_WARN_STRICT_PROTOTYPES = YES;
233 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
234 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
235 | CLANG_WARN_UNREACHABLE_CODE = YES;
236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
237 | CODE_SIGN_IDENTITY = "iPhone Developer";
238 | COPY_PHASE_STRIP = NO;
239 | DEBUG_INFORMATION_FORMAT = dwarf;
240 | ENABLE_STRICT_OBJC_MSGSEND = YES;
241 | ENABLE_TESTABILITY = YES;
242 | GCC_C_LANGUAGE_STANDARD = gnu11;
243 | GCC_DYNAMIC_NO_PIC = NO;
244 | GCC_NO_COMMON_BLOCKS = YES;
245 | GCC_OPTIMIZATION_LEVEL = 0;
246 | GCC_PREPROCESSOR_DEFINITIONS = (
247 | "DEBUG=1",
248 | "$(inherited)",
249 | );
250 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
251 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
252 | GCC_WARN_UNDECLARED_SELECTOR = YES;
253 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
254 | GCC_WARN_UNUSED_FUNCTION = YES;
255 | GCC_WARN_UNUSED_VARIABLE = YES;
256 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
257 | MTL_ENABLE_DEBUG_INFO = YES;
258 | ONLY_ACTIVE_ARCH = YES;
259 | SDKROOT = iphoneos;
260 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
261 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
262 | };
263 | name = Debug;
264 | };
265 | B58BF1A32032133C006921B2 /* Release */ = {
266 | isa = XCBuildConfiguration;
267 | buildSettings = {
268 | ALWAYS_SEARCH_USER_PATHS = NO;
269 | CLANG_ANALYZER_NONNULL = YES;
270 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
271 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
272 | CLANG_CXX_LIBRARY = "libc++";
273 | CLANG_ENABLE_MODULES = YES;
274 | CLANG_ENABLE_OBJC_ARC = YES;
275 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
276 | CLANG_WARN_BOOL_CONVERSION = YES;
277 | CLANG_WARN_COMMA = YES;
278 | CLANG_WARN_CONSTANT_CONVERSION = YES;
279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
280 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
281 | CLANG_WARN_EMPTY_BODY = YES;
282 | CLANG_WARN_ENUM_CONVERSION = YES;
283 | CLANG_WARN_INFINITE_RECURSION = YES;
284 | CLANG_WARN_INT_CONVERSION = YES;
285 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
286 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
287 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
288 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
289 | CLANG_WARN_STRICT_PROTOTYPES = YES;
290 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
291 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
292 | CLANG_WARN_UNREACHABLE_CODE = YES;
293 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
294 | CODE_SIGN_IDENTITY = "iPhone Developer";
295 | COPY_PHASE_STRIP = NO;
296 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
297 | ENABLE_NS_ASSERTIONS = NO;
298 | ENABLE_STRICT_OBJC_MSGSEND = YES;
299 | GCC_C_LANGUAGE_STANDARD = gnu11;
300 | GCC_NO_COMMON_BLOCKS = YES;
301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
303 | GCC_WARN_UNDECLARED_SELECTOR = YES;
304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
305 | GCC_WARN_UNUSED_FUNCTION = YES;
306 | GCC_WARN_UNUSED_VARIABLE = YES;
307 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
308 | MTL_ENABLE_DEBUG_INFO = NO;
309 | SDKROOT = iphoneos;
310 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
311 | VALIDATE_PRODUCT = YES;
312 | };
313 | name = Release;
314 | };
315 | B58BF1A52032133C006921B2 /* Debug */ = {
316 | isa = XCBuildConfiguration;
317 | buildSettings = {
318 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
319 | CLANG_ENABLE_MODULES = YES;
320 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
321 | CODE_SIGN_STYLE = Automatic;
322 | DEVELOPMENT_TEAM = NX84NCXAJ3;
323 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist";
324 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
325 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
326 | PRODUCT_BUNDLE_IDENTIFIER = com.maksimkurpa.Linker;
327 | PRODUCT_NAME = "$(TARGET_NAME)";
328 | PROVISIONING_PROFILE_SPECIFIER = "";
329 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
330 | SWIFT_VERSION = 4.0;
331 | TARGETED_DEVICE_FAMILY = "1,2";
332 | };
333 | name = Debug;
334 | };
335 | B58BF1A62032133C006921B2 /* Release */ = {
336 | isa = XCBuildConfiguration;
337 | buildSettings = {
338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
339 | CLANG_ENABLE_MODULES = YES;
340 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
341 | CODE_SIGN_STYLE = Automatic;
342 | DEVELOPMENT_TEAM = NX84NCXAJ3;
343 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist";
344 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
345 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
346 | PRODUCT_BUNDLE_IDENTIFIER = com.maksimkurpa.Linker;
347 | PRODUCT_NAME = "$(TARGET_NAME)";
348 | PROVISIONING_PROFILE_SPECIFIER = "";
349 | SWIFT_VERSION = 4.0;
350 | TARGETED_DEVICE_FAMILY = "1,2";
351 | };
352 | name = Release;
353 | };
354 | /* End XCBuildConfiguration section */
355 |
356 | /* Begin XCConfigurationList section */
357 | B58BF1822032133C006921B2 /* Build configuration list for PBXProject "Demo" */ = {
358 | isa = XCConfigurationList;
359 | buildConfigurations = (
360 | B58BF1A22032133C006921B2 /* Debug */,
361 | B58BF1A32032133C006921B2 /* Release */,
362 | );
363 | defaultConfigurationIsVisible = 0;
364 | defaultConfigurationName = Release;
365 | };
366 | B58BF1A42032133C006921B2 /* Build configuration list for PBXNativeTarget "Demo" */ = {
367 | isa = XCConfigurationList;
368 | buildConfigurations = (
369 | B58BF1A52032133C006921B2 /* Debug */,
370 | B58BF1A62032133C006921B2 /* Release */,
371 | );
372 | defaultConfigurationIsVisible = 0;
373 | defaultConfigurationName = Release;
374 | };
375 | /* End XCConfigurationList section */
376 | };
377 | rootObject = B58BF17F2032133C006921B2 /* Project object */;
378 | }
379 |
--------------------------------------------------------------------------------
/Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/Demo.xcodeproj/xcuserdata/maksim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/Demo/Demo.xcodeproj/xcuserdata/maksim.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Demo.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 | Linker.xcscheme
13 |
14 | orderHint
15 | 0
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | CFBundleURLTypes
45 |
46 |
47 | CFBundleTypeRole
48 | Editor
49 | CFBundleURLSchemes
50 |
51 | linker
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Demo/LNAppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LNAppDelegate.swift
3 | // Linker
4 | //
5 | // Created by Maksim Kurpa on 1/16/18.
6 | // Copyright © 2018 Maksim Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LNAppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
17 |
18 | let launchURL = URL(string: "linker://launchURL")!
19 | Linker.handle(launchURL, closure: { url in
20 | print("Your URL has been handle!")
21 | })
22 |
23 | return true
24 | }
25 |
26 | func applicationWillResignActive(_ application: UIApplication) {
27 | }
28 |
29 | func applicationDidEnterBackground(_ application: UIApplication) {
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | }
34 |
35 | func applicationDidBecomeActive(_ application: UIApplication) {
36 | }
37 |
38 | func applicationWillTerminate(_ application: UIApplication) {
39 | }
40 |
41 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
42 | print("original IMP")
43 | return true
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Demo/LNApplication.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LNApplication.swift
3 | // Linker
4 | //
5 | // Created by Maksim Kurpa on 1/26/18.
6 | // Copyright © 2018 Maksim Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LNApplication: UIApplication {
12 |
13 | override func openURL(_ url: URL) -> Bool {
14 | print("openURL original method")
15 | return true
16 | }
17 |
18 | override func open(_ url: URL, options: [String : Any] = [:], completionHandler completion: ((Bool) -> Void)? = nil) {
19 | print("openURL original method")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Demo/LNViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LNViewController.swift
3 | // Linker
4 | //
5 | // Created by Maksim Kurpa on 1/16/18.
6 | // Copyright © 2018 Maksim Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LNViewController: UIViewController {
12 |
13 | let sourceURL = URL(string: "linker://viewcontroller?title=ExampleAlert&description=ExampleDescriptionAlert")!
14 |
15 | @IBAction func action(_ sender: Any) {
16 | UIApplication.shared.open(sourceURL, options: [:], completionHandler: nil)
17 | }
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 | Linker.handle(sourceURL) { url in
22 |
23 | guard let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems! else {
24 | return }
25 | var title : String? = nil
26 | var description: String? = nil
27 |
28 | for item in queryItems {
29 | if item.name == "title" {
30 | title = item.value
31 | }
32 | if item.name == "description" {
33 | description = item.value;
34 | }
35 | }
36 |
37 | if let name = title, let message = description {
38 | let alertVC = UIAlertController(title: name, message: message, preferredStyle: UIAlertControllerStyle.alert)
39 | alertVC.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: {action in
40 | alertVC.dismiss(animated: true, completion: nil)
41 | }))
42 | self.present(alertVC, animated: false, completion: nil)
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Demo/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // Linker
4 | //
5 | // Created by Maksim Kurpa on 1/26/18.
6 | // Copyright © 2018 Maksim Kurpa. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | UIApplicationMain(
13 | CommandLine.argc,
14 | UnsafeMutableRawPointer(CommandLine.unsafeArgv)
15 | .bindMemory(
16 | to: UnsafeMutablePointer.self,
17 | capacity: Int(CommandLine.argc)),
18 | NSStringFromClass(LNApplication.self),
19 | NSStringFromClass(LNAppDelegate.self)
20 | )
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Maksim Kurpa
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Linker.podspec:
--------------------------------------------------------------------------------
1 |
2 | Pod::Spec.new do |s|
3 | s.name = 'Linker'
4 | s.version = '1.0.0'
5 | s.summary = 'Lightweight way to handle internal and external URLs in Swift for iOS.'
6 | s.ios.deployment_target = '8.0'
7 | s.license = { :type => 'MIT', :file => 'LICENSE' }
8 | s.author = { 'Maksim Kurpa' => 'maksim.kurpa@gmail.com' }
9 | s.description = 'Linker is the easiest way to handle internal and external URLs in your project!'
10 | s.homepage = 'https://github.com/MaksimKurpa/Linker'
11 | s.source = { :git => 'https://github.com/MaksimKurpa/Linker.git', :branch => 'master',:tag => s.version.to_s }
12 | s.social_media_url = 'https://www.facebook.com/maksim.kurpa'
13 | s.source_files = 'Linker/Sources/Linker/*.{swift}'
14 | s.requires_arc = true
15 | end
16 |
--------------------------------------------------------------------------------
/Linker.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | B559188F2035D00B00AFB31A /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = B559187A2035D00B00AFB31A /* logo.png */; };
11 | B55918902035D00B00AFB31A /* Linker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559187D2035D00B00AFB31A /* Linker.swift */; };
12 | B55918912035D00C00AFB31A /* LinkerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559187E2035D00B00AFB31A /* LinkerModel.swift */; };
13 | B55918922035D00C00AFB31A /* LinkerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559187F2035D00B00AFB31A /* LinkerExtensions.swift */; };
14 | B55918932035D00C00AFB31A /* LinkerTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918802035D00B00AFB31A /* LinkerTypes.swift */; };
15 | B559189F2035E27900AFB31A /* LinkerMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559188C2035D00B00AFB31A /* LinkerMocks.swift */; };
16 | B55918A02035E28300AFB31A /* LinkerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918822035D00B00AFB31A /* LinkerTests.swift */; };
17 | B55918A12035E33700AFB31A /* Expectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918842035D00B00AFB31A /* Expectation.swift */; };
18 | B55918A22035E33700AFB31A /* Failure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918852035D00B00AFB31A /* Failure.swift */; };
19 | B55918A32035E33700AFB31A /* Global.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918862035D00B00AFB31A /* Global.swift */; };
20 | B55918A42035E33700AFB31A /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918872035D00B00AFB31A /* Context.swift */; };
21 | B55918A52035E33700AFB31A /* Case.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918882035D00B00AFB31A /* Case.swift */; };
22 | B55918A62035E33700AFB31A /* GlobalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55918892035D00B00AFB31A /* GlobalContext.swift */; };
23 | B55918A72035E33700AFB31A /* Reporters.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559188A2035D00B00AFB31A /* Reporters.swift */; };
24 | B55918A82035E33700AFB31A /* Reporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559188B2035D00B00AFB31A /* Reporter.swift */; };
25 | B55918A92035E35600AFB31A /* LinkerTests.plist in Resources */ = {isa = PBXBuildFile; fileRef = B55918782035D00B00AFB31A /* LinkerTests.plist */; };
26 | B55918AA2035E35B00AFB31A /* Linker.plist in Resources */ = {isa = PBXBuildFile; fileRef = B55918792035D00B00AFB31A /* Linker.plist */; };
27 | B55918AB2035E5AF00AFB31A /* LinkerTests.plist in Resources */ = {isa = PBXBuildFile; fileRef = B55918782035D00B00AFB31A /* LinkerTests.plist */; };
28 | B5A43E8520358F62004D27CA /* Linker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A43E7B20358F62004D27CA /* Linker.framework */; };
29 | /* End PBXBuildFile section */
30 |
31 | /* Begin PBXContainerItemProxy section */
32 | B5A43E8620358F62004D27CA /* PBXContainerItemProxy */ = {
33 | isa = PBXContainerItemProxy;
34 | containerPortal = B5A43E7220358F62004D27CA /* Project object */;
35 | proxyType = 1;
36 | remoteGlobalIDString = B5A43E7A20358F62004D27CA;
37 | remoteInfo = Linker;
38 | };
39 | /* End PBXContainerItemProxy section */
40 |
41 | /* Begin PBXFileReference section */
42 | B55918782035D00B00AFB31A /* LinkerTests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = LinkerTests.plist; sourceTree = ""; };
43 | B55918792035D00B00AFB31A /* Linker.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Linker.plist; sourceTree = ""; };
44 | B559187A2035D00B00AFB31A /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = ""; };
45 | B559187D2035D00B00AFB31A /* Linker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Linker.swift; sourceTree = ""; };
46 | B559187E2035D00B00AFB31A /* LinkerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerModel.swift; sourceTree = ""; };
47 | B559187F2035D00B00AFB31A /* LinkerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerExtensions.swift; sourceTree = ""; };
48 | B55918802035D00B00AFB31A /* LinkerTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerTypes.swift; sourceTree = ""; };
49 | B55918822035D00B00AFB31A /* LinkerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerTests.swift; sourceTree = ""; };
50 | B55918842035D00B00AFB31A /* Expectation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expectation.swift; sourceTree = ""; };
51 | B55918852035D00B00AFB31A /* Failure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Failure.swift; sourceTree = ""; };
52 | B55918862035D00B00AFB31A /* Global.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Global.swift; sourceTree = ""; };
53 | B55918872035D00B00AFB31A /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = ""; };
54 | B55918882035D00B00AFB31A /* Case.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Case.swift; sourceTree = ""; };
55 | B55918892035D00B00AFB31A /* GlobalContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalContext.swift; sourceTree = ""; };
56 | B559188A2035D00B00AFB31A /* Reporters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reporters.swift; sourceTree = ""; };
57 | B559188B2035D00B00AFB31A /* Reporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reporter.swift; sourceTree = ""; };
58 | B559188C2035D00B00AFB31A /* LinkerMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkerMocks.swift; sourceTree = ""; };
59 | B5A43E7B20358F62004D27CA /* Linker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Linker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
60 | B5A43E8420358F62004D27CA /* LinkerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LinkerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
61 | /* End PBXFileReference section */
62 |
63 | /* Begin PBXFrameworksBuildPhase section */
64 | B5A43E7720358F62004D27CA /* Frameworks */ = {
65 | isa = PBXFrameworksBuildPhase;
66 | buildActionMask = 2147483647;
67 | files = (
68 | );
69 | runOnlyForDeploymentPostprocessing = 0;
70 | };
71 | B5A43E8120358F62004D27CA /* Frameworks */ = {
72 | isa = PBXFrameworksBuildPhase;
73 | buildActionMask = 2147483647;
74 | files = (
75 | B5A43E8520358F62004D27CA /* Linker.framework in Frameworks */,
76 | );
77 | runOnlyForDeploymentPostprocessing = 0;
78 | };
79 | /* End PBXFrameworksBuildPhase section */
80 |
81 | /* Begin PBXGroup section */
82 | B55918772035D00B00AFB31A /* Configs */ = {
83 | isa = PBXGroup;
84 | children = (
85 | B55918782035D00B00AFB31A /* LinkerTests.plist */,
86 | B55918792035D00B00AFB31A /* Linker.plist */,
87 | B559187A2035D00B00AFB31A /* logo.png */,
88 | );
89 | name = Configs;
90 | path = Linker/Configs;
91 | sourceTree = "";
92 | };
93 | B559187B2035D00B00AFB31A /* Sources */ = {
94 | isa = PBXGroup;
95 | children = (
96 | B559187C2035D00B00AFB31A /* Linker */,
97 | );
98 | name = Sources;
99 | path = Linker/Sources;
100 | sourceTree = "";
101 | };
102 | B559187C2035D00B00AFB31A /* Linker */ = {
103 | isa = PBXGroup;
104 | children = (
105 | B559187D2035D00B00AFB31A /* Linker.swift */,
106 | B559187E2035D00B00AFB31A /* LinkerModel.swift */,
107 | B559187F2035D00B00AFB31A /* LinkerExtensions.swift */,
108 | B55918802035D00B00AFB31A /* LinkerTypes.swift */,
109 | );
110 | path = Linker;
111 | sourceTree = "";
112 | };
113 | B55918812035D00B00AFB31A /* LinkerTests */ = {
114 | isa = PBXGroup;
115 | children = (
116 | B55918832035D00B00AFB31A /* Spectre */,
117 | B55918822035D00B00AFB31A /* LinkerTests.swift */,
118 | B559188C2035D00B00AFB31A /* LinkerMocks.swift */,
119 | );
120 | name = LinkerTests;
121 | path = Linker/LinkerTests;
122 | sourceTree = "";
123 | };
124 | B55918832035D00B00AFB31A /* Spectre */ = {
125 | isa = PBXGroup;
126 | children = (
127 | B55918842035D00B00AFB31A /* Expectation.swift */,
128 | B55918852035D00B00AFB31A /* Failure.swift */,
129 | B55918862035D00B00AFB31A /* Global.swift */,
130 | B55918872035D00B00AFB31A /* Context.swift */,
131 | B55918882035D00B00AFB31A /* Case.swift */,
132 | B55918892035D00B00AFB31A /* GlobalContext.swift */,
133 | B559188A2035D00B00AFB31A /* Reporters.swift */,
134 | B559188B2035D00B00AFB31A /* Reporter.swift */,
135 | );
136 | path = Spectre;
137 | sourceTree = "";
138 | };
139 | B5A43E7120358F62004D27CA = {
140 | isa = PBXGroup;
141 | children = (
142 | B55918772035D00B00AFB31A /* Configs */,
143 | B55918812035D00B00AFB31A /* LinkerTests */,
144 | B559187B2035D00B00AFB31A /* Sources */,
145 | B5A43E7C20358F62004D27CA /* Products */,
146 | );
147 | sourceTree = "";
148 | };
149 | B5A43E7C20358F62004D27CA /* Products */ = {
150 | isa = PBXGroup;
151 | children = (
152 | B5A43E7B20358F62004D27CA /* Linker.framework */,
153 | B5A43E8420358F62004D27CA /* LinkerTests.xctest */,
154 | );
155 | name = Products;
156 | sourceTree = "";
157 | };
158 | /* End PBXGroup section */
159 |
160 | /* Begin PBXHeadersBuildPhase section */
161 | B5A43E7820358F62004D27CA /* Headers */ = {
162 | isa = PBXHeadersBuildPhase;
163 | buildActionMask = 2147483647;
164 | files = (
165 | );
166 | runOnlyForDeploymentPostprocessing = 0;
167 | };
168 | /* End PBXHeadersBuildPhase section */
169 |
170 | /* Begin PBXNativeTarget section */
171 | B5A43E7A20358F62004D27CA /* Linker */ = {
172 | isa = PBXNativeTarget;
173 | buildConfigurationList = B5A43E8F20358F62004D27CA /* Build configuration list for PBXNativeTarget "Linker" */;
174 | buildPhases = (
175 | B5A43E7620358F62004D27CA /* Sources */,
176 | B5A43E7720358F62004D27CA /* Frameworks */,
177 | B5A43E7820358F62004D27CA /* Headers */,
178 | B5A43E7920358F62004D27CA /* Resources */,
179 | );
180 | buildRules = (
181 | );
182 | dependencies = (
183 | );
184 | name = Linker;
185 | productName = Linker;
186 | productReference = B5A43E7B20358F62004D27CA /* Linker.framework */;
187 | productType = "com.apple.product-type.framework";
188 | };
189 | B5A43E8320358F62004D27CA /* LinkerTests */ = {
190 | isa = PBXNativeTarget;
191 | buildConfigurationList = B5A43E9220358F62004D27CA /* Build configuration list for PBXNativeTarget "LinkerTests" */;
192 | buildPhases = (
193 | B5A43E8020358F62004D27CA /* Sources */,
194 | B5A43E8120358F62004D27CA /* Frameworks */,
195 | B5A43E8220358F62004D27CA /* Resources */,
196 | );
197 | buildRules = (
198 | );
199 | dependencies = (
200 | B5A43E8720358F62004D27CA /* PBXTargetDependency */,
201 | );
202 | name = LinkerTests;
203 | productName = LinkerTests;
204 | productReference = B5A43E8420358F62004D27CA /* LinkerTests.xctest */;
205 | productType = "com.apple.product-type.bundle.unit-test";
206 | };
207 | /* End PBXNativeTarget section */
208 |
209 | /* Begin PBXProject section */
210 | B5A43E7220358F62004D27CA /* Project object */ = {
211 | isa = PBXProject;
212 | attributes = {
213 | LastSwiftUpdateCheck = 0920;
214 | LastUpgradeCheck = 0920;
215 | ORGANIZATIONNAME = "Maksim Kurpa";
216 | TargetAttributes = {
217 | B5A43E7A20358F62004D27CA = {
218 | CreatedOnToolsVersion = 9.2;
219 | ProvisioningStyle = Manual;
220 | };
221 | B5A43E8320358F62004D27CA = {
222 | CreatedOnToolsVersion = 9.2;
223 | LastSwiftMigration = 0920;
224 | ProvisioningStyle = Automatic;
225 | };
226 | };
227 | };
228 | buildConfigurationList = B5A43E7520358F62004D27CA /* Build configuration list for PBXProject "Linker" */;
229 | compatibilityVersion = "Xcode 8.0";
230 | developmentRegion = en;
231 | hasScannedForEncodings = 0;
232 | knownRegions = (
233 | en,
234 | );
235 | mainGroup = B5A43E7120358F62004D27CA;
236 | productRefGroup = B5A43E7C20358F62004D27CA /* Products */;
237 | projectDirPath = "";
238 | projectRoot = "";
239 | targets = (
240 | B5A43E7A20358F62004D27CA /* Linker */,
241 | B5A43E8320358F62004D27CA /* LinkerTests */,
242 | );
243 | };
244 | /* End PBXProject section */
245 |
246 | /* Begin PBXResourcesBuildPhase section */
247 | B5A43E7920358F62004D27CA /* Resources */ = {
248 | isa = PBXResourcesBuildPhase;
249 | buildActionMask = 2147483647;
250 | files = (
251 | B559188F2035D00B00AFB31A /* logo.png in Resources */,
252 | B55918AB2035E5AF00AFB31A /* LinkerTests.plist in Resources */,
253 | B55918AA2035E35B00AFB31A /* Linker.plist in Resources */,
254 | );
255 | runOnlyForDeploymentPostprocessing = 0;
256 | };
257 | B5A43E8220358F62004D27CA /* Resources */ = {
258 | isa = PBXResourcesBuildPhase;
259 | buildActionMask = 2147483647;
260 | files = (
261 | B55918A92035E35600AFB31A /* LinkerTests.plist in Resources */,
262 | );
263 | runOnlyForDeploymentPostprocessing = 0;
264 | };
265 | /* End PBXResourcesBuildPhase section */
266 |
267 | /* Begin PBXSourcesBuildPhase section */
268 | B5A43E7620358F62004D27CA /* Sources */ = {
269 | isa = PBXSourcesBuildPhase;
270 | buildActionMask = 2147483647;
271 | files = (
272 | B55918912035D00C00AFB31A /* LinkerModel.swift in Sources */,
273 | B55918922035D00C00AFB31A /* LinkerExtensions.swift in Sources */,
274 | B55918932035D00C00AFB31A /* LinkerTypes.swift in Sources */,
275 | B55918902035D00B00AFB31A /* Linker.swift in Sources */,
276 | );
277 | runOnlyForDeploymentPostprocessing = 0;
278 | };
279 | B5A43E8020358F62004D27CA /* Sources */ = {
280 | isa = PBXSourcesBuildPhase;
281 | buildActionMask = 2147483647;
282 | files = (
283 | B55918A22035E33700AFB31A /* Failure.swift in Sources */,
284 | B55918A02035E28300AFB31A /* LinkerTests.swift in Sources */,
285 | B55918A12035E33700AFB31A /* Expectation.swift in Sources */,
286 | B55918A42035E33700AFB31A /* Context.swift in Sources */,
287 | B55918A62035E33700AFB31A /* GlobalContext.swift in Sources */,
288 | B55918A32035E33700AFB31A /* Global.swift in Sources */,
289 | B55918A82035E33700AFB31A /* Reporter.swift in Sources */,
290 | B55918A52035E33700AFB31A /* Case.swift in Sources */,
291 | B559189F2035E27900AFB31A /* LinkerMocks.swift in Sources */,
292 | B55918A72035E33700AFB31A /* Reporters.swift in Sources */,
293 | );
294 | runOnlyForDeploymentPostprocessing = 0;
295 | };
296 | /* End PBXSourcesBuildPhase section */
297 |
298 | /* Begin PBXTargetDependency section */
299 | B5A43E8720358F62004D27CA /* PBXTargetDependency */ = {
300 | isa = PBXTargetDependency;
301 | target = B5A43E7A20358F62004D27CA /* Linker */;
302 | targetProxy = B5A43E8620358F62004D27CA /* PBXContainerItemProxy */;
303 | };
304 | /* End PBXTargetDependency section */
305 |
306 | /* Begin XCBuildConfiguration section */
307 | B5A43E8D20358F62004D27CA /* Debug */ = {
308 | isa = XCBuildConfiguration;
309 | buildSettings = {
310 | ALWAYS_SEARCH_USER_PATHS = NO;
311 | CLANG_ANALYZER_NONNULL = YES;
312 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
313 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
314 | CLANG_CXX_LIBRARY = "libc++";
315 | CLANG_ENABLE_MODULES = YES;
316 | CLANG_ENABLE_OBJC_ARC = YES;
317 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
318 | CLANG_WARN_BOOL_CONVERSION = YES;
319 | CLANG_WARN_COMMA = YES;
320 | CLANG_WARN_CONSTANT_CONVERSION = YES;
321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
322 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
323 | CLANG_WARN_EMPTY_BODY = YES;
324 | CLANG_WARN_ENUM_CONVERSION = YES;
325 | CLANG_WARN_INFINITE_RECURSION = YES;
326 | CLANG_WARN_INT_CONVERSION = YES;
327 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
331 | CLANG_WARN_STRICT_PROTOTYPES = YES;
332 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
333 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
334 | CLANG_WARN_UNREACHABLE_CODE = YES;
335 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
336 | CODE_SIGN_IDENTITY = "iPhone Developer";
337 | COPY_PHASE_STRIP = NO;
338 | CURRENT_PROJECT_VERSION = 1;
339 | DEBUG_INFORMATION_FORMAT = dwarf;
340 | ENABLE_STRICT_OBJC_MSGSEND = YES;
341 | ENABLE_TESTABILITY = YES;
342 | GCC_C_LANGUAGE_STANDARD = gnu11;
343 | GCC_DYNAMIC_NO_PIC = NO;
344 | GCC_NO_COMMON_BLOCKS = YES;
345 | GCC_OPTIMIZATION_LEVEL = 0;
346 | GCC_PREPROCESSOR_DEFINITIONS = (
347 | "DEBUG=1",
348 | "$(inherited)",
349 | );
350 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
351 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
352 | GCC_WARN_UNDECLARED_SELECTOR = YES;
353 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
354 | GCC_WARN_UNUSED_FUNCTION = YES;
355 | GCC_WARN_UNUSED_VARIABLE = YES;
356 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
357 | MTL_ENABLE_DEBUG_INFO = YES;
358 | ONLY_ACTIVE_ARCH = YES;
359 | SDKROOT = iphoneos;
360 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
361 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
362 | VERSIONING_SYSTEM = "apple-generic";
363 | VERSION_INFO_PREFIX = "";
364 | };
365 | name = Debug;
366 | };
367 | B5A43E8E20358F62004D27CA /* Release */ = {
368 | isa = XCBuildConfiguration;
369 | buildSettings = {
370 | ALWAYS_SEARCH_USER_PATHS = NO;
371 | CLANG_ANALYZER_NONNULL = YES;
372 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
373 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
374 | CLANG_CXX_LIBRARY = "libc++";
375 | CLANG_ENABLE_MODULES = YES;
376 | CLANG_ENABLE_OBJC_ARC = YES;
377 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
378 | CLANG_WARN_BOOL_CONVERSION = YES;
379 | CLANG_WARN_COMMA = YES;
380 | CLANG_WARN_CONSTANT_CONVERSION = YES;
381 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
382 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
383 | CLANG_WARN_EMPTY_BODY = YES;
384 | CLANG_WARN_ENUM_CONVERSION = YES;
385 | CLANG_WARN_INFINITE_RECURSION = YES;
386 | CLANG_WARN_INT_CONVERSION = YES;
387 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
388 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
389 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
390 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
391 | CLANG_WARN_STRICT_PROTOTYPES = YES;
392 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
393 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
394 | CLANG_WARN_UNREACHABLE_CODE = YES;
395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
396 | CODE_SIGN_IDENTITY = "iPhone Developer";
397 | COPY_PHASE_STRIP = NO;
398 | CURRENT_PROJECT_VERSION = 1;
399 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
400 | ENABLE_NS_ASSERTIONS = NO;
401 | ENABLE_STRICT_OBJC_MSGSEND = YES;
402 | GCC_C_LANGUAGE_STANDARD = gnu11;
403 | GCC_NO_COMMON_BLOCKS = YES;
404 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
405 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
406 | GCC_WARN_UNDECLARED_SELECTOR = YES;
407 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
408 | GCC_WARN_UNUSED_FUNCTION = YES;
409 | GCC_WARN_UNUSED_VARIABLE = YES;
410 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
411 | MTL_ENABLE_DEBUG_INFO = NO;
412 | SDKROOT = iphoneos;
413 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
414 | VALIDATE_PRODUCT = YES;
415 | VERSIONING_SYSTEM = "apple-generic";
416 | VERSION_INFO_PREFIX = "";
417 | };
418 | name = Release;
419 | };
420 | B5A43E9020358F62004D27CA /* Debug */ = {
421 | isa = XCBuildConfiguration;
422 | buildSettings = {
423 | APPLICATION_EXTENSION_API_ONLY = NO;
424 | CODE_SIGN_IDENTITY = "";
425 | CODE_SIGN_STYLE = Manual;
426 | DEFINES_MODULE = YES;
427 | DEVELOPMENT_TEAM = "";
428 | DYLIB_COMPATIBILITY_VERSION = 1;
429 | DYLIB_CURRENT_VERSION = 1;
430 | DYLIB_INSTALL_NAME_BASE = "@rpath";
431 | INFOPLIST_FILE = "$(SRCROOT)/Linker/Configs/Linker.plist";
432 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
433 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
434 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
435 | PRODUCT_BUNDLE_IDENTIFIER = "com.maksimkurpa.Linker-iOS";
436 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
437 | PROVISIONING_PROFILE_SPECIFIER = "";
438 | SKIP_INSTALL = YES;
439 | SWIFT_VERSION = 4.0;
440 | TARGETED_DEVICE_FAMILY = "1,2";
441 | };
442 | name = Debug;
443 | };
444 | B5A43E9120358F62004D27CA /* Release */ = {
445 | isa = XCBuildConfiguration;
446 | buildSettings = {
447 | APPLICATION_EXTENSION_API_ONLY = NO;
448 | CODE_SIGN_IDENTITY = "";
449 | CODE_SIGN_STYLE = Manual;
450 | DEFINES_MODULE = YES;
451 | DEVELOPMENT_TEAM = "";
452 | DYLIB_COMPATIBILITY_VERSION = 1;
453 | DYLIB_CURRENT_VERSION = 1;
454 | DYLIB_INSTALL_NAME_BASE = "@rpath";
455 | INFOPLIST_FILE = "$(SRCROOT)/Linker/Configs/Linker.plist";
456 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
457 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
458 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
459 | PRODUCT_BUNDLE_IDENTIFIER = "com.maksimkurpa.Linker-iOS";
460 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
461 | PROVISIONING_PROFILE_SPECIFIER = "";
462 | SKIP_INSTALL = YES;
463 | SWIFT_VERSION = 4.0;
464 | TARGETED_DEVICE_FAMILY = "1,2";
465 | };
466 | name = Release;
467 | };
468 | B5A43E9320358F62004D27CA /* Debug */ = {
469 | isa = XCBuildConfiguration;
470 | buildSettings = {
471 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
472 | CLANG_ENABLE_MODULES = YES;
473 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
474 | CODE_SIGN_STYLE = Automatic;
475 | DEVELOPMENT_TEAM = "";
476 | INFOPLIST_FILE = "$(PROJECT_DIR)/Linker/Configs/LinkerTests.plist";
477 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
478 | PRODUCT_BUNDLE_IDENTIFIER = com.maksimkurpa.LinkerTests;
479 | PRODUCT_NAME = "$(TARGET_NAME)";
480 | PROVISIONING_PROFILE_SPECIFIER = "";
481 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
482 | SWIFT_VERSION = 4.0;
483 | TARGETED_DEVICE_FAMILY = "1,2";
484 | };
485 | name = Debug;
486 | };
487 | B5A43E9420358F62004D27CA /* Release */ = {
488 | isa = XCBuildConfiguration;
489 | buildSettings = {
490 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
491 | CLANG_ENABLE_MODULES = YES;
492 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
493 | CODE_SIGN_STYLE = Automatic;
494 | DEVELOPMENT_TEAM = "";
495 | INFOPLIST_FILE = "$(PROJECT_DIR)/Linker/Configs/LinkerTests.plist";
496 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
497 | PRODUCT_BUNDLE_IDENTIFIER = com.maksimkurpa.LinkerTests;
498 | PRODUCT_NAME = "$(TARGET_NAME)";
499 | PROVISIONING_PROFILE_SPECIFIER = "";
500 | SWIFT_VERSION = 4.0;
501 | TARGETED_DEVICE_FAMILY = "1,2";
502 | };
503 | name = Release;
504 | };
505 | /* End XCBuildConfiguration section */
506 |
507 | /* Begin XCConfigurationList section */
508 | B5A43E7520358F62004D27CA /* Build configuration list for PBXProject "Linker" */ = {
509 | isa = XCConfigurationList;
510 | buildConfigurations = (
511 | B5A43E8D20358F62004D27CA /* Debug */,
512 | B5A43E8E20358F62004D27CA /* Release */,
513 | );
514 | defaultConfigurationIsVisible = 0;
515 | defaultConfigurationName = Release;
516 | };
517 | B5A43E8F20358F62004D27CA /* Build configuration list for PBXNativeTarget "Linker" */ = {
518 | isa = XCConfigurationList;
519 | buildConfigurations = (
520 | B5A43E9020358F62004D27CA /* Debug */,
521 | B5A43E9120358F62004D27CA /* Release */,
522 | );
523 | defaultConfigurationIsVisible = 0;
524 | defaultConfigurationName = Release;
525 | };
526 | B5A43E9220358F62004D27CA /* Build configuration list for PBXNativeTarget "LinkerTests" */ = {
527 | isa = XCConfigurationList;
528 | buildConfigurations = (
529 | B5A43E9320358F62004D27CA /* Debug */,
530 | B5A43E9420358F62004D27CA /* Release */,
531 | );
532 | defaultConfigurationIsVisible = 0;
533 | defaultConfigurationName = Release;
534 | };
535 | /* End XCConfigurationList section */
536 | };
537 | rootObject = B5A43E7220358F62004D27CA /* Project object */;
538 | }
539 |
--------------------------------------------------------------------------------
/Linker.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Linker.xcodeproj/xcshareddata/xcschemes/Linker.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
66 |
67 |
73 |
74 |
75 |
76 |
77 |
78 |
84 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Linker.xcodeproj/xcshareddata/xcschemes/LinkerTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
66 |
67 |
68 |
69 |
75 |
76 |
78 |
79 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/Linker.xcodeproj/xcuserdata/maksim.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Linker.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | LinkerTests.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 1
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | B5A43E7A20358F62004D27CA
21 |
22 | primary
23 |
24 |
25 | B5A43E8320358F62004D27CA
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Linker/Configs/Linker.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Linker/Configs/LinkerTests.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Linker/Configs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaksimKurpa/Linker/50b28ce84fe8e6b1e73c11ab896f433a8f7a865a/Linker/Configs/logo.png
--------------------------------------------------------------------------------
/Linker/LinkerTests/LinkerMocks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Mocks.swift
3 | // Tests
4 | //
5 | // Created by Maksim Kurpa on 2/2/18.
6 | // Copyright © 2018 Maksim Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 |
12 | protocol AppDelegateURLProtocol {
13 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] ) -> Bool
14 | func application(_ application: UIApplication, handleOpen url: URL) -> Bool
15 | func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool
16 | }
17 |
18 | class AppDelegateMock: NSObject, AppDelegateURLProtocol {
19 |
20 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
21 | return true
22 | }
23 |
24 | func application(_ application: UIApplication, handleOpen url: URL) -> Bool {
25 | return true
26 | }
27 |
28 | func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
29 | return true
30 | }
31 | }
32 |
33 | protocol ApplicationURLProtocol {
34 | func openURL(_ url: URL) -> Bool
35 | func open(_ url: URL, options: [String : Any], completionHandler completion: ((Bool) -> Void)?)
36 | }
37 |
38 | class ApplicationMock: NSObject, ApplicationURLProtocol {
39 |
40 | func openURL(_ url: URL) -> Bool {
41 | return true
42 | }
43 |
44 | func open(_ url: URL, options: [String : Any] = [:], completionHandler completion: ((Bool) -> Void)? = nil) {
45 | }
46 | }
47 |
48 |
49 | class SwizzleMockObject: NSObject {
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/Linker/LinkerTests/LinkerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tests.swift
3 | // Tests
4 | //
5 | // Created by Maksim Kurpa on 1/26/18.
6 | // Copyright © 2018 Maksim Kurpa. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Foundation
11 | @testable import Linker
12 |
13 | extension SwizzleMockObject {
14 | @objc dynamic func test1(URL url: URL) -> Void {
15 | describe("testURL1", closure: {
16 | $0.it("original function with one parameter", closure: {
17 | throw failure("Called")
18 | })
19 | })
20 | }
21 | @objc dynamic func test2(_ val0: String?, URL url: URL) -> Void {
22 | describe("testURL2", closure: {
23 | $0.it("original function with two parameters", closure: {
24 | throw failure("Called")
25 | })
26 | })
27 | }
28 | @objc dynamic func test3(_ val0: String?, URL url: URL, _ val1: String?) -> Void {
29 | describe("testURL3", closure: {
30 | $0.it("original function with three parameters", closure: {
31 | throw failure("Called")
32 | })
33 | })
34 | }
35 | @objc dynamic func test4(_ val0: String?, _ val1: String?,URL url: URL, _ val2: String?) -> Void {
36 | describe("testURL4", closure: {
37 | $0.it("original function with four parameters", closure: {
38 | throw failure("Called")
39 | })
40 | })
41 | }
42 | @objc dynamic func succesTest4(_ val0: String?, _ val1: String?,URL url: URL, _ val2: String?) -> Void {
43 | describe("succesTest4", closure: {
44 | $0.it("original function with four parameters", closure: {
45 | try expect(true).beTrue()
46 | })
47 | })
48 | }
49 | }
50 |
51 | class Tests: XCTestCase {
52 |
53 | func test() {
54 | utilsTest()
55 | modelTest()
56 | businessLogicTest()
57 | }
58 |
59 |
60 | func businessLogicTest() {
61 | let urlToHandleOriginalIMP: URL = URL(string:"successURL://")!
62 | let objectToSwizzle = SwizzleMockObject()
63 |
64 | let execution: ExecutionClosureType = {url in
65 | describe("Swizzled closure called", closure: {
66 | $0.it(url.absoluteString, closure: {
67 | try expect(true).to.beTrue()
68 | })
69 | })
70 | if url == urlToHandleOriginalIMP {
71 | return url
72 | }
73 | return nil
74 | }
75 | func testModels(execution: @escaping ExecutionClosureType) -> [LNModel] {
76 | let clss = SwizzleMockObject.self
77 | return Array(arrayLiteral:
78 | LNModel(clss:clss, selector: #selector(SwizzleMockObject.test1(URL:)),
79 | execution: execution),
80 | LNModel(clss:clss,
81 | selector:#selector(SwizzleMockObject.test2(_:URL:)),
82 | execution: execution),
83 | LNModel(clss:clss,selector: #selector(SwizzleMockObject.test3(_:URL:_:)),
84 | execution: execution),
85 | LNModel(clss:clss, selector:#selector(SwizzleMockObject.test4(_:_:URL:_:)),
86 | execution: execution),
87 | LNModel(clss:clss, selector:#selector(SwizzleMockObject.succesTest4(_:_:URL:_:)),
88 | execution: execution)
89 | )}
90 |
91 | let models = testModels(execution: execution)
92 | for model in models {
93 | Linker.swizzleFunc(clss: model.clss.self, selector: model.selector, execution: model.execution as Any)
94 | }
95 |
96 | objectToSwizzle.test1(URL: URL(string: "swizzleWith1parameter://")!)
97 | objectToSwizzle.test2(nil, URL: URL(string: "swizzleWith2parameters://")!)
98 | objectToSwizzle.test3(nil,URL: URL(string: "swizzleWith3parameters://")!, nil);
99 | objectToSwizzle.test4(nil, nil, URL: URL(string: "swizzleWith4parameters://")!, nil)
100 | objectToSwizzle.succesTest4(nil, nil, URL: urlToHandleOriginalIMP, nil)
101 | }
102 |
103 | func utilsTest() {
104 | describe("Utils") {
105 | $0.it("func parse bad selector", closure: {
106 | let selector = NSSelectorFromString("badSelector")
107 | try expect(selector.argumentsCount()) == 0
108 | try expect(selector.indexOfArgument(withName: "URL")).beNil()
109 | })
110 | $0.it("func parse good selector", closure: {
111 | let selector = NSSelectorFromString("application:openURL:sourceApplication:annotation:")
112 | try expect(selector.argumentsCount()) == 4
113 | try expect(selector.indexOfArgument(withName: "annotation")) == 3
114 | })
115 | }
116 | }
117 |
118 | func modelTest() {
119 |
120 | describe("Application Model Test") {
121 | $0.it("UIApplication openURL:", closure: {
122 | let clss = ApplicationMock.self
123 |
124 | let selector = NSSelectorFromString("openURL:")
125 | let execution: ExecutionClosureType = {url in
126 | return url
127 | }
128 | let model = LNModel(clss: clss, selector: selector, execution: execution)
129 |
130 | try expect(model.selector) == selector
131 | try expect(model.clss).to.beOfType(type(of: clss))
132 | try expect(expression: { () -> Bool in
133 | model.execution != nil ? true : false
134 | }) == true
135 | })
136 |
137 | $0.it("UIApplication openURL:options:completionHandler:", closure: {
138 | let clss = ApplicationMock.self
139 | let selector = NSSelectorFromString("openURL:options:completionHandler:")
140 | let execution: ExecutionClosureType = {url in
141 | return url
142 | }
143 | let model = LNModel(clss: clss, selector: selector, execution: execution)
144 |
145 | try expect(model.selector) == selector
146 | try expect(model.clss).to.beOfType(type(of: clss))
147 | try expect(expression: { () -> Bool in
148 | model.execution != nil ? true : false
149 | }) == true
150 | })
151 | }
152 |
153 | describe("Application Delegate Model Test", closure: {
154 | $0.it("UIApplicationDelegate application:openURL:options:", closure: {
155 |
156 | let clss = AppDelegateMock.self
157 | let selector = NSSelectorFromString("application:openURL:options:")
158 | let execution: ExecutionClosureType = {url in
159 | return url
160 | }
161 | let model = LNModel(clss: clss, selector: selector, execution: execution)
162 |
163 | try expect(model.selector) == selector
164 | try expect(model.clss).to.beOfType(type(of: clss))
165 | try expect(expression: { () -> Bool in
166 | model.execution != nil ? true : false
167 | }) == true
168 | })
169 |
170 | $0.it("UIApplicationDelegate application:openURL:sourceApplication:annotation:", closure: {
171 |
172 | let clss = AppDelegateMock.self
173 | let selector = NSSelectorFromString("application:openURL:sourceApplication:annotation:")
174 | let execution: ExecutionClosureType = {url in
175 | return url
176 | }
177 | let model = LNModel(clss: clss, selector: selector, execution: execution)
178 |
179 | try expect(model.selector) == selector
180 | try expect(model.clss).to.beOfType(type(of: clss))
181 | try expect(expression: { () -> Bool in
182 | model.execution != nil ? true : false
183 | }) == true
184 | })
185 | $0.it("UIApplicationDelegate application:handleOpenURL:", closure: {
186 |
187 | let clss = AppDelegateMock.self
188 | let selector = NSSelectorFromString("application:handleOpenURL:")
189 | let execution: ExecutionClosureType = {url in
190 | return url
191 | }
192 | let model = LNModel(clss: clss, selector: selector, execution: execution)
193 |
194 | try expect(model.selector) == selector
195 | try expect(model.clss).to.beOfType(type(of: clss))
196 | try expect(expression: { () -> Bool in
197 | model.execution != nil ? true : false
198 | }) == true
199 | })
200 | })
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/Linker/LinkerTests/Spectre/Case.swift:
--------------------------------------------------------------------------------
1 | protocol CaseType {
2 | /// Run a test case in the given reporter
3 | func run(reporter: ContextReporter)
4 | }
5 |
6 | class Case : CaseType {
7 | let name:String
8 | let disabled: Bool
9 | let closure:() throws -> ()
10 |
11 | let function: String
12 | let file: String
13 | let line: Int
14 |
15 | init(name: String, disabled: Bool = false, closure: @escaping () throws -> (), function: String = #function, file: String = #file, line: Int = #line) {
16 | self.name = name
17 | self.disabled = disabled
18 | self.closure = closure
19 |
20 | self.function = function
21 | self.file = file
22 | self.line = line
23 | }
24 |
25 | func run(reporter: ContextReporter) {
26 | if disabled {
27 | reporter.addDisabled(name)
28 | return
29 | }
30 |
31 | do {
32 | try closure()
33 | reporter.addSuccess(name)
34 | } catch _ as Skip {
35 | reporter.addDisabled(name)
36 | } catch let error as FailureType {
37 | reporter.addFailure(name, failure: error)
38 | } catch {
39 | reporter.addFailure(name, failure: Failure(reason: "Unhandled error: \(error)", function: function, file: file, line: line))
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Linker/LinkerTests/Spectre/Context.swift:
--------------------------------------------------------------------------------
1 | public protocol ContextType {
2 | /// Creates a new sub-context
3 | func context(_ name: String, closure: (ContextType) -> Void)
4 |
5 | /// Creates a new sub-context
6 | func describe(_ name: String, closure: (ContextType) -> Void)
7 |
8 | /// Creates a new disabled sub-context
9 | func xcontext(_ name: String, closure: (ContextType) -> Void)
10 |
11 | /// Creates a new disabled sub-context
12 | func xdescribe(_ name: String, closure: (ContextType) -> Void)
13 |
14 | func before(_ closure: @escaping () -> Void)
15 | func after(_ closure: @escaping () -> Void)
16 |
17 | /// Adds a new test case
18 | func it(_ name: String, closure: @escaping () throws -> Void)
19 |
20 | /// Adds a disabled test case
21 | func xit(_ name: String, closure: @escaping () throws -> Void)
22 | }
23 |
24 | class Context : ContextType, CaseType {
25 | let name: String
26 | let disabled: Bool
27 | fileprivate weak var parent: Context?
28 | var cases = [CaseType]()
29 |
30 | typealias Before = (() -> Void)
31 | typealias After = (() -> Void)
32 |
33 | var befores = [Before]()
34 | var afters = [After]()
35 |
36 | init(name: String, disabled: Bool = false, parent: Context? = nil) {
37 | self.name = name
38 | self.disabled = disabled
39 | self.parent = parent
40 | }
41 |
42 | func context(_ name: String, closure: (ContextType) -> Void) {
43 | let context = Context(name: name, parent: self)
44 | closure(context)
45 | cases.append(context)
46 | }
47 |
48 | func describe(_ name: String, closure: (ContextType) -> Void) {
49 | let context = Context(name: name, parent: self)
50 | closure(context)
51 | cases.append(context)
52 | }
53 |
54 | func xcontext(_ name: String, closure: (ContextType) -> Void) {
55 | let context = Context(name: name, disabled: true, parent: self)
56 | closure(context)
57 | cases.append(context)
58 | }
59 |
60 | func xdescribe(_ name: String, closure: (ContextType) -> Void) {
61 | let context = Context(name: name, disabled: true, parent: self)
62 | closure(context)
63 | cases.append(context)
64 | }
65 |
66 | func before(_ closure: @escaping () -> Void) {
67 | befores.append(closure)
68 | }
69 |
70 | func after(_ closure: @escaping () -> Void) {
71 | afters.append(closure)
72 | }
73 |
74 | func it(_ name: String, closure: @escaping () throws -> Void) {
75 | cases.append(Case(name: name, closure: closure))
76 | }
77 |
78 | func xit(_ name: String, closure: @escaping () throws -> Void) {
79 | cases.append(Case(name: name, disabled: true, closure: closure))
80 | }
81 |
82 | func runBefores() {
83 | parent?.runBefores()
84 | befores.forEach { $0() }
85 | }
86 |
87 | func runAfters() {
88 | afters.forEach { $0() }
89 | parent?.runAfters()
90 | }
91 |
92 | func run(reporter: ContextReporter) {
93 | if disabled {
94 | reporter.addDisabled(name)
95 | return
96 | }
97 |
98 | reporter.report(name) { reporter in
99 | cases.forEach {
100 | runBefores()
101 | $0.run(reporter: reporter)
102 | runAfters()
103 | }
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Linker/LinkerTests/Spectre/Expectation.swift:
--------------------------------------------------------------------------------
1 | public protocol ExpectationType {
2 | associatedtype ValueType
3 | var expression: () throws -> ValueType? { get }
4 | func failure(_ reason: String) -> FailureType
5 | }
6 |
7 |
8 | struct ExpectationFailure : FailureType {
9 | let file: String
10 | let line: Int
11 | let function: String
12 |
13 | let reason: String
14 |
15 | init(reason: String, file: String, line: Int, function: String) {
16 | self.reason = reason
17 | self.file = file
18 | self.line = line
19 | self.function = function
20 | }
21 | }
22 |
23 | open class Expectation : ExpectationType {
24 | public typealias ValueType = T
25 | open let expression: () throws -> ValueType?
26 |
27 | let file: String
28 | let line: Int
29 | let function: String
30 |
31 | open var to: Expectation {
32 | return self
33 | }
34 |
35 | init(file: String, line: Int, function: String, expression: @escaping () throws -> ValueType?) {
36 | self.file = file
37 | self.line = line
38 | self.function = function
39 | self.expression = expression
40 | }
41 |
42 | open func failure(_ reason: String) -> FailureType {
43 | return ExpectationFailure(reason: reason, file: file, line: line, function: function)
44 | }
45 | }
46 |
47 | public func expect( _ expression: @autoclosure @escaping () throws -> T?, file: String = #file, line: Int = #line, function: String = #function) -> Expectation {
48 | return Expectation(file: file, line: line, function: function, expression: expression)
49 | }
50 |
51 | public func expect(_ file: String = #file, line: Int = #line, function: String = #function, expression: @escaping () throws -> T?) -> Expectation {
52 | return Expectation(file: file, line: line, function: function, expression: expression)
53 | }
54 |
55 | // MARK: Equatability
56 |
57 | public func == (lhs: E, rhs: E.ValueType) throws where E.ValueType: Equatable {
58 | if let value = try lhs.expression() {
59 | if value != rhs {
60 | throw lhs.failure("\(String(describing: value)) is not equal to \(rhs)")
61 | }
62 | } else {
63 | throw lhs.failure("given value is nil")
64 | }
65 | }
66 |
67 | public func != (lhs: E, rhs: E.ValueType) throws where E.ValueType: Equatable {
68 | let value = try lhs.expression()
69 | if value == rhs {
70 | throw lhs.failure("\(String(describing: value)) is equal to \(rhs)")
71 | }
72 | }
73 |
74 | // MARK: Array Equatability
75 |
76 | public func == (lhs: Expectation<[Element]>, rhs: [Element]) throws {
77 | if let value = try lhs.expression() {
78 | if value != rhs {
79 | throw lhs.failure("\(String(describing: value)) is not equal to \(rhs)")
80 | }
81 | } else {
82 | throw lhs.failure("given value is nil")
83 | }
84 | }
85 |
86 | public func != (lhs: Expectation<[Element]>, rhs: [Element]) throws {
87 | if let value = try lhs.expression() {
88 | if value == rhs {
89 | throw lhs.failure("\(String(describing: value)) is equal to \(rhs)")
90 | }
91 | } else {
92 | throw lhs.failure("given value is nil")
93 | }
94 | }
95 |
96 | // MARK: Dictionary Equatability
97 |
98 | public func == (lhs: Expectation<[Key: Value]>, rhs: [Key: Value]) throws {
99 | if let value = try lhs.expression() {
100 | if value != rhs {
101 | throw lhs.failure("\(String(describing: value)) is not equal to \(rhs)")
102 | }
103 | } else {
104 | throw lhs.failure("given value is nil")
105 | }
106 | }
107 |
108 | public func != (lhs: Expectation<[Key: Value]>, rhs: [Key: Value]) throws {
109 | if let value = try lhs.expression() {
110 | if value == rhs {
111 | throw lhs.failure("\(String(describing: value)) is equal to \(rhs)")
112 | }
113 | } else {
114 | throw lhs.failure("given value is nil")
115 | }
116 | }
117 |
118 | // MARK: Nil
119 |
120 | extension ExpectationType {
121 | public func beNil() throws {
122 | let value = try expression()
123 | if value != nil {
124 | throw failure("value is not nil")
125 | }
126 | }
127 | }
128 |
129 | // MARK: Boolean
130 |
131 | extension ExpectationType where ValueType == Bool {
132 | public func beTrue() throws {
133 | let value = try expression()
134 | if value != true {
135 | throw failure("value is not true")
136 | }
137 | }
138 |
139 | public func beFalse() throws {
140 | let value = try expression()
141 | if value != false {
142 | throw failure("value is not false")
143 | }
144 | }
145 | }
146 |
147 | // Mark: Types
148 |
149 | extension ExpectationType {
150 | public func beOfType(_ expectedType: Any.Type) throws {
151 | guard let value = try expression() else { throw failure("cannot determine type: expression threw an error or value is nil") }
152 | let valueType = Mirror(reflecting: value).subjectType
153 | if valueType != expectedType {
154 | throw failure("'\(valueType)' is not the expected type '\(expectedType)'")
155 | }
156 | }
157 | }
158 |
159 | // MARK: Error Handling
160 |
161 | extension ExpectationType {
162 | public func toThrow() throws {
163 | var didThrow = false
164 |
165 | do {
166 | _ = try expression()
167 | } catch {
168 | didThrow = true
169 | }
170 |
171 | if !didThrow {
172 | throw failure("expression did not throw an error")
173 | }
174 | }
175 |
176 | public func toThrow(_ error: T) throws {
177 | var thrownError: Error? = nil
178 |
179 | do {
180 | _ = try expression()
181 | } catch {
182 | thrownError = error
183 | }
184 |
185 | if let thrownError = thrownError {
186 | if let thrownError = thrownError as? T {
187 | if error != thrownError {
188 | throw failure("\(thrownError) is not \(error)")
189 | }
190 | } else {
191 | throw failure("\(thrownError) is not \(error)")
192 | }
193 | } else {
194 | throw failure("expression did not throw an error")
195 | }
196 | }
197 | }
198 |
199 | // MARK: Comparable
200 |
201 | public func > (lhs: E, rhs: E.ValueType) throws where E.ValueType: Comparable {
202 | let value = try lhs.expression()
203 | guard value! > rhs else {
204 | throw lhs.failure("\(String(describing: value)) is not more than \(rhs)")
205 | }
206 | }
207 |
208 | public func >= (lhs: E, rhs: E.ValueType) throws where E.ValueType: Comparable {
209 | let value = try lhs.expression()
210 | guard value! >= rhs else {
211 | throw lhs.failure("\(String(describing: value)) is not more than or equal to \(rhs)")
212 | }
213 | }
214 |
215 | public func < (lhs: E, rhs: E.ValueType) throws where E.ValueType: Comparable {
216 | let value = try lhs.expression()
217 | guard value! < rhs else {
218 | throw lhs.failure("\(String(describing: value)) is not less than \(rhs)")
219 | }
220 | }
221 |
222 | public func <= (lhs: E, rhs: E.ValueType) throws where E.ValueType: Comparable {
223 | let value = try lhs.expression()
224 | guard value! <= rhs else {
225 | throw lhs.failure("\(String(describing: value)) is not less than or equal to \(rhs)")
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/Linker/LinkerTests/Spectre/Failure.swift:
--------------------------------------------------------------------------------
1 | public protocol FailureType : Error {
2 | var function: String { get }
3 | var file: String { get }
4 | var line: Int { get }
5 |
6 | var reason: String { get }
7 | }
8 |
9 | struct Failure : FailureType {
10 | let reason: String
11 |
12 | let function: String
13 | let file: String
14 | let line: Int
15 |
16 | init(reason: String, function: String = #function, file: String = #file, line: Int = #line) {
17 | self.reason = reason
18 | self.function = function
19 | self.file = file
20 | self.line = line
21 | }
22 | }
23 |
24 | struct Skip: Error {
25 | let reason: String?
26 | }
27 |
28 | public func skip(_ reason: String? = nil) -> Error {
29 | return Skip(reason: reason)
30 | }
31 |
32 |
33 | public func failure(_ reason: String? = nil, function: String = #function, file: String = #file, line: Int = #line) -> FailureType {
34 | return Failure(reason: reason ?? "-", function: function, file: file, line: line)
35 | }
36 |
--------------------------------------------------------------------------------
/Linker/LinkerTests/Spectre/Global.swift:
--------------------------------------------------------------------------------
1 | #if os(Linux)
2 | import Glibc
3 | #else
4 | import Darwin
5 | #endif
6 |
7 |
8 | let globalContext: GlobalContext = {
9 | atexit { run() }
10 | return GlobalContext()
11 | }()
12 |
13 | public func describe(_ name: String, closure: (ContextType) -> Void) {
14 | globalContext.describe(name, closure: closure)
15 | }
16 |
17 | public func it(_ name: String, closure: @escaping () throws -> Void) {
18 | globalContext.it(name, closure: closure)
19 | }
20 |
21 | public func run() -> Never {
22 | let reporter: Reporter
23 |
24 | if CommandLine.arguments.contains("--tap") {
25 | reporter = TapReporter()
26 | } else if CommandLine.arguments.contains("-t") {
27 | reporter = DotReporter()
28 | } else {
29 | reporter = StandardReporter()
30 | }
31 |
32 | run(reporter: reporter)
33 | }
34 |
35 | public func run(reporter: Reporter) -> Never {
36 | if globalContext.run(reporter: reporter) {
37 | exit(0)
38 | }
39 | exit(1)
40 | }
41 |
--------------------------------------------------------------------------------
/Linker/LinkerTests/Spectre/GlobalContext.swift:
--------------------------------------------------------------------------------
1 | class GlobalContext {
2 | var cases = [CaseType]()
3 |
4 | func describe(_ name: String, closure: (ContextType) -> Void) {
5 | let context = Context(name: name)
6 | closure(context)
7 | cases.append(context)
8 | }
9 |
10 | func it(_ name: String, closure: @escaping () throws -> Void) {
11 | cases.append(Case(name: name, closure: closure))
12 | }
13 |
14 | func run(reporter: Reporter) -> Bool {
15 | return reporter.report { reporter in
16 | for `case` in cases {
17 | `case`.run(reporter: reporter)
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Linker/LinkerTests/Spectre/Reporter.swift:
--------------------------------------------------------------------------------
1 | public protocol Reporter {
2 | /// Create a new report
3 | func report(closure: (ContextReporter) -> Void) -> Bool
4 | }
5 |
6 | public protocol ContextReporter {
7 | func report(_ name: String, closure: (ContextReporter) -> Void)
8 |
9 | /// Add a passing test case
10 | func addSuccess(_ name: String)
11 |
12 | /// Add a disabled test case
13 | func addDisabled(_ name: String)
14 |
15 | /// Adds a failing test case
16 | func addFailure(_ name: String, failure: FailureType)
17 | }
18 |
--------------------------------------------------------------------------------
/Linker/LinkerTests/Spectre/Reporters.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | #if os(Linux)
3 | import Glibc
4 | #else
5 | import Darwin.C
6 | #endif
7 |
8 |
9 | enum ANSI : String, CustomStringConvertible {
10 | case Red = "\u{001B}[0;31m"
11 | case Green = "\u{001B}[0;32m"
12 | case Yellow = "\u{001B}[0;33m"
13 |
14 | case Bold = "\u{001B}[0;1m"
15 | case Reset = "\u{001B}[0;0m"
16 |
17 | static var supportsANSI: Bool {
18 | guard isatty(STDOUT_FILENO) != 0 else {
19 | return false
20 | }
21 |
22 | guard let termType = getenv("TERM") else {
23 | return false
24 | }
25 |
26 | guard String(cString: termType).lowercased() != "dumb" else {
27 | return false
28 | }
29 |
30 | return true
31 | }
32 |
33 | var description: String {
34 | if ANSI.supportsANSI {
35 | return rawValue
36 | }
37 |
38 | return ""
39 | }
40 | }
41 |
42 |
43 | struct CaseFailure {
44 | let position: [String]
45 | let failure: FailureType
46 |
47 | init(position: [String], failure: FailureType) {
48 | self.position = position
49 | self.failure = failure
50 | }
51 | }
52 |
53 |
54 | fileprivate func stripCurrentDirectory(_ file: String) -> String {
55 | var currentPath = FileManager.`default`.currentDirectoryPath
56 | if !currentPath.hasSuffix("/") {
57 | currentPath += "/"
58 | }
59 |
60 | if file.hasPrefix(currentPath) {
61 | return String(file.suffix(from: currentPath.endIndex))
62 | }
63 |
64 | return file
65 | }
66 |
67 |
68 | func printFailures(_ failures: [CaseFailure]) {
69 | for failure in failures {
70 | let name = failure.position.joined(separator: " ")
71 | Swift.print(ANSI.Red, name)
72 |
73 | let file = "\(stripCurrentDirectory(failure.failure.file)):\(failure.failure.line)"
74 | Swift.print(" \(ANSI.Bold)\(file)\(ANSI.Reset) \(ANSI.Yellow)\(failure.failure.reason)\(ANSI.Reset)\n")
75 |
76 | if let contents = try? String(contentsOfFile: failure.failure.file, encoding: String.Encoding.utf8) as String {
77 | let lines = contents.components(separatedBy: CharacterSet.newlines)
78 | let line = lines[failure.failure.line - 1]
79 | let trimmedLine = line.trimmingCharacters(in: CharacterSet.whitespaces)
80 | Swift.print(" ```")
81 | Swift.print(" \(trimmedLine)")
82 | Swift.print(" ```")
83 | }
84 | }
85 | }
86 |
87 |
88 | class CountReporter : Reporter, ContextReporter {
89 | var depth = 0
90 | var successes = 0
91 | var disabled = 0
92 | var position = [String]()
93 | var failures = [CaseFailure]()
94 |
95 | func printStatus() {
96 | printFailures(failures)
97 |
98 | let disabledMessage: String
99 | if disabled > 0 {
100 | disabledMessage = " \(disabled) skipped,"
101 | } else {
102 | disabledMessage = ""
103 | }
104 |
105 | if failures.count == 1 {
106 | print("\(successes) passes\(disabledMessage) and \(failures.count) failure")
107 | } else {
108 | print("\(successes) passes\(disabledMessage) and \(failures.count) failures")
109 | }
110 | }
111 |
112 | #if swift(>=3.0)
113 | func report(closure: (ContextReporter) -> Void) -> Bool {
114 | closure(self)
115 | printStatus()
116 | return failures.isEmpty
117 | }
118 | #else
119 | func report(@noescape _ closure: (ContextReporter) -> Void) -> Bool {
120 | closure(self)
121 | printStatus()
122 | return failures.isEmpty
123 | }
124 | #endif
125 |
126 | #if swift(>=3.0)
127 | func report(_ name: String, closure: (ContextReporter) -> Void) {
128 | depth += 1
129 | position.append(name)
130 | closure(self)
131 | depth -= 1
132 | position.removeLast()
133 | }
134 | #else
135 | func report(_ name: String, @noescape closure: (ContextReporter) -> Void) {
136 | depth += 1
137 | position.append(name)
138 | closure(self)
139 | depth -= 1
140 | position.removeLast()
141 | }
142 | #endif
143 |
144 | func addSuccess(_ name: String) {
145 | successes += 1
146 | }
147 |
148 | func addDisabled(_ name: String) {
149 | disabled += 1
150 | }
151 |
152 | func addFailure(_ name: String, failure: FailureType) {
153 | failures.append(CaseFailure(position: position + [name], failure: failure))
154 | }
155 | }
156 |
157 |
158 | /// Standard reporter
159 | class StandardReporter : CountReporter {
160 | override func report(_ name: String, closure: (ContextReporter) -> Void) {
161 | colour(.Bold, "-> \(name)")
162 | super.report(name, closure: closure)
163 | print("")
164 | }
165 |
166 | override func addSuccess(_ name: String) {
167 | super.addSuccess(name)
168 | colour(.Green, "-> \(name)")
169 | }
170 |
171 | override func addDisabled(_ name: String) {
172 | super.addDisabled(name)
173 | colour(.Yellow, "-> \(name)")
174 | }
175 |
176 | override func addFailure(_ name: String, failure: FailureType) {
177 | super.addFailure(name, failure: failure)
178 | colour(.Red, "-> \(name)")
179 | }
180 |
181 | func colour(_ colour: ANSI, _ message: String) {
182 | let indentation = String(repeating: " ", count: depth * 2)
183 | print("\(indentation)\(colour)\(message)\(ANSI.Reset)")
184 | }
185 | }
186 |
187 |
188 | /// Simple reporter that outputs minimal . F and S.
189 | class DotReporter : CountReporter {
190 | override func addSuccess(_ name: String) {
191 | super.addSuccess(name)
192 | print(ANSI.Green, ".", ANSI.Reset, separator: "", terminator: "")
193 | }
194 |
195 | override func addDisabled(_ name: String) {
196 | super.addDisabled(name)
197 | print(ANSI.Yellow, "S", ANSI.Reset, separator: "", terminator: "")
198 | }
199 |
200 | override func addFailure(_ name: String, failure: FailureType) {
201 | super.addFailure(name, failure: failure)
202 | print(ANSI.Red, "F", ANSI.Reset, separator: "", terminator: "")
203 | }
204 |
205 | override func printStatus() {
206 | print("\n")
207 | super.printStatus()
208 | }
209 | }
210 |
211 |
212 | /// Test Anything Protocol compatible reporter
213 | /// http://testanything.org
214 | class TapReporter : CountReporter {
215 | var count = 0
216 |
217 | override func addSuccess(_ name: String) {
218 | count += 1
219 | super.addSuccess(name)
220 |
221 | let message = (position + [name]).joined(separator: " ")
222 | print("ok \(count) - \(message)")
223 | }
224 |
225 | override func addDisabled(_ name: String) {
226 | count += 1
227 | super.addDisabled(name)
228 |
229 | let message = (position + [name]).joined(separator: " ")
230 | print("ok \(count) - # skip \(message)")
231 | }
232 |
233 | override func addFailure(_ name: String, failure: FailureType) {
234 | count += 1
235 | super.addFailure(name, failure: failure)
236 |
237 | let message = (position + [name]).joined(separator: " ")
238 | print("not ok \(count) - \(message)")
239 | print("# \(failure.reason) from \(stripCurrentDirectory(failure.file)):\(failure.line)")
240 | }
241 |
242 | override func printStatus() {
243 | print("\(min(1, count))..\(count)")
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/Linker/Sources/Linker/Linker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Linker.swift
3 | // Linker
4 | //
5 | // Created by Maksim Kurpa on 1/16/18.
6 | // Copyright © 2018 Maksim Kurpa. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreFoundation
11 |
12 | open class Linker: NSObject {
13 |
14 | open class func handle(_ url: URL, closure: @escaping (_ url: URL) -> Void) {
15 | _ = swizzleOnce
16 | let key = url.key()
17 | closuresDict[key] = closure
18 | }
19 |
20 | fileprivate static var handlingURL : URL?
21 |
22 | fileprivate static func models(execution: @escaping ExecutionClosureType) -> [LNModel] {
23 | return Array(arrayLiteral:
24 | LNModel(clss:type(of: UIApplication.shared),
25 | selector:NSSelectorFromString("openURL:options:completionHandler:"),
26 | execution: execution),
27 | LNModel(clss:type(of: UIApplication.shared),
28 | selector:NSSelectorFromString("openURL:"),
29 | execution: execution),
30 | LNModel(clss:type(of: UIApplication.shared.delegate!),
31 | selector:NSSelectorFromString("application:openURL:options:"),
32 | execution: execution),
33 | LNModel(clss:type(of: UIApplication.shared.delegate!),
34 | selector:NSSelectorFromString("application:openURL:sourceApplication:annotation:"),
35 | execution: execution),
36 | LNModel(clss:type(of: UIApplication.shared.delegate!),
37 | selector:NSSelectorFromString("application:handleOpenURL:"),
38 | execution: execution)
39 | )}
40 |
41 | internal static func swizzleFunc(clss: AnyClass, selector: Selector, execution: Any) -> Void{
42 |
43 | let replaceMethod = class_getInstanceMethod(Linker.self, #selector(Linker.fakeMethod))
44 | var method: Method? = class_getInstanceMethod(clss, selector)
45 | if method == nil {
46 | class_addMethod(clss, selector, method_getImplementation(replaceMethod!), nil)
47 | method = class_getInstanceMethod(clss, selector)
48 | }
49 |
50 | let executionIMP : IMP?
51 | let argumentsCount = selector.argumentsCount() + 1//- because val0 always object which receive a message
52 |
53 | switch argumentsCount {
54 | case 2:
55 | let closure: ObjcBlockType_2 = {val0, val1 in
56 | (execution as! (AnyObject, AnyObject) -> Void)(val0, val1)
57 | }
58 | executionIMP = imp_implementationWithBlock(closure)
59 | case 3:
60 | let closure: ObjcBlockType_3 = {val0, val1, val2 in
61 | (execution as! (AnyObject, AnyObject, AnyObject) -> Void)(val0, val1, val2)
62 | }
63 | executionIMP = imp_implementationWithBlock(closure)
64 | case 4:
65 | let closure: ObjcBlockType_4 = {val0, val1, val2, val4 in
66 | (execution as! (AnyObject, AnyObject, AnyObject, AnyObject) -> Void)(val0, val1, val2, val4)
67 | }
68 | executionIMP = imp_implementationWithBlock(closure)
69 | case 5:
70 | let closure: ObjcBlockType_5 = {val0, val1, val2, val4, val5 in
71 | (execution as! (AnyObject, AnyObject, AnyObject, AnyObject, AnyObject) -> Void)(val0, val1, val2, val4, val5)
72 | }
73 | executionIMP = imp_implementationWithBlock(closure)
74 | default:
75 | assert(true, "method has more then 4 arguments")
76 | return
77 | }
78 |
79 | method_setImplementation(replaceMethod!, executionIMP!)
80 | method_exchangeImplementations(method!, replaceMethod!)
81 | }
82 |
83 | fileprivate static var swizzleOnce: Void = {
84 |
85 | let execution: (URL) -> (URL?) = {url in
86 | return Linker.handle(url)
87 | }
88 | for swizzleClosure in models(execution: execution) {
89 | swizzleFunc(clss: swizzleClosure.clss, selector: swizzleClosure.selector, execution: swizzleClosure.execution as Any)
90 | }
91 | }()
92 |
93 | @objc fileprivate func fakeMethod() -> Void {
94 | }
95 |
96 | static fileprivate var closuresDict: [String : LinkerClosureType] = {
97 | return [:] }()
98 |
99 |
100 | fileprivate class func handle(_ url: URL) -> URL? {
101 |
102 | let urlStringKey: String = url.key()
103 | let closure: LinkerClosureType? = closuresDict[urlStringKey]
104 |
105 | guard let execution = closure else {
106 | return url
107 | }
108 |
109 | if self.handlingURL != nil {
110 | if url == self.handlingURL! {
111 | return nil
112 | }
113 | }
114 | self.handlingURL = url
115 | self.handleIfAppIsLoaded(withConmplitionBlock: {() -> Void in
116 | execution(url)
117 | })
118 | return nil
119 | }
120 |
121 | fileprivate class func handleIfAppIsLoaded(withConmplitionBlock complitionBlock: @escaping () -> ()) {
122 | if UIApplication.shared.applicationState == .active {
123 | self.handlingURL = nil
124 | complitionBlock()
125 | } else {
126 | var observer : Any? = nil
127 | observer = NotificationCenter.default.addObserver(forName: .UIApplicationDidBecomeActive, object: nil, queue: OperationQueue.main, using: {(_ note: Notification) -> Void in
128 | self.handlingURL = nil
129 | complitionBlock()
130 | NotificationCenter.default.removeObserver(observer!)
131 | observer = nil
132 | })
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/Linker/Sources/Linker/LinkerExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LinkerUtils.swift
3 | // Linker
4 | //
5 | // Created by Maksim Kurpa on 2/2/18.
6 | // Copyright © 2018 Maksim Kurpa. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Selector {
12 | public func argumentsCount()-> Int {
13 | let stringSelector = NSStringFromSelector(self);
14 | if !stringSelector.contains(":") {
15 | return 0
16 | }
17 | let colons: String = stringSelector.filter({
18 | return (":" == $0)
19 | })
20 | return colons.count
21 | }
22 |
23 | public func indexOfArgument(withName name: String) -> Int? {
24 | let subStrings = NSStringFromSelector(self).split(separator: ":", maxSplits: NSIntegerMax, omittingEmptySubsequences: false)
25 | for subString in subStrings {
26 | if (subString.contains(name)) {
27 | return subStrings.index(of: subString)
28 | }
29 | }
30 | return nil
31 | }
32 | }
33 |
34 | extension URL {
35 | public func key() -> String {
36 | let components = URLComponents(url: self, resolvingAgainstBaseURL: true)
37 | return "\(components?.scheme ?? "")://\(components?.host ?? "")"
38 | }
39 | }
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Linker/Sources/Linker/LinkerModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LNModel.swift
3 | // Linker
4 | //
5 | // Created by Maksim Kurpa on 1/23/18.
6 | // Copyright © 2018 Maksim Kurpa. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | internal struct LNModel {
12 |
13 | init(clss: AnyClass, selector: Selector, execution:@escaping ExecutionClosureType ) {
14 |
15 | let method: Method? = class_getInstanceMethod(clss, selector);
16 | var originalIMP : IMP? = nil
17 | if let originalMethod = method {
18 | originalIMP = method_getImplementation(originalMethod);
19 | }
20 |
21 | let argumentsCount = selector.argumentsCount() + 1 //because first parament is always self object
22 | let sourceParameterIndex = selector.indexOfArgument(withName: "URL")
23 |
24 | switch argumentsCount {
25 | case 2:
26 | let closure: ClosureType_2 = {val0, val1 in
27 |
28 | let urlDidntHandle = execution(val1 as! URL)
29 | if let _ = urlDidntHandle, let _ = originalIMP{
30 | let curriedImplementation = unsafeBitCast(originalIMP, to: FuncCType_2.self)
31 | curriedImplementation(val0, selector,val1)
32 | }
33 | }
34 | self._execution = closure as AnyObject
35 | case 3:
36 | let closure: ClosureType_3 = {val0, val1, val2 in
37 | guard let sourceIndex = sourceParameterIndex else {
38 | return
39 | }
40 | let urlDidntHandle = execution(sourceIndex == 0 ? val1 as! URL : val2 as! URL)
41 | if let _ = urlDidntHandle, let _ = originalIMP{
42 | let curriedImplementation = unsafeBitCast(originalIMP, to: FuncCType_3.self)
43 | curriedImplementation(val0, selector,val1, val2)
44 | }
45 | }
46 | self._execution = closure as AnyObject
47 | case 4:
48 | let closure: ClosureType_4 = {val0, val1, val2, val3 in
49 | guard let sourceIndex = sourceParameterIndex else {
50 | return
51 | }
52 | let urlDidntHandle = execution(sourceIndex == 0 ? val1 as! URL : sourceIndex == 1 ? val2 as! URL : val3 as! URL)
53 | if let _ = urlDidntHandle, let _ = originalIMP{
54 | let curriedImplementation = unsafeBitCast(originalIMP, to: FuncCType_4.self)
55 | curriedImplementation(val0, selector,val1, val2, val3)
56 | }
57 | }
58 | self._execution = closure as AnyObject
59 | case 5:
60 | let closure: ClosureType_5 = {val0, val1, val2, val3, val4 in
61 | guard let sourceIndex = sourceParameterIndex else {
62 | return
63 | }
64 | let urlDidntHandle = execution(sourceIndex == 0 ? val1 as! URL : sourceIndex == 1 ? val2 as! URL : sourceIndex == 2 ? val3 as! URL : val4 as! URL)
65 | if let _ = urlDidntHandle, let _ = originalIMP{
66 | let curriedImplementation = unsafeBitCast(originalIMP, to: FuncCType_5.self)
67 | curriedImplementation(val0, selector,val1, val2, val3, val4)
68 | }
69 | }
70 | self._execution = closure as AnyObject
71 | default:
72 | self._execution = nil
73 | }
74 | _clss = clss
75 | _selector = selector
76 | }
77 |
78 | //MARK: - Properties
79 |
80 | public var selector: Selector {
81 | return _selector
82 | }
83 | public var clss: AnyClass {
84 | return _clss
85 | }
86 | public var execution: AnyObject? {
87 | return _execution
88 | }
89 |
90 | fileprivate var _selector: Selector;
91 | fileprivate var _clss: AnyClass
92 | fileprivate var _execution: AnyObject?;
93 | }
94 |
--------------------------------------------------------------------------------
/Linker/Sources/Linker/LinkerTypes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LinkerTypes.swift
3 | // Linker
4 | //
5 | // Created by Maksim Kurpa on 1/26/18.
6 | // Copyright © 2018 Maksim Kurpa. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | typealias LinkerClosureType = (_ url: URL) -> Void
12 | typealias ExecutionClosureType = (URL) -> (URL?)?
13 |
14 | typealias ClosureType_2 = (AnyObject, AnyObject) -> Void
15 | typealias ClosureType_3 = (AnyObject, AnyObject, AnyObject) -> Void
16 | typealias ClosureType_4 = (AnyObject, AnyObject, AnyObject, AnyObject) -> Void
17 | typealias ClosureType_5 = (AnyObject, AnyObject, AnyObject, AnyObject, AnyObject) -> Void
18 |
19 | typealias FuncCType_2 = @convention(c) (AnyObject, Selector ,AnyObject) -> Void
20 | typealias FuncCType_3 = @convention(c) (AnyObject, Selector ,AnyObject, AnyObject) -> Void
21 | typealias FuncCType_4 = @convention(c) (AnyObject, Selector ,AnyObject, AnyObject, AnyObject) -> Void
22 | typealias FuncCType_5 = @convention(c) (AnyObject, Selector ,AnyObject, AnyObject, AnyObject, AnyObject) -> Void
23 |
24 | typealias ObjcBlockType_2 = @convention(block) (AnyObject, AnyObject) -> Void
25 | typealias ObjcBlockType_3 = @convention(block) (AnyObject, AnyObject, AnyObject) -> Void
26 | typealias ObjcBlockType_4 = @convention(block) (AnyObject, AnyObject, AnyObject, AnyObject) -> Void
27 | typealias ObjcBlockType_5 = @convention(block) (AnyObject, AnyObject, AnyObject, AnyObject, AnyObject) -> Void
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Linker
4 | Lightweight way to handle internal and external deeplinks in Swift for iOS.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | ---
17 |
18 |
19 | ## Installation
20 |
21 | ### Dependency Managers
22 |
23 | CocoaPods
24 |
25 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
26 |
27 | ```bash
28 | $ gem install cocoapods
29 | ```
30 |
31 | To integrate Linker into your Xcode project using CocoaPods, specify it in your `Podfile`:
32 |
33 | ```ruby
34 | source 'https://github.com/CocoaPods/Specs.git'
35 | platform :ios, '8.0'
36 | use_frameworks!
37 |
38 | pod 'Linker'
39 | ```
40 |
41 | Then, run the following command:
42 |
43 | ```bash
44 | $ pod install
45 | ```
46 |
47 |
48 |
49 |
50 | Carthage
51 |
52 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.
53 |
54 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
55 |
56 | ```bash
57 | $ brew update
58 | $ brew install carthage
59 | ```
60 |
61 | To integrate Linker into your Xcode project using Carthage, specify it in your `Cartfile`:
62 |
63 | ```ogdl
64 | github "Linker"
65 | ```
66 |
67 |
68 |
69 | ## Usage
70 |
71 | (see sample Xcode project Demo)
72 |
73 | The main thought of this framework is useful and convenient handling of external and internal URLs in your iOS application. Linker provides only one function to install your own handler to specific URL. A dependency between specific URL and your closure is based on `scheme` and `host` of each URL. That is you can configure miscellaneous behavior for different components of specific URL. You can split handling by `query` with different parameters and/or by `path`, `fragment`.
74 |
75 |
76 | Realization details
77 | On start of your application occurs swizzling methods in `UIApplication` and `UIApplicationDelegate` of your application. Original implementation exchanged on Linker's implementation, where occur handle process. If Linker can't handle specific URL, original implementation of this method will be called.
78 |
79 | Swizzled functions:
80 |
81 | `UIApplication.shared - openURL:options:completionHandler:`
82 |
83 | `UIApplication.shared - openURL:` (deprecated since iOS 10.0)
84 |
85 | `UIApplication.shared.delegate - application:openURL:options:`
86 |
87 | `UIApplication.shared.delegate - application:openURL:sourceApplication:annotation:` (deprecated since iOS 9.0)
88 |
89 | `UIApplication.shared.delegate - application:handleOpenURL:` (deprecated since iOS 9.0)
90 |
91 |
92 | For complience with URL style, use format:
93 |
94 | `your_app_url_scheme://inapp_am/buy_subscription?type=subscription&productID=com.yourapp.7days_trial#test`
95 |
96 | where:
97 |
98 | scheme - `your_app_url_scheme`,
99 |
100 | host - `inapp_am`,
101 |
102 | path - `buy_subscription`
103 |
104 | query - `type=subscription&productID=com.yourapp.7days_trial`
105 |
106 | fragment - `test`
107 |
108 | If you don't need configuration with complexed behavior, you can use URL just with `host`:
109 |
110 | `your_app_url_scheme://some_host_from_your_app`
111 |
112 | One special case - handle external URLs when app isn't launched. You should install closure for specific URL and if this url will be in pair with `UIApplicationLaunchOptionsKey` in `launchOptions` - this url will be handled.
113 |
114 | ```Swift
115 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
116 | {
117 | let launchURL = URL(string: "linker://launchURL")!
118 |
119 | //if launchUrl equal launchOptions?[UIApplicationLaunchOptionsKey.url] -> this closure will be handled after app launch
120 | Linker.handle(launchURL, closure: { url in
121 | print("Your URL has been handle!")
122 | })
123 | return true
124 | }
125 | ```
126 | In other cases of usage you should set your handle closure for special URL before calling its from somewhere. If you have a few places where you need handle one specific url, you should reassign closure where latest set closure should have right `context`.
127 |
128 |
129 | (!) Notice: Only the last sent closure for a unique URL (scheme + host) will be executed.
130 |
131 | ```Swift
132 | class ViewController: UIViewController {
133 |
134 | let sourceURL = URL(string: "linker://viewcontroller?title=ExampleAlert&description=ExampleDescriptionAlert")!
135 |
136 | @IBAction func action(_ sender: Any) {
137 | UIApplication.shared.open(sourceURL, options: [:], completionHandler: nil)
138 | }
139 |
140 | override func viewDidLoad() {
141 | super.viewDidLoad()
142 | Linker.handle(sourceURL) { url in
143 |
144 | guard let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems! else {
145 | return }
146 | var title : String? = nil
147 | var description: String? = nil
148 |
149 | for item in queryItems {
150 | if item.name == "title" {
151 | title = item.value
152 | }
153 | if item.name == "description" {
154 | description = item.value;
155 | }
156 | }
157 |
158 | if let name = title, let message = description {
159 | let alertVC = UIAlertController(title: name, message: message, preferredStyle: UIAlertControllerStyle.alert)
160 | alertVC.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: {action in
161 | alertVC.dismiss(animated: true, completion: nil)
162 | }))
163 | self.present(alertVC, animated: false, completion: nil)
164 | }
165 | }
166 | }
167 | }
168 | ```
169 | You can also find Objective-C version of this [here](https://github.com/MaksimKurpa/DeepLinksHandler).
170 |
171 | ## Contributing
172 |
173 | Issues and pull requests are welcome!
174 |
175 | ## Author
176 |
177 | Maksim Kurpa - [@maksim_kurpa](https://twitter.com/maksim_kurpa)
178 |
179 | ## License
180 |
181 | This code is distributed under the terms and conditions of the [MIT license](https://raw.githubusercontent.com/MaksimKurpa/Linker/master/LICENSE).
182 |
--------------------------------------------------------------------------------