aboutsummaryrefslogtreecommitdiffhomepage
path: root/GoogleUtilities
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2018-07-12 12:03:50 -0700
committerGravatar GitHub <noreply@github.com>2018-07-12 12:03:50 -0700
commitc586dc8747882770973b6488c9f5f9e6e3f08d6c (patch)
tree511cda1bd0c67b94ab7bbb8ba22201fecf89909b /GoogleUtilities
parent49f2493e14cd68ecc0e08ad2d9fc75739e419a3b (diff)
Separate Xcode project and tests for GoogleUtilities (#1521)
Diffstat (limited to 'GoogleUtilities')
-rw-r--r--GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj1135
-rw-r--r--GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme111
-rw-r--r--GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_macOS.xcscheme101
-rw-r--r--GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_tvOS.xcscheme101
-rw-r--r--GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_iOS.xcscheme84
-rw-r--r--GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_macOS.xcscheme90
-rw-r--r--GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_tvOS.xcscheme81
-rw-r--r--GoogleUtilities/Example/Podfile34
-rw-r--r--GoogleUtilities/Example/Tests/Environment/GULAppEnvironmentUtilTest.m62
-rw-r--r--GoogleUtilities/Example/Tests/Logger/GULLoggerTest.m210
-rw-r--r--GoogleUtilities/Example/Tests/Network/GULMutableDictionaryTest.m87
-rw-r--r--GoogleUtilities/Example/Tests/Network/GULNetworkTest.m998
-rw-r--r--GoogleUtilities/Example/Tests/Network/third_party/GTMHTTPServer.h173
-rw-r--r--GoogleUtilities/Example/Tests/Network/third_party/GTMHTTPServer.m630
-rw-r--r--GoogleUtilities/Example/Tests/Reachability/GULReachabilityCheckerTest.m358
-rw-r--r--GoogleUtilities/Example/Tests/Tests-Info.plist22
-rw-r--r--GoogleUtilities/Example/iOS/AppDelegate.h21
-rw-r--r--GoogleUtilities/Example/iOS/AppDelegate.m55
-rw-r--r--GoogleUtilities/Example/iOS/Base.lproj/LaunchScreen.storyboard27
-rw-r--r--GoogleUtilities/Example/iOS/Base.lproj/Main.storyboard27
-rw-r--r--GoogleUtilities/Example/iOS/GoogleUtilities-Info.plist54
-rw-r--r--GoogleUtilities/Example/iOS/Images.xcassets/AppIcon.appiconset/Contents.json98
-rw-r--r--GoogleUtilities/Example/iOS/ViewController.h19
-rw-r--r--GoogleUtilities/Example/iOS/ViewController.m33
-rw-r--r--GoogleUtilities/Example/iOS/main.m22
-rw-r--r--GoogleUtilities/Example/macOS/AppDelegate.h21
-rw-r--r--GoogleUtilities/Example/macOS/AppDelegate.m33
-rw-r--r--GoogleUtilities/Example/macOS/Base.lproj/Main.storyboard693
-rw-r--r--GoogleUtilities/Example/macOS/Info.plist37
-rw-r--r--GoogleUtilities/Example/macOS/ViewController.h21
-rw-r--r--GoogleUtilities/Example/macOS/ViewController.m33
-rw-r--r--GoogleUtilities/Example/macOS/main.m21
-rw-r--r--GoogleUtilities/Example/tvOS/AppDelegate.h21
-rw-r--r--GoogleUtilities/Example/tvOS/AppDelegate.m59
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json17
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json11
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json6
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json17
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json6
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json16
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json6
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json32
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json16
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json16
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/Contents.json6
-rw-r--r--GoogleUtilities/Example/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json22
-rw-r--r--GoogleUtilities/Example/tvOS/Info.plist37
-rw-r--r--GoogleUtilities/Example/tvOS/Main.storyboard28
-rw-r--r--GoogleUtilities/Example/tvOS/ViewController.h19
-rw-r--r--GoogleUtilities/Example/tvOS/ViewController.m33
-rw-r--r--GoogleUtilities/Example/tvOS/main.m22
58 files changed, 5954 insertions, 0 deletions
diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..164eb57
--- /dev/null
+++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/project.pbxproj
@@ -0,0 +1,1135 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
+ 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
+ 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
+ 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
+ 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
+ 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
+ DE5CF98E20F686310063FFDD /* GULAppEnvironmentUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5CF98C20F686290063FFDD /* GULAppEnvironmentUtilTest.m */; };
+ DEC977D720F68C3300014E20 /* GULReachabilityCheckerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977D320F68C3300014E20 /* GULReachabilityCheckerTest.m */; };
+ DEC977D820F68C3300014E20 /* GULMutableDictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977D520F68C3300014E20 /* GULMutableDictionaryTest.m */; };
+ DEC977D920F68C3300014E20 /* GULNetworkTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977D620F68C3300014E20 /* GULNetworkTest.m */; };
+ DEC977DD20F68FE100014E20 /* GTMHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977DC20F68FE100014E20 /* GTMHTTPServer.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ DEC977E120F6A7C100014E20 /* GULLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977DF20F6A7A700014E20 /* GULLoggerTest.m */; };
+ DEC977EE20F6ACDA00014E20 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977E420F6ACDA00014E20 /* ViewController.m */; };
+ DEC977EF20F6ACDA00014E20 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEC977E520F6ACDA00014E20 /* LaunchScreen.storyboard */; };
+ DEC977F020F6ACDA00014E20 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEC977E720F6ACDA00014E20 /* Main.storyboard */; };
+ DEC977F120F6ACDA00014E20 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977E920F6ACDA00014E20 /* main.m */; };
+ DEC977F320F6ACDA00014E20 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DEC977EB20F6ACDA00014E20 /* Images.xcassets */; };
+ DEC977F420F6ACDA00014E20 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977EC20F6ACDA00014E20 /* AppDelegate.m */; };
+ DEC9781820F6D37400014E20 /* GULLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977DF20F6A7A700014E20 /* GULLoggerTest.m */; };
+ DEC9781920F6D38500014E20 /* GULAppEnvironmentUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5CF98C20F686290063FFDD /* GULAppEnvironmentUtilTest.m */; };
+ DEC9781A20F6D38800014E20 /* GULReachabilityCheckerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977D320F68C3300014E20 /* GULReachabilityCheckerTest.m */; };
+ DEC9781B20F6D39500014E20 /* GULMutableDictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977D520F68C3300014E20 /* GULMutableDictionaryTest.m */; };
+ DEC9781C20F6D39500014E20 /* GULNetworkTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977D620F68C3300014E20 /* GULNetworkTest.m */; };
+ DEC9781D20F6D39900014E20 /* GTMHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977DC20F68FE100014E20 /* GTMHTTPServer.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ DEC9786820F6D65B00014E20 /* GULLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977DF20F6A7A700014E20 /* GULLoggerTest.m */; };
+ DEC9786920F6D66300014E20 /* GTMHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977DC20F68FE100014E20 /* GTMHTTPServer.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ DEC9786A20F6D66300014E20 /* GULMutableDictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977D520F68C3300014E20 /* GULMutableDictionaryTest.m */; };
+ DEC9786B20F6D66300014E20 /* GULNetworkTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977D620F68C3300014E20 /* GULNetworkTest.m */; };
+ DEC9786C20F6D66700014E20 /* GULReachabilityCheckerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC977D320F68C3300014E20 /* GULReachabilityCheckerTest.m */; };
+ DEC9786D20F6D66B00014E20 /* GULAppEnvironmentUtilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE5CF98C20F686290063FFDD /* GULAppEnvironmentUtilTest.m */; };
+ DEC9787720F6DE7200014E20 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEC9787120F6DE7200014E20 /* Main.storyboard */; };
+ DEC9787920F6DE7200014E20 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC9787420F6DE7200014E20 /* main.m */; };
+ DEC9787A20F6DE7200014E20 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC9787520F6DE7200014E20 /* AppDelegate.m */; };
+ DEC9787B20F6DE7200014E20 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC9787620F6DE7200014E20 /* ViewController.m */; };
+ DEC9788520F6E1E000014E20 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DEC9787E20F6E1DF00014E20 /* Assets.xcassets */; };
+ DEC9788620F6E1E000014E20 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC9787F20F6E1DF00014E20 /* ViewController.m */; };
+ DEC9788720F6E1E000014E20 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEC9788020F6E1DF00014E20 /* Main.storyboard */; };
+ DEC9788820F6E1E000014E20 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC9788120F6E1DF00014E20 /* main.m */; };
+ DEC9788920F6E1E000014E20 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC9788220F6E1DF00014E20 /* AppDelegate.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 6003F589195388D20070C39A;
+ remoteInfo = GoogleUtilities;
+ };
+ DEC9780C20F6D0EA00014E20 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEC977F820F6D0E900014E20;
+ remoteInfo = "GoogleUtilities-macOS";
+ };
+ DEC9785C20F6D5DA00014E20 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 6003F582195388D10070C39A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DEC9784620F6D5D800014E20;
+ remoteInfo = Example_tvOS;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 6003F58A195388D20070C39A /* Example_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+ 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ 6003F5AE195388D20070C39A /* Tests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
+ 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
+ 7BEA793625C8DE7C8EC60006 /* GoogleUtilities.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = GoogleUtilities.podspec; path = ../GoogleUtilities.podspec; sourceTree = "<group>"; };
+ DE5CF98C20F686290063FFDD /* GULAppEnvironmentUtilTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULAppEnvironmentUtilTest.m; sourceTree = "<group>"; };
+ DEC977D320F68C3300014E20 /* GULReachabilityCheckerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULReachabilityCheckerTest.m; sourceTree = "<group>"; };
+ DEC977D520F68C3300014E20 /* GULMutableDictionaryTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULMutableDictionaryTest.m; sourceTree = "<group>"; };
+ DEC977D620F68C3300014E20 /* GULNetworkTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULNetworkTest.m; sourceTree = "<group>"; };
+ DEC977DB20F68FE100014E20 /* GTMHTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPServer.h; sourceTree = "<group>"; };
+ DEC977DC20F68FE100014E20 /* GTMHTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPServer.m; sourceTree = "<group>"; };
+ DEC977DF20F6A7A700014E20 /* GULLoggerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GULLoggerTest.m; sourceTree = "<group>"; };
+ DEC977E320F6ACDA00014E20 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ DEC977E420F6ACDA00014E20 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
+ DEC977E620F6ACDA00014E20 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+ DEC977E820F6ACDA00014E20 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+ DEC977E920F6ACDA00014E20 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DEC977EA20F6ACDA00014E20 /* GoogleUtilities-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleUtilities-Info.plist"; sourceTree = "<group>"; };
+ DEC977EB20F6ACDA00014E20 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
+ DEC977EC20F6ACDA00014E20 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ DEC977ED20F6ACDA00014E20 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
+ DEC977F920F6D0E900014E20 /* Example_macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example_macOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEC9780B20F6D0EA00014E20 /* Tests_macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests_macOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEC9784720F6D5D800014E20 /* Example_tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example_tvOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEC9785B20F6D5DA00014E20 /* Tests_tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests_tvOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ DEC9786F20F6DE7200014E20 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ DEC9787020F6DE7200014E20 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
+ DEC9787220F6DE7200014E20 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+ DEC9787320F6DE7200014E20 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DEC9787420F6DE7200014E20 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DEC9787520F6DE7200014E20 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ DEC9787620F6DE7200014E20 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
+ DEC9787D20F6E1DF00014E20 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+ DEC9787E20F6E1DF00014E20 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+ DEC9787F20F6E1DF00014E20 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
+ DEC9788020F6E1DF00014E20 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
+ DEC9788120F6E1DF00014E20 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+ DEC9788220F6E1DF00014E20 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+ DEC9788320F6E1DF00014E20 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+ DEC9788420F6E1DF00014E20 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
+ E0A8D570636E99E7C3396DF8 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
+ F1F2A7C03C10A3A03F9502B8 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 6003F587195388D20070C39A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */,
+ 6003F592195388D20070C39A /* UIKit.framework in Frameworks */,
+ 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 6003F5AB195388D20070C39A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */,
+ 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */,
+ 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC977F620F6D0E900014E20 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC9780820F6D0EA00014E20 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC9784420F6D5D800014E20 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC9785820F6D5DA00014E20 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 6003F581195388D10070C39A = {
+ isa = PBXGroup;
+ children = (
+ DEC977E220F6ACDA00014E20 /* iOS */,
+ DEC9786E20F6DE7200014E20 /* macOS */,
+ DEC9787C20F6E1DF00014E20 /* tvOS */,
+ 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */,
+ 6003F5B5195388D20070C39A /* Tests */,
+ 6003F58C195388D20070C39A /* Frameworks */,
+ 6003F58B195388D20070C39A /* Products */,
+ );
+ sourceTree = "<group>";
+ };
+ 6003F58B195388D20070C39A /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 6003F58A195388D20070C39A /* Example_iOS.app */,
+ 6003F5AE195388D20070C39A /* Tests_iOS.xctest */,
+ DEC977F920F6D0E900014E20 /* Example_macOS.app */,
+ DEC9780B20F6D0EA00014E20 /* Tests_macOS.xctest */,
+ DEC9784720F6D5D800014E20 /* Example_tvOS.app */,
+ DEC9785B20F6D5DA00014E20 /* Tests_tvOS.xctest */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+ 6003F58C195388D20070C39A /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 6003F58D195388D20070C39A /* Foundation.framework */,
+ 6003F58F195388D20070C39A /* CoreGraphics.framework */,
+ 6003F591195388D20070C39A /* UIKit.framework */,
+ 6003F5AF195388D20070C39A /* XCTest.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "<group>";
+ };
+ 6003F5B5195388D20070C39A /* Tests */ = {
+ isa = PBXGroup;
+ children = (
+ DEC977DE20F6A7A700014E20 /* Logger */,
+ DEC977D420F68C3300014E20 /* Network */,
+ DEC977D220F68C3300014E20 /* Reachability */,
+ DE5CF98B20F686290063FFDD /* Environment */,
+ 6003F5B6195388D20070C39A /* Supporting Files */,
+ );
+ path = Tests;
+ sourceTree = "<group>";
+ };
+ 6003F5B6195388D20070C39A /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 6003F5B7195388D20070C39A /* Tests-Info.plist */,
+ );
+ name = "Supporting Files";
+ sourceTree = "<group>";
+ };
+ 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = {
+ isa = PBXGroup;
+ children = (
+ 7BEA793625C8DE7C8EC60006 /* GoogleUtilities.podspec */,
+ E0A8D570636E99E7C3396DF8 /* README.md */,
+ F1F2A7C03C10A3A03F9502B8 /* LICENSE */,
+ );
+ name = "Podspec Metadata";
+ sourceTree = "<group>";
+ };
+ DE5CF98B20F686290063FFDD /* Environment */ = {
+ isa = PBXGroup;
+ children = (
+ DE5CF98C20F686290063FFDD /* GULAppEnvironmentUtilTest.m */,
+ );
+ path = Environment;
+ sourceTree = "<group>";
+ };
+ DEC977D220F68C3300014E20 /* Reachability */ = {
+ isa = PBXGroup;
+ children = (
+ DEC977D320F68C3300014E20 /* GULReachabilityCheckerTest.m */,
+ );
+ path = Reachability;
+ sourceTree = "<group>";
+ };
+ DEC977D420F68C3300014E20 /* Network */ = {
+ isa = PBXGroup;
+ children = (
+ DEC977DA20F68FE100014E20 /* third_party */,
+ DEC977D520F68C3300014E20 /* GULMutableDictionaryTest.m */,
+ DEC977D620F68C3300014E20 /* GULNetworkTest.m */,
+ );
+ path = Network;
+ sourceTree = "<group>";
+ };
+ DEC977DA20F68FE100014E20 /* third_party */ = {
+ isa = PBXGroup;
+ children = (
+ DEC977DB20F68FE100014E20 /* GTMHTTPServer.h */,
+ DEC977DC20F68FE100014E20 /* GTMHTTPServer.m */,
+ );
+ path = third_party;
+ sourceTree = "<group>";
+ };
+ DEC977DE20F6A7A700014E20 /* Logger */ = {
+ isa = PBXGroup;
+ children = (
+ DEC977DF20F6A7A700014E20 /* GULLoggerTest.m */,
+ );
+ path = Logger;
+ sourceTree = "<group>";
+ };
+ DEC977E220F6ACDA00014E20 /* iOS */ = {
+ isa = PBXGroup;
+ children = (
+ DEC977E320F6ACDA00014E20 /* AppDelegate.h */,
+ DEC977E420F6ACDA00014E20 /* ViewController.m */,
+ DEC977E520F6ACDA00014E20 /* LaunchScreen.storyboard */,
+ DEC977E720F6ACDA00014E20 /* Main.storyboard */,
+ DEC977E920F6ACDA00014E20 /* main.m */,
+ DEC977EA20F6ACDA00014E20 /* GoogleUtilities-Info.plist */,
+ DEC977EB20F6ACDA00014E20 /* Images.xcassets */,
+ DEC977EC20F6ACDA00014E20 /* AppDelegate.m */,
+ DEC977ED20F6ACDA00014E20 /* ViewController.h */,
+ );
+ path = iOS;
+ sourceTree = "<group>";
+ };
+ DEC9786E20F6DE7200014E20 /* macOS */ = {
+ isa = PBXGroup;
+ children = (
+ DEC9786F20F6DE7200014E20 /* AppDelegate.h */,
+ DEC9787020F6DE7200014E20 /* ViewController.h */,
+ DEC9787120F6DE7200014E20 /* Main.storyboard */,
+ DEC9787320F6DE7200014E20 /* Info.plist */,
+ DEC9787420F6DE7200014E20 /* main.m */,
+ DEC9787520F6DE7200014E20 /* AppDelegate.m */,
+ DEC9787620F6DE7200014E20 /* ViewController.m */,
+ );
+ path = macOS;
+ sourceTree = "<group>";
+ };
+ DEC9787C20F6E1DF00014E20 /* tvOS */ = {
+ isa = PBXGroup;
+ children = (
+ DEC9787D20F6E1DF00014E20 /* AppDelegate.h */,
+ DEC9787E20F6E1DF00014E20 /* Assets.xcassets */,
+ DEC9787F20F6E1DF00014E20 /* ViewController.m */,
+ DEC9788020F6E1DF00014E20 /* Main.storyboard */,
+ DEC9788120F6E1DF00014E20 /* main.m */,
+ DEC9788220F6E1DF00014E20 /* AppDelegate.m */,
+ DEC9788320F6E1DF00014E20 /* Info.plist */,
+ DEC9788420F6E1DF00014E20 /* ViewController.h */,
+ );
+ path = tvOS;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 6003F589195388D20070C39A /* Example_iOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "Example_iOS" */;
+ buildPhases = (
+ 6003F586195388D20070C39A /* Sources */,
+ 6003F587195388D20070C39A /* Frameworks */,
+ 6003F588195388D20070C39A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Example_iOS;
+ productName = GoogleUtilities;
+ productReference = 6003F58A195388D20070C39A /* Example_iOS.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 6003F5AD195388D20070C39A /* Tests_iOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Tests_iOS" */;
+ buildPhases = (
+ 6003F5AA195388D20070C39A /* Sources */,
+ 6003F5AB195388D20070C39A /* Frameworks */,
+ 6003F5AC195388D20070C39A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 6003F5B4195388D20070C39A /* PBXTargetDependency */,
+ );
+ name = Tests_iOS;
+ productName = GoogleUtilitiesTests;
+ productReference = 6003F5AE195388D20070C39A /* Tests_iOS.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ DEC977F820F6D0E900014E20 /* Example_macOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEC9781620F6D0EB00014E20 /* Build configuration list for PBXNativeTarget "Example_macOS" */;
+ buildPhases = (
+ DEC977F520F6D0E900014E20 /* Sources */,
+ DEC977F620F6D0E900014E20 /* Frameworks */,
+ DEC977F720F6D0E900014E20 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Example_macOS;
+ productName = "GoogleUtilities-macOS";
+ productReference = DEC977F920F6D0E900014E20 /* Example_macOS.app */;
+ productType = "com.apple.product-type.application";
+ };
+ DEC9780A20F6D0EA00014E20 /* Tests_macOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEC9781720F6D0EB00014E20 /* Build configuration list for PBXNativeTarget "Tests_macOS" */;
+ buildPhases = (
+ DEC9780720F6D0EA00014E20 /* Sources */,
+ DEC9780820F6D0EA00014E20 /* Frameworks */,
+ DEC9780920F6D0EA00014E20 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DEC9780D20F6D0EA00014E20 /* PBXTargetDependency */,
+ );
+ name = Tests_macOS;
+ productName = "GoogleUtilities-macOSTests";
+ productReference = DEC9780B20F6D0EA00014E20 /* Tests_macOS.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ DEC9784620F6D5D800014E20 /* Example_tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEC9786620F6D5DA00014E20 /* Build configuration list for PBXNativeTarget "Example_tvOS" */;
+ buildPhases = (
+ DEC9784320F6D5D800014E20 /* Sources */,
+ DEC9784420F6D5D800014E20 /* Frameworks */,
+ DEC9784520F6D5D800014E20 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Example_tvOS;
+ productName = Example_tvOS;
+ productReference = DEC9784720F6D5D800014E20 /* Example_tvOS.app */;
+ productType = "com.apple.product-type.application";
+ };
+ DEC9785A20F6D5DA00014E20 /* Tests_tvOS */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DEC9786720F6D5DA00014E20 /* Build configuration list for PBXNativeTarget "Tests_tvOS" */;
+ buildPhases = (
+ DEC9785720F6D5DA00014E20 /* Sources */,
+ DEC9785820F6D5DA00014E20 /* Frameworks */,
+ DEC9785920F6D5DA00014E20 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ DEC9785D20F6D5DA00014E20 /* PBXTargetDependency */,
+ );
+ name = Tests_tvOS;
+ productName = Example_tvOSTests;
+ productReference = DEC9785B20F6D5DA00014E20 /* Tests_tvOS.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 6003F582195388D10070C39A /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ CLASSPREFIX = FIR;
+ LastUpgradeCheck = 0940;
+ ORGANIZATIONNAME = "Google, Inc.";
+ TargetAttributes = {
+ 6003F5AD195388D20070C39A = {
+ TestTargetID = 6003F589195388D20070C39A;
+ };
+ DEC977F820F6D0E900014E20 = {
+ CreatedOnToolsVersion = 9.4;
+ ProvisioningStyle = Automatic;
+ };
+ DEC9780A20F6D0EA00014E20 = {
+ CreatedOnToolsVersion = 9.4;
+ ProvisioningStyle = Automatic;
+ TestTargetID = DEC977F820F6D0E900014E20;
+ };
+ DEC9784620F6D5D800014E20 = {
+ CreatedOnToolsVersion = 9.4;
+ ProvisioningStyle = Automatic;
+ };
+ DEC9785A20F6D5DA00014E20 = {
+ CreatedOnToolsVersion = 9.4;
+ ProvisioningStyle = Automatic;
+ TestTargetID = DEC9784620F6D5D800014E20;
+ };
+ };
+ };
+ buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "GoogleUtilities" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 6003F581195388D10070C39A;
+ productRefGroup = 6003F58B195388D20070C39A /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 6003F589195388D20070C39A /* Example_iOS */,
+ 6003F5AD195388D20070C39A /* Tests_iOS */,
+ DEC977F820F6D0E900014E20 /* Example_macOS */,
+ DEC9780A20F6D0EA00014E20 /* Tests_macOS */,
+ DEC9784620F6D5D800014E20 /* Example_tvOS */,
+ DEC9785A20F6D5DA00014E20 /* Tests_tvOS */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 6003F588195388D20070C39A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEC977EF20F6ACDA00014E20 /* LaunchScreen.storyboard in Resources */,
+ DEC977F320F6ACDA00014E20 /* Images.xcassets in Resources */,
+ DEC977F020F6ACDA00014E20 /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 6003F5AC195388D20070C39A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC977F720F6D0E900014E20 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEC9787720F6DE7200014E20 /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC9780920F6D0EA00014E20 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC9784520F6D5D800014E20 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEC9788520F6E1E000014E20 /* Assets.xcassets in Resources */,
+ DEC9788720F6E1E000014E20 /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC9785920F6D5DA00014E20 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 6003F586195388D20070C39A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEC977F120F6ACDA00014E20 /* main.m in Sources */,
+ DEC977EE20F6ACDA00014E20 /* ViewController.m in Sources */,
+ DEC977F420F6ACDA00014E20 /* AppDelegate.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 6003F5AA195388D20070C39A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEC977DD20F68FE100014E20 /* GTMHTTPServer.m in Sources */,
+ DEC977D820F68C3300014E20 /* GULMutableDictionaryTest.m in Sources */,
+ DEC977E120F6A7C100014E20 /* GULLoggerTest.m in Sources */,
+ DEC977D920F68C3300014E20 /* GULNetworkTest.m in Sources */,
+ DE5CF98E20F686310063FFDD /* GULAppEnvironmentUtilTest.m in Sources */,
+ DEC977D720F68C3300014E20 /* GULReachabilityCheckerTest.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC977F520F6D0E900014E20 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEC9787A20F6DE7200014E20 /* AppDelegate.m in Sources */,
+ DEC9787920F6DE7200014E20 /* main.m in Sources */,
+ DEC9787B20F6DE7200014E20 /* ViewController.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC9780720F6D0EA00014E20 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEC9781920F6D38500014E20 /* GULAppEnvironmentUtilTest.m in Sources */,
+ DEC9781A20F6D38800014E20 /* GULReachabilityCheckerTest.m in Sources */,
+ DEC9781820F6D37400014E20 /* GULLoggerTest.m in Sources */,
+ DEC9781D20F6D39900014E20 /* GTMHTTPServer.m in Sources */,
+ DEC9781B20F6D39500014E20 /* GULMutableDictionaryTest.m in Sources */,
+ DEC9781C20F6D39500014E20 /* GULNetworkTest.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC9784320F6D5D800014E20 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEC9788820F6E1E000014E20 /* main.m in Sources */,
+ DEC9788620F6E1E000014E20 /* ViewController.m in Sources */,
+ DEC9788920F6E1E000014E20 /* AppDelegate.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ DEC9785720F6D5DA00014E20 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DEC9786920F6D66300014E20 /* GTMHTTPServer.m in Sources */,
+ DEC9786B20F6D66300014E20 /* GULNetworkTest.m in Sources */,
+ DEC9786A20F6D66300014E20 /* GULMutableDictionaryTest.m in Sources */,
+ DEC9786C20F6D66700014E20 /* GULReachabilityCheckerTest.m in Sources */,
+ DEC9786820F6D65B00014E20 /* GULLoggerTest.m in Sources */,
+ DEC9786D20F6D66B00014E20 /* GULAppEnvironmentUtilTest.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 6003F5B4195388D20070C39A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 6003F589195388D20070C39A /* Example_iOS */;
+ targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */;
+ };
+ DEC9780D20F6D0EA00014E20 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEC977F820F6D0E900014E20 /* Example_macOS */;
+ targetProxy = DEC9780C20F6D0EA00014E20 /* PBXContainerItemProxy */;
+ };
+ DEC9785D20F6D5DA00014E20 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DEC9784620F6D5D800014E20 /* Example_tvOS */;
+ targetProxy = DEC9785C20F6D5DA00014E20 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ DEC977E520F6ACDA00014E20 /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DEC977E620F6ACDA00014E20 /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "<group>";
+ };
+ DEC977E720F6ACDA00014E20 /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DEC977E820F6ACDA00014E20 /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "<group>";
+ };
+ DEC9787120F6DE7200014E20 /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DEC9787220F6DE7200014E20 /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "<group>";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 6003F5BD195388D20070C39A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.3;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 6003F5BE195388D20070C39A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = YES;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.3;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 6003F5C0195388D20070C39A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ GCC_PRECOMPILE_PREFIX_HEADER = NO;
+ INFOPLIST_FILE = "iOS/GoogleUtilities-Info.plist";
+ MODULE_NAME = ExampleApp;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Debug;
+ };
+ 6003F5C1195388D20070C39A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ GCC_PRECOMPILE_PREFIX_HEADER = NO;
+ INFOPLIST_FILE = "iOS/GoogleUtilities-Info.plist";
+ MODULE_NAME = ExampleApp;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ WRAPPER_EXTENSION = app;
+ };
+ name = Release;
+ };
+ 6003F5C3195388D20070C39A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = NO;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../Reachability",
+ );
+ INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example_iOS.app/Example_iOS";
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Debug;
+ };
+ 6003F5C4195388D20070C39A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(inherited)",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = NO;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../Reachability",
+ );
+ INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example_iOS.app/Example_iOS";
+ WRAPPER_EXTENSION = xctest;
+ };
+ name = Release;
+ };
+ DEC9781220F6D0EB00014E20 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = macOS/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.13;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.GoogleUtilities-macOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ };
+ name = Debug;
+ };
+ DEC9781320F6D0EB00014E20 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = macOS/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.13;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.GoogleUtilities-macOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ };
+ name = Release;
+ };
+ DEC9781420F6D0EB00014E20 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../Reachability",
+ );
+ INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.13;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.GoogleUtilities-macOSTests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example_macOS.app/Contents/MacOS/Example_macOS";
+ };
+ name = Debug;
+ };
+ DEC9781520F6D0EB00014E20 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../Reachability",
+ );
+ INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.13;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.GoogleUtilities-macOSTests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example_macOS.app/Contents/MacOS/Example_macOS";
+ };
+ name = Release;
+ };
+ DEC9786220F6D5DA00014E20 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = tvOS/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ DEC9786320F6D5DA00014E20 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
+ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = tvOS/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Example-tvOS";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Release;
+ };
+ DEC9786420F6D5DA00014E20 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../Reachability",
+ );
+ INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Example-tvOSTests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example_tvOS.app/Example_tvOS";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Debug;
+ };
+ DEC9786520F6D5DA00014E20 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../Reachability",
+ );
+ INFOPLIST_FILE = "Tests/Tests-Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.google.Example-tvOSTests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = appletvos;
+ TARGETED_DEVICE_FAMILY = 3;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example_tvOS.app/Example_tvOS";
+ TVOS_DEPLOYMENT_TARGET = 11.1;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 6003F585195388D10070C39A /* Build configuration list for PBXProject "GoogleUtilities" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6003F5BD195388D20070C39A /* Debug */,
+ 6003F5BE195388D20070C39A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "Example_iOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6003F5C0195388D20070C39A /* Debug */,
+ 6003F5C1195388D20070C39A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Tests_iOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6003F5C3195388D20070C39A /* Debug */,
+ 6003F5C4195388D20070C39A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEC9781620F6D0EB00014E20 /* Build configuration list for PBXNativeTarget "Example_macOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEC9781220F6D0EB00014E20 /* Debug */,
+ DEC9781320F6D0EB00014E20 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEC9781720F6D0EB00014E20 /* Build configuration list for PBXNativeTarget "Tests_macOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEC9781420F6D0EB00014E20 /* Debug */,
+ DEC9781520F6D0EB00014E20 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEC9786620F6D5DA00014E20 /* Build configuration list for PBXNativeTarget "Example_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEC9786220F6D5DA00014E20 /* Debug */,
+ DEC9786320F6D5DA00014E20 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DEC9786720F6D5DA00014E20 /* Build configuration list for PBXNativeTarget "Tests_tvOS" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DEC9786420F6D5DA00014E20 /* Debug */,
+ DEC9786520F6D5DA00014E20 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 6003F582195388D10070C39A /* Project object */;
+}
diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme
new file mode 100644
index 0000000..f4ef277
--- /dev/null
+++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_iOS.xcscheme
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0940"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "Example_iOS.app"
+ BlueprintName = "Example_iOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F5AD195388D20070C39A"
+ BuildableName = "Tests_iOS.xctest"
+ BlueprintName = "Tests_iOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "7CBFAA7D205702AB00A65866"
+ BuildableName = "GoogleUtilities_IntegrationTests.xctest"
+ BlueprintName = "GoogleUtilities_IntegrationTests"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "Example_iOS.app"
+ BlueprintName = "Example_iOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "Example_iOS.app"
+ BlueprintName = "Example_iOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "Example_iOS.app"
+ BlueprintName = "Example_iOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_macOS.xcscheme b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_macOS.xcscheme
new file mode 100644
index 0000000..550c22d
--- /dev/null
+++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_macOS.xcscheme
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0940"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC977F820F6D0E900014E20"
+ BuildableName = "Example_macOS.app"
+ BlueprintName = "Example_macOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9780A20F6D0EA00014E20"
+ BuildableName = "Tests_macOS.xctest"
+ BlueprintName = "Tests_macOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC977F820F6D0E900014E20"
+ BuildableName = "Example_macOS.app"
+ BlueprintName = "Example_macOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC977F820F6D0E900014E20"
+ BuildableName = "Example_macOS.app"
+ BlueprintName = "Example_macOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC977F820F6D0E900014E20"
+ BuildableName = "Example_macOS.app"
+ BlueprintName = "Example_macOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_tvOS.xcscheme b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_tvOS.xcscheme
new file mode 100644
index 0000000..7b087de
--- /dev/null
+++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Example_tvOS.xcscheme
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0940"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9784620F6D5D800014E20"
+ BuildableName = "Example_tvOS.app"
+ BlueprintName = "Example_tvOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9785A20F6D5DA00014E20"
+ BuildableName = "Tests_tvOS.xctest"
+ BlueprintName = "Tests_tvOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9784620F6D5D800014E20"
+ BuildableName = "Example_tvOS.app"
+ BlueprintName = "Example_tvOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9784620F6D5D800014E20"
+ BuildableName = "Example_tvOS.app"
+ BlueprintName = "Example_tvOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9784620F6D5D800014E20"
+ BuildableName = "Example_tvOS.app"
+ BlueprintName = "Example_tvOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_iOS.xcscheme b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_iOS.xcscheme
new file mode 100644
index 0000000..91ef539
--- /dev/null
+++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_iOS.xcscheme
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0940"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F5AD195388D20070C39A"
+ BuildableName = "Tests_iOS.xctest"
+ BlueprintName = "Tests_iOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "Example_iOS.app"
+ BlueprintName = "Example_iOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <BuildableProductRunnable
+ runnableDebuggingMode = "0">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "Example_iOS.app"
+ BlueprintName = "Example_iOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "6003F589195388D20070C39A"
+ BuildableName = "Example_iOS.app"
+ BlueprintName = "Example_iOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_macOS.xcscheme b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_macOS.xcscheme
new file mode 100644
index 0000000..016d313
--- /dev/null
+++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_macOS.xcscheme
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0940"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "NO"
+ buildForArchiving = "NO"
+ buildForAnalyzing = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9780A20F6D0EA00014E20"
+ BuildableName = "Tests_macOS.xctest"
+ BlueprintName = "Tests_macOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9780A20F6D0EA00014E20"
+ BuildableName = "Tests_macOS.xctest"
+ BlueprintName = "Tests_macOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9780A20F6D0EA00014E20"
+ BuildableName = "Tests_macOS.xctest"
+ BlueprintName = "Tests_macOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9780A20F6D0EA00014E20"
+ BuildableName = "Tests_macOS.xctest"
+ BlueprintName = "Tests_macOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_tvOS.xcscheme b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_tvOS.xcscheme
new file mode 100644
index 0000000..d9f14c0
--- /dev/null
+++ b/GoogleUtilities/Example/GoogleUtilities.xcodeproj/xcshareddata/xcschemes/Tests_tvOS.xcscheme
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0940"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "NO"
+ buildForProfiling = "NO"
+ buildForArchiving = "NO"
+ buildForAnalyzing = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9785A20F6D5DA00014E20"
+ BuildableName = "Tests_tvOS.xctest"
+ BlueprintName = "Tests_tvOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9785A20F6D5DA00014E20"
+ BuildableName = "Tests_tvOS.xctest"
+ BlueprintName = "Tests_tvOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ <MacroExpansion>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "DEC9785A20F6D5DA00014E20"
+ BuildableName = "Tests_tvOS.xctest"
+ BlueprintName = "Tests_tvOS"
+ ReferencedContainer = "container:GoogleUtilities.xcodeproj">
+ </BuildableReference>
+ </MacroExpansion>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/GoogleUtilities/Example/Podfile b/GoogleUtilities/Example/Podfile
new file mode 100644
index 0000000..814e93a
--- /dev/null
+++ b/GoogleUtilities/Example/Podfile
@@ -0,0 +1,34 @@
+use_frameworks!
+
+target 'Example_iOS' do
+ platform :ios, '8.0'
+
+ pod 'GoogleUtilities', :path => '../../'
+
+ target 'Tests_iOS' do
+ inherit! :search_paths
+ pod 'OCMock'
+ end
+end
+
+target 'Example_macOS' do
+ platform :osx, '10.10'
+
+ pod 'GoogleUtilities', :path => '../../'
+
+ target 'Tests_macOS' do
+ inherit! :search_paths
+ pod 'OCMock'
+ end
+end
+
+target 'Example_tvOS' do
+ platform :tvos, '10.0'
+
+ pod 'GoogleUtilities', :path => '../../'
+
+ target 'Tests_tvOS' do
+ inherit! :search_paths
+ pod 'OCMock'
+ end
+end
diff --git a/GoogleUtilities/Example/Tests/Environment/GULAppEnvironmentUtilTest.m b/GoogleUtilities/Example/Tests/Environment/GULAppEnvironmentUtilTest.m
new file mode 100644
index 0000000..62a7bf8
--- /dev/null
+++ b/GoogleUtilities/Example/Tests/Environment/GULAppEnvironmentUtilTest.m
@@ -0,0 +1,62 @@
+// Copyright 2018 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <Foundation/Foundation.h>
+#import <OCMock/OCMock.h>
+#import <XCTest/XCTest.h>
+
+#import <GoogleUtilities/GULAppEnvironmentUtil.h>
+
+@interface GULAppEnvironmentUtilTest : XCTestCase
+
+@property(nonatomic) id processInfoMock;
+
+@end
+
+@implementation GULAppEnvironmentUtilTest
+
+- (void)setUp {
+ [super setUp];
+
+ _processInfoMock = OCMPartialMock([NSProcessInfo processInfo]);
+}
+
+- (void)tearDown {
+ [super tearDown];
+
+ [_processInfoMock stopMocking];
+}
+
+- (void)testSystemVersionInfoMajorOnly {
+ NSOperatingSystemVersion osTen = {.majorVersion = 10, .minorVersion = 0, .patchVersion = 0};
+ OCMStub([self.processInfoMock operatingSystemVersion]).andReturn(osTen);
+
+ XCTAssertTrue([[GULAppEnvironmentUtil systemVersion] isEqualToString:@"10.0"]);
+}
+
+- (void)testSystemVersionInfoMajorMinor {
+ NSOperatingSystemVersion osTenTwo = {.majorVersion = 10, .minorVersion = 2, .patchVersion = 0};
+ OCMStub([self.processInfoMock operatingSystemVersion]).andReturn(osTenTwo);
+
+ XCTAssertTrue([[GULAppEnvironmentUtil systemVersion] isEqualToString:@"10.2"]);
+}
+
+- (void)testSystemVersionInfoMajorMinorPatch {
+ NSOperatingSystemVersion osTenTwoOne = {.majorVersion = 10, .minorVersion = 2, .patchVersion = 1};
+ OCMStub([self.processInfoMock operatingSystemVersion]).andReturn(osTenTwoOne);
+
+ XCTAssertTrue([[GULAppEnvironmentUtil systemVersion] isEqualToString:@"10.2.1"]);
+}
+
+@end
diff --git a/GoogleUtilities/Example/Tests/Logger/GULLoggerTest.m b/GoogleUtilities/Example/Tests/Logger/GULLoggerTest.m
new file mode 100644
index 0000000..f65c06b
--- /dev/null
+++ b/GoogleUtilities/Example/Tests/Logger/GULLoggerTest.m
@@ -0,0 +1,210 @@
+// Copyright 2018 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifdef DEBUG
+// The tests depend upon library methods only built with #ifdef DEBUG
+
+#import <OCMock/OCMock.h>
+#import <XCTest/XCTest.h>
+
+#import <GoogleUtilities/GULLogger.h>
+
+#import <asl.h>
+
+extern NSString *const kGULPersistedDebugModeKey;
+
+extern const char *kGULLoggerASLClientFacilityName;
+
+extern void GULResetLogger(void);
+
+extern aslclient getGULLoggerClient(void);
+
+extern dispatch_queue_t getGULClientQueue(void);
+
+extern BOOL getGULLoggerDebugMode(void);
+
+static NSString *const kMessageCode = @"I-COR000001";
+
+@interface GULLoggerTest : XCTestCase
+
+@property(nonatomic) NSString *randomLogString;
+
+@property(nonatomic, strong) NSUserDefaults *defaults;
+
+@end
+
+@implementation GULLoggerTest
+
+- (void)setUp {
+ [super setUp];
+ GULResetLogger();
+
+ // Stub NSUserDefaults for cleaner testing.
+ _defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.google.logger_test"];
+}
+
+- (void)tearDown {
+ [super tearDown];
+
+ _defaults = nil;
+}
+
+- (void)testInitializeASLForDebugModeWithUserDefaults {
+ // Stub.
+ NSNumber *debugMode = @YES;
+ [self.defaults setBool:debugMode.boolValue forKey:kGULPersistedDebugModeKey];
+
+ // Test.
+ GULLogError(@"my service", NO, kMessageCode, @"Some error.");
+
+ // Assert.
+ debugMode = [self.defaults objectForKey:kGULPersistedDebugModeKey];
+ XCTAssertTrue(debugMode.boolValue);
+}
+
+- (void)testMessageCodeFormat {
+ // Valid case.
+ XCTAssertNoThrow(GULLogError(@"my service", NO, @"I-APP000001", @"Message."));
+
+ // An extra dash or missing dash should fail.
+ XCTAssertThrows(GULLogError(@"my service", NO, @"I-APP-000001", @"Message."));
+ XCTAssertThrows(GULLogError(@"my service", NO, @"IAPP000001", @"Message."));
+
+ // Wrong number of digits should fail.
+ XCTAssertThrows(GULLogError(@"my service", NO, @"I-APP00001", @"Message."));
+ XCTAssertThrows(GULLogError(@"my service", NO, @"I-APP0000001", @"Message."));
+
+ // Lowercase should fail.
+ XCTAssertThrows(GULLogError(@"my service", NO, @"I-app000001", @"Message."));
+
+// nil or empty message code should fail.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+ XCTAssertThrows(GULLogError(@"my service", NO, nil, @"Message."));
+#pragma clang diagnostic pop
+
+ XCTAssertThrows(GULLogError(@"my service", NO, @"", @"Message."));
+
+ // Android message code should fail.
+ XCTAssertThrows(GULLogError(@"my service", NO, @"A-APP000001", @"Message."));
+}
+
+- (void)testLoggerInterface {
+ XCTAssertNoThrow(GULLogError(@"my service", NO, kMessageCode, @"Message."));
+ XCTAssertNoThrow(GULLogError(@"my service", NO, kMessageCode, @"Configure %@.", @"blah"));
+
+ XCTAssertNoThrow(GULLogWarning(@"my service", NO, kMessageCode, @"Message."));
+ XCTAssertNoThrow(GULLogWarning(@"my service", NO, kMessageCode, @"Configure %@.", @"blah"));
+
+ XCTAssertNoThrow(GULLogNotice(@"my service", NO, kMessageCode, @"Message."));
+ XCTAssertNoThrow(GULLogNotice(@"my service", NO, kMessageCode, @"Configure %@.", @"blah"));
+
+ XCTAssertNoThrow(GULLogInfo(@"my service", NO, kMessageCode, @"Message."));
+ XCTAssertNoThrow(GULLogInfo(@"my service", NO, kMessageCode, @"Configure %@.", @"blah"));
+
+ XCTAssertNoThrow(GULLogDebug(@"my service", NO, kMessageCode, @"Message."));
+ XCTAssertNoThrow(GULLogDebug(@"my service", NO, kMessageCode, @"Configure %@.", @"blah"));
+}
+
+// asl_set_filter does not perform as expected in unit test environment with simulator. The
+// following test only checks whether the logs have been sent to system with the default settings in
+// the unit test environment.
+- (void)testSystemLogWithDefaultStatus {
+#if !(BUG128) // Disable until https://github.com/firebase/firebase-ios-sdk/issues/128 is fixed
+ // Test fails on device and iOS 9 simulators - b/38130372
+ return;
+#else
+ // Sets the time interval that we need to wait in order to fetch all the logs.
+ NSTimeInterval timeInterval = 0.1f;
+ // Generates a random string each time and check whether it has been logged.
+ // Log messages with Notice level and below should be logged to system/device by default.
+ self.randomLogString = [NSUUID UUID].UUIDString;
+ GULLogError(@"my service", NO, kMessageCode, @"%@", self.randomLogString);
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];
+ XCTAssertTrue([self logExists]);
+
+ self.randomLogString = [NSUUID UUID].UUIDString;
+ GULLogWarning(@"my service", kMessageCode, @"%@", self.randomLogString);
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];
+ XCTAssertTrue([self logExists]);
+
+ self.randomLogString = [NSUUID UUID].UUIDString;
+ GULLogNotice(@"my service", kMessageCode, @"%@", self.randomLogString);
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];
+ XCTAssertTrue([self logExists]);
+
+ // Log messages with Info level and above should NOT be logged to system/device by default.
+ self.randomLogString = [NSUUID UUID].UUIDString;
+ GULLogInfo(@"my service", kMessageCode, @"%@", self.randomLogString);
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];
+ XCTAssertFalse([self logExists]);
+
+ self.randomLogString = [NSUUID UUID].UUIDString;
+ GULLogDebug(@"my service", kMessageCode, @"%@", self.randomLogString);
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeInterval]];
+ XCTAssertFalse([self logExists]);
+#endif
+}
+
+// The GULLoggerLevel enum must match the ASL_LEVEL_* constants, but we manually redefine
+// them in GULLoggerLevel.h since we cannot include <asl.h> (see b/34976089 for more details).
+// This test ensures the constants match.
+- (void)testGULLoggerLevelValues {
+ XCTAssertEqual(GULLoggerLevelError, ASL_LEVEL_ERR);
+ XCTAssertEqual(GULLoggerLevelWarning, ASL_LEVEL_WARNING);
+ XCTAssertEqual(GULLoggerLevelNotice, ASL_LEVEL_NOTICE);
+ XCTAssertEqual(GULLoggerLevelInfo, ASL_LEVEL_INFO);
+ XCTAssertEqual(GULLoggerLevelDebug, ASL_LEVEL_DEBUG);
+}
+
+// Helper functions.
+- (BOOL)logExists {
+ [self drainGULClientQueue];
+ NSString *correctMsg =
+ [NSString stringWithFormat:@"%@[%@] %@", @"my service", kMessageCode, self.randomLogString];
+ return [self messageWasLogged:correctMsg];
+}
+
+- (void)drainGULClientQueue {
+ dispatch_semaphore_t workerSemaphore = dispatch_semaphore_create(0);
+ dispatch_async(getGULClientQueue(), ^{
+ dispatch_semaphore_signal(workerSemaphore);
+ });
+ dispatch_semaphore_wait(workerSemaphore, DISPATCH_TIME_FOREVER);
+}
+
+- (BOOL)messageWasLogged:(NSString *)message {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ aslmsg query = asl_new(ASL_TYPE_QUERY);
+ asl_set_query(query, ASL_KEY_FACILITY, kGULLoggerASLClientFacilityName, ASL_QUERY_OP_EQUAL);
+ aslresponse r = asl_search(getGULLoggerClient(), query);
+ asl_free(query);
+ aslmsg m;
+ const char *val;
+ NSMutableArray *allMsg = [[NSMutableArray alloc] init];
+ while ((m = asl_next(r)) != NULL) {
+ val = asl_get(m, ASL_KEY_MSG);
+ if (val) {
+ [allMsg addObject:[NSString stringWithUTF8String:val]];
+ }
+ }
+ asl_free(m);
+ asl_release(r);
+ return [allMsg containsObject:message];
+#pragma clang pop
+}
+
+@end
+#endif
diff --git a/GoogleUtilities/Example/Tests/Network/GULMutableDictionaryTest.m b/GoogleUtilities/Example/Tests/Network/GULMutableDictionaryTest.m
new file mode 100644
index 0000000..6378618
--- /dev/null
+++ b/GoogleUtilities/Example/Tests/Network/GULMutableDictionaryTest.m
@@ -0,0 +1,87 @@
+// Copyright 2018 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <XCTest/XCTest.h>
+
+#import <GoogleUtilities/GULMutableDictionary.h>
+
+const static NSString *const kKey = @"testKey1";
+const static NSString *const kValue = @"testValue1";
+const static NSString *const kKey2 = @"testKey2";
+const static NSString *const kValue2 = @"testValue2";
+
+@interface GULMutableDictionaryTest : XCTestCase
+@property(nonatomic) GULMutableDictionary *dictionary;
+@end
+
+@implementation GULMutableDictionaryTest
+
+- (void)setUp {
+ [super setUp];
+ self.dictionary = [[GULMutableDictionary alloc] init];
+}
+
+- (void)tearDown {
+ self.dictionary = nil;
+ [super tearDown];
+}
+
+- (void)testSetGetAndRemove {
+ XCTAssertNil([self.dictionary objectForKey:kKey]);
+ [self.dictionary setObject:kValue forKey:kKey];
+ XCTAssertEqual(kValue, [self.dictionary objectForKey:kKey]);
+ [self.dictionary removeObjectForKey:kKey];
+ XCTAssertNil([self.dictionary objectForKey:kKey]);
+}
+
+- (void)testSetGetAndRemoveKeyed {
+ XCTAssertNil(self.dictionary[kKey]);
+ self.dictionary[kKey] = kValue;
+ XCTAssertEqual(kValue, self.dictionary[kKey]);
+ [self.dictionary removeObjectForKey:kKey];
+ XCTAssertNil(self.dictionary[kKey]);
+}
+
+- (void)testRemoveAll {
+ XCTAssertNil(self.dictionary[kKey]);
+ XCTAssertNil(self.dictionary[kKey2]);
+ self.dictionary[kKey] = kValue;
+ self.dictionary[kKey2] = kValue2;
+ [self.dictionary removeAllObjects];
+ XCTAssertNil(self.dictionary[kKey]);
+ XCTAssertNil(self.dictionary[kKey2]);
+}
+
+- (void)testCount {
+ XCTAssertEqual([self.dictionary count], 0);
+ self.dictionary[kKey] = kValue;
+ XCTAssertEqual([self.dictionary count], 1);
+ self.dictionary[kKey2] = kValue2;
+ XCTAssertEqual([self.dictionary count], 2);
+ [self.dictionary removeAllObjects];
+ XCTAssertEqual([self.dictionary count], 0);
+}
+
+- (void)testUnderlyingDictionary {
+ XCTAssertEqual([self.dictionary count], 0);
+ self.dictionary[kKey] = kValue;
+ self.dictionary[kKey2] = kValue2;
+
+ NSDictionary *dict = self.dictionary.dictionary;
+ XCTAssertEqual([dict count], 2);
+ XCTAssertEqual(dict[kKey], kValue);
+ XCTAssertEqual(dict[kKey2], kValue2);
+}
+
+@end
diff --git a/GoogleUtilities/Example/Tests/Network/GULNetworkTest.m b/GoogleUtilities/Example/Tests/Network/GULNetworkTest.m
new file mode 100644
index 0000000..4d31503
--- /dev/null
+++ b/GoogleUtilities/Example/Tests/Network/GULNetworkTest.m
@@ -0,0 +1,998 @@
+// Copyright 2018 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "GTMHTTPServer.h"
+
+#import <OCMock/OCMock.h>
+#import <XCTest/XCTest.h>
+
+#import <GoogleUtilities/GULNSData+zlib.h>
+#import <GoogleUtilities/GULNetwork.h>
+#import <GoogleUtilities/GULReachabilityChecker.h>
+
+@interface GULNetwork ()
+
+- (void)reachability:(GULReachabilityChecker *)reachability
+ statusChanged:(GULReachabilityStatus)status;
+
+@end
+
+@interface GULNetworkURLSession ()
+
+- (void)maybeRemoveTempFilesAtURL:(NSURL *)tempFile expiringTime:(NSTimeInterval)expiringTime;
+
+@end
+
+@interface GULNetworkTest : XCTestCase <GULNetworkReachabilityDelegate>
+@end
+
+@implementation GULNetworkTest {
+ dispatch_queue_t _backgroundQueue;
+ GULNetwork *_network;
+
+ /// Fake Server.
+ GTMHTTPServer *_httpServer;
+ GTMHTTPRequestMessage *_request;
+ int _statusCode;
+
+ // For network reachability test.
+ BOOL _fakeNetworkIsReachable;
+ BOOL _currentNetworkStatus;
+ GULReachabilityStatus _fakeReachabilityStatus;
+}
+
+#pragma mark - Setup and teardown
+
+- (void)setUp {
+ [super setUp];
+
+ _fakeNetworkIsReachable = YES;
+ _statusCode = 200;
+ _request = nil;
+
+ _httpServer = [[GTMHTTPServer alloc] initWithDelegate:self];
+
+ // Start the server.
+ NSError *error = nil;
+ XCTAssertTrue([_httpServer start:&error], @"Failed to start HTTP server: %@", error);
+
+ _network = [[GULNetwork alloc] init];
+ _backgroundQueue = dispatch_queue_create("Test queue", DISPATCH_QUEUE_SERIAL);
+
+ _request = nil;
+}
+
+- (void)tearDown {
+ _network = nil;
+ _backgroundQueue = nil;
+ _request = nil;
+
+ [_httpServer stop];
+ _httpServer = nil;
+
+ [super tearDown];
+}
+
+#pragma mark - Test reachability
+
+- (void)testReachability {
+ _network.reachabilityDelegate = self;
+
+ id reachability = [_network valueForKey:@"_reachability"];
+ XCTAssertNotNil(reachability);
+
+ id reachabilityMock = OCMPartialMock(reachability);
+ [[[reachabilityMock stub] andCall:@selector(reachabilityStatus) onObject:self]
+ reachabilityStatus];
+
+ // Fake scenario with connectivity.
+ _fakeNetworkIsReachable = YES;
+ _fakeReachabilityStatus = kGULReachabilityViaWifi;
+ [_network reachability:reachabilityMock statusChanged:[reachabilityMock reachabilityStatus]];
+ XCTAssertTrue([_network isNetworkConnected]);
+ XCTAssertEqual(_currentNetworkStatus, _fakeNetworkIsReachable);
+
+ // Fake scenario without connectivity.
+ _fakeNetworkIsReachable = NO;
+ _fakeReachabilityStatus = kGULReachabilityNotReachable;
+ [_network reachability:reachabilityMock statusChanged:[reachabilityMock reachabilityStatus]];
+ XCTAssertFalse([_network isNetworkConnected]);
+ XCTAssertEqual(_currentNetworkStatus, _fakeNetworkIsReachable);
+
+ [reachabilityMock stopMocking];
+ reachabilityMock = nil;
+}
+
+#pragma mark - Test POST Foreground
+
+- (void)testSessionNetwork_POST_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/2", _httpServer.port]];
+ _statusCode = 200;
+
+ [_network postURL:url
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ [self verifyResponse:response error:error];
+ [self verifyRequest];
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testSessionNetworkShouldReturnError_POST_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/3", _httpServer.port]];
+ _statusCode = 500;
+
+ [_network postURL:url
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(((NSHTTPURLResponse *)response).statusCode, 500);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testNilURLNSURLSession_POST_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ _statusCode = 200;
+
+ [_network postURL:nil
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testEmptyURLNSURLSession_POST_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ _statusCode = 200;
+
+ [_network postURL:[NSURL URLWithString:@""]
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testEmptyPayloadNSURLSession_POST_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ NSData *uncompressedData = [[NSData alloc] init];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/2", _httpServer.port]];
+ _statusCode = 200;
+
+ [_network postURL:url
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertNil(error);
+ XCTAssertNotNil(self->_request);
+ XCTAssertEqualObjects([self->_request.URL absoluteString], [url absoluteString]);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testNilQueueNSURLSession_POST_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/1", _httpServer.port]];
+ _statusCode = 200;
+
+ [_network postURL:url
+ payload:uncompressedData
+ queue:nil
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ [self verifyResponse:response error:error];
+ [self verifyRequest];
+ [expectation fulfill];
+ }];
+
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testHasRequestPendingNSURLSession_POST_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/hasRequestPending",
+ _httpServer.port]];
+ _statusCode = 200;
+
+ [_network postURL:url
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ [self verifyResponse:response error:error];
+ [self verifyRequest];
+
+ XCTAssertFalse(self->_network.hasUploadInProgress,
+ @"hasUploadInProgress must be false");
+ [expectation fulfill];
+ }];
+
+ XCTAssertTrue(self->_network.hasUploadInProgress, @"hasUploadInProgress must be true");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+#pragma mark - Test POST Background
+
+- (void)testSessionNetwork_POST_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/2", _httpServer.port]];
+ _statusCode = 200;
+
+ [_network postURL:url
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ [self verifyResponse:response error:error];
+ [self verifyRequest];
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testSessionNetworkShouldReturnError_POST_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/3", _httpServer.port]];
+ _statusCode = 500;
+
+ [_network postURL:url
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(((NSHTTPURLResponse *)response).statusCode, 500);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testNilURLNSURLSession_POST_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ _statusCode = 200;
+
+ [_network postURL:nil
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testEmptyURLNSURLSession_POST_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ _statusCode = 200;
+
+ [_network postURL:[NSURL URLWithString:@""]
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testEmptyPayloadNSURLSession_POST_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ NSData *uncompressedData = [[NSData alloc] init];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/2", _httpServer.port]];
+ _statusCode = 200;
+
+ [_network postURL:url
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertNil(error);
+ XCTAssertNotNil(self->_request);
+ XCTAssertEqualObjects([self->_request.URL absoluteString], [url absoluteString]);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testNilQueueNSURLSession_POST_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/1", _httpServer.port]];
+ _statusCode = 200;
+
+ [_network postURL:url
+ payload:uncompressedData
+ queue:nil
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ [self verifyResponse:response error:error];
+ [self verifyRequest];
+ [expectation fulfill];
+ }];
+
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testHasRequestPendingNSURLSession_POST_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/hasRequestPending",
+ _httpServer.port]];
+ _statusCode = 200;
+
+ [_network postURL:url
+ payload:uncompressedData
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ [self verifyResponse:response error:error];
+ [self verifyRequest];
+
+ XCTAssertFalse(self->_network.hasUploadInProgress,
+ @"hasUploadInProgress must be false");
+ [expectation fulfill];
+ }];
+
+ XCTAssertTrue(self->_network.hasUploadInProgress, @"hasUploadInProgress must be true");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+#pragma mark - GET Methods Foreground
+
+- (void)testSessionNetworkAsync_GET_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/2", _httpServer.port]];
+ _statusCode = 200;
+
+ [_network getURL:url
+ headers:nil
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertNotNil(data);
+ NSString *responseBody =
+ [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ XCTAssertEqualObjects(responseBody, @"<html><body>Hello, World!</body></html>");
+ XCTAssertNil(error);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testSessionNetworkShouldReturnError_GET_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/3", _httpServer.port]];
+ _statusCode = 500;
+
+ [_network getURL:url
+ headers:nil
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(((NSHTTPURLResponse *)response).statusCode, 500);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testNilURLNSURLSession_GET_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ _statusCode = 200;
+
+ [_network getURL:nil
+ headers:nil
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testEmptyURLNSURLSession_GET_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ _statusCode = 200;
+
+ [_network getURL:[NSURL URLWithString:@""]
+ headers:nil
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testNilQueueNSURLSession_GET_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/1", _httpServer.port]];
+ _statusCode = 200;
+
+ [_network getURL:url
+ headers:nil
+ queue:nil
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertNotNil(data);
+ NSString *responseBody =
+ [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ XCTAssertEqualObjects(responseBody, @"<html><body>Hello, World!</body></html>");
+ XCTAssertNil(error);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testHasRequestPendingNSURLSession_GET_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/hasRequestPending",
+ _httpServer.port]];
+ _statusCode = 200;
+
+ [_network getURL:url
+ headers:nil
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertNotNil(data);
+ NSString *responseBody =
+ [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ XCTAssertEqualObjects(responseBody, @"<html><body>Hello, World!</body></html>");
+ XCTAssertNil(error);
+ XCTAssertFalse(self->_network.hasUploadInProgress,
+ @"hasUploadInProgress must be false");
+ [expectation fulfill];
+ }];
+
+ XCTAssertTrue(self->_network.hasUploadInProgress, @"hasUploadInProgress must be true");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testHeaders_foreground {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/3", _httpServer.port]];
+ _statusCode = 200;
+
+ NSDictionary *headers = @{@"Version" : @"123"};
+
+ [_network getURL:url
+ headers:headers
+ queue:_backgroundQueue
+ usingBackgroundSession:NO
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertNotNil(data);
+ NSString *responseBody =
+ [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ XCTAssertEqualObjects(responseBody, @"<html><body>Hello, World!</body></html>");
+ XCTAssertNil(error);
+
+ NSString *version = [self->_request.allHeaderFieldValues valueForKey:@"Version"];
+ XCTAssertEqualObjects(version, @"123");
+
+ [expectation fulfill];
+ }];
+
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+#pragma mark - GET Methods Background
+
+- (void)testSessionNetworkAsync_GET_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/2", _httpServer.port]];
+ _statusCode = 200;
+
+ [_network getURL:url
+ headers:nil
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertNotNil(data);
+ NSString *responseBody =
+ [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ XCTAssertEqualObjects(responseBody, @"<html><body>Hello, World!</body></html>");
+ XCTAssertNil(error);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testSessionNetworkShouldReturnError_GET_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/3", _httpServer.port]];
+ _statusCode = 500;
+
+ [_network getURL:url
+ headers:nil
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(((NSHTTPURLResponse *)response).statusCode, 500);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testNilURLNSURLSession_GET_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ _statusCode = 200;
+
+ [_network getURL:nil
+ headers:nil
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testEmptyURLNSURLSession_GET_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ _statusCode = 200;
+
+ [_network getURL:[NSURL URLWithString:@""]
+ headers:nil
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testNilQueueNSURLSession_GET_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/1", _httpServer.port]];
+ _statusCode = 200;
+
+ [_network getURL:url
+ headers:nil
+ queue:nil
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertNotNil(data);
+ NSString *responseBody =
+ [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ XCTAssertEqualObjects(responseBody, @"<html><body>Hello, World!</body></html>");
+ XCTAssertNil(error);
+ XCTAssertFalse(self->_network.hasUploadInProgress, "There must be no pending request");
+ [expectation fulfill];
+ }];
+ XCTAssertTrue(self->_network.hasUploadInProgress, "There must be a pending request");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testHasRequestPendingNSURLSession_GET_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/hasRequestPending",
+ _httpServer.port]];
+ _statusCode = 200;
+
+ [_network getURL:url
+ headers:nil
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertNotNil(data);
+ NSString *responseBody =
+ [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ XCTAssertEqualObjects(responseBody, @"<html><body>Hello, World!</body></html>");
+ XCTAssertNil(error);
+ XCTAssertFalse(self->_network.hasUploadInProgress,
+ @"hasUploadInProgress must be false");
+ [expectation fulfill];
+ }];
+
+ XCTAssertTrue(self->_network.hasUploadInProgress, @"hasUploadInProgress must be true");
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+- (void)testHeaders_background {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"];
+
+ NSURL *url =
+ [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/3", _httpServer.port]];
+ _statusCode = 200;
+
+ NSDictionary *headers = @{@"Version" : @"123"};
+
+ [_network getURL:url
+ headers:headers
+ queue:_backgroundQueue
+ usingBackgroundSession:YES
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ XCTAssertNotNil(data);
+ NSString *responseBody =
+ [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ XCTAssertEqualObjects(responseBody, @"<html><body>Hello, World!</body></html>");
+ XCTAssertNil(error);
+
+ NSString *version = [self->_request.allHeaderFieldValues valueForKey:@"Version"];
+ XCTAssertEqualObjects(version, @"123");
+
+ [expectation fulfill];
+ }];
+
+ // Wait a little bit so the server has enough time to respond.
+ [self waitForExpectationsWithTimeout:10
+ handler:^(NSError *error) {
+ if (error) {
+ XCTFail(@"Timeout Error: %@", error);
+ }
+ }];
+}
+
+#pragma mark - Test clean up files
+
+- (void)testRemoveExpiredFiles {
+ NSError *writeError = nil;
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+
+ GULNetworkURLSession *session = [[GULNetworkURLSession alloc]
+ initWithNetworkLoggerDelegate:(id<GULNetworkLoggerDelegate>)_network];
+ NSArray *paths =
+ NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
+ NSString *applicationSupportDirectory = paths.firstObject;
+ NSArray *tempPathComponents = @[
+ applicationSupportDirectory, kGULNetworkApplicationSupportSubdirectory,
+ @"GULNetworkTemporaryDirectory"
+ ];
+ NSURL *folderURL = [NSURL fileURLWithPathComponents:tempPathComponents];
+ [fileManager createDirectoryAtURL:folderURL
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&writeError];
+
+ NSURL *tempFile1 = [folderURL URLByAppendingPathComponent:@"FIRUpload_temp_123"];
+ [self createTempFileAtURL:tempFile1];
+ NSURL *tempFile2 = [folderURL URLByAppendingPathComponent:@"FIRUpload_temp_456"];
+ [self createTempFileAtURL:tempFile2];
+
+ XCTAssertTrue([fileManager fileExistsAtPath:tempFile1.path]);
+ XCTAssertTrue([fileManager fileExistsAtPath:tempFile2.path]);
+
+ NSDate *now =
+ [[NSDate date] dateByAddingTimeInterval:1]; // Start mocking the clock to avoid flakiness.
+ id mockDate = OCMStrictClassMock([NSDate class]);
+ [[[mockDate stub] andReturn:now] date];
+
+ // The file should not be removed since it is not expired yet.
+ [session maybeRemoveTempFilesAtURL:folderURL expiringTime:20];
+ XCTAssertTrue([fileManager fileExistsAtPath:tempFile1.path]);
+ XCTAssertTrue([fileManager fileExistsAtPath:tempFile2.path]);
+
+ [mockDate stopMocking];
+ mockDate = nil;
+
+ now = [[NSDate date] dateByAddingTimeInterval:100]; // Move forward in time 100s.
+ mockDate = OCMStrictClassMock([NSDate class]);
+ [[[mockDate stub] andReturn:now] date];
+
+ [session maybeRemoveTempFilesAtURL:folderURL expiringTime:20];
+ XCTAssertFalse([fileManager fileExistsAtPath:tempFile1.path]);
+ XCTAssertFalse([fileManager fileExistsAtPath:tempFile2.path]);
+ [mockDate stopMocking];
+ mockDate = nil;
+}
+
+#pragma mark - Internal Methods
+
+- (void)createTempFileAtURL:(NSURL *)fileURL {
+ // Create a dictionary and write it to file.
+ NSDictionary *someContent = @{@"object" : @"key"};
+ [someContent writeToURL:fileURL atomically:YES];
+}
+
+- (void)verifyResponse:(NSHTTPURLResponse *)response error:(NSError *)error {
+ XCTAssertNil(error, @"Error is not expected");
+ XCTAssertNotNil(response, @"Error is not expected");
+}
+
+- (void)verifyRequest {
+ XCTAssertNotNil(_request, @"Request cannot be nil");
+
+ // Test whether the request is compressed correctly.
+ NSData *requestBody = [_request body];
+ NSData *decompressedRequestData = [NSData gul_dataByInflatingGzippedData:requestBody error:NULL];
+ NSString *requestString =
+ [[NSString alloc] initWithData:decompressedRequestData encoding:NSUTF8StringEncoding];
+ XCTAssertEqualObjects(requestString, @"Google", @"Request is not compressed correctly.");
+
+ // The request has to be a POST.
+ XCTAssertEqualObjects([_request method], @"POST", @"Request method has to be POST");
+
+ // Content length has to be set correctly.
+ NSString *contentLength = [_request.allHeaderFieldValues valueForKey:@"Content-Length"];
+ XCTAssertEqualObjects(contentLength, @"26", @"Content Length is incorrect");
+
+ NSString *contentEncoding = [_request.allHeaderFieldValues valueForKey:@"Content-Encoding"];
+ XCTAssertEqualObjects(contentEncoding, @"gzip", @"Content Encoding is incorrect");
+}
+
+#pragma mark - Helper Methods
+
+- (GTMHTTPResponseMessage *)httpServer:(GTMHTTPServer *)server
+ handleRequest:(GTMHTTPRequestMessage *)request {
+ _request = request;
+
+ NSData *html =
+ [@"<html><body>Hello, World!</body></html>" dataUsingEncoding:NSUTF8StringEncoding];
+ return [GTMHTTPResponseMessage responseWithBody:html
+ contentType:@"text/html; charset=UTF-8"
+ statusCode:_statusCode];
+}
+
+- (BOOL)isReachable {
+ return _fakeNetworkIsReachable;
+}
+
+- (GULReachabilityStatus)reachabilityStatus {
+ return _fakeReachabilityStatus;
+}
+
+#pragma mark - FIRReachabilityDelegate
+
+- (void)reachabilityDidChange {
+ _currentNetworkStatus = _fakeNetworkIsReachable;
+}
+
+@end
diff --git a/GoogleUtilities/Example/Tests/Network/third_party/GTMHTTPServer.h b/GoogleUtilities/Example/Tests/Network/third_party/GTMHTTPServer.h
new file mode 100644
index 0000000..cc51207
--- /dev/null
+++ b/GoogleUtilities/Example/Tests/Network/third_party/GTMHTTPServer.h
@@ -0,0 +1,173 @@
+/* Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// GTMHTTPServer.h
+//
+// This is a *very* *simple* webserver that can be built into something, it is
+// not meant to stand up a site, it sends all requests to its delegate for
+// processing on the main thread. It does not support pipelining, etc. It's
+// great for places where you need a simple webserver to unittest some code
+// that hits a server.
+//
+// NOTE: there are several TODOs left in here as markers for things that could
+// be done if one wanted to add more to this class.
+//
+// Based a little on HTTPServer, part of the CocoaHTTPServer sample code found at
+// https://opensource.apple.com/source/HTTPServer/HTTPServer-11/CocoaHTTPServer/
+// License for the CocoaHTTPServer sample code:
+//
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2011, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above
+// copyright notice, this list of conditions and the
+// following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its
+// contributors may be used to endorse or promote products
+// derived from this software without specific prior
+// written permission of Deusty, LLC.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+#import <Foundation/Foundation.h>
+
+#if GTM_IPHONE_SDK
+#import <CFNetwork/CFNetwork.h>
+#endif // GTM_IPHONE_SDK
+
+// Global contants needed for errors from start
+
+#undef _EXTERN
+#undef _INITIALIZE_AS
+#ifdef GTMHTTPSERVER_DEFINE_GLOBALS
+#define _EXTERN
+#define _INITIALIZE_AS(x) = x
+#else
+#define _EXTERN extern
+#define _INITIALIZE_AS(x)
+#endif
+
+_EXTERN NSString *const kGTMHTTPServerErrorDomain
+ _INITIALIZE_AS(@"com.google.mactoolbox.HTTPServerDomain");
+enum {
+ kGTMHTTPServerSocketCreateFailedError = -100,
+ kGTMHTTPServerBindFailedError = -101,
+ kGTMHTTPServerListenFailedError = -102,
+ kGTMHTTPServerHandleCreateFailedError = -103,
+};
+
+@class GTMHTTPRequestMessage, GTMHTTPResponseMessage;
+
+// ----------------------------------------------------------------------------
+
+// See comment at top of file for the intened use of this class.
+@interface GTMHTTPServer : NSObject {
+ @private
+ id delegate_; // WEAK
+ uint16_t port_;
+ BOOL reusePort_;
+ BOOL localhostOnly_;
+ NSFileHandle *listenHandle_;
+ NSMutableArray *connections_;
+}
+
+// The delegate must support the httpServer:handleRequest: method in
+// NSObject(GTMHTTPServerDelegateMethods) below.
+- (id)initWithDelegate:(id)delegate;
+
+- (id)delegate;
+
+// Passing port zero will let one get assigned.
+- (uint16_t)port;
+- (void)setPort:(uint16_t)port;
+
+// Controls listening socket behavior: SO_REUSEADDR vs SO_REUSEPORT.
+// The default is NO (SO_REUSEADDR)
+- (BOOL)reusePort;
+- (void)setReusePort:(BOOL)reusePort;
+
+// Receive connections on the localHost loopback address only or on all
+// interfaces for this machine. The default is to only listen on localhost.
+- (BOOL)localhostOnly;
+- (void)setLocalhostOnly:(BOOL)yesno;
+
+// Start/Stop the web server. If there is an error starting up the server, |NO|
+// is returned, and the specific startup failure can be returned in |error| (see
+// above for the error domain and error codes). If the server is started, |YES|
+// is returned and the server's delegate is called for any requests that come
+// in.
+- (BOOL)start:(NSError **)error;
+- (void)stop;
+
+// returns the number of requests currently active in the server (i.e.-being
+// read in, sent replies).
+- (NSUInteger)activeRequestCount;
+
+@end
+
+@interface NSObject (GTMHTTPServerDelegateMethods)
+- (GTMHTTPResponseMessage *)httpServer:(GTMHTTPServer *)server
+ handleRequest:(GTMHTTPRequestMessage *)request;
+@end
+
+// ----------------------------------------------------------------------------
+
+// Encapsulates an http request, one of these is sent to the server's delegate
+// for each request.
+@interface GTMHTTPRequestMessage : NSObject {
+ @private
+ CFHTTPMessageRef message_;
+}
+- (NSString *)version;
+- (NSURL *)URL;
+- (NSString *)method;
+- (NSData *)body;
+- (NSDictionary *)allHeaderFieldValues;
+@end
+
+// ----------------------------------------------------------------------------
+
+// Encapsulates an http response, the server's delegate should return one for
+// each request received.
+@interface GTMHTTPResponseMessage : NSObject {
+ @private
+ CFHTTPMessageRef message_;
+}
++ (instancetype)responseWithString:(NSString *)plainText;
++ (instancetype)responseWithHTMLString:(NSString *)htmlString;
++ (instancetype)responseWithBody:(NSData *)body
+ contentType:(NSString *)contentType
+ statusCode:(int)statusCode;
++ (instancetype)emptyResponseWithCode:(int)statusCode;
+// TODO: class method for redirections?
+// TODO: add helper for expire/no-cache
+- (void)setValue:(NSString *)value forHeaderField:(NSString *)headerField;
+- (void)setHeaderValuesFromDictionary:(NSDictionary *)dict;
+@end
diff --git a/GoogleUtilities/Example/Tests/Network/third_party/GTMHTTPServer.m b/GoogleUtilities/Example/Tests/Network/third_party/GTMHTTPServer.m
new file mode 100644
index 0000000..526d8c5
--- /dev/null
+++ b/GoogleUtilities/Example/Tests/Network/third_party/GTMHTTPServer.m
@@ -0,0 +1,630 @@
+/* Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Based a little on HTTPServer, part of the CocoaHTTPServer sample code found at
+// https://opensource.apple.com/source/HTTPServer/HTTPServer-11/CocoaHTTPServer/
+// License for the CocoaHTTPServer sample code:
+//
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2011, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above
+// copyright notice, this list of conditions and the
+// following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its
+// contributors may be used to endorse or promote products
+// derived from this software without specific prior
+// written permission of Deusty, LLC.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#import <netinet/in.h>
+#import <sys/socket.h>
+#import <unistd.h>
+
+#define GTMHTTPSERVER_DEFINE_GLOBALS
+#import "GTMHTTPServer.h"
+
+// avoid some of GTM's promiscuous dependencies
+#ifndef _GTMDevLog
+#define _GTMDevLog NSLog
+#endif
+
+#ifndef GTM_STATIC_CAST
+#define GTM_STATIC_CAST(type, object) ((type *)(object))
+#endif
+
+#ifndef GTMCFAutorelease
+#define GTMCFAutorelease(x) ([(id)x autorelease])
+#endif
+
+@interface GTMHTTPServer (PrivateMethods)
+- (void)acceptedConnectionNotification:(NSNotification *)notification;
+- (NSMutableDictionary *)connectionWithFileHandle:(NSFileHandle *)fileHandle;
+- (void)dataAvailableNotification:(NSNotification *)notification;
+- (NSMutableDictionary *)lookupConnection:(NSFileHandle *)fileHandle;
+- (void)closeConnection:(NSMutableDictionary *)connDict;
+- (void)sendResponseOnNewThread:(NSMutableDictionary *)connDict;
+- (void)sentResponse:(NSMutableDictionary *)connDict;
+@end
+
+// keys for our connection dictionaries
+static NSString *kFileHandle = @"FileHandle";
+static NSString *kRequest = @"Request";
+static NSString *kResponse = @"Response";
+
+@interface GTMHTTPRequestMessage (PrivateHelpers)
+- (BOOL)isHeaderComplete;
+- (BOOL)appendData:(NSData *)data;
+- (NSString *)headerFieldValueForKey:(NSString *)key;
+- (UInt32)contentLength;
+- (void)setBody:(NSData *)body;
+@end
+
+@interface GTMHTTPResponseMessage ()
+- (id)initWithBody:(NSData *)body contentType:(NSString *)contentType statusCode:(int)statusCode;
+- (NSData *)serializedData;
+@end
+
+@implementation GTMHTTPServer
+
+- (id)init {
+ return [self initWithDelegate:nil];
+}
+
+- (id)initWithDelegate:(id)delegate {
+ self = [super init];
+ if (self) {
+ if (!delegate) {
+ _GTMDevLog(@"missing delegate");
+ [self release];
+ return nil;
+ }
+ delegate_ = delegate;
+
+#ifndef NS_BLOCK_ASSERTIONS
+ BOOL isDelegateOK = [delegate_ respondsToSelector:@selector(httpServer:handleRequest:)];
+ NSAssert(isDelegateOK, @"GTMHTTPServer delegate lacks handleRequest sel");
+#endif
+
+ localhostOnly_ = YES;
+ connections_ = [[NSMutableArray alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self stop];
+ [connections_ release];
+ [super dealloc];
+}
+
+#if !TARGET_OS_IPHONE
+- (void)finalize {
+ [self stop];
+ [super finalize];
+}
+#endif
+
+- (id)delegate {
+ return delegate_;
+}
+
+- (uint16_t)port {
+ return port_;
+}
+
+- (void)setPort:(uint16_t)port {
+ port_ = port;
+}
+
+- (BOOL)reusePort {
+ return reusePort_;
+}
+
+- (void)setReusePort:(BOOL)yesno {
+ reusePort_ = yesno;
+}
+
+- (BOOL)localhostOnly {
+ return localhostOnly_;
+}
+
+- (void)setLocalhostOnly:(BOOL)yesno {
+ localhostOnly_ = yesno;
+}
+
+- (BOOL)start:(NSError **)error {
+ NSAssert(listenHandle_ == nil, @"start called when we already have a listenHandle_");
+
+ if (error) *error = NULL;
+
+ NSInteger startFailureCode = 0;
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd <= 0) {
+ // COV_NF_START - we'd need to use up *all* sockets to test this?
+ startFailureCode = kGTMHTTPServerSocketCreateFailedError;
+ goto startFailed;
+ // COV_NF_END
+ }
+
+ // enable address reuse quicker after we are done w/ our socket
+ int yes = 1;
+ int sock_opt = reusePort_ ? SO_REUSEPORT : SO_REUSEADDR;
+ if (setsockopt(fd, SOL_SOCKET, sock_opt, (void *)&yes, (socklen_t)sizeof(yes)) != 0) {
+ _GTMDevLog(@"failed to mark the socket as reusable"); // COV_NF_LINE
+ }
+
+ // bind
+ struct sockaddr_in addr;
+ bzero(&addr, sizeof(addr));
+ addr.sin_len = sizeof(addr);
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port_);
+ if (localhostOnly_) {
+ addr.sin_addr.s_addr = htonl(0x7F000001);
+ } else {
+ // COV_NF_START - testing this could cause a leopard firewall prompt during tests.
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ // COV_NF_END
+ }
+ if (bind(fd, (struct sockaddr *)(&addr), (socklen_t)sizeof(addr)) != 0) {
+ startFailureCode = kGTMHTTPServerBindFailedError;
+ goto startFailed;
+ }
+
+ // collect the port back out
+ if (port_ == 0) {
+ socklen_t len = (socklen_t)sizeof(addr);
+ if (getsockname(fd, (struct sockaddr *)(&addr), &len) == 0) {
+ port_ = ntohs(addr.sin_port);
+ }
+ }
+
+ // tell it to listen for connections
+ if (listen(fd, 5) != 0) {
+ // COV_NF_START
+ startFailureCode = kGTMHTTPServerListenFailedError;
+ goto startFailed;
+ // COV_NF_END
+ }
+
+ // now use a filehandle to accept connections
+ listenHandle_ = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
+ if (listenHandle_ == nil) {
+ // COV_NF_START - we'd need to run out of memory to test this?
+ startFailureCode = kGTMHTTPServerHandleCreateFailedError;
+ goto startFailed;
+ // COV_NF_END
+ }
+
+ // setup notifications for connects
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(acceptedConnectionNotification:)
+ name:NSFileHandleConnectionAcceptedNotification
+ object:listenHandle_];
+ [listenHandle_ acceptConnectionInBackgroundAndNotify];
+
+ // TODO: maybe hit the delegate incase it wants to register w/ NSNetService,
+ // or just know we're up and running?
+
+ return YES;
+
+startFailed:
+ if (error) {
+ *error = [[[NSError alloc] initWithDomain:kGTMHTTPServerErrorDomain
+ code:startFailureCode
+ userInfo:nil] autorelease];
+ }
+ if (fd > 0) {
+ close(fd);
+ }
+ return NO;
+}
+
+- (void)stop {
+ if (listenHandle_) {
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ [center removeObserver:self
+ name:NSFileHandleConnectionAcceptedNotification
+ object:listenHandle_];
+ [listenHandle_ release];
+ listenHandle_ = nil;
+ // TODO: maybe hit the delegate in case it wants to unregister w/
+ // NSNetService, or just know we've stopped running?
+ }
+ [connections_ removeAllObjects];
+}
+
+- (NSUInteger)activeRequestCount {
+ return [connections_ count];
+}
+
+- (NSString *)description {
+ NSString *result =
+ [NSString stringWithFormat:@"%@<%p>{ port=%d localHostOnly=%@ status=%@ }", [self class],
+ self, port_, (localhostOnly_ ? @"YES" : @"NO"),
+ (listenHandle_ != nil ? @"Started" : @"Stopped")];
+ return result;
+}
+
+@end
+
+@implementation GTMHTTPServer (PrivateMethods)
+
+- (void)acceptedConnectionNotification:(NSNotification *)notification {
+ NSDictionary *userInfo = [notification userInfo];
+ NSFileHandle *newConnection = [userInfo objectForKey:NSFileHandleNotificationFileHandleItem];
+ NSAssert1(newConnection != nil, @"failed to get the connection in the notification: %@",
+ notification);
+
+ // make sure we accept more...
+ [listenHandle_ acceptConnectionInBackgroundAndNotify];
+
+ // TODO: could let the delegate look at the address, before we start working
+ // on it.
+
+ NSMutableDictionary *connDict = [self connectionWithFileHandle:newConnection];
+ [connections_ addObject:connDict];
+}
+
+- (NSMutableDictionary *)connectionWithFileHandle:(NSFileHandle *)fileHandle {
+ NSMutableDictionary *result = [NSMutableDictionary dictionary];
+
+ [result setObject:fileHandle forKey:kFileHandle];
+
+ GTMHTTPRequestMessage *request = [[[GTMHTTPRequestMessage alloc] init] autorelease];
+ [result setObject:request forKey:kRequest];
+
+ // setup for data notifications
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ [center addObserver:self
+ selector:@selector(dataAvailableNotification:)
+ name:NSFileHandleReadCompletionNotification
+ object:fileHandle];
+ [fileHandle readInBackgroundAndNotify];
+
+ return result;
+}
+
+- (void)dataAvailableNotification:(NSNotification *)notification {
+ NSFileHandle *connectionHandle = GTM_STATIC_CAST(NSFileHandle, [notification object]);
+ NSMutableDictionary *connDict = [self lookupConnection:connectionHandle];
+ if (connDict == nil) return; // we are no longer tracking this one
+
+ NSDictionary *userInfo = [notification userInfo];
+ NSData *readData = [userInfo objectForKey:NSFileHandleNotificationDataItem];
+ if ([readData length] == 0) {
+ // remote side closed
+ [self closeConnection:connDict];
+ return;
+ }
+
+ // Use a local pool to keep memory down incase the runloop we're in doesn't
+ // drain until it gets a UI event.
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ @try {
+ // Like Apple's sample, we just keep adding data until we get a full header
+ // and any referenced body.
+
+ GTMHTTPRequestMessage *request = [connDict objectForKey:kRequest];
+ [request appendData:readData];
+
+ // Is the header complete yet?
+ if (![request isHeaderComplete]) {
+ // more data...
+ [connectionHandle readInBackgroundAndNotify];
+ } else {
+ // Do we have all the body?
+ UInt32 contentLength = [request contentLength];
+ NSData *body = [request body];
+ NSUInteger bodyLength = [body length];
+ if (contentLength > bodyLength) {
+ // need more data...
+ [connectionHandle readInBackgroundAndNotify];
+ } else {
+ if (contentLength < bodyLength) {
+ // We got extra (probably someone trying to pipeline on us), trim
+ // and let the extra data go...
+ NSData *newBody = [NSData dataWithBytes:[body bytes] length:contentLength];
+ [request setBody:newBody];
+ _GTMDevLog(@"Got %lu extra bytes on http request, ignoring them",
+ (unsigned long)(bodyLength - contentLength));
+ }
+
+ GTMHTTPResponseMessage *response = nil;
+ @try {
+ // Off to the delegate
+ response = [delegate_ httpServer:self handleRequest:request];
+ } @catch (NSException *e) {
+ _GTMDevLog(@"Exception trying to handle http request: %@", e);
+ } // COV_NF_LINE - radar 5851992 only reachable w/ an uncaught exception which isn't
+ // testable
+
+ if (response) {
+ // We don't support connection reuse, so we add (force) the header to
+ // close every connection.
+ [response setValue:@"close" forHeaderField:@"Connection"];
+
+ // spawn thread to send reply (since we do a blocking send)
+ [connDict setObject:response forKey:kResponse];
+ [NSThread detachNewThreadSelector:@selector(sendResponseOnNewThread:)
+ toTarget:self
+ withObject:connDict];
+ } else {
+ // No response, shut it down
+ [self closeConnection:connDict];
+ }
+ }
+ }
+ } @catch (NSException *e) { // COV_NF_START
+ _GTMDevLog(@"exception while read data: %@", e);
+ // exception while dealing with the connection, close it
+ } // COV_NF_END
+ @finally {
+ [pool drain];
+ }
+}
+
+- (NSMutableDictionary *)lookupConnection:(NSFileHandle *)fileHandle {
+ NSMutableDictionary *result = nil;
+ for (NSMutableDictionary *connDict in connections_) {
+ if (fileHandle == [connDict objectForKey:kFileHandle]) {
+ result = connDict;
+ break;
+ }
+ }
+ return result;
+}
+
+- (void)closeConnection:(NSMutableDictionary *)connDict {
+ // remove the notification
+ NSFileHandle *connectionHandle = [connDict objectForKey:kFileHandle];
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ [center removeObserver:self name:NSFileHandleReadCompletionNotification object:connectionHandle];
+ // in a non GC world, we're fine just letting the connect get closed when
+ // the object is release when it comes out of connections_, but in a GC world
+ // it won't get cleaned up
+ [connectionHandle closeFile];
+
+ // remove it from the list
+ [connections_ removeObject:connDict];
+}
+
+- (void)sendResponseOnNewThread:(NSMutableDictionary *)connDict {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ @try {
+ GTMHTTPResponseMessage *response = [connDict objectForKey:kResponse];
+ NSFileHandle *connectionHandle = [connDict objectForKey:kFileHandle];
+ NSData *serialized = [response serializedData];
+ [connectionHandle writeData:serialized];
+ } @catch (NSException *e) { // COV_NF_START - causing an exception here is to hard in a test
+ // TODO: let the delegate know about the exception (but do it on the main
+ // thread)
+ _GTMDevLog(@"exception while sending reply: %@", e);
+ } // COV_NF_END
+
+ // back to the main thread to close things down
+ [self performSelectorOnMainThread:@selector(sentResponse:) withObject:connDict waitUntilDone:NO];
+
+ [pool release];
+}
+
+- (void)sentResponse:(NSMutableDictionary *)connDict {
+ // make sure we're still tracking this connection (in case server was stopped)
+ NSFileHandle *connection = [connDict objectForKey:kFileHandle];
+ NSMutableDictionary *connDict2 = [self lookupConnection:connection];
+ if (connDict != connDict2) return;
+
+ // TODO: message the delegate that it was sent
+
+ // close it down
+ [self closeConnection:connDict];
+}
+
+@end
+
+#pragma mark -
+
+@implementation GTMHTTPRequestMessage
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ message_ = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, YES);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if (message_) {
+ CFRelease(message_);
+ }
+ [super dealloc];
+}
+
+- (NSString *)version {
+ return GTMCFAutorelease(CFHTTPMessageCopyVersion(message_));
+}
+
+- (NSURL *)URL {
+ return GTMCFAutorelease(CFHTTPMessageCopyRequestURL(message_));
+}
+
+- (NSString *)method {
+ return GTMCFAutorelease(CFHTTPMessageCopyRequestMethod(message_));
+}
+
+- (NSData *)body {
+ return GTMCFAutorelease(CFHTTPMessageCopyBody(message_));
+}
+
+- (NSDictionary *)allHeaderFieldValues {
+ return GTMCFAutorelease(CFHTTPMessageCopyAllHeaderFields(message_));
+}
+
+- (NSString *)description {
+ CFStringRef desc = CFCopyDescription(message_);
+ NSString *result = [NSString stringWithFormat:@"%@<%p>{ message=%@ }", [self class], self, desc];
+ CFRelease(desc);
+ return result;
+}
+
+@end
+
+@implementation GTMHTTPRequestMessage (PrivateHelpers)
+
+- (BOOL)isHeaderComplete {
+ return CFHTTPMessageIsHeaderComplete(message_) ? YES : NO;
+}
+
+- (BOOL)appendData:(NSData *)data {
+ return CFHTTPMessageAppendBytes(message_, [data bytes], (CFIndex)[data length]) ? YES : NO;
+}
+
+- (NSString *)headerFieldValueForKey:(NSString *)key {
+ CFStringRef value = NULL;
+ if (key) {
+ value = CFHTTPMessageCopyHeaderFieldValue(message_, (CFStringRef)key);
+ }
+ return GTMCFAutorelease(value);
+}
+
+- (UInt32)contentLength {
+ return (UInt32)[[self headerFieldValueForKey:@"Content-Length"] intValue];
+}
+
+- (void)setBody:(NSData *)body {
+ if (!body) {
+ body = [NSData data]; // COV_NF_LINE - can only happen in we fail to make the new data object
+ }
+ CFHTTPMessageSetBody(message_, (CFDataRef)body);
+}
+
+@end
+
+#pragma mark -
+
+@implementation GTMHTTPResponseMessage
+
+- (id)init {
+ return [self initWithBody:nil contentType:nil statusCode:0];
+}
+
+- (id)initWithBody:(NSData *)body contentType:(NSString *)contentType statusCode:(int)statusCode {
+ self = [super init];
+ if (self) {
+ if ((statusCode < 100) || (statusCode > 599)) {
+ [self release];
+ return nil;
+ }
+ message_ =
+ CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_0);
+ if (!message_) {
+ // COV_NF_START
+ [self release];
+ return nil;
+ // COV_NF_END
+ }
+ NSUInteger bodyLength = 0;
+ if (body) {
+ bodyLength = [body length];
+ CFHTTPMessageSetBody(message_, (CFDataRef)body);
+ }
+ if ([contentType length] == 0) {
+ contentType = @"text/html";
+ }
+ NSString *bodyLenStr = [NSString stringWithFormat:@"%lu", (unsigned long)bodyLength];
+ [self setValue:bodyLenStr forHeaderField:@"Content-Length"];
+ [self setValue:contentType forHeaderField:@"Content-Type"];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if (message_) {
+ CFRelease(message_);
+ }
+ [super dealloc];
+}
+
++ (instancetype)responseWithString:(NSString *)plainText {
+ NSData *body = [plainText dataUsingEncoding:NSUTF8StringEncoding];
+ return [self responseWithBody:body contentType:@"text/plain; charset=UTF-8" statusCode:200];
+}
+
++ (instancetype)responseWithHTMLString:(NSString *)htmlString {
+ return [self responseWithBody:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
+ contentType:@"text/html; charset=UTF-8"
+ statusCode:200];
+}
+
++ (instancetype)responseWithBody:(NSData *)body
+ contentType:(NSString *)contentType
+ statusCode:(int)statusCode {
+ return [[[[self class] alloc] initWithBody:body contentType:contentType statusCode:statusCode]
+ autorelease];
+}
+
++ (instancetype)emptyResponseWithCode:(int)statusCode {
+ return
+ [[[[self class] alloc] initWithBody:nil contentType:nil statusCode:statusCode] autorelease];
+}
+
+- (void)setValue:(NSString *)value forHeaderField:(NSString *)headerField {
+ if ([headerField length] == 0) return;
+ if (value == nil) {
+ value = @"";
+ }
+ CFHTTPMessageSetHeaderFieldValue(message_, (CFStringRef)headerField, (CFStringRef)value);
+}
+
+- (void)setHeaderValuesFromDictionary:(NSDictionary *)dict {
+ for (id key in dict) {
+ id value = [dict valueForKey:key];
+ [self setValue:value forHeaderField:key];
+ }
+}
+
+- (NSString *)description {
+ CFStringRef desc = CFCopyDescription(message_);
+ NSString *result = [NSString stringWithFormat:@"%@<%p>{ message=%@ }", [self class], self, desc];
+ CFRelease(desc);
+ return result;
+}
+
+- (NSData *)serializedData {
+ return GTMCFAutorelease(CFHTTPMessageCopySerializedMessage(message_));
+}
+
+@end
diff --git a/GoogleUtilities/Example/Tests/Reachability/GULReachabilityCheckerTest.m b/GoogleUtilities/Example/Tests/Reachability/GULReachabilityCheckerTest.m
new file mode 100644
index 0000000..da9fbf1
--- /dev/null
+++ b/GoogleUtilities/Example/Tests/Reachability/GULReachabilityCheckerTest.m
@@ -0,0 +1,358 @@
+// Copyright 2018 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <GoogleUtilities/GULReachabilityChecker.h>
+
+#import <XCTest/XCTest.h>
+
+#import "GULReachabilityChecker+Internal.h"
+
+@interface GULReachabilityCheckerTest : XCTestCase <GULReachabilityDelegate> {
+ @private
+ GULReachabilityChecker *checker_;
+ NSMutableArray *statuses_;
+ BOOL createFail_;
+ BOOL setCallbackFail_;
+ BOOL scheduleUnscheduleFail_;
+}
+
+- (void *)createReachabilityWithAllocator:(CFAllocatorRef)allocator withName:(const char *)hostname;
+- (BOOL)reachability:(const void *)reachability
+ setCallback:(SCNetworkReachabilityCallBack)callback
+ withContext:(SCNetworkReachabilityContext *)context;
+- (BOOL)scheduleReachability:(const void *)reachability
+ runLoop:(CFRunLoopRef)runLoop
+ runLoopMode:(CFStringRef)runLoopMode;
+- (BOOL)unscheduleReachability:(const void *)reachability
+ runLoop:(CFRunLoopRef)runLoop
+ runLoopMode:(CFStringRef)runLoopMode;
+- (void)releaseReachability:(const void *)reachability;
+@end
+
+static NSString *const kHostname = @"www.google.com";
+static const void *kFakeReachabilityObject = (const void *)0x8badf00d;
+
+static GULReachabilityCheckerTest *FakeReachabilityTest = nil;
+
+static struct {
+ int callsMade;
+ int createCall;
+ int setCallbackCall;
+ int scheduleCall;
+ int unscheduleCall;
+ int releaseCall;
+ void (*callback)(SCNetworkReachabilityRef, SCNetworkReachabilityFlags, void *info);
+ void *callbackInfo;
+} FakeReachability;
+
+static SCNetworkReachabilityRef ReachabilityCreateWithName(CFAllocatorRef allocator,
+ const char *hostname) {
+ return (SCNetworkReachabilityRef)
+ [FakeReachabilityTest createReachabilityWithAllocator:allocator withName:hostname];
+}
+
+static Boolean ReachabilitySetCallback(SCNetworkReachabilityRef reachability,
+ SCNetworkReachabilityCallBack callback,
+ SCNetworkReachabilityContext *context) {
+ return [FakeReachabilityTest reachability:reachability setCallback:callback withContext:context];
+}
+
+static Boolean ReachabilityScheduleWithRunLoop(SCNetworkReachabilityRef reachability,
+ CFRunLoopRef runLoop,
+ CFStringRef runLoopMode) {
+ return [FakeReachabilityTest scheduleReachability:reachability
+ runLoop:runLoop
+ runLoopMode:runLoopMode];
+}
+
+static Boolean ReachabilityUnscheduleFromRunLoop(SCNetworkReachabilityRef reachability,
+ CFRunLoopRef runLoop,
+ CFStringRef runLoopMode) {
+ return [FakeReachabilityTest unscheduleReachability:reachability
+ runLoop:runLoop
+ runLoopMode:runLoopMode];
+}
+
+static void ReachabilityRelease(CFTypeRef reachability) {
+ [FakeReachabilityTest releaseReachability:reachability];
+}
+
+static const struct GULReachabilityApi kTestReachabilityApi = {
+ ReachabilityCreateWithName, ReachabilitySetCallback, ReachabilityScheduleWithRunLoop,
+ ReachabilityUnscheduleFromRunLoop, ReachabilityRelease,
+};
+
+@implementation GULReachabilityCheckerTest
+
+- (void)resetFakeReachability {
+ FakeReachabilityTest = self;
+ FakeReachability.callsMade = 0;
+ FakeReachability.createCall = -1;
+ FakeReachability.setCallbackCall = -1;
+ FakeReachability.scheduleCall = -1;
+ FakeReachability.unscheduleCall = -1;
+ FakeReachability.releaseCall = -1;
+ FakeReachability.callback = NULL;
+ FakeReachability.callbackInfo = NULL;
+}
+
+- (void)setUp {
+ [super setUp];
+
+ [self resetFakeReachability];
+ createFail_ = NO;
+ setCallbackFail_ = NO;
+ scheduleUnscheduleFail_ = NO;
+
+ checker_ = [[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:kHostname];
+ statuses_ = [[NSMutableArray alloc] init];
+}
+
+- (void *)createReachabilityWithAllocator:(CFAllocatorRef)allocator
+ withName:(const char *)hostname {
+ XCTAssertTrue(allocator == kCFAllocatorDefault, @"");
+ XCTAssertEqual(strcmp(hostname, [kHostname UTF8String]), 0, @"");
+ XCTAssertEqual(FakeReachability.callsMade, 0, @"create call must always come first.");
+ XCTAssertEqual(FakeReachability.createCall, -1, @"create call must only be called once.");
+ FakeReachability.createCall = ++FakeReachability.callsMade;
+ return createFail_ ? NULL : (void *)kFakeReachabilityObject;
+}
+
+- (BOOL)reachability:(const void *)reachability
+ setCallback:(SCNetworkReachabilityCallBack)callback
+ withContext:(SCNetworkReachabilityContext *)context {
+ XCTAssertEqual(reachability, kFakeReachabilityObject, @"got bad object");
+ XCTAssertEqual((int)context->version, 0, @"");
+ XCTAssertEqual(context->info, (__bridge void *)checker_, @"");
+ XCTAssertEqual((void *)context->retain, NULL, @"");
+ XCTAssertEqual((void *)context->release, NULL, @"");
+ XCTAssertEqual((void *)context->copyDescription, NULL, @"");
+ XCTAssertEqual(FakeReachability.setCallbackCall, -1, @"setCallback should only be called once.");
+ FakeReachability.setCallbackCall = ++FakeReachability.callsMade;
+ XCTAssertTrue(callback != NULL, @"");
+ FakeReachability.callback = callback;
+ XCTAssertTrue(context->info != NULL, @"");
+ FakeReachability.callbackInfo = context->info;
+ return setCallbackFail_ ? NO : YES;
+}
+
+- (BOOL)scheduleReachability:(const void *)reachability
+ runLoop:(CFRunLoopRef)runLoop
+ runLoopMode:(CFStringRef)runLoopMode {
+ XCTAssertEqual(reachability, kFakeReachabilityObject, @"got bad object");
+ XCTAssertEqual(runLoop, CFRunLoopGetMain(), @"bad run loop");
+ XCTAssertEqualObjects((__bridge NSString *)runLoopMode,
+ (__bridge NSString *)kCFRunLoopCommonModes, @"bad run loop mode");
+ XCTAssertEqual(FakeReachability.scheduleCall, -1,
+ @"scheduleWithRunLoop should only be called once.");
+ FakeReachability.scheduleCall = ++FakeReachability.callsMade;
+ return scheduleUnscheduleFail_ ? NO : YES;
+}
+
+- (BOOL)unscheduleReachability:(const void *)reachability
+ runLoop:(CFRunLoopRef)runLoop
+ runLoopMode:(CFStringRef)runLoopMode {
+ XCTAssertEqual(reachability, kFakeReachabilityObject, @"got bad object");
+ XCTAssertEqual(runLoop, CFRunLoopGetMain(), @"bad run loop");
+ XCTAssertEqualObjects((__bridge NSString *)runLoopMode,
+ (__bridge NSString *)kCFRunLoopCommonModes, @"bad run loop mode");
+ XCTAssertEqual(FakeReachability.unscheduleCall, -1,
+ @"unscheduleFromRunLoop should only be called once.");
+ FakeReachability.unscheduleCall = ++FakeReachability.callsMade;
+ return scheduleUnscheduleFail_ ? NO : YES;
+}
+
+- (void)releaseReachability:(const void *)reachability {
+ XCTAssertEqual(reachability, kFakeReachabilityObject, @"got bad object");
+ XCTAssertEqual(FakeReachability.releaseCall, -1, @"release should only be called once.");
+ FakeReachability.releaseCall = ++FakeReachability.callsMade;
+}
+
+- (void)reachability:(GULReachabilityChecker *)reachability
+ statusChanged:(GULReachabilityStatus)status {
+ [statuses_ addObject:[NSNumber numberWithInt:(int)status]];
+}
+
+#pragma mark - Test
+
+- (void)testApiHappyPath {
+ [checker_ setReachabilityApi:&kTestReachabilityApi];
+ XCTAssertEqual([checker_ reachabilityApi], &kTestReachabilityApi, @"");
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ XCTAssertTrue([checker_ start], @"");
+
+ XCTAssertTrue(checker_.isActive, @"");
+ XCTAssertEqual([statuses_ count], (NSUInteger)0, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ FakeReachability.callback(kFakeReachabilityObject, 0, FakeReachability.callbackInfo);
+
+ XCTAssertEqual([statuses_ count], (NSUInteger)1, @"");
+ XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:0] intValue],
+ (int)kGULReachabilityNotReachable, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityNotReachable, @"");
+
+ FakeReachability.callback(kFakeReachabilityObject, kSCNetworkReachabilityFlagsReachable,
+ FakeReachability.callbackInfo);
+
+ XCTAssertEqual([statuses_ count], (NSUInteger)2, @"");
+ XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:1] intValue], (int)kGULReachabilityViaWifi,
+ @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityViaWifi, @"");
+
+ FakeReachability.callback(
+ kFakeReachabilityObject,
+ kSCNetworkReachabilityFlagsReachable | kSCNetworkReachabilityFlagsConnectionRequired,
+ FakeReachability.callbackInfo);
+
+ XCTAssertEqual([statuses_ count], (NSUInteger)3, @"");
+ XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:2] intValue],
+ (int)kGULReachabilityNotReachable, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityNotReachable, @"");
+
+#if TARGET_OS_IOS || TARGET_OS_TV
+ FakeReachability.callback(
+ kFakeReachabilityObject,
+ kSCNetworkReachabilityFlagsReachable | kSCNetworkReachabilityFlagsIsWWAN,
+ FakeReachability.callbackInfo);
+
+ XCTAssertEqual([statuses_ count], (NSUInteger)4, @"");
+ XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:3] intValue],
+ (int)kGULReachabilityViaCellular, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityViaCellular, @"");
+
+ FakeReachability.callback(kFakeReachabilityObject, kSCNetworkReachabilityFlagsIsWWAN,
+ FakeReachability.callbackInfo);
+
+ XCTAssertEqual([statuses_ count], (NSUInteger)5, @"");
+ XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:4] intValue],
+ (int)kGULReachabilityNotReachable, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityNotReachable, @"");
+#endif
+
+ [checker_ stop];
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ XCTAssertEqual(FakeReachability.callsMade, 5, @"");
+
+ XCTAssertEqual(FakeReachability.createCall, 1, @"");
+ XCTAssertEqual(FakeReachability.setCallbackCall, 2, @"");
+ XCTAssertEqual(FakeReachability.scheduleCall, 3, @"");
+ XCTAssertEqual(FakeReachability.unscheduleCall, 4, @"");
+ XCTAssertEqual(FakeReachability.releaseCall, 5, @"");
+}
+
+- (void)testApiCreateFail {
+ [checker_ setReachabilityApi:&kTestReachabilityApi];
+ XCTAssertEqual([checker_ reachabilityApi], &kTestReachabilityApi, @"");
+
+ createFail_ = YES;
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ XCTAssertFalse([checker_ start], @"");
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ [checker_ stop];
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ XCTAssertEqual(FakeReachability.callsMade, 1, @"");
+
+ XCTAssertEqual(FakeReachability.createCall, 1, @"");
+ XCTAssertEqual(FakeReachability.setCallbackCall, -1, @"");
+ XCTAssertEqual(FakeReachability.scheduleCall, -1, @"");
+ XCTAssertEqual(FakeReachability.unscheduleCall, -1, @"");
+ XCTAssertEqual(FakeReachability.releaseCall, -1, @"");
+
+ XCTAssertEqual([statuses_ count], (NSUInteger)0, @"");
+}
+
+- (void)testApiCallbackFail {
+ [checker_ setReachabilityApi:&kTestReachabilityApi];
+ XCTAssertEqual([checker_ reachabilityApi], &kTestReachabilityApi, @"");
+
+ setCallbackFail_ = YES;
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ XCTAssertFalse([checker_ start], @"");
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ [checker_ stop];
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ XCTAssertEqual(FakeReachability.callsMade, 3, @"");
+
+ XCTAssertEqual(FakeReachability.createCall, 1, @"");
+ XCTAssertEqual(FakeReachability.setCallbackCall, 2, @"");
+ XCTAssertEqual(FakeReachability.scheduleCall, -1, @"");
+ XCTAssertEqual(FakeReachability.unscheduleCall, -1, @"");
+ XCTAssertEqual(FakeReachability.releaseCall, 3, @"");
+
+ XCTAssertEqual([statuses_ count], (NSUInteger)0, @"");
+}
+
+- (void)testApiScheduleFail {
+ [checker_ setReachabilityApi:&kTestReachabilityApi];
+ XCTAssertEqual([checker_ reachabilityApi], &kTestReachabilityApi, @"");
+
+ scheduleUnscheduleFail_ = YES;
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ XCTAssertFalse([checker_ start], @"");
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ [checker_ stop];
+
+ XCTAssertFalse(checker_.isActive, @"");
+ XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @"");
+
+ XCTAssertEqual(FakeReachability.callsMade, 4, @"");
+
+ XCTAssertEqual(FakeReachability.createCall, 1, @"");
+ XCTAssertEqual(FakeReachability.setCallbackCall, 2, @"");
+ XCTAssertEqual(FakeReachability.scheduleCall, 3, @"");
+ XCTAssertEqual(FakeReachability.unscheduleCall, -1, @"");
+ XCTAssertEqual(FakeReachability.releaseCall, 4, @"");
+
+ XCTAssertEqual([statuses_ count], (NSUInteger)0, @"");
+}
+
+- (void)testBadHost {
+ XCTAssertNil([[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:nil],
+ @"Creating a checker with nil hostname must fail.");
+ XCTAssertNil([[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:@""],
+ @"Creating a checker with empty hostname must fail.");
+}
+
+@end
diff --git a/GoogleUtilities/Example/Tests/Tests-Info.plist b/GoogleUtilities/Example/Tests/Tests-Info.plist
new file mode 100644
index 0000000..169b6f7
--- /dev/null
+++ b/GoogleUtilities/Example/Tests/Tests-Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+</dict>
+</plist>
diff --git a/GoogleUtilities/Example/iOS/AppDelegate.h b/GoogleUtilities/Example/iOS/AppDelegate.h
new file mode 100644
index 0000000..aaabd8f
--- /dev/null
+++ b/GoogleUtilities/Example/iOS/AppDelegate.h
@@ -0,0 +1,21 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+@import UIKit;
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+
+@property(strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/GoogleUtilities/Example/iOS/AppDelegate.m b/GoogleUtilities/Example/iOS/AppDelegate.m
new file mode 100644
index 0000000..395e235
--- /dev/null
+++ b/GoogleUtilities/Example/iOS/AppDelegate.m
@@ -0,0 +1,55 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "AppDelegate.h"
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Override point for customization after application launch.
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ // Sent when the application is about to move from active to inactive state. This can occur for
+ // certain types of temporary interruptions (such as an incoming phone call or SMS message) or
+ // when the user quits the application and it begins the transition to the background state. Use
+ // this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates.
+ // Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ // Use this method to release shared resources, save user data, invalidate timers, and store
+ // enough application state information to restore your application to its current state in case
+ // it is terminated later. If your application supports background execution, this method is
+ // called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ // Called as part of the transition from the background to the inactive state; here you can undo
+ // many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If
+ // the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Called when the application is about to terminate. Save data if appropriate. See also
+ // applicationDidEnterBackground:.
+}
+
+@end
diff --git a/GoogleUtilities/Example/iOS/Base.lproj/LaunchScreen.storyboard b/GoogleUtilities/Example/iOS/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..66a7681
--- /dev/null
+++ b/GoogleUtilities/Example/iOS/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="EHf-IW-A2E">
+ <objects>
+ <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+ <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="53" y="375"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/GoogleUtilities/Example/iOS/Base.lproj/Main.storyboard b/GoogleUtilities/Example/iOS/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..d164a23
--- /dev/null
+++ b/GoogleUtilities/Example/iOS/Base.lproj/Main.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="whP-gf-Uak">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="wQg-tq-qST">
+ <objects>
+ <viewController id="whP-gf-Uak" customClass="FIRViewController" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="uEw-UM-LJ8"/>
+ <viewControllerLayoutGuide type="bottom" id="Mvr-aV-6Um"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="TpU-gO-2f1">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="tc2-Qw-aMS" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="305" y="433"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/GoogleUtilities/Example/iOS/GoogleUtilities-Info.plist b/GoogleUtilities/Example/iOS/GoogleUtilities-Info.plist
new file mode 100644
index 0000000..fc26896
--- /dev/null
+++ b/GoogleUtilities/Example/iOS/GoogleUtilities-Info.plist
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>NSAppTransportSecurity</key>
+ <dict>
+ <key>NSAllowsArbitraryLoads</key>
+ <true/>
+ </dict>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/GoogleUtilities/Example/iOS/Images.xcassets/AppIcon.appiconset/Contents.json b/GoogleUtilities/Example/iOS/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d8db8d6
--- /dev/null
+++ b/GoogleUtilities/Example/iOS/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "20x20",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "20x20",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "83.5x83.5",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "size" : "1024x1024",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/iOS/ViewController.h b/GoogleUtilities/Example/iOS/ViewController.h
new file mode 100644
index 0000000..5db7982
--- /dev/null
+++ b/GoogleUtilities/Example/iOS/ViewController.h
@@ -0,0 +1,19 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+@import UIKit;
+
+@interface ViewController : UIViewController
+
+@end
diff --git a/GoogleUtilities/Example/iOS/ViewController.m b/GoogleUtilities/Example/iOS/ViewController.m
new file mode 100644
index 0000000..6d4676b
--- /dev/null
+++ b/GoogleUtilities/Example/iOS/ViewController.m
@@ -0,0 +1,33 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "ViewController.h"
+
+@interface ViewController ()
+
+@end
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/GoogleUtilities/Example/iOS/main.m b/GoogleUtilities/Example/iOS/main.m
new file mode 100644
index 0000000..266ff2d
--- /dev/null
+++ b/GoogleUtilities/Example/iOS/main.m
@@ -0,0 +1,22 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+@import UIKit;
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/GoogleUtilities/Example/macOS/AppDelegate.h b/GoogleUtilities/Example/macOS/AppDelegate.h
new file mode 100644
index 0000000..bb974b0
--- /dev/null
+++ b/GoogleUtilities/Example/macOS/AppDelegate.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+
+@end
diff --git a/GoogleUtilities/Example/macOS/AppDelegate.m b/GoogleUtilities/Example/macOS/AppDelegate.m
new file mode 100644
index 0000000..da2832c
--- /dev/null
+++ b/GoogleUtilities/Example/macOS/AppDelegate.m
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+ // Insert code here to initialize your application
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification {
+ // Insert code here to tear down your application
+}
+
+@end
diff --git a/GoogleUtilities/Example/macOS/Base.lproj/Main.storyboard b/GoogleUtilities/Example/macOS/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..1cd523a
--- /dev/null
+++ b/GoogleUtilities/Example/macOS/Base.lproj/Main.storyboard
@@ -0,0 +1,693 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11134"/>
+ </dependencies>
+ <scenes>
+ <!--Application-->
+ <scene sceneID="JPo-4y-FX3">
+ <objects>
+ <application id="hnw-xV-0zn" sceneMemberID="viewController">
+ <menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+ <items>
+ <menuItem title="Core_Example_macOS" id="1Xt-HY-uBw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Core_Example_macOS" systemMenu="apple" id="uQy-DD-JDr">
+ <items>
+ <menuItem title="About Core_Example_macOS" id="5kV-Vb-QxS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+ <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
+ <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+ <menuItem title="Services" id="NMo-om-nkz">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+ <menuItem title="Hide Core_Example_macOS" keyEquivalent="h" id="Olw-nP-bQN">
+ <connections>
+ <action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show All" id="Kd2-mp-pUS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+ <menuItem title="Quit Core_Example_macOS" keyEquivalent="q" id="4sb-4s-VLi">
+ <connections>
+ <action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="File" id="dMs-cI-mzQ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="File" id="bib-Uj-vzu">
+ <items>
+ <menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
+ <connections>
+ <action selector="newDocument:" target="Ady-hI-5gd" id="4Si-XN-c54"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
+ <connections>
+ <action selector="openDocument:" target="Ady-hI-5gd" id="bVn-NM-KNZ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Open Recent" id="tXI-mr-wws">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
+ <items>
+ <menuItem title="Clear Menu" id="vNY-rz-j42">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="Daa-9d-B3U"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
+ <menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
+ <connections>
+ <action selector="performClose:" target="Ady-hI-5gd" id="HmO-Ls-i7Q"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
+ <connections>
+ <action selector="saveDocument:" target="Ady-hI-5gd" id="teZ-XB-qJY"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
+ <connections>
+ <action selector="saveDocumentAs:" target="Ady-hI-5gd" id="mDf-zr-I0C"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Revert to Saved" keyEquivalent="r" id="KaW-ft-85H">
+ <connections>
+ <action selector="revertDocumentToSaved:" target="Ady-hI-5gd" id="iJ3-Pv-kwq"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
+ <menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
+ <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
+ <connections>
+ <action selector="runPageLayout:" target="Ady-hI-5gd" id="Din-rz-gC5"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
+ <connections>
+ <action selector="print:" target="Ady-hI-5gd" id="qaZ-4w-aoO"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Edit" id="5QF-Oa-p0T">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Edit" id="W48-6f-4Dl">
+ <items>
+ <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
+ <connections>
+ <action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
+ <connections>
+ <action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
+ <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
+ <connections>
+ <action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
+ <connections>
+ <action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
+ <connections>
+ <action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Delete" id="pa3-QI-u2k">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
+ <connections>
+ <action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
+ <menuItem title="Find" id="4EN-yA-p0u">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Find" id="1b7-l0-nxx">
+ <items>
+ <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
+ <connections>
+ <action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
+ <connections>
+ <action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
+ <connections>
+ <action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
+ <connections>
+ <action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
+ <connections>
+ <action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
+ <items>
+ <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
+ <connections>
+ <action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
+ <connections>
+ <action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
+ <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Substitutions" id="9ic-FL-obx">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
+ <items>
+ <menuItem title="Show Substitutions" id="z6F-FW-3nz">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
+ <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Quotes" id="hQb-2v-fYv">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Dashes" id="rgM-f4-ycn">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Links" id="cwL-P1-jid">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Data Detectors" id="tRr-pd-1PS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Text Replacement" id="HFQ-gK-NFA">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Transformations" id="2oI-Rn-ZJC">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
+ <items>
+ <menuItem title="Make Upper Case" id="vmV-6d-7jI">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Make Lower Case" id="d9M-CD-aMd">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Capitalize" id="UEZ-Bs-lqG">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Speech" id="xrE-MZ-jX0">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
+ <items>
+ <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Format" id="jxT-CU-nIS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Format" id="GEO-Iw-cKr">
+ <items>
+ <menuItem title="Font" id="Gi5-1S-RQB">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
+ <items>
+ <menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq"/>
+ <menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27"/>
+ <menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq"/>
+ <menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
+ <connections>
+ <action selector="underline:" target="Ady-hI-5gd" id="FYS-2b-JAY"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
+ <menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL"/>
+ <menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST"/>
+ <menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
+ <menuItem title="Kern" id="jBQ-r6-VK2">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Kern" id="tlD-Oa-oAM">
+ <items>
+ <menuItem title="Use Default" id="GUa-eO-cwY">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="useStandardKerning:" target="Ady-hI-5gd" id="6dk-9l-Ckg"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use None" id="cDB-IK-hbR">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="turnOffKerning:" target="Ady-hI-5gd" id="U8a-gz-Maa"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Tighten" id="46P-cB-AYj">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="tightenKerning:" target="Ady-hI-5gd" id="hr7-Nz-8ro"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Loosen" id="ogc-rX-tC1">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="loosenKerning:" target="Ady-hI-5gd" id="8i4-f9-FKE"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Ligatures" id="o6e-r0-MWq">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
+ <items>
+ <menuItem title="Use Default" id="agt-UL-0e3">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="useStandardLigatures:" target="Ady-hI-5gd" id="7uR-wd-Dx6"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use None" id="J7y-lM-qPV">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="turnOffLigatures:" target="Ady-hI-5gd" id="iX2-gA-Ilz"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use All" id="xQD-1f-W4t">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="useAllLigatures:" target="Ady-hI-5gd" id="KcB-kA-TuK"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Baseline" id="OaQ-X3-Vso">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Baseline" id="ijk-EB-dga">
+ <items>
+ <menuItem title="Use Default" id="3Om-Ey-2VK">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="unscript:" target="Ady-hI-5gd" id="0vZ-95-Ywn"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Superscript" id="Rqc-34-cIF">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="superscript:" target="Ady-hI-5gd" id="3qV-fo-wpU"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Subscript" id="I0S-gh-46l">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="subscript:" target="Ady-hI-5gd" id="Q6W-4W-IGz"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Raise" id="2h7-ER-AoG">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="raiseBaseline:" target="Ady-hI-5gd" id="4sk-31-7Q9"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Lower" id="1tx-W0-xDw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="lowerBaseline:" target="Ady-hI-5gd" id="OF1-bc-KW4"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
+ <menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
+ <connections>
+ <action selector="orderFrontColorPanel:" target="Ady-hI-5gd" id="mSX-Xz-DV3"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
+ <menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="copyFont:" target="Ady-hI-5gd" id="GJO-xA-L4q"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="pasteFont:" target="Ady-hI-5gd" id="JfD-CL-leO"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Text" id="Fal-I4-PZk">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Text" id="d9c-me-L2H">
+ <items>
+ <menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
+ <connections>
+ <action selector="alignLeft:" target="Ady-hI-5gd" id="zUv-R1-uAa"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
+ <connections>
+ <action selector="alignCenter:" target="Ady-hI-5gd" id="spX-mk-kcS"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Justify" id="J5U-5w-g23">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="alignJustified:" target="Ady-hI-5gd" id="ljL-7U-jND"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
+ <connections>
+ <action selector="alignRight:" target="Ady-hI-5gd" id="r48-bG-YeY"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
+ <menuItem title="Writing Direction" id="H1b-Si-o9J">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
+ <items>
+ <menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem id="YGs-j5-SAR">
+ <string key="title"> Default</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeBaseWritingDirectionNatural:" target="Ady-hI-5gd" id="qtV-5e-UBP"/>
+ </connections>
+ </menuItem>
+ <menuItem id="Lbh-J2-qVU">
+ <string key="title"> Left to Right</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeBaseWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="S0X-9S-QSf"/>
+ </connections>
+ </menuItem>
+ <menuItem id="jFq-tB-4Kx">
+ <string key="title"> Right to Left</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeBaseWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="5fk-qB-AqJ"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
+ <menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem id="Nop-cj-93Q">
+ <string key="title"> Default</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeTextWritingDirectionNatural:" target="Ady-hI-5gd" id="lPI-Se-ZHp"/>
+ </connections>
+ </menuItem>
+ <menuItem id="BgM-ve-c93">
+ <string key="title"> Left to Right</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeTextWritingDirectionLeftToRight:" target="Ady-hI-5gd" id="caW-Bv-w94"/>
+ </connections>
+ </menuItem>
+ <menuItem id="RB4-Sm-HuC">
+ <string key="title"> Right to Left</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="makeTextWritingDirectionRightToLeft:" target="Ady-hI-5gd" id="EXD-6r-ZUu"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
+ <menuItem title="Show Ruler" id="vLm-3I-IUL">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleRuler:" target="Ady-hI-5gd" id="FOx-HJ-KwY"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
+ <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+ <connections>
+ <action selector="copyRuler:" target="Ady-hI-5gd" id="71i-fW-3W2"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
+ <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+ <connections>
+ <action selector="pasteRuler:" target="Ady-hI-5gd" id="cSh-wd-qM2"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="View" id="H8h-7b-M4v">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="View" id="HyV-fh-RgO">
+ <items>
+ <menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="toggleToolbarShown:" target="Ady-hI-5gd" id="BXY-wc-z0C"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="runToolbarCustomizationPalette:" target="Ady-hI-5gd" id="pQI-g3-MTW"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="hB3-LF-h0Y"/>
+ <menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
+ <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+ <connections>
+ <action selector="toggleSourceList:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
+ <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+ <connections>
+ <action selector="toggleFullScreen:" target="Ady-hI-5gd" id="dU3-MA-1Rq"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Window" id="aUF-d1-5bR">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+ <items>
+ <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+ <connections>
+ <action selector="performMiniaturize:" target="Ady-hI-5gd" id="VwT-WD-YPe"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Zoom" id="R4o-n2-Eq4">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="performZoom:" target="Ady-hI-5gd" id="DIl-cC-cCs"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+ <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="arrangeInFront:" target="Ady-hI-5gd" id="DRN-fu-gQh"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Help" id="wpr-3q-Mcd">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
+ <items>
+ <menuItem title="Core_Example_macOS Help" keyEquivalent="?" id="FKE-Sm-Kum">
+ <connections>
+ <action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ <connections>
+ <outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
+ </connections>
+ </application>
+ <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModuleProvider=""/>
+ <customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="75" y="0.0"/>
+ </scene>
+ <!--Window Controller-->
+ <scene sceneID="R2V-B0-nI4">
+ <objects>
+ <windowController id="B8D-0N-5wS" sceneMemberID="viewController">
+ <window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+ <rect key="contentRect" x="196" y="240" width="480" height="270"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
+ </window>
+ <connections>
+ <segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
+ </connections>
+ </windowController>
+ <customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="75" y="250"/>
+ </scene>
+ <!--View Controller-->
+ <scene sceneID="hIz-AP-VOD">
+ <objects>
+ <viewController id="XfG-lQ-9wD" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
+ <view key="view" wantsLayer="YES" id="m2S-Jp-Qdl">
+ <rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </view>
+ </viewController>
+ <customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="75" y="655"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/GoogleUtilities/Example/macOS/Info.plist b/GoogleUtilities/Example/macOS/Info.plist
new file mode 100644
index 0000000..f5a2636
--- /dev/null
+++ b/GoogleUtilities/Example/macOS/Info.plist
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
+ <key>NSAppTransportSecurity</key>
+ <dict>
+ <key>NSAllowsArbitraryLoads</key>
+ <true/>
+ </dict>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2017 Google. All rights reserved.</string>
+ <key>NSMainStoryboardFile</key>
+ <string>Main</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/GoogleUtilities/Example/macOS/ViewController.h b/GoogleUtilities/Example/macOS/ViewController.h
new file mode 100644
index 0000000..18a57af
--- /dev/null
+++ b/GoogleUtilities/Example/macOS/ViewController.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@interface ViewController : NSViewController
+
+@end
diff --git a/GoogleUtilities/Example/macOS/ViewController.m b/GoogleUtilities/Example/macOS/ViewController.m
new file mode 100644
index 0000000..b94574f
--- /dev/null
+++ b/GoogleUtilities/Example/macOS/ViewController.m
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "ViewController.h"
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ // Do any additional setup after loading the view.
+}
+
+- (void)setRepresentedObject:(id)representedObject {
+ [super setRepresentedObject:representedObject];
+
+ // Update the view, if already loaded.
+}
+
+@end
diff --git a/GoogleUtilities/Example/macOS/main.m b/GoogleUtilities/Example/macOS/main.m
new file mode 100644
index 0000000..45ed70f
--- /dev/null
+++ b/GoogleUtilities/Example/macOS/main.m
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, const char* argv[]) {
+ return NSApplicationMain(argc, argv);
+}
diff --git a/GoogleUtilities/Example/tvOS/AppDelegate.h b/GoogleUtilities/Example/tvOS/AppDelegate.h
new file mode 100644
index 0000000..013891c
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/AppDelegate.h
@@ -0,0 +1,21 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <UIKit/UIKit.h>
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+
+@property(strong, nonatomic) UIWindow *window;
+
+@end
diff --git a/GoogleUtilities/Example/tvOS/AppDelegate.m b/GoogleUtilities/Example/tvOS/AppDelegate.m
new file mode 100644
index 0000000..2e4e32f
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/AppDelegate.m
@@ -0,0 +1,59 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ // Override point for customization after application launch.
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+ // Sent when the application is about to move from active to inactive state. This can occur for
+ // certain types of temporary interruptions (such as an incoming phone call or SMS message) or
+ // when the user quits the application and it begins the transition to the background state. Use
+ // this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates.
+ // Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+ // Use this method to release shared resources, save user data, invalidate timers, and store
+ // enough application state information to restore your application to its current state in case
+ // it is terminated later. If your application supports background execution, this method is
+ // called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+ // Called as part of the transition from the background to the active state; here you can undo
+ // many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If
+ // the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+ // Called when the application is about to terminate. Save data if appropriate. See also
+ // applicationDidEnterBackground:.
+}
+
+@end
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..7f06667
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
new file mode 100644
index 0000000..d29f024
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
@@ -0,0 +1,17 @@
+{
+ "layers" : [
+ {
+ "filename" : "Front.imagestacklayer"
+ },
+ {
+ "filename" : "Middle.imagestacklayer"
+ },
+ {
+ "filename" : "Back.imagestacklayer"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
new file mode 100644
index 0000000..b03ded1
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
@@ -0,0 +1,32 @@
+{
+ "assets" : [
+ {
+ "size" : "1280x768",
+ "idiom" : "tv",
+ "filename" : "App Icon - App Store.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "400x240",
+ "idiom" : "tv",
+ "filename" : "App Icon.imagestack",
+ "role" : "primary-app-icon"
+ },
+ {
+ "size" : "2320x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image Wide.imageset",
+ "role" : "top-shelf-image-wide"
+ },
+ {
+ "size" : "1920x720",
+ "idiom" : "tv",
+ "filename" : "Top Shelf Image.imageset",
+ "role" : "top-shelf-image"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
new file mode 100644
index 0000000..16a370d
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "tv",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "tv",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/GoogleUtilities/Example/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 0000000..d746a60
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "11.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "tv",
+ "extent" : "full-screen",
+ "minimum-system-version" : "9.0",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/GoogleUtilities/Example/tvOS/Info.plist b/GoogleUtilities/Example/tvOS/Info.plist
new file mode 100644
index 0000000..d76f80a
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Info.plist
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>NSAppTransportSecurity</key>
+ <dict>
+ <key>NSAllowsArbitraryLoads</key>
+ <true/>
+ </dict>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>arm64</string>
+ </array>
+ <key>UIUserInterfaceStyle</key>
+ <string>Automatic</string>
+</dict>
+</plist>
diff --git a/GoogleUtilities/Example/tvOS/Main.storyboard b/GoogleUtilities/Example/tvOS/Main.storyboard
new file mode 100644
index 0000000..72d5e22
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/Main.storyboard
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="13122.16" systemVersion="17A278a" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
+ <viewLayoutGuide key="safeArea" id="wu6-TO-1qx"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ </scene>
+ </scenes>
+</document>
diff --git a/GoogleUtilities/Example/tvOS/ViewController.h b/GoogleUtilities/Example/tvOS/ViewController.h
new file mode 100644
index 0000000..b6115b8
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/ViewController.h
@@ -0,0 +1,19 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <UIKit/UIKit.h>
+
+@interface ViewController : UIViewController
+
+@end
diff --git a/GoogleUtilities/Example/tvOS/ViewController.m b/GoogleUtilities/Example/tvOS/ViewController.m
new file mode 100644
index 0000000..6d4676b
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/ViewController.m
@@ -0,0 +1,33 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "ViewController.h"
+
+@interface ViewController ()
+
+@end
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+}
+
+- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+@end
diff --git a/GoogleUtilities/Example/tvOS/main.m b/GoogleUtilities/Example/tvOS/main.m
new file mode 100644
index 0000000..d9e6654
--- /dev/null
+++ b/GoogleUtilities/Example/tvOS/main.m
@@ -0,0 +1,22 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <UIKit/UIKit.h>
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}