Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
868ce2083b | ||
|
|
e9b6fa3f59 | ||
|
|
a39a5683e0 | ||
|
|
aa0cee4fac | ||
|
|
d1f75a811b |
24
.gitignore
vendored
24
.gitignore
vendored
@ -1,11 +1,6 @@
|
||||
## Mac OS X
|
||||
# Xcode
|
||||
.DS_Store
|
||||
|
||||
## Xcode - Build
|
||||
build/
|
||||
DerivedData
|
||||
|
||||
## Xcode - Settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
@ -14,17 +9,10 @@ DerivedData
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
*.xcworkspace
|
||||
!default.xcworkspace
|
||||
xcuserdata
|
||||
|
||||
## Xcode - Other
|
||||
*.xccheckout
|
||||
profile
|
||||
*.moved-aside
|
||||
*.xcuserstate
|
||||
|
||||
# Carthage
|
||||
Carthage/Checkouts
|
||||
Carthage/Build
|
||||
|
||||
## Custom
|
||||
LumberjackUser.h
|
||||
LumberjackUser.temp.h
|
||||
DerivedData
|
||||
.idea/
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
language: objective-c
|
||||
osx_image: xcode9.2
|
||||
|
||||
before_script:
|
||||
- export LANG=en_US.UTF-8
|
||||
script:
|
||||
- cd ./Testing/Xcode-desktop
|
||||
- xcodebuild -workspace YapDatabaseTesting.xcworkspace -scheme YapDatabaseTesting clean
|
||||
- xcodebuild -workspace YapDatabaseTesting.xcworkspace -scheme YapDatabaseTesting -configuration release test
|
||||
@ -1 +0,0 @@
|
||||
github "CocoaLumberjack/CocoaLumberjack" "3.3.0"
|
||||
@ -1,523 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
51EE553F951BF40945032BD5 /* libPods-CloudKitTodo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ABB1832E9FCC349F48EB4C9C /* libPods-CloudKitTodo.a */; };
|
||||
DC212CE11F6D98F600C11BF0 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC212CE01F6D98F600C11BF0 /* CloudKit.framework */; };
|
||||
DC6071471A33F3FF00207DE9 /* TodoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6071461A33F3FF00207DE9 /* TodoCell.m */; };
|
||||
DC60714A1A33FC3900207DE9 /* checkmark-off.png in Resources */ = {isa = PBXBuildFile; fileRef = DC6071481A33FC3900207DE9 /* checkmark-off.png */; };
|
||||
DC60714B1A33FC3900207DE9 /* checkmark-on.png in Resources */ = {isa = PBXBuildFile; fileRef = DC6071491A33FC3900207DE9 /* checkmark-on.png */; };
|
||||
DC927FFD1A344BA500FEEAEA /* TodoTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = DC927FFC1A344BA500FEEAEA /* TodoTextView.m */; };
|
||||
DCDE77211A3023E9001D8FCE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDE77201A3023E9001D8FCE /* main.m */; };
|
||||
DCDE77241A3023E9001D8FCE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDE77231A3023E9001D8FCE /* AppDelegate.m */; };
|
||||
DCDE772A1A3023E9001D8FCE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DCDE77281A3023E9001D8FCE /* Main.storyboard */; };
|
||||
DCDE772C1A3023E9001D8FCE /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DCDE772B1A3023E9001D8FCE /* Images.xcassets */; };
|
||||
DCDE772F1A3023E9001D8FCE /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = DCDE772D1A3023E9001D8FCE /* LaunchScreen.xib */; };
|
||||
DCDE77701A302A44001D8FCE /* CloudKitManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDE776D1A302A44001D8FCE /* CloudKitManager.m */; };
|
||||
DCDE77711A302A44001D8FCE /* DatabaseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDE776F1A302A44001D8FCE /* DatabaseManager.m */; };
|
||||
DCDE77761A302A66001D8FCE /* EditViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDE77731A302A66001D8FCE /* EditViewController.m */; };
|
||||
DCDE77771A302A66001D8FCE /* RootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDE77751A302A66001D8FCE /* RootViewController.m */; };
|
||||
DCDE78801A302B29001D8FCE /* MyDatabaseObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDE787D1A302B29001D8FCE /* MyDatabaseObject.m */; };
|
||||
DCDE78811A302B29001D8FCE /* MyTodo.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDE787F1A302B29001D8FCE /* MyTodo.m */; };
|
||||
DCDE78831A302B46001D8FCE /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DCDE78821A302B46001D8FCE /* libsqlite3.dylib */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
703B5BE0F88E9B9E927805E5 /* Pods-CloudKitTodo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CloudKitTodo.release.xcconfig"; path = "Pods/Target Support Files/Pods-CloudKitTodo/Pods-CloudKitTodo.release.xcconfig"; sourceTree = "<group>"; };
|
||||
ABB1832E9FCC349F48EB4C9C /* libPods-CloudKitTodo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CloudKitTodo.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DC212CE01F6D98F600C11BF0 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
|
||||
DC6071451A33F3FF00207DE9 /* TodoCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TodoCell.h; sourceTree = "<group>"; };
|
||||
DC6071461A33F3FF00207DE9 /* TodoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TodoCell.m; sourceTree = "<group>"; };
|
||||
DC6071481A33FC3900207DE9 /* checkmark-off.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "checkmark-off.png"; sourceTree = "<group>"; };
|
||||
DC6071491A33FC3900207DE9 /* checkmark-on.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "checkmark-on.png"; sourceTree = "<group>"; };
|
||||
DC927FFB1A344BA500FEEAEA /* TodoTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TodoTextView.h; sourceTree = "<group>"; };
|
||||
DC927FFC1A344BA500FEEAEA /* TodoTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TodoTextView.m; sourceTree = "<group>"; };
|
||||
DCDE771B1A3023E9001D8FCE /* CloudKitTodo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CloudKitTodo.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DCDE771F1A3023E9001D8FCE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DCDE77201A3023E9001D8FCE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
DCDE77221A3023E9001D8FCE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
DCDE77231A3023E9001D8FCE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
DCDE77291A3023E9001D8FCE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
DCDE772B1A3023E9001D8FCE /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
DCDE772E1A3023E9001D8FCE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
DCDE77681A302873001D8FCE /* CloudKitTodo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = CloudKitTodo.entitlements; sourceTree = "<group>"; };
|
||||
DCDE776C1A302A44001D8FCE /* CloudKitManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CloudKitManager.h; sourceTree = "<group>"; };
|
||||
DCDE776D1A302A44001D8FCE /* CloudKitManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CloudKitManager.m; sourceTree = "<group>"; };
|
||||
DCDE776E1A302A44001D8FCE /* DatabaseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DatabaseManager.h; sourceTree = "<group>"; };
|
||||
DCDE776F1A302A44001D8FCE /* DatabaseManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DatabaseManager.m; sourceTree = "<group>"; };
|
||||
DCDE77721A302A66001D8FCE /* EditViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditViewController.h; sourceTree = "<group>"; };
|
||||
DCDE77731A302A66001D8FCE /* EditViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EditViewController.m; sourceTree = "<group>"; };
|
||||
DCDE77741A302A66001D8FCE /* RootViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RootViewController.h; sourceTree = "<group>"; };
|
||||
DCDE77751A302A66001D8FCE /* RootViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RootViewController.m; sourceTree = "<group>"; };
|
||||
DCDE787C1A302B29001D8FCE /* MyDatabaseObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyDatabaseObject.h; sourceTree = "<group>"; };
|
||||
DCDE787D1A302B29001D8FCE /* MyDatabaseObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MyDatabaseObject.m; sourceTree = "<group>"; };
|
||||
DCDE787E1A302B29001D8FCE /* MyTodo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyTodo.h; sourceTree = "<group>"; };
|
||||
DCDE787F1A302B29001D8FCE /* MyTodo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MyTodo.m; sourceTree = "<group>"; };
|
||||
DCDE78821A302B46001D8FCE /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
|
||||
F32EDD3AE8334674ADF30C82 /* Pods-CloudKitTodo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CloudKitTodo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CloudKitTodo/Pods-CloudKitTodo.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
DCDE77181A3023E9001D8FCE /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DC212CE11F6D98F600C11BF0 /* CloudKit.framework in Frameworks */,
|
||||
DCDE78831A302B46001D8FCE /* libsqlite3.dylib in Frameworks */,
|
||||
51EE553F951BF40945032BD5 /* libPods-CloudKitTodo.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
2BCD426F1D5F7D0B429BB734 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F32EDD3AE8334674ADF30C82 /* Pods-CloudKitTodo.debug.xcconfig */,
|
||||
703B5BE0F88E9B9E927805E5 /* Pods-CloudKitTodo.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCDE77121A3023E9001D8FCE = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DCDE771D1A3023E9001D8FCE /* CloudKitTodo */,
|
||||
DCDE776B1A3028EA001D8FCE /* Frameworks */,
|
||||
DCDE771C1A3023E9001D8FCE /* Products */,
|
||||
2BCD426F1D5F7D0B429BB734 /* Pods */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCDE771C1A3023E9001D8FCE /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DCDE771B1A3023E9001D8FCE /* CloudKitTodo.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCDE771D1A3023E9001D8FCE /* CloudKitTodo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DCDE77221A3023E9001D8FCE /* AppDelegate.h */,
|
||||
DCDE77231A3023E9001D8FCE /* AppDelegate.m */,
|
||||
DCDE78781A302AD6001D8FCE /* Managers */,
|
||||
DCDE787B1A302B1B001D8FCE /* Model */,
|
||||
DCDE787A1A302AFB001D8FCE /* UI */,
|
||||
DCDE771E1A3023E9001D8FCE /* Supporting Files */,
|
||||
);
|
||||
path = CloudKitTodo;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCDE771E1A3023E9001D8FCE /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DCDE77681A302873001D8FCE /* CloudKitTodo.entitlements */,
|
||||
DCDE771F1A3023E9001D8FCE /* Info.plist */,
|
||||
DCDE77201A3023E9001D8FCE /* main.m */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCDE776B1A3028EA001D8FCE /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DC212CE01F6D98F600C11BF0 /* CloudKit.framework */,
|
||||
DCDE78821A302B46001D8FCE /* libsqlite3.dylib */,
|
||||
ABB1832E9FCC349F48EB4C9C /* libPods-CloudKitTodo.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCDE78781A302AD6001D8FCE /* Managers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DCDE776E1A302A44001D8FCE /* DatabaseManager.h */,
|
||||
DCDE776F1A302A44001D8FCE /* DatabaseManager.m */,
|
||||
DCDE776C1A302A44001D8FCE /* CloudKitManager.h */,
|
||||
DCDE776D1A302A44001D8FCE /* CloudKitManager.m */,
|
||||
);
|
||||
name = Managers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCDE787A1A302AFB001D8FCE /* UI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DCDE77741A302A66001D8FCE /* RootViewController.h */,
|
||||
DCDE77751A302A66001D8FCE /* RootViewController.m */,
|
||||
DCDE77721A302A66001D8FCE /* EditViewController.h */,
|
||||
DCDE77731A302A66001D8FCE /* EditViewController.m */,
|
||||
DC6071451A33F3FF00207DE9 /* TodoCell.h */,
|
||||
DC6071461A33F3FF00207DE9 /* TodoCell.m */,
|
||||
DC927FFB1A344BA500FEEAEA /* TodoTextView.h */,
|
||||
DC927FFC1A344BA500FEEAEA /* TodoTextView.m */,
|
||||
DCDE77281A3023E9001D8FCE /* Main.storyboard */,
|
||||
DCDE772B1A3023E9001D8FCE /* Images.xcassets */,
|
||||
DC6071481A33FC3900207DE9 /* checkmark-off.png */,
|
||||
DC6071491A33FC3900207DE9 /* checkmark-on.png */,
|
||||
DCDE772D1A3023E9001D8FCE /* LaunchScreen.xib */,
|
||||
);
|
||||
name = UI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCDE787B1A302B1B001D8FCE /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DCDE787C1A302B29001D8FCE /* MyDatabaseObject.h */,
|
||||
DCDE787D1A302B29001D8FCE /* MyDatabaseObject.m */,
|
||||
DCDE787E1A302B29001D8FCE /* MyTodo.h */,
|
||||
DCDE787F1A302B29001D8FCE /* MyTodo.m */,
|
||||
);
|
||||
name = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
DCDE771A1A3023E9001D8FCE /* CloudKitTodo */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DCDE773E1A3023E9001D8FCE /* Build configuration list for PBXNativeTarget "CloudKitTodo" */;
|
||||
buildPhases = (
|
||||
4D70F164321A80912E0E0351 /* [CP] Check Pods Manifest.lock */,
|
||||
DCDE77171A3023E9001D8FCE /* Sources */,
|
||||
DCDE77181A3023E9001D8FCE /* Frameworks */,
|
||||
DCDE77191A3023E9001D8FCE /* Resources */,
|
||||
A7E36A79EF95353C65F5D2B6 /* [CP] Embed Pods Frameworks */,
|
||||
ED83D0453CE4BE42D9A22D78 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = CloudKitTodo;
|
||||
productName = CloudKitTodo;
|
||||
productReference = DCDE771B1A3023E9001D8FCE /* CloudKitTodo.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
DCDE77131A3023E9001D8FCE /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0900;
|
||||
ORGANIZATIONNAME = "Deusty LLC";
|
||||
TargetAttributes = {
|
||||
DCDE771A1A3023E9001D8FCE = {
|
||||
CreatedOnToolsVersion = 6.1;
|
||||
DevelopmentTeam = VT5GYGYX83;
|
||||
SystemCapabilities = {
|
||||
com.apple.BackgroundModes = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.Push = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.iCloud = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = DCDE77161A3023E9001D8FCE /* Build configuration list for PBXProject "CloudKitTodo" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = DCDE77121A3023E9001D8FCE;
|
||||
productRefGroup = DCDE771C1A3023E9001D8FCE /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
DCDE771A1A3023E9001D8FCE /* CloudKitTodo */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
DCDE77191A3023E9001D8FCE /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DCDE772A1A3023E9001D8FCE /* Main.storyboard in Resources */,
|
||||
DC60714A1A33FC3900207DE9 /* checkmark-off.png in Resources */,
|
||||
DCDE772F1A3023E9001D8FCE /* LaunchScreen.xib in Resources */,
|
||||
DCDE772C1A3023E9001D8FCE /* Images.xcassets in Resources */,
|
||||
DC60714B1A33FC3900207DE9 /* checkmark-on.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
4D70F164321A80912E0E0351 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-CloudKitTodo-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
A7E36A79EF95353C65F5D2B6 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CloudKitTodo/Pods-CloudKitTodo-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
ED83D0453CE4BE42D9A22D78 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CloudKitTodo/Pods-CloudKitTodo-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
DCDE77171A3023E9001D8FCE /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DCDE78801A302B29001D8FCE /* MyDatabaseObject.m in Sources */,
|
||||
DCDE77771A302A66001D8FCE /* RootViewController.m in Sources */,
|
||||
DCDE77701A302A44001D8FCE /* CloudKitManager.m in Sources */,
|
||||
DCDE77761A302A66001D8FCE /* EditViewController.m in Sources */,
|
||||
DC927FFD1A344BA500FEEAEA /* TodoTextView.m in Sources */,
|
||||
DCDE77241A3023E9001D8FCE /* AppDelegate.m in Sources */,
|
||||
DCDE77211A3023E9001D8FCE /* main.m in Sources */,
|
||||
DCDE78811A302B29001D8FCE /* MyTodo.m in Sources */,
|
||||
DC6071471A33F3FF00207DE9 /* TodoCell.m in Sources */,
|
||||
DCDE77711A302A44001D8FCE /* DatabaseManager.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
DCDE77281A3023E9001D8FCE /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
DCDE77291A3023E9001D8FCE /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DCDE772D1A3023E9001D8FCE /* LaunchScreen.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
DCDE772E1A3023E9001D8FCE /* Base */,
|
||||
);
|
||||
name = LaunchScreen.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
DCDE773C1A3023E9001D8FCE /* 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_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_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 = 10.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DCDE773D1A3023E9001D8FCE /* 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_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_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 = 10.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
DCDE773F1A3023E9001D8FCE /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = F32EDD3AE8334674ADF30C82 /* Pods-CloudKitTodo.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = CloudKitTodo/CloudKitTodo.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"${PODS_ROOT}/Headers/Public\"",
|
||||
"\"${PODS_ROOT}/Headers/Public/CocoaLumberjack\"",
|
||||
"\"${PODS_ROOT}/Headers/Public/Reachability\"",
|
||||
"\"${PODS_ROOT}/Headers/Public/YapDatabase\"",
|
||||
"\"${PODS_ROOT}/Headers/Private/YapDatabase\"",
|
||||
);
|
||||
INFOPLIST_FILE = CloudKitTodo/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.4th-a.CloudKitTodo";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
DCDE77401A3023E9001D8FCE /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 703B5BE0F88E9B9E927805E5 /* Pods-CloudKitTodo.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = CloudKitTodo/CloudKitTodo.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"${PODS_ROOT}/Headers/Public\"",
|
||||
"\"${PODS_ROOT}/Headers/Public/CocoaLumberjack\"",
|
||||
"\"${PODS_ROOT}/Headers/Public/Reachability\"",
|
||||
"\"${PODS_ROOT}/Headers/Public/YapDatabase\"",
|
||||
"\"${PODS_ROOT}/Headers/Private/YapDatabase\"",
|
||||
);
|
||||
INFOPLIST_FILE = CloudKitTodo/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.4th-a.CloudKitTodo";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
DCDE77161A3023E9001D8FCE /* Build configuration list for PBXProject "CloudKitTodo" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DCDE773C1A3023E9001D8FCE /* Debug */,
|
||||
DCDE773D1A3023E9001D8FCE /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
DCDE773E1A3023E9001D8FCE /* Build configuration list for PBXNativeTarget "CloudKitTodo" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
DCDE773F1A3023E9001D8FCE /* Debug */,
|
||||
DCDE77401A3023E9001D8FCE /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = DCDE77131A3023E9001D8FCE /* Project object */;
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:CloudKitTodo.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:CloudKitTodo.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@ -1,17 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Reachability/Reachability.h>
|
||||
|
||||
#import <YapDatabase/YapDatabase.h>
|
||||
#import <YapDatabase/YapDatabaseCloudKit.h>
|
||||
|
||||
@class AppDelegate;
|
||||
extern AppDelegate *MyAppDelegate;
|
||||
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIWindow *window;
|
||||
|
||||
@property (nonatomic, strong, readonly) Reachability *reachability;
|
||||
|
||||
@end
|
||||
@ -1,165 +0,0 @@
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import "CloudKitManager.h"
|
||||
#import "DatabaseManager.h"
|
||||
#import "MyTodo.h"
|
||||
#import "YapDatabaseLogging.h"
|
||||
|
||||
#import <CocoaLumberjack/CocoaLumberjack.h>
|
||||
#import <CocoaLumberjack/DDTTYLogger.h>
|
||||
#import <CloudKit/CloudKit.h>
|
||||
#import <Reachability/Reachability.h>
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
#if DEBUG
|
||||
static const NSUInteger ddLogLevel = DDLogLevelAll;
|
||||
#else
|
||||
static const NSUInteger ddLogLevel = DDLogLevelAll;
|
||||
#endif
|
||||
|
||||
AppDelegate *MyAppDelegate;
|
||||
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
@synthesize reachability = reachability;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
// Store global reference
|
||||
MyAppDelegate = self;
|
||||
|
||||
// Configure logging
|
||||
[DDLog addLogger:[DDTTYLogger sharedInstance]];
|
||||
|
||||
[[DDTTYLogger sharedInstance] setColorsEnabled:YES];
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
UIColor *redColor = [UIColor redColor];
|
||||
UIColor *orangeColor = [UIColor orangeColor];
|
||||
UIColor *grayColor = [UIColor grayColor];
|
||||
#else
|
||||
NSColor *redColor = [NSColor redColor];
|
||||
NSColor *orangeColor = [NSColor orangeColor];
|
||||
NSColor *grayColor = [NSColor grayColor];
|
||||
#endif
|
||||
|
||||
[[DDTTYLogger sharedInstance] setForegroundColor:redColor
|
||||
backgroundColor:nil
|
||||
forFlag:YDB_LOG_FLAG_ERROR // errors
|
||||
context:YDBLogContext]; // from YapDatabase
|
||||
|
||||
[[DDTTYLogger sharedInstance] setForegroundColor:orangeColor
|
||||
backgroundColor:nil
|
||||
forFlag:YDB_LOG_FLAG_WARN // warnings
|
||||
context:YDBLogContext]; // from YapDatabase
|
||||
|
||||
[[DDTTYLogger sharedInstance] setForegroundColor:grayColor
|
||||
backgroundColor:nil
|
||||
forFlag:YDB_LOG_FLAG_TRACE // trace (method invocations)
|
||||
context:YDBLogContext]; // from YapDatabase
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
DDLogVerbose(@"application:didFinishLaunchingWithOptions: %@", launchOptions);
|
||||
|
||||
// Start database & cloudKit (in that order)
|
||||
|
||||
[DatabaseManager initialize];
|
||||
[CloudKitManager initialize];
|
||||
|
||||
// Register for push notifications
|
||||
|
||||
UNAuthorizationOptions options = UNAuthorizationOptionBadge;
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter]
|
||||
requestAuthorizationWithOptions:options
|
||||
completionHandler:^(BOOL granted, NSError *_Nullable error)
|
||||
{
|
||||
if (granted)
|
||||
DDLogVerbose(@"UNAuthorizationOptionBadge: granted");
|
||||
else
|
||||
DDLogWarn(@"UNAuthorizationOptionBadge: NOT granted !");
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[application registerForRemoteNotifications];
|
||||
});
|
||||
}];
|
||||
|
||||
// Start reachability
|
||||
|
||||
reachability = [Reachability reachabilityForInternetConnection];
|
||||
[reachability startNotifier];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application
|
||||
{
|
||||
DDLogVerbose(@"applicationWillEnterForeground:");
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application
|
||||
{
|
||||
DDLogVerbose(@"applicationDidBecomeActive:");
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application
|
||||
{
|
||||
DDLogVerbose(@"applicationWillResignActive:");
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application
|
||||
{
|
||||
DDLogVerbose(@"applicationDidEnterBackground:");
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application
|
||||
{
|
||||
DDLogVerbose(@"applicationWillTerminate:");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Push (iOS 8)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
|
||||
{
|
||||
DDLogVerbose(@"Registered for Push notifications with token: %@", deviceToken);
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
|
||||
{
|
||||
DDLogVerbose(@"Push subscription failed: %@", error);
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application
|
||||
didReceiveRemoteNotification:(NSDictionary *)userInfo
|
||||
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
|
||||
{
|
||||
DDLogVerbose(@"Push received: %@", userInfo);
|
||||
|
||||
__block UIBackgroundFetchResult combinedFetchResult = UIBackgroundFetchResultNoData;
|
||||
|
||||
[[CloudKitManager sharedInstance] fetchRecordChangesWithCompletionHandler:
|
||||
^(UIBackgroundFetchResult fetchResult, BOOL moreComing)
|
||||
{
|
||||
if (fetchResult == UIBackgroundFetchResultNewData) {
|
||||
combinedFetchResult = UIBackgroundFetchResultNewData;
|
||||
}
|
||||
else if (fetchResult == UIBackgroundFetchResultFailed && combinedFetchResult == UIBackgroundFetchResultNoData) {
|
||||
combinedFetchResult = UIBackgroundFetchResultFailed;
|
||||
}
|
||||
|
||||
if (!moreComing) {
|
||||
completionHandler(combinedFetchResult);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6751" systemVersion="14D87h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6736"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2014 Deusty LLC. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CloudKitTodo" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
@ -1,373 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9060" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="AeC-oa-Ew0">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9051"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="ZJV-Az-MTO">
|
||||
<objects>
|
||||
<navigationController id="AeC-oa-Ew0" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="Y4G-bB-bZZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="ZMP-7o-CDj" kind="relationship" relationship="rootViewController" id="dk8-fM-IxH"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="3d1-7W-dOB" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-68" y="377"/>
|
||||
</scene>
|
||||
<!--CloudKitTodo-->
|
||||
<scene sceneID="YUY-zP-mvy">
|
||||
<objects>
|
||||
<viewController id="ZMP-7o-CDj" customClass="RootViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="h36-Bm-gfx"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="2sE-br-UK9"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="phY-xl-hFk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="Yft-Qg-2YH">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<animations/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Todo" id="DEc-iV-D5X" customClass="TodoCell">
|
||||
<rect key="frame" x="0.0" y="86" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="DEc-iV-D5X" id="S4Y-Fw-wMI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GdB-2i-Ayi">
|
||||
<rect key="frame" x="8" y="7" width="28" height="28"/>
|
||||
<animations/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="28" id="Fgt-hn-iBH"/>
|
||||
<constraint firstAttribute="width" constant="28" id="gY6-Ti-iMM"/>
|
||||
</constraints>
|
||||
<state key="normal" image="checkmark-on.png">
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="didTapImageView:" destination="DEc-iV-D5X" eventType="touchUpInside" id="Wp6-kb-ZnM"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="todo.title" lineBreakMode="wordWrap" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p45-y8-7NQ">
|
||||
<rect key="frame" x="44" y="11" width="548" height="21"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<constraints>
|
||||
<constraint firstItem="p45-y8-7NQ" firstAttribute="top" secondItem="S4Y-Fw-wMI" secondAttribute="topMargin" constant="3" id="BCf-jV-ho9"/>
|
||||
<constraint firstItem="p45-y8-7NQ" firstAttribute="leading" secondItem="GdB-2i-Ayi" secondAttribute="trailing" constant="8" id="I8v-Lt-4JL"/>
|
||||
<constraint firstItem="p45-y8-7NQ" firstAttribute="trailing" secondItem="S4Y-Fw-wMI" secondAttribute="trailingMargin" id="Uac-UT-IYW"/>
|
||||
<constraint firstAttribute="centerY" secondItem="GdB-2i-Ayi" secondAttribute="centerY" id="YrZ-F2-Knd"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="p45-y8-7NQ" secondAttribute="bottom" constant="3" id="iCR-pW-7ZL"/>
|
||||
<constraint firstItem="GdB-2i-Ayi" firstAttribute="leading" secondItem="S4Y-Fw-wMI" secondAttribute="leadingMargin" id="ktZ-QC-b1Z"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<animations/>
|
||||
<connections>
|
||||
<outlet property="checkmarkButton" destination="GdB-2i-Ayi" id="RJI-fG-Pc1"/>
|
||||
<outlet property="titleLabel" destination="p45-y8-7NQ" id="jDr-KC-Tqy"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="ZMP-7o-CDj" id="Lii-lq-iyd"/>
|
||||
<outlet property="delegate" destination="ZMP-7o-CDj" id="tuy-Kr-OMv"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<view alpha="0.84999999999999987" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TqX-YI-hcf">
|
||||
<rect key="frame" x="0.0" y="505" width="600" height="95"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" misplaced="YES" text="YapDatabaseCloudKit" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="muy-ee-zR0">
|
||||
<rect key="frame" x="74" y="8" width="457" height="17"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KjR-xN-kmG">
|
||||
<rect key="frame" x="539" y="4" width="53" height="29"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<state key="normal" title="Resume">
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="resumeButtonTapped:" destination="ZMP-7o-CDj" eventType="touchUpInside" id="5Ah-rs-sXb"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="TNj-1O-PFd">
|
||||
<rect key="frame" x="8" y="4" width="58" height="29"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<state key="normal" title="Suspend">
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="suspendButtonTapped:" destination="ZMP-7o-CDj" eventType="touchUpInside" id="agt-dv-yPe"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Status: Unknown (suspendCount = X)" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="14" translatesAutoresizingMaskIntoConstraints="NO" id="OaA-1V-vVR" userLabel="TopStatusLabel">
|
||||
<rect key="frame" x="8" y="39" width="584" height="20"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Queue: InFlight(X), Queued(Y)" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="14" translatesAutoresizingMaskIntoConstraints="NO" id="5fa-al-XJj" userLabel="BottomStatusLabel">
|
||||
<rect key="frame" x="8" y="67" width="584" height="20"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="muy-ee-zR0" firstAttribute="top" secondItem="TqX-YI-hcf" secondAttribute="top" constant="8" id="4BI-IS-9nP"/>
|
||||
<constraint firstItem="KjR-xN-kmG" firstAttribute="top" secondItem="TqX-YI-hcf" secondAttribute="top" constant="4" id="4sW-fT-l7G"/>
|
||||
<constraint firstItem="muy-ee-zR0" firstAttribute="leading" secondItem="TNj-1O-PFd" secondAttribute="trailing" constant="8" id="5aj-cJ-XsL"/>
|
||||
<constraint firstItem="5fa-al-XJj" firstAttribute="leading" secondItem="TqX-YI-hcf" secondAttribute="leading" constant="8" id="65z-hg-ftU"/>
|
||||
<constraint firstItem="OaA-1V-vVR" firstAttribute="leading" secondItem="TqX-YI-hcf" secondAttribute="leading" constant="8" id="8dy-8l-gcv"/>
|
||||
<constraint firstAttribute="trailing" secondItem="5fa-al-XJj" secondAttribute="trailing" constant="8" id="AUe-6l-Vjc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="KjR-xN-kmG" secondAttribute="trailing" constant="8" id="BBo-LR-THi"/>
|
||||
<constraint firstItem="TNj-1O-PFd" firstAttribute="top" secondItem="TqX-YI-hcf" secondAttribute="top" constant="4" id="Cle-Jt-lbO"/>
|
||||
<constraint firstAttribute="bottom" secondItem="5fa-al-XJj" secondAttribute="bottom" constant="8" id="Gc8-aZ-ijr"/>
|
||||
<constraint firstItem="5fa-al-XJj" firstAttribute="top" secondItem="OaA-1V-vVR" secondAttribute="bottom" constant="8" id="Jrt-C4-i3n"/>
|
||||
<constraint firstAttribute="height" constant="95" id="QQb-T0-Jzw"/>
|
||||
<constraint firstItem="KjR-xN-kmG" firstAttribute="leading" secondItem="muy-ee-zR0" secondAttribute="trailing" constant="8" id="eeR-BQ-hsV"/>
|
||||
<constraint firstItem="TNj-1O-PFd" firstAttribute="leading" secondItem="TqX-YI-hcf" secondAttribute="leading" constant="8" id="gZz-8R-fHA"/>
|
||||
<constraint firstAttribute="trailing" secondItem="OaA-1V-vVR" secondAttribute="trailing" constant="8" id="sdL-QQ-2Vf"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Yft-Qg-2YH" firstAttribute="top" secondItem="phY-xl-hFk" secondAttribute="top" id="7oZ-d9-Xaa"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Yft-Qg-2YH" secondAttribute="trailing" id="I66-q6-PrM"/>
|
||||
<constraint firstItem="Yft-Qg-2YH" firstAttribute="leading" secondItem="phY-xl-hFk" secondAttribute="leading" id="MUz-Zs-U3o"/>
|
||||
<constraint firstItem="2sE-br-UK9" firstAttribute="top" secondItem="TqX-YI-hcf" secondAttribute="bottom" id="RlJ-hC-za6"/>
|
||||
<constraint firstItem="Yft-Qg-2YH" firstAttribute="top" secondItem="h36-Bm-gfx" secondAttribute="bottom" id="SsR-ma-4a4"/>
|
||||
<constraint firstItem="2sE-br-UK9" firstAttribute="top" secondItem="Yft-Qg-2YH" secondAttribute="bottom" id="bDE-pV-Tio"/>
|
||||
<constraint firstItem="TqX-YI-hcf" firstAttribute="leading" secondItem="phY-xl-hFk" secondAttribute="leading" id="iU1-jP-3h9"/>
|
||||
<constraint firstAttribute="trailing" secondItem="TqX-YI-hcf" secondAttribute="trailing" id="vUI-gF-59l"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="SsR-ma-4a4"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="CloudKitTodo" id="DlO-tp-Pgo">
|
||||
<barButtonItem key="rightBarButtonItem" systemItem="add" id="F8l-sr-wHR">
|
||||
<connections>
|
||||
<segue destination="Djq-af-TVZ" kind="show" id="F7i-tx-Hib"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="ckBottomStatusLabel" destination="5fa-al-XJj" id="XcZ-c8-tMa"/>
|
||||
<outlet property="ckStatusView" destination="TqX-YI-hcf" id="605-us-SxD"/>
|
||||
<outlet property="ckTopStatusLabel" destination="OaA-1V-vVR" id="lik-XF-cIr"/>
|
||||
<outlet property="tableView" destination="Yft-Qg-2YH" id="7hC-jh-ZYc"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="LDj-cP-95A" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="616" y="365"/>
|
||||
</scene>
|
||||
<!--Edit View Controller-->
|
||||
<scene sceneID="Edl-TL-NCx">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="EditViewController" id="Djq-af-TVZ" customClass="EditViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="W48-CL-qfT"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="AGR-ke-4LO"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="xtZ-Fd-aXi">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="Hxe-hH-R2n">
|
||||
<rect key="frame" x="212" y="110" width="176" height="29"/>
|
||||
<animations/>
|
||||
<segments>
|
||||
<segment title="Low"/>
|
||||
<segment title="Normal"/>
|
||||
<segment title="High"/>
|
||||
</segments>
|
||||
</segmentedControl>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="uuid:" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eCf-kK-soY">
|
||||
<rect key="frame" x="16" y="170" width="65" height="18"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="created:" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mDG-e8-7zm">
|
||||
<rect key="frame" x="16" y="196" width="65" height="18"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="modified:" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DTM-os-yIa">
|
||||
<rect key="frame" x="16" y="222" width="65" height="18"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Base CKRecord:" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iif-aq-YRM">
|
||||
<rect key="frame" x="16" y="256" width="111" height="18"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" text="<uuid>" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="WMm-Hf-0CA" userLabel="uuidLabel">
|
||||
<rect key="frame" x="89" y="170" width="495" height="18"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" text="<fomatted date>" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="Bpm-ml-r5G" userLabel="creationDateLabel">
|
||||
<rect key="frame" x="89" y="196" width="495" height="18"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" text="<formatted date>" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="CcB-Qz-GVQ" userLabel="lastModifiedLabel">
|
||||
<rect key="frame" x="89" y="222" width="495" height="18"/>
|
||||
<animations/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" lineBreakMode="tailTruncation" numberOfLines="5" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="c8d-5n-gpH" userLabel="baseRecordLabel">
|
||||
<rect key="frame" x="32" y="282" width="552" height="74"/>
|
||||
<animations/>
|
||||
<string key="text"><CKRecord: 0x7afb1e10; recordType=todo, recordID=D850F8D1-334E-4F14-B64C-A8504C119BEE:(zone1:__defaultOwner__), recordChangeTag=x, values={}><CKRecord: 0x7afb1e10; recordType=todo, recordID=D850F8D1-334E-4F14-B64C-A8504C119BEE:(zone1:__defaultOwner__), recordChangeTag=x, values={}></string>
|
||||
<fontDescription key="fontDescription" name="CourierNewPSMT" family="Courier New" pointSize="13"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" placeholderIntrinsicWidth="infinite" placeholderIntrinsicHeight="30" text="todo.title" translatesAutoresizingMaskIntoConstraints="NO" id="KGF-p9-dfP" customClass="TodoTextView">
|
||||
<rect key="frame" x="20" y="72" width="560" height="30"/>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="30" id="3Yb-Jy-Wnf"/>
|
||||
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="120" id="7Xn-vR-qit"/>
|
||||
<constraint firstAttribute="height" constant="30" id="E9A-nT-yRh"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="30" id="uhA-yB-Lf8"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="3Yb-Jy-Wnf"/>
|
||||
<exclude reference="E9A-nT-yRh"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</textView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="55O-gs-uHQ">
|
||||
<rect key="frame" x="20" y="110" width="28" height="28"/>
|
||||
<animations/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="28" id="bSw-Rh-V08"/>
|
||||
<constraint firstAttribute="height" constant="28" id="j3K-qB-b8y"/>
|
||||
</constraints>
|
||||
<state key="normal" image="checkmark-on.png">
|
||||
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="checkmarkButtonTapped:" destination="Djq-af-TVZ" eventType="touchUpInside" id="wlv-GJ-ZYY"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="iif-aq-YRM" firstAttribute="leading" secondItem="xtZ-Fd-aXi" secondAttribute="leading" constant="16" id="1e8-D9-GEg"/>
|
||||
<constraint firstItem="Hxe-hH-R2n" firstAttribute="top" secondItem="KGF-p9-dfP" secondAttribute="bottom" constant="8" id="1sU-78-fYB"/>
|
||||
<constraint firstAttribute="trailing" secondItem="CcB-Qz-GVQ" secondAttribute="trailing" constant="16" id="40g-ge-xFQ"/>
|
||||
<constraint firstItem="KGF-p9-dfP" firstAttribute="leading" secondItem="xtZ-Fd-aXi" secondAttribute="leadingMargin" id="55m-5S-nCD"/>
|
||||
<constraint firstItem="eCf-kK-soY" firstAttribute="top" secondItem="Hxe-hH-R2n" secondAttribute="bottom" constant="32" id="5QK-Vj-kiR"/>
|
||||
<constraint firstItem="55O-gs-uHQ" firstAttribute="centerY" secondItem="Hxe-hH-R2n" secondAttribute="centerY" id="7dt-EN-H8C"/>
|
||||
<constraint firstItem="55O-gs-uHQ" firstAttribute="leading" secondItem="xtZ-Fd-aXi" secondAttribute="leadingMargin" id="BRC-GU-TyO"/>
|
||||
<constraint firstAttribute="trailing" secondItem="WMm-Hf-0CA" secondAttribute="trailing" constant="16" id="C0L-s1-4rm"/>
|
||||
<constraint firstItem="DTM-os-yIa" firstAttribute="baseline" secondItem="CcB-Qz-GVQ" secondAttribute="baseline" id="Ck5-V4-iyz"/>
|
||||
<constraint firstItem="CcB-Qz-GVQ" firstAttribute="leading" secondItem="DTM-os-yIa" secondAttribute="trailing" constant="8" id="EOG-dZ-S18"/>
|
||||
<constraint firstItem="eCf-kK-soY" firstAttribute="leading" secondItem="xtZ-Fd-aXi" secondAttribute="leading" constant="16" id="GGd-KP-RRx"/>
|
||||
<constraint firstItem="DTM-os-yIa" firstAttribute="leading" secondItem="xtZ-Fd-aXi" secondAttribute="leading" constant="16" id="H69-vy-afT"/>
|
||||
<constraint firstItem="eCf-kK-soY" firstAttribute="baseline" secondItem="WMm-Hf-0CA" secondAttribute="baseline" id="J2I-sB-95S"/>
|
||||
<constraint firstAttribute="centerX" secondItem="Hxe-hH-R2n" secondAttribute="centerX" id="J7X-l8-ffp"/>
|
||||
<constraint firstItem="55O-gs-uHQ" firstAttribute="top" secondItem="KGF-p9-dfP" secondAttribute="bottom" constant="7" id="NYH-PS-JOx"/>
|
||||
<constraint firstItem="c8d-5n-gpH" firstAttribute="leading" secondItem="xtZ-Fd-aXi" secondAttribute="leading" constant="32" id="Nmz-3C-kDz"/>
|
||||
<constraint firstItem="KGF-p9-dfP" firstAttribute="trailing" secondItem="xtZ-Fd-aXi" secondAttribute="trailingMargin" id="PJ6-n9-IP7"/>
|
||||
<constraint firstItem="DTM-os-yIa" firstAttribute="top" secondItem="mDG-e8-7zm" secondAttribute="bottom" constant="8" id="Qoz-zg-xAq"/>
|
||||
<constraint firstItem="mDG-e8-7zm" firstAttribute="baseline" secondItem="Bpm-ml-r5G" secondAttribute="baseline" id="T2u-mV-02h"/>
|
||||
<constraint firstAttribute="centerX" secondItem="iif-aq-YRM" secondAttribute="centerX" id="XOq-wM-TfU"/>
|
||||
<constraint firstItem="mDG-e8-7zm" firstAttribute="top" secondItem="eCf-kK-soY" secondAttribute="bottom" constant="8" id="Xet-k1-951"/>
|
||||
<constraint firstItem="c8d-5n-gpH" firstAttribute="top" secondItem="iif-aq-YRM" secondAttribute="bottom" constant="8" id="YFf-26-Pat"/>
|
||||
<constraint firstItem="WMm-Hf-0CA" firstAttribute="leading" secondItem="eCf-kK-soY" secondAttribute="trailing" constant="8" id="YnV-r4-zHo"/>
|
||||
<constraint firstItem="iif-aq-YRM" firstAttribute="top" secondItem="CcB-Qz-GVQ" secondAttribute="bottom" constant="16" id="aVw-Hg-rj2"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Bpm-ml-r5G" secondAttribute="trailing" constant="16" id="dHo-XS-spi"/>
|
||||
<constraint firstItem="iif-aq-YRM" firstAttribute="top" secondItem="CcB-Qz-GVQ" secondAttribute="bottom" constant="16" id="eQQ-8n-RTb"/>
|
||||
<constraint firstItem="Bpm-ml-r5G" firstAttribute="leading" secondItem="mDG-e8-7zm" secondAttribute="trailing" constant="8" id="q6g-hi-JU0"/>
|
||||
<constraint firstItem="KGF-p9-dfP" firstAttribute="top" secondItem="W48-CL-qfT" secondAttribute="bottom" constant="8" id="ryz-01-zR5"/>
|
||||
<constraint firstItem="mDG-e8-7zm" firstAttribute="width" secondItem="DTM-os-yIa" secondAttribute="width" id="vU0-hF-uh6"/>
|
||||
<constraint firstItem="mDG-e8-7zm" firstAttribute="leading" secondItem="xtZ-Fd-aXi" secondAttribute="leading" constant="16" id="xf0-dg-Axi"/>
|
||||
<constraint firstAttribute="trailing" secondItem="c8d-5n-gpH" secondAttribute="trailing" constant="16" id="y8s-Qh-pox"/>
|
||||
<constraint firstItem="mDG-e8-7zm" firstAttribute="width" secondItem="eCf-kK-soY" secondAttribute="width" id="yV8-24-HKj"/>
|
||||
</constraints>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="NYH-PS-JOx"/>
|
||||
<exclude reference="XOq-wM-TfU"/>
|
||||
<exclude reference="eQQ-8n-RTb"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="baseRecordLabel" destination="c8d-5n-gpH" id="US0-en-TDT"/>
|
||||
<outlet property="checkmarkButton" destination="55O-gs-uHQ" id="bSj-8e-8cW"/>
|
||||
<outlet property="creationDateLabel" destination="Bpm-ml-r5G" id="1CY-TE-C3O"/>
|
||||
<outlet property="lastModifiedLabel" destination="CcB-Qz-GVQ" id="99e-Ch-8we"/>
|
||||
<outlet property="priority" destination="Hxe-hH-R2n" id="nM5-m6-p6q"/>
|
||||
<outlet property="titleView" destination="KGF-p9-dfP" id="Xeh-fP-7tB"/>
|
||||
<outlet property="titleViewHeightConstraint" destination="E9A-nT-yRh" id="ign-dE-WgA"/>
|
||||
<outlet property="titleViewMaxHeightConstraint" destination="7Xn-vR-qit" id="gjI-EA-i3k"/>
|
||||
<outlet property="titleViewMinHeightConstraint" destination="3Yb-Jy-Wnf" id="Ok2-0O-NlP"/>
|
||||
<outlet property="uuidLabel" destination="WMm-Hf-0CA" id="wkX-1O-zgF"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Ugd-z3-iNa" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1346" y="367"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="checkmark-on.png" width="434" height="435"/>
|
||||
</resources>
|
||||
</document>
|
||||
@ -1,69 +0,0 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class CloudKitManager;
|
||||
|
||||
/**
|
||||
* You can use this as an alternative to the sharedInstance:
|
||||
* [[CloudKitManager sharedInstance] foobar] -> MyCloudKitManager.foobar
|
||||
**/
|
||||
extern CloudKitManager *MyCloudKitManager;
|
||||
|
||||
|
||||
@interface CloudKitManager : NSObject
|
||||
|
||||
/**
|
||||
* Standard singleton pattern.
|
||||
* As a shortcut, you can use the global MyCloudKitManager ivar instead.
|
||||
**/
|
||||
+ (instancetype)sharedInstance; // Or MyCloudKitManager global ivar
|
||||
|
||||
/**
|
||||
* Invoke me if you get one of the following errors via YapDatabaseCloudKitOperationErrorBlock:
|
||||
* - CKErrorNetworkUnavailable
|
||||
* - CKErrorNetworkFailure
|
||||
**/
|
||||
- (void)handleNetworkError;
|
||||
|
||||
/**
|
||||
* Invoke me if you get one of the following errors via YapDatabaseCloudKitOperationErrorBlock:
|
||||
* - CKErrorPartialFailure
|
||||
**/
|
||||
- (void)handlePartialFailure;
|
||||
|
||||
/**
|
||||
* Invoke me if you get one of the following errors via YapDatabaseCloudKitOperationErrorBlock:
|
||||
* - CKErrorNotAuthenticated
|
||||
**/
|
||||
- (void)handleNotAuthenticated;
|
||||
|
||||
/**
|
||||
* This method uses CKFetchRecordChangesOperation to fetch changes.
|
||||
* It continues fetching until it's reported that we're caught up.
|
||||
*
|
||||
* This method is invoked once automatically, when the CloudKitManager is initialized.
|
||||
* After that, one should invoke it anytime a corresponding push notification is received.
|
||||
**/
|
||||
- (void)fetchRecordChangesWithCompletionHandler:
|
||||
(void (^)(UIBackgroundFetchResult result, BOOL moreComing))completionHandler;
|
||||
|
||||
/**
|
||||
* This method forces a re-fetch & merge operation.
|
||||
* This can be handly for records that have already been fetched via CKFetchRecordChangesOperation,
|
||||
* however we somehow managed to screw up merging the information into our local object(s).
|
||||
*
|
||||
* This is usually due to bugs in the data model implementation, or perhaps your YapDatabaseCloudKitMergeBlock.
|
||||
* But bugs are a normal and expected part of development.
|
||||
*
|
||||
* For example:
|
||||
* A few new propertie were added to our local object.
|
||||
* We remembered to add these to the CKRecord(s) upon saving (so the new proerties got uploaded fine).
|
||||
* But we forgot to update init method that sets the localObject.property from the new CKRecord.propertly. Oops!
|
||||
* So now we have a few devices that have synced objects that are missing these properties.
|
||||
*
|
||||
* So rather than deleting & re-installing the app,
|
||||
* we provide this method as a way to force another fetch & merge operation.
|
||||
**/
|
||||
- (void)refetchMissedRecordIDs:(NSArray *)recordIDs withCompletionHandler:(void (^)(NSError *error))completionHandler;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,16 +0,0 @@
|
||||
<?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>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array>
|
||||
<string>iCloud.$(CFBundleIdentifier)</string>
|
||||
</array>
|
||||
<key>com.apple.developer.icloud-services</key>
|
||||
<array>
|
||||
<string>CloudKit</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,95 +0,0 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <YapDatabase/YapDatabase.h>
|
||||
#import <YapDatabase/YapDatabaseAutoView.h>
|
||||
#import <YapDatabase/YapDatabaseCloudKit.h>
|
||||
|
||||
@class DatabaseManager;
|
||||
|
||||
/**
|
||||
* The following notifications are automatically posted for the uiDatabaseConnection:
|
||||
*
|
||||
* - UIDatabaseConnectionWillUpdateNotification
|
||||
* - UIDatabaseConnectionDidUpdateNotification
|
||||
*
|
||||
* The notifications correspond with the longLivedReadTransaction of the uiDatabaseConnection.
|
||||
* The DatabaseManager class listens for YapDatabaseModifiedNotification's.
|
||||
*
|
||||
* The UIDatabaseConnectionWillUpdateNotification is posted immediately before the uiDatabaseConnection
|
||||
* is moved to the latest commit. And the UIDatabaseConnectionDidUpdateNotification is posted immediately after
|
||||
* the uiDatabaseConnection was moved to the latest commit.
|
||||
*
|
||||
* These notifications are always posted to the main thread.
|
||||
*
|
||||
* The UIDatabaseConnectionDidUpdateNotification will always contain a userInfo dictionary with:
|
||||
*
|
||||
* - kNotificationsKey
|
||||
* Contains the NSArray returned by [uiDatabaseConnection beginLongLivedReadTransaction].
|
||||
* That is, the array of commit info from each commit the connection jumped.
|
||||
* This is the information that is fed into the various YapDatabase API's to figure out what changed.
|
||||
**/
|
||||
extern NSString *const UIDatabaseConnectionWillUpdateNotification;
|
||||
extern NSString *const UIDatabaseConnectionDidUpdateNotification;
|
||||
extern NSString *const kNotificationsKey;
|
||||
|
||||
/**
|
||||
* The following constants are the database collection names.
|
||||
*
|
||||
* E.g.: [transaction objectForKey:todoId inCollection:Collection_Todos]
|
||||
**/
|
||||
extern NSString *const Collection_Todos;
|
||||
extern NSString *const Collection_CloudKit;
|
||||
|
||||
/**
|
||||
* The following constants are the database extension names.
|
||||
*
|
||||
* E.g.: [[transaction ext:Ext_View_Order] objectAtIndexPath:indexPath withMappings:mappings]
|
||||
**/
|
||||
extern NSString *const Ext_View_Order;
|
||||
extern NSString *const Ext_CloudKit;
|
||||
|
||||
/**
|
||||
* The following constants are the CloudKit zone names.
|
||||
**/
|
||||
extern NSString *const CloudKitZoneName;
|
||||
|
||||
/**
|
||||
* You can use this as an alternative to the sharedInstance:
|
||||
* [[DatabaseManager sharedInstance] uiDatabaseConnection] -> STDatabaseManager.uiDatabaseConnection
|
||||
**/
|
||||
extern DatabaseManager *MyDatabaseManager;
|
||||
|
||||
|
||||
@interface DatabaseManager : NSObject
|
||||
|
||||
/**
|
||||
* Standard singleton pattern.
|
||||
* As a shortcut, you can use the global MyDatabaseManager ivar instead.
|
||||
**/
|
||||
+ (instancetype)sharedInstance; // Or MyDatabaseManager global ivar
|
||||
|
||||
/**
|
||||
* The path of the raw database file.
|
||||
**/
|
||||
+ (NSString *)databasePath;
|
||||
|
||||
/**
|
||||
* The root database class, and extension(s)
|
||||
**/
|
||||
@property (nonatomic, strong, readonly) YapDatabase *database;
|
||||
@property (nonatomic, strong, readonly) YapDatabaseCloudKit *cloudKitExtension;
|
||||
|
||||
/**
|
||||
* The databaseConnection for the main thread.
|
||||
* Will throw an exception if:
|
||||
* - you attempt to use it on a background thread (as that would block a read on the main thread)
|
||||
* - you attempt an async transaction (as that could also block a later read on the main thread)
|
||||
**/
|
||||
@property (nonatomic, strong, readonly) YapDatabaseConnection *uiDatabaseConnection;
|
||||
|
||||
/**
|
||||
* A generic databaseConnection for other asynchronous & background stuff.
|
||||
**/
|
||||
@property (nonatomic, strong, readonly) YapDatabaseConnection *bgDatabaseConnection;
|
||||
|
||||
@end
|
||||
@ -1,468 +0,0 @@
|
||||
#import "DatabaseManager.h"
|
||||
#import "CloudKitManager.h"
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import "MyDatabaseObject.h"
|
||||
#import "MyTodo.h"
|
||||
|
||||
#import <CocoaLumberjack/CocoaLumberjack.h>
|
||||
|
||||
// Log Levels: off, error, warn, info, verbose
|
||||
// Log Flags : trace
|
||||
#if DEBUG
|
||||
static const NSUInteger ddLogLevel = DDLogLevelAll;
|
||||
#else
|
||||
static const NSUInteger ddLogLevel = DDLogLevelAll;
|
||||
#endif
|
||||
|
||||
NSString *const UIDatabaseConnectionWillUpdateNotification = @"UIDatabaseConnectionWillUpdateNotification";
|
||||
NSString *const UIDatabaseConnectionDidUpdateNotification = @"UIDatabaseConnectionDidUpdateNotification";
|
||||
NSString *const kNotificationsKey = @"notifications";
|
||||
|
||||
NSString *const Collection_Todos = @"todos";
|
||||
NSString *const Collection_CloudKit = @"cloudKit";
|
||||
|
||||
NSString *const Ext_View_Order = @"order";
|
||||
NSString *const Ext_CloudKit = @"ck";
|
||||
|
||||
NSString *const CloudKitZoneName = @"zone1";
|
||||
|
||||
DatabaseManager *MyDatabaseManager;
|
||||
|
||||
|
||||
@implementation DatabaseManager
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
MyDatabaseManager = [[DatabaseManager alloc] init];
|
||||
});
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
return MyDatabaseManager;
|
||||
}
|
||||
|
||||
+ (NSString *)databasePath
|
||||
{
|
||||
NSString *databaseName = @"MyAwesomeApp.sqlite";
|
||||
|
||||
NSURL *baseURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory
|
||||
inDomain:NSUserDomainMask
|
||||
appropriateForURL:nil
|
||||
create:YES
|
||||
error:NULL];
|
||||
|
||||
NSURL *databaseURL = [baseURL URLByAppendingPathComponent:databaseName isDirectory:NO];
|
||||
|
||||
return databaseURL.filePathURL.path;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Instance
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@synthesize database = database;
|
||||
@synthesize cloudKitExtension = cloudKitExtension;
|
||||
|
||||
@synthesize uiDatabaseConnection = uiDatabaseConnection;
|
||||
@synthesize bgDatabaseConnection = bgDatabaseConnection;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
NSAssert(MyDatabaseManager == nil, @"Must use sharedInstance singleton (global MyDatabaseManager)");
|
||||
|
||||
if ((self = [super init]))
|
||||
{
|
||||
[self setupDatabase];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Setup
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (YapDatabaseSerializer)databaseSerializer
|
||||
{
|
||||
// This is actually the default serializer.
|
||||
// We just included it here for completeness.
|
||||
|
||||
YapDatabaseSerializer serializer = ^(NSString *collection, NSString *key, id object){
|
||||
|
||||
return [NSKeyedArchiver archivedDataWithRootObject:object];
|
||||
};
|
||||
|
||||
return serializer;
|
||||
}
|
||||
|
||||
- (YapDatabaseDeserializer)databaseDeserializer
|
||||
{
|
||||
// Pretty much the default serializer,
|
||||
// but it also ensures that objects coming out of the database are immutable.
|
||||
|
||||
YapDatabaseDeserializer deserializer = ^(NSString *collection, NSString *key, NSData *data){
|
||||
|
||||
id object = [NSKeyedUnarchiver unarchiveObjectWithData:data];
|
||||
|
||||
if ([object isKindOfClass:[MyDatabaseObject class]])
|
||||
{
|
||||
[(MyDatabaseObject *)object makeImmutable];
|
||||
}
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
return deserializer;
|
||||
}
|
||||
|
||||
- (YapDatabasePreSanitizer)databasePreSanitizer
|
||||
{
|
||||
YapDatabasePreSanitizer preSanitizer = ^(NSString *collection, NSString *key, id object){
|
||||
|
||||
if ([object isKindOfClass:[MyDatabaseObject class]])
|
||||
{
|
||||
[object makeImmutable];
|
||||
}
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
return preSanitizer;
|
||||
}
|
||||
|
||||
- (YapDatabasePostSanitizer)databasePostSanitizer
|
||||
{
|
||||
YapDatabasePostSanitizer postSanitizer = ^(NSString *collection, NSString *key, id object){
|
||||
|
||||
if ([object isKindOfClass:[MyDatabaseObject class]])
|
||||
{
|
||||
[object clearChangedProperties];
|
||||
}
|
||||
};
|
||||
|
||||
return postSanitizer;
|
||||
}
|
||||
|
||||
- (void)setupDatabase
|
||||
{
|
||||
NSString *databasePath = [[self class] databasePath];
|
||||
DDLogVerbose(@"databasePath: %@", databasePath);
|
||||
|
||||
// Configure custom class mappings for NSCoding.
|
||||
// In a previous version of the app, the "MyTodo" class was named "MyTodoItem".
|
||||
// We renamed the class in a recent version.
|
||||
|
||||
[NSKeyedUnarchiver setClass:[MyTodo class] forClassName:@"MyTodoItem"];
|
||||
|
||||
// Create the database
|
||||
|
||||
database = [[YapDatabase alloc] initWithPath:databasePath
|
||||
serializer:[self databaseSerializer]
|
||||
deserializer:[self databaseDeserializer]
|
||||
preSanitizer:[self databasePreSanitizer]
|
||||
postSanitizer:[self databasePostSanitizer]
|
||||
options:nil];
|
||||
|
||||
// FOR ADVANCED USERS ONLY
|
||||
//
|
||||
// Do NOT copy this blindly into your app unless you know exactly what you're doing.
|
||||
// https://github.com/yapstudios/YapDatabase/wiki/Object-Policy
|
||||
//
|
||||
database.defaultObjectPolicy = YapDatabasePolicyShare;
|
||||
database.defaultMetadataPolicy = YapDatabasePolicyShare;
|
||||
//
|
||||
// ^^^ FOR ADVANCED USERS ONLY ^^^
|
||||
|
||||
// Setup the extensions
|
||||
|
||||
[self setupOrderViewExtension];
|
||||
[self setupCloudKitExtension];
|
||||
|
||||
// Setup database connection(s)
|
||||
|
||||
uiDatabaseConnection = [database newConnection];
|
||||
uiDatabaseConnection.objectCacheLimit = 400;
|
||||
uiDatabaseConnection.metadataCacheEnabled = NO;
|
||||
|
||||
#if YapDatabaseEnforcePermittedTransactions
|
||||
uiDatabaseConnection.permittedTransactions = YDB_SyncReadTransaction | YDB_MainThreadOnly;
|
||||
#endif
|
||||
|
||||
bgDatabaseConnection = [database newConnection];
|
||||
bgDatabaseConnection.objectCacheLimit = 400;
|
||||
bgDatabaseConnection.metadataCacheEnabled = NO;
|
||||
|
||||
// Start the longLivedReadTransaction on the UI connection.
|
||||
|
||||
[uiDatabaseConnection enableExceptionsForImplicitlyEndingLongLivedReadTransaction];
|
||||
[uiDatabaseConnection beginLongLivedReadTransaction];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(yapDatabaseModified:)
|
||||
name:YapDatabaseModifiedNotification
|
||||
object:database];
|
||||
}
|
||||
|
||||
- (void)setupOrderViewExtension
|
||||
{
|
||||
//
|
||||
// What is a YapDatabaseView ?
|
||||
//
|
||||
// https://github.com/yapstudios/YapDatabase/wiki/Views
|
||||
//
|
||||
// > If you're familiar with Core Data, it's kinda like a NSFetchedResultsController.
|
||||
// > But you should really read that wiki article, or you're likely to be a bit confused.
|
||||
//
|
||||
//
|
||||
// This view keeps a persistent "list" of MyTodo items sorted by timestamp.
|
||||
// We use it to drive the tableView.
|
||||
//
|
||||
|
||||
YapDatabaseViewGrouping *orderGrouping = [YapDatabaseViewGrouping withObjectBlock:
|
||||
^NSString *(YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object)
|
||||
{
|
||||
if ([object isKindOfClass:[MyTodo class]])
|
||||
{
|
||||
return @""; // include in view
|
||||
}
|
||||
|
||||
return nil; // exclude from view
|
||||
}];
|
||||
|
||||
YapDatabaseViewSorting *orderSorting = [YapDatabaseViewSorting withObjectBlock:
|
||||
^(YapDatabaseReadTransaction *transaction, NSString *group,
|
||||
NSString *collection1, NSString *key1, MyTodo *todo1,
|
||||
NSString *collection2, NSString *key2, MyTodo *todo2)
|
||||
{
|
||||
// We want:
|
||||
// - Most recently created Todo at index 0.
|
||||
// - Least recent created Todo at the end.
|
||||
//
|
||||
// This is descending order (opposite of "standard" in Cocoa) so we swap the normal comparison.
|
||||
|
||||
NSComparisonResult cmp = [todo1.creationDate compare:todo2.creationDate];
|
||||
|
||||
if (cmp == NSOrderedAscending) return NSOrderedDescending;
|
||||
if (cmp == NSOrderedDescending) return NSOrderedAscending;
|
||||
|
||||
return NSOrderedSame;
|
||||
}];
|
||||
|
||||
YapDatabaseAutoView *orderView =
|
||||
[[YapDatabaseAutoView alloc] initWithGrouping:orderGrouping
|
||||
sorting:orderSorting
|
||||
versionTag:@"sortedByCreationDate"];
|
||||
|
||||
[database asyncRegisterExtension:orderView withName:Ext_View_Order completionBlock:^(BOOL ready) {
|
||||
if (!ready) {
|
||||
DDLogError(@"Error registering %@ !!!", Ext_View_Order);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setupCloudKitExtension
|
||||
{
|
||||
YapDatabaseCloudKitRecordHandler *recordHandler = [YapDatabaseCloudKitRecordHandler withObjectBlock:
|
||||
^(YapDatabaseReadTransaction *transaction, CKRecord *__autoreleasing *inOutRecordPtr,
|
||||
YDBCKRecordInfo *recordInfo, NSString *collection, NSString *key, MyTodo *todo)
|
||||
{
|
||||
CKRecord *record = inOutRecordPtr ? *inOutRecordPtr : nil;
|
||||
if (record && // not a newly inserted object
|
||||
!todo.hasChangedCloudProperties && // no sync'd properties changed in the todo
|
||||
!recordInfo.keysToRestore ) // and we don't need to restore "truth" values
|
||||
{
|
||||
// Thus we don't have any changes we need to push to the cloud
|
||||
return;
|
||||
}
|
||||
|
||||
// The CKRecord will be nil when we first insert an object into the database.
|
||||
// Or if we've never included this item for syncing before.
|
||||
//
|
||||
// Otherwise we'll be handed a bare CKRecord, with only the proper CKRecordID
|
||||
// and the sync metadata set.
|
||||
|
||||
BOOL isNewRecord = NO;
|
||||
|
||||
if (record == nil)
|
||||
{
|
||||
CKRecordZoneID *zoneID =
|
||||
[[CKRecordZoneID alloc] initWithZoneName:CloudKitZoneName ownerName:CKCurrentUserDefaultName];
|
||||
|
||||
CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:todo.uuid zoneID:zoneID];
|
||||
|
||||
record = [[CKRecord alloc] initWithRecordType:@"todo" recordID:recordID];
|
||||
|
||||
*inOutRecordPtr = record;
|
||||
isNewRecord = YES;
|
||||
}
|
||||
|
||||
id <NSFastEnumeration> cloudKeys = nil;
|
||||
|
||||
if (recordInfo.keysToRestore)
|
||||
{
|
||||
// We need to restore "truth" values for YapDatabaseCloudKit.
|
||||
// This happens when the extension is restarted,
|
||||
// and it needs to restore its change-set queue (to pick up where it left off).
|
||||
|
||||
cloudKeys = recordInfo.keysToRestore;
|
||||
}
|
||||
else if (isNewRecord)
|
||||
{
|
||||
// This is a CKRecord for a newly inserted todo item.
|
||||
// So we want to get every single property,
|
||||
// including those that are read-only, and may have been set directly via the init method.
|
||||
|
||||
cloudKeys = todo.allCloudProperties;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We changed one or more properties of our Todo item.
|
||||
// So we need to copy only these changed values into the CKRecord.
|
||||
// That way YapDatabaseCloudKit can handle syncing it to the cloud.
|
||||
|
||||
cloudKeys = todo.changedCloudProperties;
|
||||
|
||||
// We can also instruct YapDatabaseCloudKit to store the originalValues for us.
|
||||
// This is optional, but comes in handy if we run into conflicts.
|
||||
recordInfo.originalValues = todo.originalCloudValues;
|
||||
}
|
||||
|
||||
for (NSString *cloudKey in cloudKeys)
|
||||
{
|
||||
id cloudValue = [todo cloudValueForCloudKey:cloudKey];
|
||||
[record setObject:cloudValue forKey:cloudKey];
|
||||
}
|
||||
}];
|
||||
|
||||
YapDatabaseCloudKitMergeBlock mergeBlock =
|
||||
^(YapDatabaseReadWriteTransaction *transaction, NSString *collection, NSString *key,
|
||||
CKRecord *remoteRecord, YDBCKMergeInfo *mergeInfo)
|
||||
{
|
||||
if ([remoteRecord.recordType isEqualToString:@"todo"])
|
||||
{
|
||||
MyTodo *todo = [transaction objectForKey:key inCollection:collection];
|
||||
todo = [todo copy]; // make mutable copy
|
||||
|
||||
// CloudKit doesn't tell us exactly what changed.
|
||||
// We're just being given the latest version of the CKRecord.
|
||||
// So it's up to us to figure out what changed.
|
||||
|
||||
NSArray *allKeys = remoteRecord.allKeys;
|
||||
NSMutableArray *remoteChangedKeys = [NSMutableArray arrayWithCapacity:allKeys.count];
|
||||
|
||||
for (NSString *key in allKeys)
|
||||
{
|
||||
id remoteValue = [remoteRecord objectForKey:key];
|
||||
id localValue = [todo cloudValueForCloudKey:key];
|
||||
|
||||
if (![remoteValue isEqual:localValue])
|
||||
{
|
||||
id originalLocalValue = [mergeInfo.originalValues objectForKey:key];
|
||||
if (![remoteValue isEqual:originalLocalValue])
|
||||
{
|
||||
[remoteChangedKeys addObject:key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableSet *localChangedKeys = [NSMutableSet setWithArray:mergeInfo.pendingLocalRecord.changedKeys];
|
||||
|
||||
for (NSString *remoteChangedKey in remoteChangedKeys)
|
||||
{
|
||||
id remoteChangedValue = [remoteRecord valueForKey:remoteChangedKey];
|
||||
|
||||
[todo setLocalValueFromCloudValue:remoteChangedValue forCloudKey:remoteChangedKey];
|
||||
[localChangedKeys removeObject:remoteChangedKey];
|
||||
}
|
||||
for (NSString *localChangedKey in localChangedKeys)
|
||||
{
|
||||
id localChangedValue = [mergeInfo.pendingLocalRecord valueForKey:localChangedKey];
|
||||
[mergeInfo.updatedPendingLocalRecord setObject:localChangedValue forKey:localChangedKey];
|
||||
}
|
||||
|
||||
[transaction setObject:todo forKey:key inCollection:collection];
|
||||
}
|
||||
};
|
||||
|
||||
YapDatabaseCloudKitOperationErrorBlock opErrorBlock =
|
||||
^(NSString *databaseIdentifier, NSError *operationError)
|
||||
{
|
||||
NSInteger ckErrorCode = operationError.code;
|
||||
|
||||
if (ckErrorCode == CKErrorNetworkUnavailable ||
|
||||
ckErrorCode == CKErrorNetworkFailure )
|
||||
{
|
||||
[MyCloudKitManager handleNetworkError];
|
||||
}
|
||||
else if (ckErrorCode == CKErrorPartialFailure)
|
||||
{
|
||||
[MyCloudKitManager handlePartialFailure];
|
||||
}
|
||||
else if (ckErrorCode == CKErrorNotAuthenticated)
|
||||
{
|
||||
[MyCloudKitManager handleNotAuthenticated];
|
||||
}
|
||||
else
|
||||
{
|
||||
// You'll want to add more error handling here.
|
||||
|
||||
DDLogError(@"Unhandled ckErrorCode: %ld", (long)ckErrorCode);
|
||||
}
|
||||
};
|
||||
|
||||
NSSet *todos = [NSSet setWithObject:Collection_Todos];
|
||||
YapWhitelistBlacklist *whitelist = [[YapWhitelistBlacklist alloc] initWithWhitelist:todos];
|
||||
|
||||
YapDatabaseCloudKitOptions *options = [[YapDatabaseCloudKitOptions alloc] init];
|
||||
options.allowedCollections = whitelist;
|
||||
|
||||
cloudKitExtension = [[YapDatabaseCloudKit alloc] initWithRecordHandler:recordHandler
|
||||
mergeBlock:mergeBlock
|
||||
operationErrorBlock:opErrorBlock
|
||||
versionTag:@"1"
|
||||
versionInfo:nil
|
||||
options:options];
|
||||
|
||||
[cloudKitExtension suspend]; // Create zone(s)
|
||||
[cloudKitExtension suspend]; // Create zone subscription(s)
|
||||
[cloudKitExtension suspend]; // Initial fetchRecordChanges operation
|
||||
|
||||
[database asyncRegisterExtension:cloudKitExtension withName:Ext_CloudKit completionBlock:^(BOOL ready) {
|
||||
if (!ready) {
|
||||
DDLogError(@"Error registering %@ !!!", Ext_CloudKit);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Notifications
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)yapDatabaseModified:(NSNotification *)ignored
|
||||
{
|
||||
// Notify observers we're about to update the database connection
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:UIDatabaseConnectionWillUpdateNotification
|
||||
object:self];
|
||||
|
||||
// Move uiDatabaseConnection to the latest commit.
|
||||
// Do so atomically, and fetch all the notifications for each commit we jump.
|
||||
|
||||
NSArray *notifications = [uiDatabaseConnection beginLongLivedReadTransaction];
|
||||
|
||||
// Notify observers that the uiDatabaseConnection was updated
|
||||
|
||||
NSDictionary *userInfo = @{
|
||||
kNotificationsKey : notifications,
|
||||
};
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:UIDatabaseConnectionDidUpdateNotification
|
||||
object:self
|
||||
userInfo:userInfo];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,24 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TodoTextView.h"
|
||||
|
||||
|
||||
@interface EditViewController : UIViewController <UITextViewDelegate>
|
||||
|
||||
@property (nonatomic, strong, readwrite) NSString *todoID;
|
||||
|
||||
@property (nonatomic, weak) IBOutlet TodoTextView *titleView;
|
||||
|
||||
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *titleViewHeightConstraint;
|
||||
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *titleViewMinHeightConstraint;
|
||||
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *titleViewMaxHeightConstraint;
|
||||
|
||||
@property (nonatomic, weak) IBOutlet UIButton *checkmarkButton;
|
||||
@property (nonatomic, weak) IBOutlet UISegmentedControl *priority;
|
||||
|
||||
@property (nonatomic, weak) IBOutlet UILabel *uuidLabel;
|
||||
@property (nonatomic, weak) IBOutlet UILabel *creationDateLabel;
|
||||
@property (nonatomic, weak) IBOutlet UILabel *lastModifiedLabel;
|
||||
|
||||
@property (nonatomic, weak) IBOutlet UILabel *baseRecordLabel;
|
||||
|
||||
@end
|
||||
@ -1,274 +0,0 @@
|
||||
#import "EditViewController.h"
|
||||
#import "DatabaseManager.h"
|
||||
#import "MyTodo.h"
|
||||
|
||||
|
||||
@implementation EditViewController
|
||||
{
|
||||
YapDatabaseConnection *databaseConnection;
|
||||
}
|
||||
|
||||
@synthesize todoID = todoID;
|
||||
|
||||
@synthesize titleView = titleView;
|
||||
|
||||
@synthesize titleViewHeightConstraint = titleViewHeightConstraint;
|
||||
@synthesize titleViewMinHeightConstraint = titleViewMinHeightConstraint;
|
||||
@synthesize titleViewMaxHeightConstraint = titleViewMaxHeightConstraint;
|
||||
|
||||
@synthesize checkmarkButton = checkmarkButton;
|
||||
@synthesize priority = priority;
|
||||
|
||||
@synthesize uuidLabel = uuidLabel;
|
||||
@synthesize creationDateLabel = creationDateLabel;
|
||||
@synthesize lastModifiedLabel = lastModifiedLabel;
|
||||
|
||||
@synthesize baseRecordLabel = baseRecordLabel;
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
titleView.textColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1.0];
|
||||
|
||||
titleView.layer.borderColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0].CGColor;
|
||||
titleView.layer.borderWidth = 1.75;
|
||||
titleView.layer.cornerRadius = 5.5;
|
||||
|
||||
titleView.delegate = self;
|
||||
|
||||
UIEdgeInsets modifiedInsets = titleView.textContainerInset;
|
||||
modifiedInsets.top -= 2;
|
||||
modifiedInsets.bottom -= 2;
|
||||
titleView.textContainerInset = modifiedInsets;
|
||||
|
||||
self.navigationItem.leftBarButtonItem =
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
|
||||
target:self
|
||||
action:@selector(cancelButtonTapped:)];
|
||||
|
||||
self.navigationItem.rightBarButtonItem =
|
||||
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
|
||||
target:self
|
||||
action:@selector(saveButtonTapped:)];
|
||||
|
||||
databaseConnection = MyDatabaseManager.uiDatabaseConnection;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(databaseConnectionDidUpdate:)
|
||||
name:UIDatabaseConnectionDidUpdateNotification
|
||||
object:nil];
|
||||
|
||||
[self updateView];
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews
|
||||
{
|
||||
[super viewDidLayoutSubviews];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self textViewDidChange:titleView];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[titleView becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)setTodoID:(NSString *)newTodoID
|
||||
{
|
||||
todoID = newTodoID;
|
||||
|
||||
if (self.isViewLoaded) {
|
||||
[self updateView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)databaseConnectionDidUpdate:(NSNotification *)notification
|
||||
{
|
||||
NSArray *notifications = [notification.userInfo objectForKey:kNotificationsKey];
|
||||
|
||||
if ([databaseConnection hasChangeForKey:todoID inCollection:Collection_Todos inNotifications:notifications])
|
||||
{
|
||||
[self updateView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateView
|
||||
{
|
||||
__block MyTodo *todo = nil;
|
||||
__block CKRecord *record = nil;
|
||||
if (todoID)
|
||||
{
|
||||
[databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
|
||||
todo = [transaction objectForKey:todoID inCollection:Collection_Todos];
|
||||
record = [[transaction ext:Ext_CloudKit] recordForKey:todoID inCollection:Collection_Todos];
|
||||
}];
|
||||
}
|
||||
|
||||
if (todo)
|
||||
{
|
||||
titleView.text = todo.title;
|
||||
|
||||
if (todo.isDone)
|
||||
{
|
||||
checkmarkButton.tag = 1;
|
||||
[checkmarkButton setImage:[UIImage imageNamed:@"checkmark-on"] forState:UIControlStateNormal];
|
||||
}
|
||||
else
|
||||
{
|
||||
checkmarkButton.tag = 0;
|
||||
[checkmarkButton setImage:[UIImage imageNamed:@"checkmark-off"] forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
if (todo.priority == TodoPriorityLow)
|
||||
priority.selectedSegmentIndex = 0;
|
||||
else if (todo.priority == TodoPriorityHigh)
|
||||
priority.selectedSegmentIndex = 2;
|
||||
else
|
||||
priority.selectedSegmentIndex = 1;
|
||||
|
||||
uuidLabel.text = todo.uuid;
|
||||
|
||||
NSDateFormatter *df = [[NSDateFormatter alloc] init];
|
||||
df.dateStyle = NSDateFormatterMediumStyle;
|
||||
df.timeStyle = NSDateFormatterMediumStyle;
|
||||
|
||||
creationDateLabel.text = [df stringFromDate:todo.creationDate];
|
||||
lastModifiedLabel.text = [df stringFromDate:todo.lastModified];
|
||||
|
||||
baseRecordLabel.text = [record description];
|
||||
}
|
||||
else
|
||||
{
|
||||
titleView.text = nil;
|
||||
|
||||
checkmarkButton.tag = 0;
|
||||
[checkmarkButton setImage:[UIImage imageNamed:@"checkmark-off"] forState:UIControlStateNormal];
|
||||
|
||||
priority.selectedSegmentIndex = 1;
|
||||
|
||||
uuidLabel.text = [[NSUUID UUID] UUIDString];
|
||||
creationDateLabel.text = @"<now>";
|
||||
lastModifiedLabel.text = @"<now>";
|
||||
|
||||
baseRecordLabel.text = @"<New CKRecord>";
|
||||
}
|
||||
|
||||
if (self.isViewLoaded) {
|
||||
[self textViewDidChange:titleView];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)checkmarkButtonTapped:(id)sender
|
||||
{
|
||||
if (checkmarkButton.tag == 1)
|
||||
{
|
||||
checkmarkButton.tag = 0;
|
||||
[checkmarkButton setImage:[UIImage imageNamed:@"checkmark-off"] forState:UIControlStateNormal];
|
||||
}
|
||||
else
|
||||
{
|
||||
checkmarkButton.tag = 1;
|
||||
[checkmarkButton setImage:[UIImage imageNamed:@"checkmark-on"] forState:UIControlStateNormal];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelButtonTapped:(id)sender
|
||||
{
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)saveButtonTapped:(id)sender
|
||||
{
|
||||
NSString *newTitle = titleView.text;
|
||||
|
||||
BOOL newIsDone = (checkmarkButton.tag == 1);
|
||||
|
||||
TodoPriority newPriority;
|
||||
if (priority.selectedSegmentIndex == 0)
|
||||
newPriority = TodoPriorityLow;
|
||||
else if (priority.selectedSegmentIndex == 2)
|
||||
newPriority = TodoPriorityHigh;
|
||||
else
|
||||
newPriority = TodoPriorityNormal;
|
||||
|
||||
[MyDatabaseManager.bgDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
|
||||
MyTodo *todo = [transaction objectForKey:todoID inCollection:Collection_Todos];
|
||||
|
||||
if (todo == nil)
|
||||
{
|
||||
todo = [[MyTodo alloc] initWithUUID:uuidLabel.text];
|
||||
|
||||
todo.title = newTitle;
|
||||
todo.isDone = newIsDone;
|
||||
todo.priority = newPriority;
|
||||
|
||||
[transaction setObject:todo forKey:todo.uuid inCollection:Collection_Todos];
|
||||
}
|
||||
else
|
||||
{
|
||||
todo = [todo copy]; // mutable copy
|
||||
|
||||
// If the user didn't effectively make any changes,
|
||||
// we're going to try to avoid updating the properties.
|
||||
// Which, in turn, avoids uploading unchanged properties.
|
||||
|
||||
if (![todo.title isEqualToString:newTitle]) {
|
||||
todo.title = newTitle;
|
||||
}
|
||||
if (todo.isDone != newIsDone) {
|
||||
todo.isDone = newIsDone;
|
||||
}
|
||||
if (todo.priority != newPriority) {
|
||||
todo.priority = newPriority;
|
||||
}
|
||||
|
||||
if (todo.hasChangedProperties)
|
||||
{
|
||||
todo.lastModified = [NSDate date];
|
||||
|
||||
[transaction setObject:todo forKey:todo.uuid inCollection:Collection_Todos];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)textViewDidChange:(UITextView *)sender
|
||||
{
|
||||
CGFloat intrinsicHeight = [titleView intrinsicContentSize].height;
|
||||
CGFloat currentHeight = titleView.frame.size.height;
|
||||
|
||||
BOOL needsUpdate = NO;
|
||||
|
||||
if (intrinsicHeight != currentHeight)
|
||||
{
|
||||
if ((intrinsicHeight > currentHeight) && (currentHeight != titleViewMaxHeightConstraint.constant))
|
||||
{
|
||||
needsUpdate = YES;
|
||||
}
|
||||
if ((intrinsicHeight < currentHeight) && (currentHeight != titleViewMinHeightConstraint.constant))
|
||||
{
|
||||
needsUpdate = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsUpdate)
|
||||
{
|
||||
[UIView animateWithDuration:0.1 animations:^{
|
||||
|
||||
titleView.needsScrollIfNeeded = YES;
|
||||
|
||||
[titleView invalidateIntrinsicContentSize];
|
||||
[self.view layoutIfNeeded];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,98 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
<?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>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</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<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>
|
||||
@ -1,152 +0,0 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* This is an example base class that demonstrates certain concepts you'll likely find useful when using YapDatabase.
|
||||
*
|
||||
* **** You do NOT have to use this base class. ****
|
||||
*
|
||||
* If you thought, for a moment, that YapDatabase enforces a base class like NSManagedObject (gag)
|
||||
* please banish that thought from your mind now.
|
||||
*
|
||||
* Truth be told, YapDatabase doesn't care what your objects look like.
|
||||
* Just so long as you're able to properly serialize & deserialize them.
|
||||
* Which means that you're more than welcome to use plain old NSObject subclasses.
|
||||
* Or maybe even an open source project like one of these:
|
||||
*
|
||||
* - https://github.com/Mantle/Mantle
|
||||
* - https://github.com/nicklockwood/FastCoding
|
||||
*
|
||||
* It's completely up to you!
|
||||
*
|
||||
* That being said, I think you'll find some of the concepts demonstrated here to be useful.
|
||||
* So feel free to copy & modify anything in this class. Meld it to fit your needs.
|
||||
* Maybe you'll even merge some of the concepts in this class with other popular open source projects
|
||||
* to create your own super base class. :)
|
||||
*
|
||||
* ***** Concept #1 *****
|
||||
*
|
||||
* With a highly concurrent database (like YapDatabase), thread-safety becomes important.
|
||||
* But you don't want thread-safety to become burdensome. What you really want is something so simple and
|
||||
* straight-forward that you don't have to think about it (yet it's automatically thread-safe).
|
||||
*
|
||||
* I touch on several thread-safety issues in the "Thread Safety" wiki article:
|
||||
* https://github.com/yapstudios/YapDatabase/wiki/Thread-Safety
|
||||
*
|
||||
* But you can make life really easy on yourself if you use immutable objects.
|
||||
* Of course, you must be thinking, "easier said than done" right?
|
||||
* Or perhaps "that sounds like a pain in the ass"?
|
||||
*
|
||||
* Surprisingly, it only takes a few lines of code!
|
||||
* I explain the concept in depth here (along with a bonus performance improvement you can get from it):
|
||||
* https://github.com/yapstudios/YapDatabase/wiki/Object-Policy
|
||||
*
|
||||
* But here's a 10,000 foot summary:
|
||||
* - Your model objects extend this one (or you rip off this class - feel free)
|
||||
* - You simply call [object makeImmutable]
|
||||
* - Voilà, you've got yourself an immutable thread-safe object,
|
||||
* ready to go in the database and be passed around from thread-to-thread like a ... well behaved object.
|
||||
*
|
||||
* ***** Concept #2 *****
|
||||
*
|
||||
* Tracking which properties of an object have been changed.
|
||||
*
|
||||
* This has a variety of useful applications.
|
||||
* You can use it distinguish UI fields which have been changed.
|
||||
* Or to mark a window as dirty when the corresponding object has unsaved changes.
|
||||
*
|
||||
* It can also simplify code by allowing you to directly pass an object to another method for mutation.
|
||||
* And the caller can simply check the object afterwards to see if it needs to be saved.
|
||||
*
|
||||
* ***** Concept #3 *****
|
||||
*
|
||||
* Syncing the object with a cloud-based service.
|
||||
*
|
||||
* There are often differences between the object that you store locally, and the object that's stored in the cloud.
|
||||
* For example:
|
||||
*
|
||||
* - Locally you store a UIColor object, but in the cloud you store a string (r,g,b,a)
|
||||
* - Locally your variable is named 'isComplete', but in the cloud it's unfortunately misspelled as 'isCompete'.
|
||||
*
|
||||
* These changes accumulate over time.
|
||||
* And this class provides several easy techniques to provide easy mapping between local & remote (keys/values).
|
||||
*
|
||||
* This technique is recommended for the YapDatabaseCloudKit extension:
|
||||
* https://github.com/yapstudios/YapDatabase/wiki/YapDatabaseCloudKit
|
||||
**/
|
||||
|
||||
|
||||
@interface MyDatabaseObject : NSObject <NSCopying>
|
||||
|
||||
|
||||
#pragma mark Class configuration
|
||||
|
||||
+ (NSMutableSet *)monitoredProperties;
|
||||
@property (nonatomic, readonly) NSSet *monitoredProperties;
|
||||
|
||||
+ (NSMutableDictionary *)mappings_localKeyToCloudKey;
|
||||
@property (nonatomic, readonly) NSDictionary *mappings_localKeyToCloudKey;
|
||||
|
||||
+ (NSMutableDictionary *)mappings_cloudKeyToLocalKey;
|
||||
@property (nonatomic, readonly) NSDictionary *mappings_cloudKeyToLocalKey;
|
||||
|
||||
+ (BOOL)storesOriginalCloudValues;
|
||||
|
||||
|
||||
#pragma mark Immutability
|
||||
|
||||
@property (nonatomic, readonly) BOOL isImmutable;
|
||||
|
||||
- (void)makeImmutable;
|
||||
|
||||
- (NSException *)immutableExceptionForKey:(NSString *)key;
|
||||
|
||||
|
||||
#pragma mark Monitoring (local)
|
||||
|
||||
@property (nonatomic, readonly) NSSet *changedProperties;
|
||||
@property (nonatomic, readonly) BOOL hasChangedProperties;
|
||||
|
||||
- (void)clearChangedProperties;
|
||||
|
||||
|
||||
#pragma mark Monitoring (cloud)
|
||||
|
||||
@property (nonatomic, readonly) NSSet *allCloudProperties;
|
||||
|
||||
@property (nonatomic, readonly) NSSet *changedCloudProperties;
|
||||
@property (nonatomic, readonly) BOOL hasChangedCloudProperties;
|
||||
|
||||
@property (nonatomic, readonly) NSDictionary *originalCloudValues;
|
||||
|
||||
|
||||
#pragma mark Getters & Setters (cloud)
|
||||
|
||||
- (NSString *)cloudKeyForLocalKey:(NSString *)localKey;
|
||||
- (NSString *)localKeyForCloudKey:(NSString *)cloudKey;
|
||||
|
||||
- (id)cloudValueForCloudKey:(NSString *)key;
|
||||
- (id)cloudValueForLocalKey:(NSString *)key;
|
||||
|
||||
- (id)localValueForCloudKey:(NSString *)key;
|
||||
- (id)localValueForLocalKey:(NSString *)key;
|
||||
|
||||
- (void)setLocalValueFromCloudValue:(id)cloudValue forCloudKey:(NSString *)cloudKey;
|
||||
|
||||
/**
|
||||
* You can use this macro WITHIN SUBCLASSES to fetch the CloudKey for a given property.
|
||||
* For example, say your class is configured with the following mappings_localKeyToCloudKey:
|
||||
* @{ @"uuid" : @"uuid"
|
||||
* @"foo" : @"bar"
|
||||
* }
|
||||
*
|
||||
* Then:
|
||||
* - CloudKey(uuid) => [self.mappings_localKeyToCloudKey objectForKey:@"uuid"] => @"uuid"
|
||||
* - CloudKey(foo) => [self.mappings_localKeyToCloudKey objectForKey:@"foo"] => @"bar"
|
||||
*
|
||||
* If using Apple's CloudKit framework,
|
||||
* then this macro returns the name of the corresponding property within the CKRecord.
|
||||
**/
|
||||
#define CloudKey(ivar) [self.mappings_localKeyToCloudKey objectForKey:@"" # ivar]
|
||||
// translation ==> [self.mappings_localKeyToCloudKey objectForKey:@"ivar"]
|
||||
|
||||
@end
|
||||
@ -1,585 +0,0 @@
|
||||
#import "MyDatabaseObject.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
|
||||
@implementation MyDatabaseObject {
|
||||
@private
|
||||
|
||||
BOOL isImmutable;
|
||||
NSMutableSet *changedProperties;
|
||||
NSMutableDictionary *originalCloudValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure all your subclasses call this method ([super init]).
|
||||
**/
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
// Turn on KVO for object.
|
||||
// We do this so we can get notified if the user is about to make changes to one of the object's properties.
|
||||
//
|
||||
// Don't worry, this doesn't create a retain cycle.
|
||||
|
||||
[self addObserver:self forKeyPath:@"isImmutable" options:0 context:NULL];
|
||||
|
||||
if ([[self class] storesOriginalCloudValues]) {
|
||||
originalCloudValues = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self removeObserver:self forKeyPath:@"isImmutable" context:NULL];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark NSCopying
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* In this example, all copies are automatically mutable.
|
||||
* So all you have to do in your code is something like this:
|
||||
*
|
||||
* [databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction]{
|
||||
*
|
||||
* Car *car = [transaction objectForKey:carId inCollection:@"cars"];
|
||||
* car = [car copy]; // make mutable copy
|
||||
* car.speed = newSpeed;
|
||||
*
|
||||
* [transaction setObject:car forKey:carId inCollection:@"cars"];
|
||||
* }];
|
||||
*
|
||||
* Which means all you have to do is implement the copyWithZone method in your model classes.
|
||||
**/
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
// Subclasses should call this method via [super copyWithZone:zone].
|
||||
// For example:
|
||||
//
|
||||
// MySubclass *copy = [super copyWithZone:zone];
|
||||
// copy->ivar1 = [ivar1 copy];
|
||||
// copy->ivar2 = ivar2;
|
||||
// return copy;
|
||||
|
||||
MyDatabaseObject *copy = [[[self class] alloc] init];
|
||||
copy->isImmutable = NO;
|
||||
copy->changedProperties = [self->changedProperties mutableCopy];
|
||||
copy->originalCloudValues = [self->originalCloudValues mutableCopy];
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative is to have [object copy] return an immutable copy,
|
||||
* and [object mutableCopy] to return a mutable copy.
|
||||
*
|
||||
* Some people prefer it like this. If so then:
|
||||
* - uncomment this method
|
||||
* - change 'copy->isImmutable = NO' to 'copy->isImmutable = YES' in copyWithZone
|
||||
* - and add NSMutableCopying to the list of protocols in the header file
|
||||
*
|
||||
* Note: The implemenation below just uses a regular copy, and then sets the isImmutable flag to NO.
|
||||
* So if you go this route, you don't have to implement mutableCopyWithZone (just copyWithZone).
|
||||
**/
|
||||
//- (instancetype)mutableCopyWithZone:(NSZone *)zone
|
||||
//{
|
||||
// MyDatabaseObject *copy = [self copy];
|
||||
// copy->isImmutable = NO;
|
||||
//
|
||||
// return copy;
|
||||
//}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Class Configuration
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* This method returns a list of all properties that should be monitored.
|
||||
* That is, these properties should show up in the changedProperties set if they are modified.
|
||||
* And they should be considered immutable once the makeImmutable method has been invoked.
|
||||
*
|
||||
* By default this method returns a list of all properties in each subclass in the
|
||||
* hierarchy leading to "[self class]".
|
||||
*
|
||||
* However, this is not always exactly what you want.
|
||||
* For example, you may have properties which are simply used for caching:
|
||||
*
|
||||
* @property (nonatomic, strong, readwrite) UIImage *avatarImage;
|
||||
* @property (nonatomic, strong, readwrite) UIImage *cachedTransformedAvatarImage;
|
||||
*
|
||||
* In this example, you store the user's plain avatar image.
|
||||
* However, your code transforms the avatar in various ways for display in the UI.
|
||||
* So to reduce overhead, you'd like to cache these transformed images in the user object.
|
||||
* Thus the 'cachedTransformedAvatarImage' property doesn't actually mutate the user object. It's just a temp cache.
|
||||
*
|
||||
* So your subclass would override this method like so:
|
||||
*
|
||||
* + (NSMutableSet *)monitoredProperties
|
||||
* {
|
||||
* NSMutableSet *monitoredProperties = [super immutableProperties];
|
||||
* [monitoredProperties removeObject:@"cachedTransformedAvatarImage"];
|
||||
*
|
||||
* return monitoredProperties;
|
||||
* }
|
||||
**/
|
||||
+ (NSMutableSet *)monitoredProperties
|
||||
{
|
||||
// Steps to override me (if needed):
|
||||
//
|
||||
// - Invoke [super monitoredProperties]
|
||||
// - Modify resulting mutable set
|
||||
// - Return modified set
|
||||
|
||||
NSMutableSet *properties = nil;
|
||||
|
||||
Class rootClass = [MyDatabaseObject class];
|
||||
Class subClass = [self class];
|
||||
|
||||
while (subClass != rootClass)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
objc_property_t *propertyList = class_copyPropertyList(subClass, &count);
|
||||
if (propertyList)
|
||||
{
|
||||
if (properties == nil)
|
||||
properties = [NSMutableSet setWithCapacity:count];
|
||||
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
const char *name = property_getName(propertyList[i]);
|
||||
NSString *property = [NSString stringWithUTF8String:name];
|
||||
|
||||
[properties addObject:property];
|
||||
}
|
||||
|
||||
free(propertyList);
|
||||
}
|
||||
|
||||
subClass = [subClass superclass];
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generally you should NOT override this method.
|
||||
* Just override the class version of this method (above).
|
||||
**/
|
||||
- (NSSet *)monitoredProperties
|
||||
{
|
||||
NSSet *cached = objc_getAssociatedObject([self class], _cmd);
|
||||
if (cached) return cached;
|
||||
|
||||
NSSet *monitoredProperties = [[[self class] monitoredProperties] copy];
|
||||
|
||||
objc_setAssociatedObject([self class], _cmd, monitoredProperties, OBJC_ASSOCIATION_RETAIN);
|
||||
return monitoredProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a mapping from localPropertyName to cloudPropertyName.
|
||||
*
|
||||
* By default this method returns a dictionary including everything in [self monitoredProperties],
|
||||
* where the key is equal to the value for every item.
|
||||
*
|
||||
* For example:
|
||||
* @{ @"title" : @"title",
|
||||
* @"isComplete" : @"isComplete",
|
||||
* @"creationDate" : @"creationDate",
|
||||
* @"lastModified" : @"lastModified",
|
||||
* @"isSeen" : @"isSeen"
|
||||
* }
|
||||
*
|
||||
* However, this is not always exactly what you want.
|
||||
* For example, you may not want to sync the 'isSeen' property because it's device-specific.
|
||||
*
|
||||
* Additionally you discover that CKRecord has a built-in creationDate property,
|
||||
* and so CKRecord doesn't allow you to use that key for your own purposes. (It's reserved.)
|
||||
*
|
||||
* You can still name your own property "creationDate",
|
||||
* but you'll be forced to use a different name for the CKRecord.
|
||||
* So let's say we decide to use "created" as the corresponding key in the CKRecord.
|
||||
*
|
||||
* Thus your subclass overrides this method like so:
|
||||
*
|
||||
* + (NSMutableDictionary *)mappings_localKeyToCloudKey
|
||||
* {
|
||||
* NSMutableDictionary *mappings_localKeyToCloudKey = [super mappings_localKeyToCloudKey];
|
||||
*
|
||||
* [mappings_localKeyToCloudKey removeObjectForKey:@"isSeen"];
|
||||
* [mappings_localKeyToCloudKey setObject:@"created" forKey:@"creationDate"];
|
||||
*
|
||||
* return mappings_localKeyToCloudKey;
|
||||
* }
|
||||
**/
|
||||
+ (NSMutableDictionary *)mappings_localKeyToCloudKey
|
||||
{
|
||||
// Steps to override me (if needed):
|
||||
//
|
||||
// - Invoke [super mappings_localKeyToCloudKey]
|
||||
// - Modify resulting mutable dictionary
|
||||
// - Return modified dictionary
|
||||
|
||||
NSMutableSet *properties = [self monitoredProperties];
|
||||
|
||||
NSMutableDictionary *mappings_localKeyToCloudKey = [NSMutableDictionary dictionaryWithCapacity:properties.count];
|
||||
|
||||
for (NSString *propertyName in properties)
|
||||
{
|
||||
[mappings_localKeyToCloudKey setObject:propertyName forKey:propertyName];
|
||||
}
|
||||
|
||||
return mappings_localKeyToCloudKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generally you should NOT override this method.
|
||||
* Just override the class version of this method (above).
|
||||
**/
|
||||
- (NSDictionary *)mappings_localKeyToCloudKey
|
||||
{
|
||||
NSDictionary *cached = objc_getAssociatedObject([self class], _cmd);
|
||||
if (cached) return cached;
|
||||
|
||||
NSDictionary *mappings_localKeyToCloudKey = [[[self class] mappings_localKeyToCloudKey] copy];
|
||||
|
||||
objc_setAssociatedObject([self class], _cmd, mappings_localKeyToCloudKey, OBJC_ASSOCIATION_RETAIN);
|
||||
return mappings_localKeyToCloudKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is the inverse of mappings_localKeyToCloudKey.
|
||||
* There is generally no need to override this method.
|
||||
**/
|
||||
+ (NSMutableDictionary *)mappings_cloudKeyToLocalKey
|
||||
{
|
||||
NSMutableDictionary *mappings_localKeyToCloudKey = [self mappings_localKeyToCloudKey];
|
||||
NSUInteger capacity = mappings_localKeyToCloudKey.count;
|
||||
|
||||
NSMutableDictionary *mappings_cloudKeyToLocalKey = [NSMutableDictionary dictionaryWithCapacity:capacity];
|
||||
|
||||
[mappings_localKeyToCloudKey enumerateKeysAndObjectsUsingBlock:^(id localKey, id cloudKey, BOOL *stop) {
|
||||
|
||||
mappings_cloudKeyToLocalKey[cloudKey] = localKey;
|
||||
}];
|
||||
|
||||
return mappings_cloudKeyToLocalKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* There is generally no need to override this method.
|
||||
**/
|
||||
- (NSDictionary *)mappings_cloudKeyToLocalKey
|
||||
{
|
||||
NSDictionary *cached = objc_getAssociatedObject([self class], _cmd);
|
||||
if (cached) return cached;
|
||||
|
||||
NSDictionary *mappings_cloudKeyToLocalKey = [[[self class] mappings_cloudKeyToLocalKey] copy];
|
||||
|
||||
objc_setAssociatedObject([self class], _cmd, mappings_cloudKeyToLocalKey, OBJC_ASSOCIATION_RETAIN);
|
||||
return mappings_cloudKeyToLocalKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* If storesOriginalCloudValues is enabled, then in addition to monitoring which properties change,
|
||||
* the object will also keep a dictionary of the original cloudValues that have changed.
|
||||
*
|
||||
* This is disabled by default.
|
||||
* So you'll need to "opt-in" for those classes where you want this feature.
|
||||
**/
|
||||
+ (BOOL)storesOriginalCloudValues
|
||||
{
|
||||
// Override me (and return YES), if you want to store originalCloudValues.
|
||||
// These are cleared when clearChangedProperties is invoked.
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Immutability
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@synthesize isImmutable = isImmutable;
|
||||
|
||||
- (void)makeImmutable
|
||||
{
|
||||
if (!isImmutable)
|
||||
{
|
||||
// Set immutable flag
|
||||
isImmutable = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSException *)immutableExceptionForKey:(NSString *)key
|
||||
{
|
||||
NSString *reason;
|
||||
if (key)
|
||||
reason = [NSString stringWithFormat:
|
||||
@"Attempting to mutate immutable object. Class = %@, property = %@", NSStringFromClass([self class]), key];
|
||||
else
|
||||
reason = [NSString stringWithFormat:
|
||||
@"Attempting to mutate immutable object. Class = %@", NSStringFromClass([self class])];
|
||||
|
||||
NSDictionary *userInfo = @{ NSLocalizedRecoverySuggestionErrorKey:
|
||||
@"To make modifications you should create a copy via [object copy]."
|
||||
@" You may then make changes to the copy before saving it back to the database."};
|
||||
|
||||
return [NSException exceptionWithName:@"STDatabaseObjectException" reason:reason userInfo:userInfo];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Monitoring (local)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSSet *)changedProperties
|
||||
{
|
||||
if ([changedProperties count] == 0) return nil;
|
||||
|
||||
// Remove untracked properties from the list.
|
||||
[changedProperties intersectSet:[self monitoredProperties]];
|
||||
|
||||
// And return immutable copy
|
||||
return [changedProperties copy];
|
||||
}
|
||||
|
||||
- (BOOL)hasChangedProperties
|
||||
{
|
||||
return ([changedProperties count] > 0);
|
||||
}
|
||||
|
||||
- (void)clearChangedProperties
|
||||
{
|
||||
changedProperties = nil;
|
||||
[originalCloudValues removeAllObjects];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Monitoring (cloud)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSSet *)allCloudProperties
|
||||
{
|
||||
NSSet *cached = objc_getAssociatedObject([self class], _cmd);
|
||||
if (cached) return cached;
|
||||
|
||||
NSDictionary *mappings_localKeyToCloudKey = self.mappings_localKeyToCloudKey;
|
||||
NSUInteger capacity = mappings_localKeyToCloudKey.count;
|
||||
|
||||
NSMutableSet *allCloudProperties = [NSMutableSet setWithCapacity:capacity];
|
||||
|
||||
for (NSString *cloudKey in [mappings_localKeyToCloudKey objectEnumerator])
|
||||
{
|
||||
[allCloudProperties addObject:cloudKey];
|
||||
}
|
||||
|
||||
NSSet *result = [allCloudProperties copy];
|
||||
|
||||
objc_setAssociatedObject([self class], _cmd, result, OBJC_ASSOCIATION_RETAIN);
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSSet *)changedCloudProperties
|
||||
{
|
||||
if ([changedProperties count] == 0) return nil;
|
||||
|
||||
NSMutableSet *changedCloudProperties = [NSMutableSet setWithCapacity:changedProperties.count];
|
||||
NSDictionary *mappings_localKeyToCloudKey = self.mappings_localKeyToCloudKey;
|
||||
|
||||
for (NSString *localKey in changedProperties)
|
||||
{
|
||||
NSString *cloudKey = mappings_localKeyToCloudKey[localKey];
|
||||
if (cloudKey) {
|
||||
[changedCloudProperties addObject:cloudKey];
|
||||
}
|
||||
}
|
||||
|
||||
return changedCloudProperties;
|
||||
}
|
||||
|
||||
- (BOOL)hasChangedCloudProperties
|
||||
{
|
||||
if ([changedProperties count] == 0) return NO;
|
||||
|
||||
NSDictionary *mappings_localKeyToCloudKey = self.mappings_localKeyToCloudKey;
|
||||
|
||||
for (NSString *localKey in changedProperties)
|
||||
{
|
||||
NSString *cloudKey = mappings_localKeyToCloudKey[localKey];
|
||||
if (cloudKey) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSDictionary *)originalCloudValues
|
||||
{
|
||||
return [originalCloudValues copy];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Getters & Setters (cloud)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSString *)cloudKeyForLocalKey:(NSString *)localKey
|
||||
{
|
||||
return self.mappings_localKeyToCloudKey[localKey];
|
||||
}
|
||||
|
||||
- (NSString *)localKeyForCloudKey:(NSString *)cloudKey
|
||||
{
|
||||
return self.mappings_cloudKeyToLocalKey[cloudKey];
|
||||
}
|
||||
|
||||
- (id)cloudValueForCloudKey:(NSString *)cloudKey
|
||||
{
|
||||
// Override me if needed.
|
||||
// For example:
|
||||
//
|
||||
// - (id)cloudValueForCloudKey:(NSString *)cloudKey
|
||||
// {
|
||||
// if ([cloudKey isEqualToString:@"color"])
|
||||
// {
|
||||
// // We store UIColor in the cloud as a string (r,g,b,a)
|
||||
// return ConvertUIColorToNSString(self.color);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return [super cloudValueForCloudKey:cloudKey];
|
||||
// }
|
||||
// }
|
||||
|
||||
return [self localValueForCloudKey:cloudKey];
|
||||
}
|
||||
|
||||
- (id)cloudValueForLocalKey:(NSString *)localKey
|
||||
{
|
||||
NSString *cloudKey = [self cloudKeyForLocalKey:localKey];
|
||||
return [self cloudValueForCloudKey:cloudKey];
|
||||
}
|
||||
|
||||
- (id)localValueForCloudKey:(NSString *)cloudKey
|
||||
{
|
||||
NSString *localKey = [self localKeyForCloudKey:cloudKey];
|
||||
return [self valueForKey:localKey];
|
||||
}
|
||||
|
||||
- (id)localValueForLocalKey:(NSString *)localKey
|
||||
{
|
||||
return [self valueForKey:localKey];
|
||||
}
|
||||
|
||||
- (void)setLocalValueFromCloudValue:(id)cloudValue forCloudKey:(NSString *)cloudKey
|
||||
{
|
||||
// Override me if needed.
|
||||
// For example:
|
||||
//
|
||||
// - (void)setLocalValueFromCloudValue:(id)cloudValue forCloudKey:(NSString *)cloudKey
|
||||
// {
|
||||
// if ([cloudKey isEqualToString:@"color"])
|
||||
// {
|
||||
// // We store UIColor in the cloud as a string (r,g,b,a)
|
||||
// self.color = ConvertNSStringToUIColor(cloudValue);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return [super setLocalValueForCloudValue:cloudValue cloudKey:cloudKey];
|
||||
// }
|
||||
// }
|
||||
|
||||
NSString *localKey = [self localKeyForCloudKey:cloudKey];
|
||||
if (localKey)
|
||||
{
|
||||
[self setValue:cloudValue forKey:localKey];
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark KVO
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
|
||||
{
|
||||
if ([key isEqualToString:@"isImmutable"])
|
||||
return YES;
|
||||
else
|
||||
return [super automaticallyNotifiesObserversForKey:key];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingIsImmutable
|
||||
{
|
||||
// In order for the KVO magic to work, we specify that the isImmutable property is dependent
|
||||
// upon all other properties in the class that should become immutable.
|
||||
//
|
||||
// The code below ** attempts ** to do this automatically.
|
||||
// It does so by creating a list of all the properties in the class.
|
||||
//
|
||||
// Obviously this will not work for every situation.
|
||||
// In particular:
|
||||
//
|
||||
// - if you have custom setter methods that aren't specified as properties
|
||||
// - if you have other custom methods that modify the object
|
||||
//
|
||||
// To cover these edge cases, simply add code like the following at the beginning of such methods:
|
||||
//
|
||||
// - (void)recalculateFoo
|
||||
// {
|
||||
// if (self.isImmutable) {
|
||||
// @throw [self immutableExceptionForKey:@"foo"];
|
||||
// }
|
||||
//
|
||||
// // ... normal code that modifies foo ivar ...
|
||||
// }
|
||||
|
||||
return [self monitoredProperties];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
{
|
||||
// Nothing to do (but method is required to exist)
|
||||
}
|
||||
|
||||
- (void)willChangeValueForKey:(NSString *)key
|
||||
{
|
||||
if (isImmutable)
|
||||
{
|
||||
@throw [self immutableExceptionForKey:key];
|
||||
}
|
||||
|
||||
if (originalCloudValues)
|
||||
{
|
||||
NSString *cloudKey = [self cloudKeyForLocalKey:key];
|
||||
|
||||
if ([self.allCloudProperties containsObject:cloudKey])
|
||||
{
|
||||
if (!CFDictionaryContainsKey((CFDictionaryRef)originalCloudValues, (const void *)cloudKey))
|
||||
{
|
||||
id originalCloudValue = [self cloudValueForCloudKey:cloudKey];
|
||||
if (originalCloudValue) {
|
||||
[originalCloudValues setObject:originalCloudValue forKey:cloudKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[super willChangeValueForKey:key];
|
||||
}
|
||||
|
||||
- (void)didChangeValueForKey:(NSString *)key
|
||||
{
|
||||
if (changedProperties == nil)
|
||||
changedProperties = [[NSMutableSet alloc] init];
|
||||
|
||||
[changedProperties addObject:key];
|
||||
[super didChangeValueForKey:key];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,27 +0,0 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CloudKit/CloudKit.h>
|
||||
#import "MyDatabaseObject.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, TodoPriority) {
|
||||
TodoPriorityLow = -1,
|
||||
TodoPriorityNormal = 0,
|
||||
TodoPriorityHigh = 1,
|
||||
};
|
||||
|
||||
@interface MyTodo : MyDatabaseObject <NSCoding, NSCopying>
|
||||
|
||||
- (instancetype)init;
|
||||
- (instancetype)initWithUUID:(NSString *)uuid;
|
||||
|
||||
- (instancetype)initWithRecord:(CKRecord *)record;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *uuid;
|
||||
|
||||
@property (nonatomic, copy, readwrite) NSString *title;
|
||||
@property (nonatomic, assign, readwrite) TodoPriority priority;
|
||||
@property (nonatomic, assign, readwrite) BOOL isDone;
|
||||
|
||||
@property (nonatomic, strong, readwrite) NSDate *creationDate;
|
||||
@property (nonatomic, strong, readwrite) NSDate *lastModified;
|
||||
|
||||
@end
|
||||
@ -1,191 +0,0 @@
|
||||
#import "MyTodo.h"
|
||||
|
||||
/**
|
||||
* Keys for encoding / decoding (to avoid typos)
|
||||
**/
|
||||
static NSString *const k_version = @"version";
|
||||
static NSString *const k_uuid = @"uuid";
|
||||
static NSString *const k_title = @"title";
|
||||
static NSString *const k_priority = @"priority";
|
||||
static NSString *const k_isDone = @"isDone";
|
||||
static NSString *const k_creationDate = @"created";
|
||||
static NSString *const k_lastModified = @"lastModified";
|
||||
|
||||
|
||||
@implementation MyTodo
|
||||
|
||||
@synthesize uuid = uuid;
|
||||
@synthesize title = title;
|
||||
@synthesize priority = priority;
|
||||
@synthesize isDone = isDone;
|
||||
@synthesize creationDate = creationDate;
|
||||
@synthesize lastModified = lastModified;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithUUID:[[NSUUID UUID] UUIDString]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithUUID:(NSString *)inUUID
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
if (inUUID)
|
||||
uuid = [inUUID copy];
|
||||
else
|
||||
uuid = [[NSUUID UUID] UUIDString];
|
||||
|
||||
priority = TodoPriorityNormal;
|
||||
|
||||
NSDate *now = [NSDate date];
|
||||
creationDate = now;
|
||||
lastModified = now;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithRecord:(CKRecord *)record
|
||||
{
|
||||
if (![record.recordType isEqualToString:@"todo"])
|
||||
{
|
||||
NSAssert(NO, @"Attempting to create todo from non-todo record"); // For debug builds
|
||||
return nil; // For release builds
|
||||
}
|
||||
|
||||
if ((self = [super init]))
|
||||
{
|
||||
uuid = record.recordID.recordName;
|
||||
|
||||
NSSet *cloudKeys = self.allCloudProperties;
|
||||
for (NSString *cloudKey in cloudKeys)
|
||||
{
|
||||
if (![cloudKey isEqualToString:@"uuid"])
|
||||
{
|
||||
[self setLocalValueFromCloudValue:[record objectForKey:cloudKey] forCloudKey:cloudKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark NSCoding
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
// The version can be used to handle on-the-fly upgrades to objects as they're decoded.
|
||||
// For more information, see the wiki article:
|
||||
// https://github.com/yapstudios/YapDatabase/wiki/Storing-Objects
|
||||
// int version = [decoder decodeIntForKey:k_version];
|
||||
|
||||
uuid = [decoder decodeObjectForKey:k_uuid];
|
||||
title = [decoder decodeObjectForKey:k_title];
|
||||
priority = [decoder decodeIntegerForKey:k_priority];
|
||||
isDone = [decoder decodeBoolForKey:k_isDone];
|
||||
creationDate = [decoder decodeObjectForKey:k_creationDate];
|
||||
lastModified = [decoder decodeObjectForKey:k_lastModified];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder
|
||||
{
|
||||
[coder encodeInt:1 forKey:k_version];
|
||||
|
||||
[coder encodeObject:uuid forKey:k_uuid];
|
||||
[coder encodeObject:title forKey:k_title];
|
||||
[coder encodeInteger:priority forKey:k_priority];
|
||||
[coder encodeBool:isDone forKey:k_isDone];
|
||||
[coder encodeObject:creationDate forKey:k_creationDate];
|
||||
[coder encodeObject:lastModified forKey:k_lastModified];
|
||||
}
|
||||
|
||||
#pragma mark NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
MyTodo *copy = [super copyWithZone:zone]; // Be sure to invoke [MyDatabaseObject copyWithZone:] !
|
||||
copy->uuid = uuid;
|
||||
copy->title = title;
|
||||
copy->priority = priority;
|
||||
copy->isDone = isDone;
|
||||
copy->creationDate = creationDate;
|
||||
copy->lastModified = lastModified;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark MyDatabaseObject overrides
|
||||
|
||||
+ (BOOL)storesOriginalCloudValues
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSMutableDictionary *)mappings_localKeyToCloudKey
|
||||
{
|
||||
NSMutableDictionary *mappings_localKeyToCloudKey = [super mappings_localKeyToCloudKey];
|
||||
mappings_localKeyToCloudKey[@"creationDate"] = @"created";
|
||||
|
||||
return mappings_localKeyToCloudKey;
|
||||
}
|
||||
|
||||
- (id)cloudValueForCloudKey:(NSString *)cloudKey
|
||||
{
|
||||
// Override me if needed.
|
||||
// For example:
|
||||
//
|
||||
// - (id)cloudValueForCloudKey:(NSString *)cloudKey
|
||||
// {
|
||||
// if ([cloudKey isEqualToString:@"color"])
|
||||
// {
|
||||
// // We store UIColor in the cloud as a string (r,g,b,a)
|
||||
// return ConvertUIColorToNSString(self.color);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return [super cloudValueForCloudKey:cloudKey];
|
||||
// }
|
||||
// }
|
||||
|
||||
return [super cloudValueForCloudKey:cloudKey];
|
||||
}
|
||||
|
||||
- (void)setLocalValueFromCloudValue:(id)cloudValue forCloudKey:(NSString *)cloudKey
|
||||
{
|
||||
// Override me if needed.
|
||||
// For example:
|
||||
//
|
||||
// - (void)setLocalValueFromCloudValue:(id)cloudValue forCloudKey:(NSString *)cloudKey
|
||||
// {
|
||||
// if ([cloudKey isEqualToString:@"color"])
|
||||
// {
|
||||
// // We store UIColor in the cloud as a string (r,g,b,a)
|
||||
// self.color = ConvertNSStringToUIColor(cloudValue);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return [super setLocalValueForCloudValue:cloudValue cloudKey:cloudKey];
|
||||
// }
|
||||
// }
|
||||
|
||||
return [super setLocalValueFromCloudValue:cloudValue forCloudKey:cloudKey];
|
||||
}
|
||||
|
||||
#pragma mark KVO overrides
|
||||
|
||||
- (void)setNilValueForKey:(NSString *)key
|
||||
{
|
||||
if ([key isEqualToString:@"priority"]) {
|
||||
self.priority = TodoPriorityNormal;
|
||||
}
|
||||
if ([key isEqualToString:@"isDone"]) {
|
||||
self.isDone = NO;
|
||||
}
|
||||
else {
|
||||
[super setNilValueForKey:key];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,15 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
||||
@interface RootViewController : UIViewController
|
||||
|
||||
@property (nonatomic, weak) IBOutlet UITableView *tableView;
|
||||
|
||||
@property (nonatomic, weak) IBOutlet UIView *ckStatusView;
|
||||
@property (nonatomic, weak) IBOutlet UILabel *ckTopStatusLabel;
|
||||
@property (nonatomic, weak) IBOutlet UILabel *ckBottomStatusLabel;
|
||||
|
||||
- (IBAction)suspendButtonTapped:(id)sender;
|
||||
- (IBAction)resumeButtonTapped:(id)sender;
|
||||
|
||||
@end
|
||||
@ -1,342 +0,0 @@
|
||||
#import "RootViewController.h"
|
||||
#import "EditViewController.h"
|
||||
#import "TodoCell.h"
|
||||
#import "DatabaseManager.h"
|
||||
#import "CloudKitManager.h"
|
||||
#import "MyTodo.h"
|
||||
|
||||
#import <CocoaLumberjack/CocoaLumberjack.h>
|
||||
|
||||
// Log Levels: off, error, warn, info, verbose
|
||||
#if DEBUG
|
||||
static const NSUInteger ddLogLevel = DDLogLevelAll;
|
||||
#else
|
||||
static const NSUInteger ddLogLevel = DDLogLevelAll;
|
||||
#endif
|
||||
|
||||
static NSString *const TodoCellIdentifier = @"Todo";
|
||||
|
||||
@implementation RootViewController
|
||||
{
|
||||
YapDatabaseConnection *databaseConnection;
|
||||
YapDatabaseViewMappings *mappings;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
// Configure tableView
|
||||
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
self.tableView.estimatedRowHeight = 44.0;
|
||||
|
||||
CGRect ckViewFrame = self.ckStatusView.frame;
|
||||
|
||||
UIEdgeInsets insets = self.tableView.scrollIndicatorInsets;
|
||||
insets.bottom = ckViewFrame.size.height;
|
||||
|
||||
self.tableView.contentInset = insets;
|
||||
self.tableView.scrollIndicatorInsets = insets;
|
||||
|
||||
// Configure database stuff
|
||||
|
||||
databaseConnection = MyDatabaseManager.uiDatabaseConnection;
|
||||
[self initializeMappings];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(databaseConnectionDidUpdate:)
|
||||
name:UIDatabaseConnectionDidUpdateNotification
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(cloudKitSuspendCountChanged:)
|
||||
name:YapDatabaseCloudKitSuspendCountChangedNotification
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(cloudKitInFlightChangeSetChanged:)
|
||||
name:YapDatabaseCloudKitInFlightChangeSetChangedNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
[self updateStatusLabels];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Database Stuff
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)initializeMappings
|
||||
{
|
||||
[databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
|
||||
if ([transaction ext:Ext_View_Order])
|
||||
{
|
||||
mappings = [[YapDatabaseViewMappings alloc] initWithGroups:@[@""] view:Ext_View_Order];
|
||||
[mappings updateWithTransaction:transaction];
|
||||
}
|
||||
else
|
||||
{
|
||||
// The view isn't ready yet.
|
||||
// We'll try again when we get a databaseConnectionDidUpdate notification.
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)databaseConnectionDidUpdate:(NSNotification *)notification
|
||||
{
|
||||
[self updateStatusLabels];
|
||||
|
||||
if (mappings == nil)
|
||||
{
|
||||
[self initializeMappings];
|
||||
[self.tableView reloadData];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *notifications = [notification.userInfo objectForKey:kNotificationsKey];
|
||||
|
||||
NSArray *rowChanges = nil;
|
||||
[[databaseConnection ext:Ext_View_Order] getSectionChanges:NULL
|
||||
rowChanges:&rowChanges
|
||||
forNotifications:notifications
|
||||
withMappings:mappings];
|
||||
|
||||
if ([rowChanges count] == 0)
|
||||
{
|
||||
// There aren't any changes that affect our tableView
|
||||
return;
|
||||
}
|
||||
|
||||
[self.tableView beginUpdates];
|
||||
|
||||
for (YapDatabaseViewRowChange *rowChange in rowChanges)
|
||||
{
|
||||
switch (rowChange.type)
|
||||
{
|
||||
case YapDatabaseViewChangeDelete :
|
||||
{
|
||||
[self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
break;
|
||||
}
|
||||
case YapDatabaseViewChangeInsert :
|
||||
{
|
||||
[self.tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
break;
|
||||
}
|
||||
case YapDatabaseViewChangeMove :
|
||||
{
|
||||
[self.tableView deleteRowsAtIndexPaths:@[ rowChange.indexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
[self.tableView insertRowsAtIndexPaths:@[ rowChange.newIndexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
break;
|
||||
}
|
||||
case YapDatabaseViewChangeUpdate :
|
||||
{
|
||||
[self.tableView reloadRowsAtIndexPaths:@[ rowChange.indexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationFade];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[self.tableView endUpdates];
|
||||
}
|
||||
|
||||
- (void)cloudKitSuspendCountChanged:(NSNotification *)notification
|
||||
{
|
||||
[self updateStatusLabels];
|
||||
}
|
||||
|
||||
- (void)cloudKitInFlightChangeSetChanged:(NSNotification *)notification
|
||||
{
|
||||
[self updateStatusLabels];
|
||||
}
|
||||
|
||||
- (MyTodo *)todoAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
__block MyTodo *todo = nil;
|
||||
[databaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
|
||||
todo = [[transaction ext:Ext_View_Order] objectAtIndexPath:indexPath withMappings:mappings];
|
||||
}];
|
||||
|
||||
return todo;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Status
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)updateStatusLabels
|
||||
{
|
||||
NSUInteger suspendCount = [MyDatabaseManager.cloudKitExtension suspendCount];
|
||||
if (suspendCount > 0)
|
||||
{
|
||||
self.ckTopStatusLabel.text =
|
||||
[NSString stringWithFormat:@"Status: Suspended (suspendCount = %lu)", (unsigned long)suspendCount];
|
||||
}
|
||||
else
|
||||
{
|
||||
self.ckTopStatusLabel.text = @"Status: Resumed";
|
||||
}
|
||||
|
||||
NSUInteger inFlightCount = 0;
|
||||
NSUInteger queuedCount = 0;
|
||||
[MyDatabaseManager.cloudKitExtension getNumberOfInFlightChangeSets:&inFlightCount queuedChangeSets:&queuedCount];
|
||||
|
||||
self.ckBottomStatusLabel.text =
|
||||
[NSString stringWithFormat:@"ChangeSets: InFlight(%lu), Queued(%lu)",
|
||||
(unsigned long)inFlightCount,
|
||||
(unsigned long)queuedCount];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Actions
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (IBAction)suspendButtonTapped:(id)sender
|
||||
{
|
||||
DDLogVerbose(@"RootViewController - suspendButtonTapped:");
|
||||
|
||||
[MyDatabaseManager.cloudKitExtension suspend];
|
||||
}
|
||||
|
||||
- (IBAction)resumeButtonTapped:(id)sender
|
||||
{
|
||||
DDLogVerbose(@"RootViewController - resumeButtonTapped:");
|
||||
|
||||
[MyDatabaseManager.cloudKitExtension resume];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark UITableView
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)sender
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)sender numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return [mappings numberOfItemsInSection:section];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)sender cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
TodoCell *cell = (TodoCell *)[self.tableView dequeueReusableCellWithIdentifier:TodoCellIdentifier];
|
||||
|
||||
MyTodo *todo = [self todoAtIndexPath:indexPath];
|
||||
|
||||
NSString *title = todo.title;
|
||||
if (title == nil)
|
||||
title = @"< missing title >";
|
||||
|
||||
if (todo.isDone)
|
||||
{
|
||||
UIImage *image = [UIImage imageNamed:@"checkmark-on"];
|
||||
[cell.checkmarkButton setImage:image forState:UIControlStateNormal];
|
||||
|
||||
|
||||
|
||||
NSDictionary *attr = @{NSStrikethroughStyleAttributeName: @(NSUnderlineStyleSingle)};
|
||||
NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:title attributes:attr];
|
||||
|
||||
cell.titleLabel.attributedText = attrStr;
|
||||
}
|
||||
else
|
||||
{
|
||||
UIImage *image = [UIImage imageNamed:@"checkmark-off"];
|
||||
[cell.checkmarkButton setImage:image forState:UIControlStateNormal];
|
||||
|
||||
cell.titleLabel.text = title;
|
||||
}
|
||||
|
||||
switch (todo.priority)
|
||||
{
|
||||
case TodoPriorityLow : cell.titleLabel.textColor = [UIColor darkGrayColor]; break;
|
||||
case TodoPriorityHigh : cell.titleLabel.textColor = [UIColor redColor]; break;
|
||||
default : cell.titleLabel.textColor = [UIColor blackColor]; break;
|
||||
}
|
||||
|
||||
cell.delegate = self;
|
||||
|
||||
[cell setNeedsUpdateConstraints];
|
||||
[cell updateConstraintsIfNeeded];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)sender didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
MyTodo *todo = [self todoAtIndexPath:indexPath];
|
||||
|
||||
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
|
||||
EditViewController *evc = [storyboard instantiateViewControllerWithIdentifier:@"EditViewController"];
|
||||
|
||||
evc.todoID = todo.uuid;
|
||||
|
||||
[self.navigationController pushViewController:evc animated:YES];
|
||||
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
|
||||
- (NSArray *)tableView:(UITableView *)sender editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
UITableViewRowAction *deleteAction =
|
||||
[UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive
|
||||
title:@"Delete"
|
||||
handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
|
||||
{
|
||||
MyTodo *todo = [self todoAtIndexPath:indexPath];
|
||||
|
||||
YapDatabaseConnection *rwDatabaseConnection = MyDatabaseManager.bgDatabaseConnection;
|
||||
[rwDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
|
||||
[transaction removeObjectForKey:todo.uuid inCollection:Collection_Todos];
|
||||
|
||||
} completionBlock:^{
|
||||
|
||||
[self updateStatusLabels];
|
||||
}];
|
||||
}];
|
||||
|
||||
return @[ deleteAction ];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)sender commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
|
||||
forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
// No statement or algorithm is needed in here. Just the implementation
|
||||
}
|
||||
|
||||
- (void)didTapImageViewInCell:(TodoCell *)sender
|
||||
{
|
||||
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
|
||||
|
||||
MyTodo *originalTodo = [self todoAtIndexPath:indexPath];
|
||||
NSString *todoID = originalTodo.uuid;
|
||||
BOOL newIsDone = originalTodo.isDone ? NO : YES;
|
||||
|
||||
[MyDatabaseManager.bgDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
|
||||
MyTodo *todo = [transaction objectForKey:todoID inCollection:Collection_Todos];
|
||||
todo = [todo copy]; // make mutable copy
|
||||
|
||||
todo.isDone = newIsDone;
|
||||
todo.lastModified = [NSDate date];
|
||||
|
||||
[transaction setObject:todo forKey:todoID inCollection:Collection_Todos];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,18 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
||||
@interface TodoCell : UITableViewCell
|
||||
|
||||
@property (nonatomic, weak) id <NSObject> delegate;
|
||||
|
||||
@property (nonatomic, weak) IBOutlet UIButton *checkmarkButton;
|
||||
@property (nonatomic, weak) IBOutlet UILabel *titleLabel;
|
||||
|
||||
@end
|
||||
|
||||
@protocol TodoCellDelegate
|
||||
@optional
|
||||
|
||||
- (void)didTapImageViewInCell:(TodoCell *)sender;
|
||||
|
||||
@end
|
||||
@ -1,20 +0,0 @@
|
||||
#import "TodoCell.h"
|
||||
|
||||
|
||||
@implementation TodoCell
|
||||
|
||||
@synthesize delegate = _weak_delegate;
|
||||
|
||||
@synthesize checkmarkButton = checkmarkButton;
|
||||
@synthesize titleLabel = titleLabel;
|
||||
|
||||
- (IBAction)didTapImageView:(id)sender
|
||||
{
|
||||
id <NSObject> delegate = self.delegate;
|
||||
|
||||
if ([delegate respondsToSelector:@selector(didTapImageViewInCell:)]) {
|
||||
[(id <TodoCellDelegate>)delegate didTapImageViewInCell:self];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,8 +0,0 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
||||
@interface TodoTextView : UITextView
|
||||
|
||||
@property (nonatomic, readwrite) BOOL needsScrollIfNeeded;
|
||||
|
||||
@end
|
||||
@ -1,37 +0,0 @@
|
||||
#import "TodoTextView.h"
|
||||
|
||||
|
||||
@implementation TodoTextView
|
||||
|
||||
@synthesize needsScrollIfNeeded;
|
||||
|
||||
- (CGSize)intrinsicContentSize
|
||||
{
|
||||
CGSize maxSize = self.frame.size;
|
||||
maxSize.height = 9999;
|
||||
|
||||
CGSize size = [self sizeThatFits:maxSize];
|
||||
return size;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
if (self.needsScrollIfNeeded)
|
||||
{
|
||||
self.needsScrollIfNeeded = NO;
|
||||
|
||||
if (self.contentSize.height > self.frame.size.height)
|
||||
{
|
||||
CGFloat y = self.contentSize.height - self.frame.size.height;
|
||||
[self setContentOffset:CGPointMake(0, y) animated:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setContentOffset:CGPointMake(0, 0) animated:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
@ -1,16 +0,0 @@
|
||||
//
|
||||
// main.m
|
||||
// CloudKitTodo
|
||||
//
|
||||
// Created by Robbie Hanson on 12/3/14.
|
||||
// Copyright (c) 2014 Deusty LLC. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
platform :ios, '10.0'
|
||||
|
||||
target 'CloudKitTodo' do
|
||||
pod 'Reachability', '3.2'
|
||||
pod "YapDatabase", path: '../../'
|
||||
end
|
||||
@ -1,85 +0,0 @@
|
||||
PODS:
|
||||
- CocoaLumberjack (3.2.1):
|
||||
- CocoaLumberjack/Default (= 3.2.1)
|
||||
- CocoaLumberjack/Extensions (= 3.2.1)
|
||||
- CocoaLumberjack/Default (3.2.1)
|
||||
- CocoaLumberjack/Extensions (3.2.1):
|
||||
- CocoaLumberjack/Default
|
||||
- Reachability (3.2)
|
||||
- YapDatabase (3.0.1):
|
||||
- YapDatabase/Standard (= 3.0.1)
|
||||
- YapDatabase/Standard (3.0.1):
|
||||
- YapDatabase/Standard/Core (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions (= 3.0.1)
|
||||
- YapDatabase/Standard/Core (3.0.1):
|
||||
- CocoaLumberjack
|
||||
- YapDatabase/Standard/Extensions (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/ActionManager (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/AutoView (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/CloudCore (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/CloudKit (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/ConnectionProxy (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/CrossProcessNotification (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/FilteredView (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/FullTextSearch (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/Hooks (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/ManualView (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/Relationships (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/RTreeIndex (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/SearchResultsView (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/SecondaryIndex (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/View (= 3.0.1)
|
||||
- YapDatabase/Standard/Extensions/ActionManager (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/AutoView
|
||||
- YapDatabase/Standard/Extensions/AutoView (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/View
|
||||
- YapDatabase/Standard/Extensions/CloudCore (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/CloudKit (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/ConnectionProxy (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/CrossProcessNotification (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/FilteredView (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/View
|
||||
- YapDatabase/Standard/Extensions/FullTextSearch (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/Hooks (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/ManualView (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/View
|
||||
- YapDatabase/Standard/Extensions/Relationships (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/RTreeIndex (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/SearchResultsView (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/AutoView
|
||||
- YapDatabase/Standard/Extensions/FullTextSearch
|
||||
- YapDatabase/Standard/Extensions/SecondaryIndex (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
- YapDatabase/Standard/Extensions/View (3.0.1):
|
||||
- YapDatabase/Standard/Core
|
||||
|
||||
DEPENDENCIES:
|
||||
- Reachability (= 3.2)
|
||||
- YapDatabase (from `../../`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
YapDatabase:
|
||||
:path: ../../
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
CocoaLumberjack: 2800c03334042fe80589423c8d80e582dcaec482
|
||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
||||
YapDatabase: 69ca88a2dfee305fde4412cf670c76f75aa7ccd0
|
||||
|
||||
PODFILE CHECKSUM: 39de1b3e71c8e5fd8a2fd5205a886ee12a55be4a
|
||||
|
||||
COCOAPODS: 1.3.1
|
||||
@ -1,86 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
/**
|
||||
* Welcome to CocoaLumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/CocoaLumberjack/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read "Getting Started" at:
|
||||
* Documentation/GettingStarted.md
|
||||
*
|
||||
* Otherwise, here is a quick refresher.
|
||||
* There are three steps to using the macros:
|
||||
*
|
||||
* Step 1:
|
||||
* Import the header in your implementation or prefix file:
|
||||
*
|
||||
* #import <CocoaLumberjack/CocoaLumberjack.h>
|
||||
*
|
||||
* Step 2:
|
||||
* Define your logging level in your implementation file:
|
||||
*
|
||||
* // Log levels: off, error, warn, info, verbose
|
||||
* static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
|
||||
*
|
||||
* Step 2 [3rd party frameworks]:
|
||||
*
|
||||
* Define your LOG_LEVEL_DEF to a different variable/function than ddLogLevel:
|
||||
*
|
||||
* // #undef LOG_LEVEL_DEF // Undefine first only if needed
|
||||
* #define LOG_LEVEL_DEF myLibLogLevel
|
||||
*
|
||||
* Define your logging level in your implementation file:
|
||||
*
|
||||
* // Log levels: off, error, warn, info, verbose
|
||||
* static const DDLogLevel myLibLogLevel = DDLogLevelVerbose;
|
||||
*
|
||||
* Step 3:
|
||||
* Replace your NSLog statements with DDLog statements according to the severity of the message.
|
||||
*
|
||||
* NSLog(@"Fatal error, no dohickey found!"); -> DDLogError(@"Fatal error, no dohickey found!");
|
||||
*
|
||||
* DDLog works exactly the same as NSLog.
|
||||
* This means you can pass it multiple variables just like NSLog.
|
||||
**/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
// Core
|
||||
#import "DDLog.h"
|
||||
|
||||
// Main macros
|
||||
#import "DDLogMacros.h"
|
||||
#import "DDAssertMacros.h"
|
||||
|
||||
// Capture ASL
|
||||
#import "DDASLLogCapture.h"
|
||||
|
||||
// Loggers
|
||||
#import "DDTTYLogger.h"
|
||||
#import "DDASLLogger.h"
|
||||
#import "DDFileLogger.h"
|
||||
#import "DDOSLogger.h"
|
||||
|
||||
// CLI
|
||||
#if __has_include("CLIColor.h") && TARGET_OS_OSX
|
||||
#import "CLIColor.h"
|
||||
#endif
|
||||
@ -1,95 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2014-2016, 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension DDLogFlag {
|
||||
public static func from(_ logLevel: DDLogLevel) -> DDLogFlag {
|
||||
return DDLogFlag(rawValue: logLevel.rawValue)
|
||||
}
|
||||
|
||||
public init(_ logLevel: DDLogLevel) {
|
||||
self = DDLogFlag(rawValue: logLevel.rawValue)
|
||||
}
|
||||
|
||||
///returns the log level, or the lowest equivalant.
|
||||
public func toLogLevel() -> DDLogLevel {
|
||||
if let ourValid = DDLogLevel(rawValue: rawValue) {
|
||||
return ourValid
|
||||
} else {
|
||||
if contains(.verbose) {
|
||||
return .verbose
|
||||
} else if contains(.debug) {
|
||||
return .debug
|
||||
} else if contains(.info) {
|
||||
return .info
|
||||
} else if contains(.warning) {
|
||||
return .warning
|
||||
} else if contains(.error) {
|
||||
return .error
|
||||
} else {
|
||||
return .off
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var defaultDebugLevel = DDLogLevel.verbose
|
||||
|
||||
public func resetDefaultDebugLevel() {
|
||||
defaultDebugLevel = DDLogLevel.verbose
|
||||
}
|
||||
|
||||
public func _DDLogMessage(_ message: @autoclosure () -> String, level: DDLogLevel, flag: DDLogFlag, context: Int, file: StaticString, function: StaticString, line: UInt, tag: Any?, asynchronous: Bool, ddlog: DDLog) {
|
||||
if level.rawValue & flag.rawValue != 0 {
|
||||
// Tell the DDLogMessage constructor to copy the C strings that get passed to it.
|
||||
let logMessage = DDLogMessage(message: message(), level: level, flag: flag, context: context, file: String(describing: file), function: String(describing: function), line: line, tag: tag, options: [.copyFile, .copyFunction], timestamp: nil)
|
||||
ddlog.log(asynchronous: asynchronous, message: logMessage)
|
||||
}
|
||||
}
|
||||
|
||||
public func DDLogDebug(_ message: @autoclosure () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, tag: Any? = nil, asynchronous async: Bool = true, ddlog: DDLog = DDLog.sharedInstance) {
|
||||
_DDLogMessage(message, level: level, flag: .debug, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
|
||||
}
|
||||
|
||||
public func DDLogInfo(_ message: @autoclosure () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, tag: Any? = nil, asynchronous async: Bool = true, ddlog: DDLog = DDLog.sharedInstance) {
|
||||
_DDLogMessage(message, level: level, flag: .info, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
|
||||
}
|
||||
|
||||
public func DDLogWarn(_ message: @autoclosure () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, tag: Any? = nil, asynchronous async: Bool = true, ddlog: DDLog = DDLog.sharedInstance) {
|
||||
_DDLogMessage(message, level: level, flag: .warning, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
|
||||
}
|
||||
|
||||
public func DDLogVerbose(_ message: @autoclosure () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, tag: Any? = nil, asynchronous async: Bool = true, ddlog: DDLog = DDLog.sharedInstance) {
|
||||
_DDLogMessage(message, level: level, flag: .verbose, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
|
||||
}
|
||||
|
||||
public func DDLogError(_ message: @autoclosure () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, tag: Any? = nil, asynchronous async: Bool = false, ddlog: DDLog = DDLog.sharedInstance) {
|
||||
_DDLogMessage(message, level: level, flag: .error, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
|
||||
}
|
||||
|
||||
/// Returns a String of the current filename, without full path or extension.
|
||||
///
|
||||
/// Analogous to the C preprocessor macro `THIS_FILE`.
|
||||
public func CurrentFileName(_ fileName: StaticString = #file) -> String {
|
||||
var str = String(describing: fileName)
|
||||
if let idx = str.range(of: "/", options: .backwards)?.upperBound {
|
||||
str = str.substring(from: idx)
|
||||
}
|
||||
if let idx = str.range(of: ".", options: .backwards)?.lowerBound {
|
||||
str = str.substring(to: idx)
|
||||
}
|
||||
return str
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import "DDASLLogger.h"
|
||||
|
||||
@protocol DDLogger;
|
||||
|
||||
/**
|
||||
* This class provides the ability to capture the ASL (Apple System Logs)
|
||||
*/
|
||||
@interface DDASLLogCapture : NSObject
|
||||
|
||||
/**
|
||||
* Start capturing logs
|
||||
*/
|
||||
+ (void)start;
|
||||
|
||||
/**
|
||||
* Stop capturing logs
|
||||
*/
|
||||
+ (void)stop;
|
||||
|
||||
/**
|
||||
* The current capture level.
|
||||
* @note Default log level: DDLogLevelVerbose (i.e. capture all ASL messages).
|
||||
*/
|
||||
@property (class) DDLogLevel captureLevel;
|
||||
|
||||
@end
|
||||
@ -1,230 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import "DDASLLogCapture.h"
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
#include <asl.h>
|
||||
#include <notify.h>
|
||||
#include <notify_keys.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
static BOOL _cancel = YES;
|
||||
static DDLogLevel _captureLevel = DDLogLevelVerbose;
|
||||
|
||||
#ifdef __IPHONE_8_0
|
||||
#define DDASL_IOS_PIVOT_VERSION __IPHONE_8_0
|
||||
#endif
|
||||
#ifdef __MAC_10_10
|
||||
#define DDASL_OSX_PIVOT_VERSION __MAC_10_10
|
||||
#endif
|
||||
|
||||
@implementation DDASLLogCapture
|
||||
|
||||
static aslmsg (*dd_asl_next)(aslresponse obj);
|
||||
static void (*dd_asl_release)(aslresponse obj);
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
#if (defined(DDASL_IOS_PIVOT_VERSION) && __IPHONE_OS_VERSION_MAX_ALLOWED >= DDASL_IOS_PIVOT_VERSION) || (defined(DDASL_OSX_PIVOT_VERSION) && __MAC_OS_X_VERSION_MAX_ALLOWED >= DDASL_OSX_PIVOT_VERSION)
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < DDASL_IOS_PIVOT_VERSION || __MAC_OS_X_VERSION_MIN_REQUIRED < DDASL_OSX_PIVOT_VERSION
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
// Building on falsely advertised SDK, targeting deprecated API
|
||||
dd_asl_next = &aslresponse_next;
|
||||
dd_asl_release = &aslresponse_free;
|
||||
#pragma GCC diagnostic pop
|
||||
#else
|
||||
// Building on lastest, correct SDK, targeting latest API
|
||||
dd_asl_next = &asl_next;
|
||||
dd_asl_release = &asl_release;
|
||||
#endif
|
||||
#else
|
||||
// Building on old SDKs, targeting deprecated API
|
||||
dd_asl_next = &aslresponse_next;
|
||||
dd_asl_release = &aslresponse_free;
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (void)start {
|
||||
// Ignore subsequent calls
|
||||
if (!_cancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
_cancel = NO;
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
|
||||
[self captureAslLogs];
|
||||
});
|
||||
}
|
||||
|
||||
+ (void)stop {
|
||||
_cancel = YES;
|
||||
}
|
||||
|
||||
+ (DDLogLevel)captureLevel {
|
||||
return _captureLevel;
|
||||
}
|
||||
|
||||
+ (void)setCaptureLevel:(DDLogLevel)level {
|
||||
_captureLevel = level;
|
||||
}
|
||||
|
||||
#pragma mark - Private methods
|
||||
|
||||
+ (void)configureAslQuery:(aslmsg)query {
|
||||
const char param[] = "7"; // ASL_LEVEL_DEBUG, which is everything. We'll rely on regular DDlog log level to filter
|
||||
|
||||
asl_set_query(query, ASL_KEY_LEVEL, param, ASL_QUERY_OP_LESS_EQUAL | ASL_QUERY_OP_NUMERIC);
|
||||
|
||||
// Don't retrieve logs from our own DDASLLogger
|
||||
asl_set_query(query, kDDASLKeyDDLog, kDDASLDDLogValue, ASL_QUERY_OP_NOT_EQUAL);
|
||||
|
||||
#if !TARGET_OS_IPHONE || TARGET_SIMULATOR
|
||||
int processId = [[NSProcessInfo processInfo] processIdentifier];
|
||||
char pid[16];
|
||||
sprintf(pid, "%d", processId);
|
||||
asl_set_query(query, ASL_KEY_PID, pid, ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_NUMERIC);
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (void)aslMessageReceived:(aslmsg)msg {
|
||||
const char* messageCString = asl_get( msg, ASL_KEY_MSG );
|
||||
if ( messageCString == NULL )
|
||||
return;
|
||||
|
||||
int flag;
|
||||
BOOL async;
|
||||
|
||||
const char* levelCString = asl_get(msg, ASL_KEY_LEVEL);
|
||||
switch (levelCString? atoi(levelCString) : 0) {
|
||||
// By default all NSLog's with a ASL_LEVEL_WARNING level
|
||||
case ASL_LEVEL_EMERG :
|
||||
case ASL_LEVEL_ALERT :
|
||||
case ASL_LEVEL_CRIT : flag = DDLogFlagError; async = NO; break;
|
||||
case ASL_LEVEL_ERR : flag = DDLogFlagWarning; async = YES; break;
|
||||
case ASL_LEVEL_WARNING : flag = DDLogFlagInfo; async = YES; break;
|
||||
case ASL_LEVEL_NOTICE : flag = DDLogFlagDebug; async = YES; break;
|
||||
case ASL_LEVEL_INFO :
|
||||
case ASL_LEVEL_DEBUG :
|
||||
default : flag = DDLogFlagVerbose; async = YES; break;
|
||||
}
|
||||
|
||||
if (!(_captureLevel & flag)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NSString * sender = [NSString stringWithCString:asl_get(msg, ASL_KEY_SENDER) encoding:NSUTF8StringEncoding];
|
||||
NSString *message = @(messageCString);
|
||||
|
||||
const char* secondsCString = asl_get( msg, ASL_KEY_TIME );
|
||||
const char* nanoCString = asl_get( msg, ASL_KEY_TIME_NSEC );
|
||||
NSTimeInterval seconds = secondsCString ? strtod(secondsCString, NULL) : [NSDate timeIntervalSinceReferenceDate] - NSTimeIntervalSince1970;
|
||||
double nanoSeconds = nanoCString? strtod(nanoCString, NULL) : 0;
|
||||
NSTimeInterval totalSeconds = seconds + (nanoSeconds / 1e9);
|
||||
|
||||
NSDate *timeStamp = [NSDate dateWithTimeIntervalSince1970:totalSeconds];
|
||||
|
||||
DDLogMessage *logMessage = [[DDLogMessage alloc]initWithMessage:message
|
||||
level:_captureLevel
|
||||
flag:flag
|
||||
context:0
|
||||
file:@"DDASLLogCapture"
|
||||
function:0
|
||||
line:0
|
||||
tag:nil
|
||||
options:0
|
||||
timestamp:timeStamp];
|
||||
|
||||
[DDLog log:async message:logMessage];
|
||||
}
|
||||
|
||||
+ (void)captureAslLogs {
|
||||
@autoreleasepool
|
||||
{
|
||||
/*
|
||||
We use ASL_KEY_MSG_ID to see each message once, but there's no
|
||||
obvious way to get the "next" ID. To bootstrap the process, we'll
|
||||
search by timestamp until we've seen a message.
|
||||
*/
|
||||
|
||||
struct timeval timeval = {
|
||||
.tv_sec = 0
|
||||
};
|
||||
gettimeofday(&timeval, NULL);
|
||||
unsigned long long startTime = timeval.tv_sec;
|
||||
__block unsigned long long lastSeenID = 0;
|
||||
|
||||
/*
|
||||
syslogd posts kNotifyASLDBUpdate (com.apple.system.logger.message)
|
||||
through the notify API when it saves messages to the ASL database.
|
||||
There is some coalescing - currently it is sent at most twice per
|
||||
second - but there is no documented guarantee about this. In any
|
||||
case, there may be multiple messages per notification.
|
||||
|
||||
Notify notifications don't carry any payload, so we need to search
|
||||
for the messages.
|
||||
*/
|
||||
int notifyToken = 0; // Can be used to unregister with notify_cancel().
|
||||
notify_register_dispatch(kNotifyASLDBUpdate, ¬ifyToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int token)
|
||||
{
|
||||
// At least one message has been posted; build a search query.
|
||||
@autoreleasepool
|
||||
{
|
||||
aslmsg query = asl_new(ASL_TYPE_QUERY);
|
||||
char stringValue[64];
|
||||
|
||||
if (lastSeenID > 0) {
|
||||
snprintf(stringValue, sizeof stringValue, "%llu", lastSeenID);
|
||||
asl_set_query(query, ASL_KEY_MSG_ID, stringValue, ASL_QUERY_OP_GREATER | ASL_QUERY_OP_NUMERIC);
|
||||
} else {
|
||||
snprintf(stringValue, sizeof stringValue, "%llu", startTime);
|
||||
asl_set_query(query, ASL_KEY_TIME, stringValue, ASL_QUERY_OP_GREATER_EQUAL | ASL_QUERY_OP_NUMERIC);
|
||||
}
|
||||
|
||||
[self configureAslQuery:query];
|
||||
|
||||
// Iterate over new messages.
|
||||
aslmsg msg;
|
||||
aslresponse response = asl_search(NULL, query);
|
||||
|
||||
while ((msg = dd_asl_next(response)))
|
||||
{
|
||||
[self aslMessageReceived:msg];
|
||||
|
||||
// Keep track of which messages we've seen.
|
||||
lastSeenID = atoll(asl_get(msg, ASL_KEY_MSG_ID));
|
||||
}
|
||||
dd_asl_release(response);
|
||||
asl_free(query);
|
||||
|
||||
if (_cancel) {
|
||||
notify_cancel(token);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,58 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
// Custom key set on messages sent to ASL
|
||||
extern const char* const kDDASLKeyDDLog;
|
||||
|
||||
// Value set for kDDASLKeyDDLog
|
||||
extern const char* const kDDASLDDLogValue;
|
||||
|
||||
/**
|
||||
* This class provides a logger for the Apple System Log facility.
|
||||
*
|
||||
* As described in the "Getting Started" page,
|
||||
* the traditional NSLog() function directs its output to two places:
|
||||
*
|
||||
* - Apple System Log
|
||||
* - StdErr (if stderr is a TTY) so log statements show up in Xcode console
|
||||
*
|
||||
* To duplicate NSLog() functionality you can simply add this logger and a tty logger.
|
||||
* However, if you instead choose to use file logging (for faster performance),
|
||||
* you may choose to use a file logger and a tty logger.
|
||||
**/
|
||||
@interface DDASLLogger : DDAbstractLogger <DDLogger>
|
||||
|
||||
/**
|
||||
* Singleton method
|
||||
*
|
||||
* @return the shared instance
|
||||
*/
|
||||
@property (class, readonly, strong) DDASLLogger *sharedInstance;
|
||||
|
||||
// Inherited from DDAbstractLogger
|
||||
|
||||
// - (id <DDLogFormatter>)logFormatter;
|
||||
// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
@end
|
||||
@ -1,121 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import "DDASLLogger.h"
|
||||
#import <asl.h>
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
const char* const kDDASLKeyDDLog = "DDLog";
|
||||
|
||||
const char* const kDDASLDDLogValue = "1";
|
||||
|
||||
static DDASLLogger *sharedInstance;
|
||||
|
||||
@interface DDASLLogger () {
|
||||
aslclient _client;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation DDASLLogger
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static dispatch_once_t DDASLLoggerOnceToken;
|
||||
|
||||
dispatch_once(&DDASLLoggerOnceToken, ^{
|
||||
sharedInstance = [[[self class] alloc] init];
|
||||
});
|
||||
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
if (sharedInstance != nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ((self = [super init])) {
|
||||
// A default asl client is provided for the main thread,
|
||||
// but background threads need to create their own client.
|
||||
|
||||
_client = asl_open(NULL, "com.apple.console", 0);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)logMessage:(DDLogMessage *)logMessage {
|
||||
// Skip captured log messages
|
||||
if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString * message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message;
|
||||
|
||||
if (message) {
|
||||
const char *msg = [message UTF8String];
|
||||
|
||||
size_t aslLogLevel;
|
||||
switch (logMessage->_flag) {
|
||||
// Note: By default ASL will filter anything above level 5 (Notice).
|
||||
// So our mappings shouldn't go above that level.
|
||||
case DDLogFlagError : aslLogLevel = ASL_LEVEL_CRIT; break;
|
||||
case DDLogFlagWarning : aslLogLevel = ASL_LEVEL_ERR; break;
|
||||
case DDLogFlagInfo : aslLogLevel = ASL_LEVEL_WARNING; break; // Regular NSLog's level
|
||||
case DDLogFlagDebug :
|
||||
case DDLogFlagVerbose :
|
||||
default : aslLogLevel = ASL_LEVEL_NOTICE; break;
|
||||
}
|
||||
|
||||
static char const *const level_strings[] = { "0", "1", "2", "3", "4", "5", "6", "7" };
|
||||
|
||||
// NSLog uses the current euid to set the ASL_KEY_READ_UID.
|
||||
uid_t const readUID = geteuid();
|
||||
|
||||
char readUIDString[16];
|
||||
#ifndef NS_BLOCK_ASSERTIONS
|
||||
size_t l = snprintf(readUIDString, sizeof(readUIDString), "%d", readUID);
|
||||
#else
|
||||
snprintf(readUIDString, sizeof(readUIDString), "%d", readUID);
|
||||
#endif
|
||||
|
||||
NSAssert(l < sizeof(readUIDString),
|
||||
@"Formatted euid is too long.");
|
||||
NSAssert(aslLogLevel < (sizeof(level_strings) / sizeof(level_strings[0])),
|
||||
@"Unhandled ASL log level.");
|
||||
|
||||
aslmsg m = asl_new(ASL_TYPE_MSG);
|
||||
if (m != NULL) {
|
||||
if (asl_set(m, ASL_KEY_LEVEL, level_strings[aslLogLevel]) == 0 &&
|
||||
asl_set(m, ASL_KEY_MSG, msg) == 0 &&
|
||||
asl_set(m, ASL_KEY_READ_UID, readUIDString) == 0 &&
|
||||
asl_set(m, kDDASLKeyDDLog, kDDASLDDLogValue) == 0) {
|
||||
asl_send(_client, m);
|
||||
}
|
||||
asl_free(m);
|
||||
}
|
||||
//TODO handle asl_* failures non-silently?
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)loggerName {
|
||||
return @"cocoa.lumberjack.aslLogger";
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,123 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* This class provides an abstract implementation of a database logger.
|
||||
*
|
||||
* That is, it provides the base implementation for a database logger to build atop of.
|
||||
* All that is needed for a concrete database logger is to extend this class
|
||||
* and override the methods in the implementation file that are prefixed with "db_".
|
||||
**/
|
||||
@interface DDAbstractDatabaseLogger : DDAbstractLogger {
|
||||
|
||||
@protected
|
||||
NSUInteger _saveThreshold;
|
||||
NSTimeInterval _saveInterval;
|
||||
NSTimeInterval _maxAge;
|
||||
NSTimeInterval _deleteInterval;
|
||||
BOOL _deleteOnEverySave;
|
||||
|
||||
BOOL _saveTimerSuspended;
|
||||
NSUInteger _unsavedCount;
|
||||
dispatch_time_t _unsavedTime;
|
||||
dispatch_source_t _saveTimer;
|
||||
dispatch_time_t _lastDeleteTime;
|
||||
dispatch_source_t _deleteTimer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies how often to save the data to disk.
|
||||
* Since saving is an expensive operation (disk io) it is not done after every log statement.
|
||||
* These properties allow you to configure how/when the logger saves to disk.
|
||||
*
|
||||
* A save is done when either (whichever happens first):
|
||||
*
|
||||
* - The number of unsaved log entries reaches saveThreshold
|
||||
* - The amount of time since the oldest unsaved log entry was created reaches saveInterval
|
||||
*
|
||||
* You can optionally disable the saveThreshold by setting it to zero.
|
||||
* If you disable the saveThreshold you are entirely dependent on the saveInterval.
|
||||
*
|
||||
* You can optionally disable the saveInterval by setting it to zero (or a negative value).
|
||||
* If you disable the saveInterval you are entirely dependent on the saveThreshold.
|
||||
*
|
||||
* It's not wise to disable both saveThreshold and saveInterval.
|
||||
*
|
||||
* The default saveThreshold is 500.
|
||||
* The default saveInterval is 60 seconds.
|
||||
**/
|
||||
@property (assign, readwrite) NSUInteger saveThreshold;
|
||||
|
||||
/**
|
||||
* See the description for the `saveThreshold` property
|
||||
*/
|
||||
@property (assign, readwrite) NSTimeInterval saveInterval;
|
||||
|
||||
/**
|
||||
* It is likely you don't want the log entries to persist forever.
|
||||
* Doing so would allow the database to grow infinitely large over time.
|
||||
*
|
||||
* The maxAge property provides a way to specify how old a log statement can get
|
||||
* before it should get deleted from the database.
|
||||
*
|
||||
* The deleteInterval specifies how often to sweep for old log entries.
|
||||
* Since deleting is an expensive operation (disk io) is is done on a fixed interval.
|
||||
*
|
||||
* An alternative to the deleteInterval is the deleteOnEverySave option.
|
||||
* This specifies that old log entries should be deleted during every save operation.
|
||||
*
|
||||
* You can optionally disable the maxAge by setting it to zero (or a negative value).
|
||||
* If you disable the maxAge then old log statements are not deleted.
|
||||
*
|
||||
* You can optionally disable the deleteInterval by setting it to zero (or a negative value).
|
||||
*
|
||||
* If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted.
|
||||
*
|
||||
* It's not wise to enable both deleteInterval and deleteOnEverySave.
|
||||
*
|
||||
* The default maxAge is 7 days.
|
||||
* The default deleteInterval is 5 minutes.
|
||||
* The default deleteOnEverySave is NO.
|
||||
**/
|
||||
@property (assign, readwrite) NSTimeInterval maxAge;
|
||||
|
||||
/**
|
||||
* See the description for the `maxAge` property
|
||||
*/
|
||||
@property (assign, readwrite) NSTimeInterval deleteInterval;
|
||||
|
||||
/**
|
||||
* See the description for the `maxAge` property
|
||||
*/
|
||||
@property (assign, readwrite) BOOL deleteOnEverySave;
|
||||
|
||||
/**
|
||||
* Forces a save of any pending log entries (flushes log entries to disk).
|
||||
**/
|
||||
- (void)savePendingLogEntries;
|
||||
|
||||
/**
|
||||
* Removes any log entries that are older than maxAge.
|
||||
**/
|
||||
- (void)deleteOldLogEntries;
|
||||
|
||||
@end
|
||||
@ -1,660 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import "DDAbstractDatabaseLogger.h"
|
||||
#import <math.h>
|
||||
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
@interface DDAbstractDatabaseLogger ()
|
||||
|
||||
- (void)destroySaveTimer;
|
||||
- (void)destroyDeleteTimer;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation DDAbstractDatabaseLogger
|
||||
|
||||
- (instancetype)init {
|
||||
if ((self = [super init])) {
|
||||
_saveThreshold = 500;
|
||||
_saveInterval = 60; // 60 seconds
|
||||
_maxAge = (60 * 60 * 24 * 7); // 7 days
|
||||
_deleteInterval = (60 * 5); // 5 minutes
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self destroySaveTimer];
|
||||
[self destroyDeleteTimer];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Override Me
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (BOOL)db_log:(DDLogMessage *)logMessage {
|
||||
// Override me and add your implementation.
|
||||
//
|
||||
// Return YES if an item was added to the buffer.
|
||||
// Return NO if the logMessage was ignored.
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)db_save {
|
||||
// Override me and add your implementation.
|
||||
}
|
||||
|
||||
- (void)db_delete {
|
||||
// Override me and add your implementation.
|
||||
}
|
||||
|
||||
- (void)db_saveAndDelete {
|
||||
// Override me and add your implementation.
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Private API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)performSaveAndSuspendSaveTimer {
|
||||
if (_unsavedCount > 0) {
|
||||
if (_deleteOnEverySave) {
|
||||
[self db_saveAndDelete];
|
||||
} else {
|
||||
[self db_save];
|
||||
}
|
||||
}
|
||||
|
||||
_unsavedCount = 0;
|
||||
_unsavedTime = 0;
|
||||
|
||||
if (_saveTimer && !_saveTimerSuspended) {
|
||||
dispatch_suspend(_saveTimer);
|
||||
_saveTimerSuspended = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)performDelete {
|
||||
if (_maxAge > 0.0) {
|
||||
[self db_delete];
|
||||
|
||||
_lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Timers
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)destroySaveTimer {
|
||||
if (_saveTimer) {
|
||||
dispatch_source_cancel(_saveTimer);
|
||||
|
||||
if (_saveTimerSuspended) {
|
||||
// Must resume a timer before releasing it (or it will crash)
|
||||
dispatch_resume(_saveTimer);
|
||||
_saveTimerSuspended = NO;
|
||||
}
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(_saveTimer);
|
||||
#endif
|
||||
_saveTimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateAndResumeSaveTimer {
|
||||
if ((_saveTimer != NULL) && (_saveInterval > 0.0) && (_unsavedTime > 0.0)) {
|
||||
uint64_t interval = (uint64_t)(_saveInterval * (NSTimeInterval) NSEC_PER_SEC);
|
||||
dispatch_time_t startTime = dispatch_time(_unsavedTime, interval);
|
||||
|
||||
dispatch_source_set_timer(_saveTimer, startTime, interval, 1ull * NSEC_PER_SEC);
|
||||
|
||||
if (_saveTimerSuspended) {
|
||||
dispatch_resume(_saveTimer);
|
||||
_saveTimerSuspended = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)createSuspendedSaveTimer {
|
||||
if ((_saveTimer == NULL) && (_saveInterval > 0.0)) {
|
||||
_saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue);
|
||||
|
||||
dispatch_source_set_event_handler(_saveTimer, ^{ @autoreleasepool {
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
} });
|
||||
|
||||
_saveTimerSuspended = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)destroyDeleteTimer {
|
||||
if (_deleteTimer) {
|
||||
dispatch_source_cancel(_deleteTimer);
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(_deleteTimer);
|
||||
#endif
|
||||
_deleteTimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateDeleteTimer {
|
||||
if ((_deleteTimer != NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) {
|
||||
uint64_t interval = (uint64_t)(_deleteInterval * (NSTimeInterval) NSEC_PER_SEC);
|
||||
dispatch_time_t startTime;
|
||||
|
||||
if (_lastDeleteTime > 0) {
|
||||
startTime = dispatch_time(_lastDeleteTime, interval);
|
||||
} else {
|
||||
startTime = dispatch_time(DISPATCH_TIME_NOW, interval);
|
||||
}
|
||||
|
||||
dispatch_source_set_timer(_deleteTimer, startTime, interval, 1ull * NSEC_PER_SEC);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)createAndStartDeleteTimer {
|
||||
if ((_deleteTimer == NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) {
|
||||
_deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue);
|
||||
|
||||
if (_deleteTimer != NULL) {
|
||||
dispatch_source_set_event_handler(_deleteTimer, ^{ @autoreleasepool {
|
||||
[self performDelete];
|
||||
} });
|
||||
|
||||
[self updateDeleteTimer];
|
||||
|
||||
if (_deleteTimer != NULL) {
|
||||
dispatch_resume(_deleteTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Configuration
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSUInteger)saveThreshold {
|
||||
// The design of this method is taken from the DDAbstractLogger implementation.
|
||||
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
// Note: The internal implementation MUST access the colorsEnabled variable directly,
|
||||
// This method is designed explicitly for external access.
|
||||
//
|
||||
// Using "self." syntax to go through this method will cause immediate deadlock.
|
||||
// This is the intended result. Fix it by accessing the ivar directly.
|
||||
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
||||
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
||||
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
|
||||
__block NSUInteger result;
|
||||
|
||||
dispatch_sync(globalLoggingQueue, ^{
|
||||
dispatch_sync(self.loggerQueue, ^{
|
||||
result = _saveThreshold;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setSaveThreshold:(NSUInteger)threshold {
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
if (_saveThreshold != threshold) {
|
||||
_saveThreshold = threshold;
|
||||
|
||||
// Since the saveThreshold has changed,
|
||||
// we check to see if the current unsavedCount has surpassed the new threshold.
|
||||
//
|
||||
// If it has, we immediately save the log.
|
||||
|
||||
if ((_unsavedCount >= _saveThreshold) && (_saveThreshold > 0)) {
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The design of the setter logic below is taken from the DDAbstractLogger implementation.
|
||||
// For documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
if ([self isOnInternalLoggerQueue]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
|
||||
dispatch_async(globalLoggingQueue, ^{
|
||||
dispatch_async(self.loggerQueue, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTimeInterval)saveInterval {
|
||||
// The design of this method is taken from the DDAbstractLogger implementation.
|
||||
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
// Note: The internal implementation MUST access the colorsEnabled variable directly,
|
||||
// This method is designed explicitly for external access.
|
||||
//
|
||||
// Using "self." syntax to go through this method will cause immediate deadlock.
|
||||
// This is the intended result. Fix it by accessing the ivar directly.
|
||||
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
||||
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
||||
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
|
||||
__block NSTimeInterval result;
|
||||
|
||||
dispatch_sync(globalLoggingQueue, ^{
|
||||
dispatch_sync(self.loggerQueue, ^{
|
||||
result = _saveInterval;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setSaveInterval:(NSTimeInterval)interval {
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
// C99 recommended floating point comparison macro
|
||||
// Read: isLessThanOrGreaterThan(floatA, floatB)
|
||||
|
||||
if (/* saveInterval != interval */ islessgreater(_saveInterval, interval)) {
|
||||
_saveInterval = interval;
|
||||
|
||||
// There are several cases we need to handle here.
|
||||
//
|
||||
// 1. If the saveInterval was previously enabled and it just got disabled,
|
||||
// then we need to stop the saveTimer. (And we might as well release it.)
|
||||
//
|
||||
// 2. If the saveInterval was previously disabled and it just got enabled,
|
||||
// then we need to setup the saveTimer. (Plus we might need to do an immediate save.)
|
||||
//
|
||||
// 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date.
|
||||
//
|
||||
// 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date.
|
||||
// (Plus we might need to do an immediate save.)
|
||||
|
||||
if (_saveInterval > 0.0) {
|
||||
if (_saveTimer == NULL) {
|
||||
// Handles #2
|
||||
//
|
||||
// Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
|
||||
// if a save is needed the timer will fire immediately.
|
||||
|
||||
[self createSuspendedSaveTimer];
|
||||
[self updateAndResumeSaveTimer];
|
||||
} else {
|
||||
// Handles #3
|
||||
// Handles #4
|
||||
//
|
||||
// Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
|
||||
// if a save is needed the timer will fire immediately.
|
||||
|
||||
[self updateAndResumeSaveTimer];
|
||||
}
|
||||
} else if (_saveTimer) {
|
||||
// Handles #1
|
||||
|
||||
[self destroySaveTimer];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The design of the setter logic below is taken from the DDAbstractLogger implementation.
|
||||
// For documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
if ([self isOnInternalLoggerQueue]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
|
||||
dispatch_async(globalLoggingQueue, ^{
|
||||
dispatch_async(self.loggerQueue, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTimeInterval)maxAge {
|
||||
// The design of this method is taken from the DDAbstractLogger implementation.
|
||||
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
// Note: The internal implementation MUST access the colorsEnabled variable directly,
|
||||
// This method is designed explicitly for external access.
|
||||
//
|
||||
// Using "self." syntax to go through this method will cause immediate deadlock.
|
||||
// This is the intended result. Fix it by accessing the ivar directly.
|
||||
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
||||
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
||||
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
|
||||
__block NSTimeInterval result;
|
||||
|
||||
dispatch_sync(globalLoggingQueue, ^{
|
||||
dispatch_sync(self.loggerQueue, ^{
|
||||
result = _maxAge;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setMaxAge:(NSTimeInterval)interval {
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
// C99 recommended floating point comparison macro
|
||||
// Read: isLessThanOrGreaterThan(floatA, floatB)
|
||||
|
||||
if (/* maxAge != interval */ islessgreater(_maxAge, interval)) {
|
||||
NSTimeInterval oldMaxAge = _maxAge;
|
||||
NSTimeInterval newMaxAge = interval;
|
||||
|
||||
_maxAge = interval;
|
||||
|
||||
// There are several cases we need to handle here.
|
||||
//
|
||||
// 1. If the maxAge was previously enabled and it just got disabled,
|
||||
// then we need to stop the deleteTimer. (And we might as well release it.)
|
||||
//
|
||||
// 2. If the maxAge was previously disabled and it just got enabled,
|
||||
// then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
|
||||
//
|
||||
// 3. If the maxAge was increased,
|
||||
// then we don't need to do anything.
|
||||
//
|
||||
// 4. If the maxAge was decreased,
|
||||
// then we should do an immediate delete.
|
||||
|
||||
BOOL shouldDeleteNow = NO;
|
||||
|
||||
if (oldMaxAge > 0.0) {
|
||||
if (newMaxAge <= 0.0) {
|
||||
// Handles #1
|
||||
|
||||
[self destroyDeleteTimer];
|
||||
} else if (oldMaxAge > newMaxAge) {
|
||||
// Handles #4
|
||||
shouldDeleteNow = YES;
|
||||
}
|
||||
} else if (newMaxAge > 0.0) {
|
||||
// Handles #2
|
||||
shouldDeleteNow = YES;
|
||||
}
|
||||
|
||||
if (shouldDeleteNow) {
|
||||
[self performDelete];
|
||||
|
||||
if (_deleteTimer) {
|
||||
[self updateDeleteTimer];
|
||||
} else {
|
||||
[self createAndStartDeleteTimer];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The design of the setter logic below is taken from the DDAbstractLogger implementation.
|
||||
// For documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
if ([self isOnInternalLoggerQueue]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
|
||||
dispatch_async(globalLoggingQueue, ^{
|
||||
dispatch_async(self.loggerQueue, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTimeInterval)deleteInterval {
|
||||
// The design of this method is taken from the DDAbstractLogger implementation.
|
||||
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
// Note: The internal implementation MUST access the colorsEnabled variable directly,
|
||||
// This method is designed explicitly for external access.
|
||||
//
|
||||
// Using "self." syntax to go through this method will cause immediate deadlock.
|
||||
// This is the intended result. Fix it by accessing the ivar directly.
|
||||
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
||||
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
||||
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
|
||||
__block NSTimeInterval result;
|
||||
|
||||
dispatch_sync(globalLoggingQueue, ^{
|
||||
dispatch_sync(self.loggerQueue, ^{
|
||||
result = _deleteInterval;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setDeleteInterval:(NSTimeInterval)interval {
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
// C99 recommended floating point comparison macro
|
||||
// Read: isLessThanOrGreaterThan(floatA, floatB)
|
||||
|
||||
if (/* deleteInterval != interval */ islessgreater(_deleteInterval, interval)) {
|
||||
_deleteInterval = interval;
|
||||
|
||||
// There are several cases we need to handle here.
|
||||
//
|
||||
// 1. If the deleteInterval was previously enabled and it just got disabled,
|
||||
// then we need to stop the deleteTimer. (And we might as well release it.)
|
||||
//
|
||||
// 2. If the deleteInterval was previously disabled and it just got enabled,
|
||||
// then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
|
||||
//
|
||||
// 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date.
|
||||
//
|
||||
// 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date.
|
||||
// (Plus we might need to do an immediate delete.)
|
||||
|
||||
if (_deleteInterval > 0.0) {
|
||||
if (_deleteTimer == NULL) {
|
||||
// Handles #2
|
||||
//
|
||||
// Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
|
||||
// if a delete is needed the timer will fire immediately.
|
||||
|
||||
[self createAndStartDeleteTimer];
|
||||
} else {
|
||||
// Handles #3
|
||||
// Handles #4
|
||||
//
|
||||
// Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
|
||||
// if a save is needed the timer will fire immediately.
|
||||
|
||||
[self updateDeleteTimer];
|
||||
}
|
||||
} else if (_deleteTimer) {
|
||||
// Handles #1
|
||||
|
||||
[self destroyDeleteTimer];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The design of the setter logic below is taken from the DDAbstractLogger implementation.
|
||||
// For documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
if ([self isOnInternalLoggerQueue]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
|
||||
dispatch_async(globalLoggingQueue, ^{
|
||||
dispatch_async(self.loggerQueue, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)deleteOnEverySave {
|
||||
// The design of this method is taken from the DDAbstractLogger implementation.
|
||||
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
// Note: The internal implementation MUST access the colorsEnabled variable directly,
|
||||
// This method is designed explicitly for external access.
|
||||
//
|
||||
// Using "self." syntax to go through this method will cause immediate deadlock.
|
||||
// This is the intended result. Fix it by accessing the ivar directly.
|
||||
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
||||
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
||||
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
|
||||
__block BOOL result;
|
||||
|
||||
dispatch_sync(globalLoggingQueue, ^{
|
||||
dispatch_sync(self.loggerQueue, ^{
|
||||
result = _deleteOnEverySave;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setDeleteOnEverySave:(BOOL)flag {
|
||||
dispatch_block_t block = ^{
|
||||
_deleteOnEverySave = flag;
|
||||
};
|
||||
|
||||
// The design of the setter logic below is taken from the DDAbstractLogger implementation.
|
||||
// For documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
if ([self isOnInternalLoggerQueue]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
|
||||
dispatch_async(globalLoggingQueue, ^{
|
||||
dispatch_async(self.loggerQueue, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Public API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)savePendingLogEntries {
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
}
|
||||
};
|
||||
|
||||
if ([self isOnInternalLoggerQueue]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_async(self.loggerQueue, block);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deleteOldLogEntries {
|
||||
dispatch_block_t block = ^{
|
||||
@autoreleasepool {
|
||||
[self performDelete];
|
||||
}
|
||||
};
|
||||
|
||||
if ([self isOnInternalLoggerQueue]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_async(self.loggerQueue, block);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark DDLogger
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)didAddLogger {
|
||||
// If you override me be sure to invoke [super didAddLogger];
|
||||
|
||||
[self createSuspendedSaveTimer];
|
||||
|
||||
[self createAndStartDeleteTimer];
|
||||
}
|
||||
|
||||
- (void)willRemoveLogger {
|
||||
// If you override me be sure to invoke [super willRemoveLogger];
|
||||
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
|
||||
[self destroySaveTimer];
|
||||
[self destroyDeleteTimer];
|
||||
}
|
||||
|
||||
- (void)logMessage:(DDLogMessage *)logMessage {
|
||||
if ([self db_log:logMessage]) {
|
||||
BOOL firstUnsavedEntry = (++_unsavedCount == 1);
|
||||
|
||||
if ((_unsavedCount >= _saveThreshold) && (_saveThreshold > 0)) {
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
} else if (firstUnsavedEntry) {
|
||||
_unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0);
|
||||
[self updateAndResumeSaveTimer];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)flush {
|
||||
// This method is invoked by DDLog's flushLog method.
|
||||
//
|
||||
// It is called automatically when the application quits,
|
||||
// or if the developer invokes DDLog's flushLog method prior to crashing or something.
|
||||
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,26 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
/**
|
||||
* NSAsset replacement that will output a log message even when assertions are disabled.
|
||||
**/
|
||||
#define DDAssert(condition, frmt, ...) \
|
||||
if (!(condition)) { \
|
||||
NSString *description = [NSString stringWithFormat:frmt, ## __VA_ARGS__]; \
|
||||
DDLogError(@"%@", description); \
|
||||
NSAssert(NO, description); \
|
||||
}
|
||||
#define DDAssertCondition(condition) DDAssert(condition, @"Condition not satisfied: %s", #condition)
|
||||
|
||||
@ -1,512 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
@class DDLogFileInfo;
|
||||
|
||||
/**
|
||||
* This class provides a logger to write log statements to a file.
|
||||
**/
|
||||
|
||||
|
||||
// Default configuration and safety/sanity values.
|
||||
//
|
||||
// maximumFileSize -> kDDDefaultLogMaxFileSize
|
||||
// rollingFrequency -> kDDDefaultLogRollingFrequency
|
||||
// maximumNumberOfLogFiles -> kDDDefaultLogMaxNumLogFiles
|
||||
// logFilesDiskQuota -> kDDDefaultLogFilesDiskQuota
|
||||
//
|
||||
// You should carefully consider the proper configuration values for your application.
|
||||
|
||||
extern unsigned long long const kDDDefaultLogMaxFileSize;
|
||||
extern NSTimeInterval const kDDDefaultLogRollingFrequency;
|
||||
extern NSUInteger const kDDDefaultLogMaxNumLogFiles;
|
||||
extern unsigned long long const kDDDefaultLogFilesDiskQuota;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The LogFileManager protocol is designed to allow you to control all aspects of your log files.
|
||||
*
|
||||
* The primary purpose of this is to allow you to do something with the log files after they have been rolled.
|
||||
* Perhaps you want to compress them to save disk space.
|
||||
* Perhaps you want to upload them to an FTP server.
|
||||
* Perhaps you want to run some analytics on the file.
|
||||
*
|
||||
* A default LogFileManager is, of course, provided.
|
||||
* The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property.
|
||||
*
|
||||
* This protocol provides various methods to fetch the list of log files.
|
||||
*
|
||||
* There are two variants: sorted and unsorted.
|
||||
* If sorting is not necessary, the unsorted variant is obviously faster.
|
||||
* The sorted variant will return an array sorted by when the log files were created,
|
||||
* with the most recently created log file at index 0, and the oldest log file at the end of the array.
|
||||
*
|
||||
* You can fetch only the log file paths (full path including name), log file names (name only),
|
||||
* or an array of `DDLogFileInfo` objects.
|
||||
* The `DDLogFileInfo` class is documented below, and provides a handy wrapper that
|
||||
* gives you easy access to various file attributes such as the creation date or the file size.
|
||||
*/
|
||||
@protocol DDLogFileManager <NSObject>
|
||||
@required
|
||||
|
||||
// Public properties
|
||||
|
||||
/**
|
||||
* The maximum number of archived log files to keep on disk.
|
||||
* For example, if this property is set to 3,
|
||||
* then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk.
|
||||
* Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted.
|
||||
*
|
||||
* You may optionally disable this option by setting it to zero.
|
||||
**/
|
||||
@property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;
|
||||
|
||||
/**
|
||||
* The maximum space that logs can take. On rolling logfile all old logfiles that exceed logFilesDiskQuota will
|
||||
* be deleted.
|
||||
*
|
||||
* You may optionally disable this option by setting it to zero.
|
||||
**/
|
||||
@property (readwrite, assign, atomic) unsigned long long logFilesDiskQuota;
|
||||
|
||||
// Public methods
|
||||
|
||||
/**
|
||||
* Returns the logs directory (path)
|
||||
*/
|
||||
@property (nonatomic, readonly, copy) NSString *logsDirectory;
|
||||
|
||||
/**
|
||||
* Returns an array of `NSString` objects,
|
||||
* each of which is the filePath to an existing log file on disk.
|
||||
**/
|
||||
@property (nonatomic, readonly, strong) NSArray<NSString *> *unsortedLogFilePaths;
|
||||
|
||||
/**
|
||||
* Returns an array of `NSString` objects,
|
||||
* each of which is the fileName of an existing log file on disk.
|
||||
**/
|
||||
@property (nonatomic, readonly, strong) NSArray<NSString *> *unsortedLogFileNames;
|
||||
|
||||
/**
|
||||
* Returns an array of `DDLogFileInfo` objects,
|
||||
* each representing an existing log file on disk,
|
||||
* and containing important information about the log file such as it's modification date and size.
|
||||
**/
|
||||
@property (nonatomic, readonly, strong) NSArray<DDLogFileInfo *> *unsortedLogFileInfos;
|
||||
|
||||
/**
|
||||
* Just like the `unsortedLogFilePaths` method, but sorts the array.
|
||||
* The items in the array are sorted by creation date.
|
||||
* The first item in the array will be the most recently created log file.
|
||||
**/
|
||||
@property (nonatomic, readonly, strong) NSArray<NSString *> *sortedLogFilePaths;
|
||||
|
||||
/**
|
||||
* Just like the `unsortedLogFileNames` method, but sorts the array.
|
||||
* The items in the array are sorted by creation date.
|
||||
* The first item in the array will be the most recently created log file.
|
||||
**/
|
||||
@property (nonatomic, readonly, strong) NSArray<NSString *> *sortedLogFileNames;
|
||||
|
||||
/**
|
||||
* Just like the `unsortedLogFileInfos` method, but sorts the array.
|
||||
* The items in the array are sorted by creation date.
|
||||
* The first item in the array will be the most recently created log file.
|
||||
**/
|
||||
@property (nonatomic, readonly, strong) NSArray<DDLogFileInfo *> *sortedLogFileInfos;
|
||||
|
||||
// Private methods (only to be used by DDFileLogger)
|
||||
|
||||
/**
|
||||
* Generates a new unique log file path, and creates the corresponding log file.
|
||||
**/
|
||||
- (NSString *)createNewLogFile;
|
||||
|
||||
@optional
|
||||
|
||||
// Notifications from DDFileLogger
|
||||
|
||||
/**
|
||||
* Called when a log file was archieved
|
||||
*/
|
||||
- (void)didArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didArchiveLogFile(atPath:));
|
||||
|
||||
/**
|
||||
* Called when the roll action was executed and the log was archieved
|
||||
*/
|
||||
- (void)didRollAndArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didRollAndArchiveLogFile(atPath:));
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Default log file manager.
|
||||
*
|
||||
* All log files are placed inside the logsDirectory.
|
||||
* If a specific logsDirectory isn't specified, the default directory is used.
|
||||
* On Mac, this is in `~/Library/Logs/<Application Name>`.
|
||||
* On iPhone, this is in `~/Library/Caches/Logs`.
|
||||
*
|
||||
* Log files are named `"<bundle identifier> <date> <time>.log"`
|
||||
* Example: `com.organization.myapp 2013-12-03 17-14.log`
|
||||
*
|
||||
* Archived log files are automatically deleted according to the `maximumNumberOfLogFiles` property.
|
||||
**/
|
||||
@interface DDLogFileManagerDefault : NSObject <DDLogFileManager>
|
||||
|
||||
/**
|
||||
* Default initializer
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
* Designated initialized, requires the logs directory
|
||||
*/
|
||||
- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
/*
|
||||
* Calling this constructor you can override the default "automagically" chosen NSFileProtection level.
|
||||
* Useful if you are writing a command line utility / CydiaSubstrate addon for iOS that has no NSBundle
|
||||
* or like SpringBoard no BackgroundModes key in the NSBundle:
|
||||
* iPhone:~ root# cycript -p SpringBoard
|
||||
* cy# [NSBundle mainBundle]
|
||||
* #"NSBundle </System/Library/CoreServices/SpringBoard.app> (loaded)"
|
||||
* cy# [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
|
||||
* null
|
||||
* cy#
|
||||
**/
|
||||
- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory defaultFileProtectionLevel:(NSFileProtectionType)fileProtectionLevel;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Methods to override.
|
||||
*
|
||||
* Log files are named `"<bundle identifier> <date> <time>.log"`
|
||||
* Example: `com.organization.myapp 2013-12-03 17-14.log`
|
||||
*
|
||||
* If you wish to change default filename, you can override following two methods.
|
||||
* - `newLogFileName` method would be called on new logfile creation.
|
||||
* - `isLogFile:` method would be called to filter logfiles from all other files in logsDirectory.
|
||||
* You have to parse given filename and return YES if it is logFile.
|
||||
*
|
||||
* **NOTE**
|
||||
* `newLogFileName` returns filename. If appropriate file already exists, number would be added
|
||||
* to filename before extension. You have to handle this case in isLogFile: method.
|
||||
*
|
||||
* Example:
|
||||
* - newLogFileName returns `"com.organization.myapp 2013-12-03.log"`,
|
||||
* file `"com.organization.myapp 2013-12-03.log"` would be created.
|
||||
* - after some time `"com.organization.myapp 2013-12-03.log"` is archived
|
||||
* - newLogFileName again returns `"com.organization.myapp 2013-12-03.log"`,
|
||||
* file `"com.organization.myapp 2013-12-03 2.log"` would be created.
|
||||
* - after some time `"com.organization.myapp 2013-12-03 1.log"` is archived
|
||||
* - newLogFileName again returns `"com.organization.myapp 2013-12-03.log"`,
|
||||
* file `"com.organization.myapp 2013-12-03 3.log"` would be created.
|
||||
**/
|
||||
|
||||
/**
|
||||
* Generates log file name with default format `"<bundle identifier> <date> <time>.log"`
|
||||
* Example: `MobileSafari 2013-12-03 17-14.log`
|
||||
*
|
||||
* You can change it by overriding `newLogFileName` and `isLogFile:` methods.
|
||||
**/
|
||||
@property (readonly, copy) NSString *newLogFileName;
|
||||
|
||||
/**
|
||||
* Default log file name is `"<bundle identifier> <date> <time>.log"`.
|
||||
* Example: `MobileSafari 2013-12-03 17-14.log`
|
||||
*
|
||||
* You can change it by overriding `newLogFileName` and `isLogFile:` methods.
|
||||
**/
|
||||
- (BOOL)isLogFile:(NSString *)fileName NS_SWIFT_NAME(isLogFile(withName:));
|
||||
|
||||
/* Inherited from DDLogFileManager protocol:
|
||||
|
||||
@property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;
|
||||
@property (readwrite, assign, atomic) NSUInteger logFilesDiskQuota;
|
||||
|
||||
- (NSString *)logsDirectory;
|
||||
|
||||
- (NSArray *)unsortedLogFilePaths;
|
||||
- (NSArray *)unsortedLogFileNames;
|
||||
- (NSArray *)unsortedLogFileInfos;
|
||||
|
||||
- (NSArray *)sortedLogFilePaths;
|
||||
- (NSArray *)sortedLogFileNames;
|
||||
- (NSArray *)sortedLogFileInfos;
|
||||
|
||||
*/
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Most users will want file log messages to be prepended with the date and time.
|
||||
* Rather than forcing the majority of users to write their own formatter,
|
||||
* we will supply a logical default formatter.
|
||||
* Users can easily replace this formatter with their own by invoking the `setLogFormatter:` method.
|
||||
* It can also be removed by calling `setLogFormatter:`, and passing a nil parameter.
|
||||
*
|
||||
* In addition to the convenience of having a logical default formatter,
|
||||
* it will also provide a template that makes it easy for developers to copy and change.
|
||||
**/
|
||||
@interface DDLogFileFormatterDefault : NSObject <DDLogFormatter>
|
||||
|
||||
/**
|
||||
* Default initializer
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
* Designated initializer, requires a date formatter
|
||||
*/
|
||||
- (instancetype)initWithDateFormatter:(NSDateFormatter *)dateFormatter NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The standard implementation for a file logger
|
||||
*/
|
||||
@interface DDFileLogger : DDAbstractLogger <DDLogger> {
|
||||
DDLogFileInfo *_currentLogFileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default initializer
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
* Designated initializer, requires a `DDLogFileManager` instance
|
||||
*/
|
||||
- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)logFileManager NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Called when the logger is about to write message. Call super before your implementation.
|
||||
*/
|
||||
- (void)willLogMessage NS_REQUIRES_SUPER;
|
||||
|
||||
/**
|
||||
* Called when the logger wrote message. Call super after your implementation.
|
||||
*/
|
||||
- (void)didLogMessage NS_REQUIRES_SUPER;
|
||||
|
||||
/**
|
||||
* Called when the logger checks archive or not current log file.
|
||||
* Override this method to exdend standart behavior. By default returns NO.
|
||||
*/
|
||||
- (BOOL)shouldArchiveRecentLogFileInfo:(DDLogFileInfo *)recentLogFileInfo;
|
||||
|
||||
/**
|
||||
* Log File Rolling:
|
||||
*
|
||||
* `maximumFileSize`:
|
||||
* The approximate maximum size (in bytes) to allow log files to grow.
|
||||
* If a log file is larger than this value after a log statement is appended,
|
||||
* then the log file is rolled.
|
||||
*
|
||||
* `rollingFrequency`
|
||||
* How often to roll the log file.
|
||||
* The frequency is given as an `NSTimeInterval`, which is a double that specifies the interval in seconds.
|
||||
* Once the log file gets to be this old, it is rolled.
|
||||
*
|
||||
* `doNotReuseLogFiles`
|
||||
* When set, will always create a new log file at application launch.
|
||||
*
|
||||
* Both the `maximumFileSize` and the `rollingFrequency` are used to manage rolling.
|
||||
* Whichever occurs first will cause the log file to be rolled.
|
||||
*
|
||||
* For example:
|
||||
* The `rollingFrequency` is 24 hours,
|
||||
* but the log file surpasses the `maximumFileSize` after only 20 hours.
|
||||
* The log file will be rolled at that 20 hour mark.
|
||||
* A new log file will be created, and the 24 hour timer will be restarted.
|
||||
*
|
||||
* You may optionally disable rolling due to filesize by setting `maximumFileSize` to zero.
|
||||
* If you do so, rolling is based solely on `rollingFrequency`.
|
||||
*
|
||||
* You may optionally disable rolling due to time by setting `rollingFrequency` to zero (or any non-positive number).
|
||||
* If you do so, rolling is based solely on `maximumFileSize`.
|
||||
*
|
||||
* If you disable both `maximumFileSize` and `rollingFrequency`, then the log file won't ever be rolled.
|
||||
* This is strongly discouraged.
|
||||
**/
|
||||
@property (readwrite, assign) unsigned long long maximumFileSize;
|
||||
|
||||
/**
|
||||
* See description for `maximumFileSize`
|
||||
*/
|
||||
@property (readwrite, assign) NSTimeInterval rollingFrequency;
|
||||
|
||||
/**
|
||||
* See description for `maximumFileSize`
|
||||
*/
|
||||
@property (readwrite, assign, atomic) BOOL doNotReuseLogFiles;
|
||||
|
||||
/**
|
||||
* The DDLogFileManager instance can be used to retrieve the list of log files,
|
||||
* and configure the maximum number of archived log files to keep.
|
||||
*
|
||||
* @see DDLogFileManager.maximumNumberOfLogFiles
|
||||
**/
|
||||
@property (strong, nonatomic, readonly) id <DDLogFileManager> logFileManager;
|
||||
|
||||
/**
|
||||
* When using a custom formatter you can set the `logMessage` method not to append
|
||||
* `\n` character after each output. This allows for some greater flexibility with
|
||||
* custom formatters. Default value is YES.
|
||||
**/
|
||||
@property (nonatomic, readwrite, assign) BOOL automaticallyAppendNewlineForCustomFormatters;
|
||||
|
||||
/**
|
||||
* You can optionally force the current log file to be rolled with this method.
|
||||
* CompletionBlock will be called on main queue.
|
||||
*/
|
||||
- (void)rollLogFileWithCompletionBlock:(void (^)(void))completionBlock NS_SWIFT_NAME(rollLogFile(withCompletion:));
|
||||
|
||||
/**
|
||||
* Method is deprecated.
|
||||
* @deprecated Use `rollLogFileWithCompletionBlock:` method instead.
|
||||
*/
|
||||
- (void)rollLogFile __attribute((deprecated));
|
||||
|
||||
// Inherited from DDAbstractLogger
|
||||
|
||||
// - (id <DDLogFormatter>)logFormatter;
|
||||
// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
/**
|
||||
* Returns the log file that should be used.
|
||||
* If there is an existing log file that is suitable,
|
||||
* within the constraints of `maximumFileSize` and `rollingFrequency`, then it is returned.
|
||||
*
|
||||
* Otherwise a new file is created and returned.
|
||||
**/
|
||||
@property (nonatomic, readonly, strong) DDLogFileInfo *currentLogFileInfo;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* `DDLogFileInfo` is a simple class that provides access to various file attributes.
|
||||
* It provides good performance as it only fetches the information if requested,
|
||||
* and it caches the information to prevent duplicate fetches.
|
||||
*
|
||||
* It was designed to provide quick snapshots of the current state of log files,
|
||||
* and to help sort log files in an array.
|
||||
*
|
||||
* This class does not monitor the files, or update it's cached attribute values if the file changes on disk.
|
||||
* This is not what the class was designed for.
|
||||
*
|
||||
* If you absolutely must get updated values,
|
||||
* you can invoke the reset method which will clear the cache.
|
||||
**/
|
||||
@interface DDLogFileInfo : NSObject
|
||||
|
||||
@property (strong, nonatomic, readonly) NSString *filePath;
|
||||
@property (strong, nonatomic, readonly) NSString *fileName;
|
||||
|
||||
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
|
||||
@property (strong, nonatomic, readonly) NSDictionary<NSFileAttributeKey, id> *fileAttributes;
|
||||
#else
|
||||
@property (strong, nonatomic, readonly) NSDictionary<NSString *, id> *fileAttributes;
|
||||
#endif
|
||||
|
||||
@property (strong, nonatomic, readonly) NSDate *creationDate;
|
||||
@property (strong, nonatomic, readonly) NSDate *modificationDate;
|
||||
|
||||
@property (nonatomic, readonly) unsigned long long fileSize;
|
||||
|
||||
@property (nonatomic, readonly) NSTimeInterval age;
|
||||
|
||||
@property (nonatomic, readwrite) BOOL isArchived;
|
||||
|
||||
+ (instancetype)logFileWithPath:(NSString *)filePath NS_SWIFT_UNAVAILABLE("Use init(filePath:)");
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (void)reset;
|
||||
- (void)renameFile:(NSString *)newFileName NS_SWIFT_NAME(renameFile(to:));
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
|
||||
// So here's the situation.
|
||||
// Extended attributes are perfect for what we're trying to do here (marking files as archived).
|
||||
// This is exactly what extended attributes were designed for.
|
||||
//
|
||||
// But Apple screws us over on the simulator.
|
||||
// Everytime you build-and-go, they copy the application into a new folder on the hard drive,
|
||||
// and as part of the process they strip extended attributes from our log files.
|
||||
// Normally, a copy of a file preserves extended attributes.
|
||||
// So obviously Apple has gone to great lengths to piss us off.
|
||||
//
|
||||
// Thus we use a slightly different tactic for marking log files as archived in the simulator.
|
||||
// That way it "just works" and there's no confusion when testing.
|
||||
//
|
||||
// The difference in method names is indicative of the difference in functionality.
|
||||
// On the simulator we add an attribute by appending a filename extension.
|
||||
//
|
||||
// For example:
|
||||
// "mylog.txt" -> "mylog.archived.txt"
|
||||
// "mylog" -> "mylog.archived"
|
||||
|
||||
- (BOOL)hasExtensionAttributeWithName:(NSString *)attrName;
|
||||
|
||||
- (void)addExtensionAttributeWithName:(NSString *)attrName;
|
||||
- (void)removeExtensionAttributeWithName:(NSString *)attrName;
|
||||
|
||||
#else /* if TARGET_IPHONE_SIMULATOR */
|
||||
|
||||
// Normal use of extended attributes used everywhere else,
|
||||
// such as on Macs and on iPhone devices.
|
||||
|
||||
- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName;
|
||||
|
||||
- (void)addExtendedAttributeWithName:(NSString *)attrName;
|
||||
- (void)removeExtendedAttributeWithName:(NSString *)attrName;
|
||||
|
||||
#endif /* if TARGET_IPHONE_SIMULATOR */
|
||||
|
||||
- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another;
|
||||
- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,75 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
/**
|
||||
* Legacy macros used for 1.9.x backwards compatibility.
|
||||
*
|
||||
* Imported by default when importing a DDLog.h directly and DD_LEGACY_MACROS is not defined and set to 0.
|
||||
**/
|
||||
#if DD_LEGACY_MACROS
|
||||
|
||||
#warning CocoaLumberjack 1.9.x legacy macros enabled. \
|
||||
Disable legacy macros by importing CocoaLumberjack.h or DDLogMacros.h instead of DDLog.h or add `#define DD_LEGACY_MACROS 0` before importing DDLog.h.
|
||||
|
||||
#ifndef LOG_LEVEL_DEF
|
||||
#define LOG_LEVEL_DEF ddLogLevel
|
||||
#endif
|
||||
|
||||
#define LOG_FLAG_ERROR DDLogFlagError
|
||||
#define LOG_FLAG_WARN DDLogFlagWarning
|
||||
#define LOG_FLAG_INFO DDLogFlagInfo
|
||||
#define LOG_FLAG_DEBUG DDLogFlagDebug
|
||||
#define LOG_FLAG_VERBOSE DDLogFlagVerbose
|
||||
|
||||
#define LOG_LEVEL_OFF DDLogLevelOff
|
||||
#define LOG_LEVEL_ERROR DDLogLevelError
|
||||
#define LOG_LEVEL_WARN DDLogLevelWarning
|
||||
#define LOG_LEVEL_INFO DDLogLevelInfo
|
||||
#define LOG_LEVEL_DEBUG DDLogLevelDebug
|
||||
#define LOG_LEVEL_VERBOSE DDLogLevelVerbose
|
||||
#define LOG_LEVEL_ALL DDLogLevelAll
|
||||
|
||||
#define LOG_ASYNC_ENABLED YES
|
||||
|
||||
#define LOG_ASYNC_ERROR ( NO && LOG_ASYNC_ENABLED)
|
||||
#define LOG_ASYNC_WARN (YES && LOG_ASYNC_ENABLED)
|
||||
#define LOG_ASYNC_INFO (YES && LOG_ASYNC_ENABLED)
|
||||
#define LOG_ASYNC_DEBUG (YES && LOG_ASYNC_ENABLED)
|
||||
#define LOG_ASYNC_VERBOSE (YES && LOG_ASYNC_ENABLED)
|
||||
|
||||
#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \
|
||||
[DDLog log : isAsynchronous \
|
||||
level : lvl \
|
||||
flag : flg \
|
||||
context : ctx \
|
||||
file : __FILE__ \
|
||||
function : fnct \
|
||||
line : __LINE__ \
|
||||
tag : atag \
|
||||
format : (frmt), ## __VA_ARGS__]
|
||||
|
||||
#define LOG_MAYBE(async, lvl, flg, ctx, fnct, frmt, ...) \
|
||||
do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, nil, fnct, frmt, ##__VA_ARGS__); } while(0)
|
||||
|
||||
#define LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \
|
||||
LOG_MAYBE(async, lvl, flg, ctx, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
|
||||
|
||||
#define DDLogError(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_ERROR, LOG_LEVEL_DEF, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogWarn(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_WARN, LOG_LEVEL_DEF, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogInfo(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_INFO, LOG_LEVEL_DEF, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogDebug(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_DEBUG, LOG_LEVEL_DEF, LOG_FLAG_DEBUG, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogVerbose(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_VERBOSE, LOG_LEVEL_DEF, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__)
|
||||
|
||||
#endif
|
||||
@ -1,83 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* The constant/variable/method responsible for controlling the current log level.
|
||||
**/
|
||||
#ifndef LOG_LEVEL_DEF
|
||||
#define LOG_LEVEL_DEF ddLogLevel
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Whether async should be used by log messages, excluding error messages that are always sent sync.
|
||||
**/
|
||||
#ifndef LOG_ASYNC_ENABLED
|
||||
#define LOG_ASYNC_ENABLED YES
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This is the single macro that all other macros below compile into.
|
||||
* This big multiline macro makes all the other macros easier to read.
|
||||
**/
|
||||
#define LOGV_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, avalist) \
|
||||
[DDLog log : isAsynchronous \
|
||||
level : lvl \
|
||||
flag : flg \
|
||||
context : ctx \
|
||||
file : __FILE__ \
|
||||
function : fnct \
|
||||
line : __LINE__ \
|
||||
tag : atag \
|
||||
format : frmt \
|
||||
args : avalist]
|
||||
|
||||
/**
|
||||
* Define version of the macro that only execute if the log level is above the threshold.
|
||||
* The compiled versions essentially look like this:
|
||||
*
|
||||
* if (logFlagForThisLogMsg & ddLogLevel) { execute log message }
|
||||
*
|
||||
* When LOG_LEVEL_DEF is defined as ddLogLevel.
|
||||
*
|
||||
* As shown further below, Lumberjack actually uses a bitmask as opposed to primitive log levels.
|
||||
* This allows for a great amount of flexibility and some pretty advanced fine grained logging techniques.
|
||||
*
|
||||
* Note that when compiler optimizations are enabled (as they are for your release builds),
|
||||
* the log messages above your logging threshold will automatically be compiled out.
|
||||
*
|
||||
* (If the compiler sees LOG_LEVEL_DEF/ddLogLevel declared as a constant, the compiler simply checks to see
|
||||
* if the 'if' statement would execute, and if not it strips it from the binary.)
|
||||
*
|
||||
* We also define shorthand versions for asynchronous and synchronous logging.
|
||||
**/
|
||||
#define LOGV_MAYBE(async, lvl, flg, ctx, tag, fnct, frmt, avalist) \
|
||||
do { if(lvl & flg) LOGV_MACRO(async, lvl, flg, ctx, tag, fnct, frmt, avalist); } while(0)
|
||||
|
||||
/**
|
||||
* Ready to use log macros with no context or tag.
|
||||
**/
|
||||
#define DDLogVError(frmt, avalist) LOGV_MAYBE(NO, LOG_LEVEL_DEF, DDLogFlagError, 0, nil, __PRETTY_FUNCTION__, frmt, avalist)
|
||||
#define DDLogVWarn(frmt, avalist) LOGV_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagWarning, 0, nil, __PRETTY_FUNCTION__, frmt, avalist)
|
||||
#define DDLogVInfo(frmt, avalist) LOGV_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, 0, nil, __PRETTY_FUNCTION__, frmt, avalist)
|
||||
#define DDLogVDebug(frmt, avalist) LOGV_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug, 0, nil, __PRETTY_FUNCTION__, frmt, avalist)
|
||||
#define DDLogVVerbose(frmt, avalist) LOGV_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagVerbose, 0, nil, __PRETTY_FUNCTION__, frmt, avalist)
|
||||
|
||||
@ -1,909 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Enable 1.9.x legacy macros if imported directly
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 1
|
||||
#endif
|
||||
// DD_LEGACY_MACROS is checked in the file itself
|
||||
#import "DDLegacyMacros.h"
|
||||
|
||||
#if OS_OBJECT_USE_OBJC
|
||||
#define DISPATCH_QUEUE_REFERENCE_TYPE strong
|
||||
#else
|
||||
#define DISPATCH_QUEUE_REFERENCE_TYPE assign
|
||||
#endif
|
||||
|
||||
@class DDLogMessage;
|
||||
@class DDLoggerInformation;
|
||||
@protocol DDLogger;
|
||||
@protocol DDLogFormatter;
|
||||
|
||||
/**
|
||||
* Define the standard options.
|
||||
*
|
||||
* We default to only 4 levels because it makes it easier for beginners
|
||||
* to make the transition to a logging framework.
|
||||
*
|
||||
* More advanced users may choose to completely customize the levels (and level names) to suite their needs.
|
||||
* For more information on this see the "Custom Log Levels" page:
|
||||
* Documentation/CustomLogLevels.md
|
||||
*
|
||||
* Advanced users may also notice that we're using a bitmask.
|
||||
* This is to allow for custom fine grained logging:
|
||||
* Documentation/FineGrainedLogging.md
|
||||
*
|
||||
* -- Flags --
|
||||
*
|
||||
* Typically you will use the LOG_LEVELS (see below), but the flags may be used directly in certain situations.
|
||||
* For example, say you have a lot of warning log messages, and you wanted to disable them.
|
||||
* However, you still needed to see your error and info log messages.
|
||||
* You could accomplish that with the following:
|
||||
*
|
||||
* static const DDLogLevel ddLogLevel = DDLogFlagError | DDLogFlagInfo;
|
||||
*
|
||||
* When LOG_LEVEL_DEF is defined as ddLogLevel.
|
||||
*
|
||||
* Flags may also be consulted when writing custom log formatters,
|
||||
* as the DDLogMessage class captures the individual flag that caused the log message to fire.
|
||||
*
|
||||
* -- Levels --
|
||||
*
|
||||
* Log levels are simply the proper bitmask of the flags.
|
||||
*
|
||||
* -- Booleans --
|
||||
*
|
||||
* The booleans may be used when your logging code involves more than one line.
|
||||
* For example:
|
||||
*
|
||||
* if (LOG_VERBOSE) {
|
||||
* for (id sprocket in sprockets)
|
||||
* DDLogVerbose(@"sprocket: %@", [sprocket description])
|
||||
* }
|
||||
*
|
||||
* -- Async --
|
||||
*
|
||||
* Defines the default asynchronous options.
|
||||
* The default philosophy for asynchronous logging is very simple:
|
||||
*
|
||||
* Log messages with errors should be executed synchronously.
|
||||
* After all, an error just occurred. The application could be unstable.
|
||||
*
|
||||
* All other log messages, such as debug output, are executed asynchronously.
|
||||
* After all, if it wasn't an error, then it was just informational output,
|
||||
* or something the application was easily able to recover from.
|
||||
*
|
||||
* -- Changes --
|
||||
*
|
||||
* You are strongly discouraged from modifying this file.
|
||||
* If you do, you make it more difficult on yourself to merge future bug fixes and improvements from the project.
|
||||
* Instead, create your own MyLogging.h or ApplicationNameLogging.h or CompanyLogging.h
|
||||
*
|
||||
* For an example of customizing your logging experience, see the "Custom Log Levels" page:
|
||||
* Documentation/CustomLogLevels.md
|
||||
**/
|
||||
|
||||
/**
|
||||
* Flags accompany each log. They are used together with levels to filter out logs.
|
||||
*/
|
||||
typedef NS_OPTIONS(NSUInteger, DDLogFlag){
|
||||
/**
|
||||
* 0...00001 DDLogFlagError
|
||||
*/
|
||||
DDLogFlagError = (1 << 0),
|
||||
|
||||
/**
|
||||
* 0...00010 DDLogFlagWarning
|
||||
*/
|
||||
DDLogFlagWarning = (1 << 1),
|
||||
|
||||
/**
|
||||
* 0...00100 DDLogFlagInfo
|
||||
*/
|
||||
DDLogFlagInfo = (1 << 2),
|
||||
|
||||
/**
|
||||
* 0...01000 DDLogFlagDebug
|
||||
*/
|
||||
DDLogFlagDebug = (1 << 3),
|
||||
|
||||
/**
|
||||
* 0...10000 DDLogFlagVerbose
|
||||
*/
|
||||
DDLogFlagVerbose = (1 << 4)
|
||||
};
|
||||
|
||||
/**
|
||||
* Log levels are used to filter out logs. Used together with flags.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, DDLogLevel){
|
||||
/**
|
||||
* No logs
|
||||
*/
|
||||
DDLogLevelOff = 0,
|
||||
|
||||
/**
|
||||
* Error logs only
|
||||
*/
|
||||
DDLogLevelError = (DDLogFlagError),
|
||||
|
||||
/**
|
||||
* Error and warning logs
|
||||
*/
|
||||
DDLogLevelWarning = (DDLogLevelError | DDLogFlagWarning),
|
||||
|
||||
/**
|
||||
* Error, warning and info logs
|
||||
*/
|
||||
DDLogLevelInfo = (DDLogLevelWarning | DDLogFlagInfo),
|
||||
|
||||
/**
|
||||
* Error, warning, info and debug logs
|
||||
*/
|
||||
DDLogLevelDebug = (DDLogLevelInfo | DDLogFlagDebug),
|
||||
|
||||
/**
|
||||
* Error, warning, info, debug and verbose logs
|
||||
*/
|
||||
DDLogLevelVerbose = (DDLogLevelDebug | DDLogFlagVerbose),
|
||||
|
||||
/**
|
||||
* All logs (1...11111)
|
||||
*/
|
||||
DDLogLevelAll = NSUIntegerMax
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Extracts just the file name, no path or extension
|
||||
*
|
||||
* @param filePath input file path
|
||||
* @param copy YES if we want the result to be copied
|
||||
*
|
||||
* @return the file name
|
||||
*/
|
||||
NSString * __nullable DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy);
|
||||
|
||||
/**
|
||||
* The THIS_FILE macro gives you an NSString of the file name.
|
||||
* For simplicity and clarity, the file name does not include the full path or file extension.
|
||||
*
|
||||
* For example: DDLogWarn(@"%@: Unable to find thingy", THIS_FILE) -> @"MyViewController: Unable to find thingy"
|
||||
**/
|
||||
#define THIS_FILE (DDExtractFileNameWithoutExtension(__FILE__, NO))
|
||||
|
||||
/**
|
||||
* The THIS_METHOD macro gives you the name of the current objective-c method.
|
||||
*
|
||||
* For example: DDLogWarn(@"%@ - Requires non-nil strings", THIS_METHOD) -> @"setMake:model: requires non-nil strings"
|
||||
*
|
||||
* Note: This does NOT work in straight C functions (non objective-c).
|
||||
* Instead you should use the predefined __FUNCTION__ macro.
|
||||
**/
|
||||
#define THIS_METHOD NSStringFromSelector(_cmd)
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The main class, exposes all logging mechanisms, loggers, ...
|
||||
* For most of the users, this class is hidden behind the logging functions like `DDLogInfo`
|
||||
*/
|
||||
@interface DDLog : NSObject
|
||||
|
||||
/**
|
||||
* Returns the singleton `DDLog`.
|
||||
* The instance is used by `DDLog` class methods.
|
||||
*/
|
||||
@property (class, nonatomic, strong, readonly) DDLog *sharedInstance;
|
||||
|
||||
/**
|
||||
* Provides access to the underlying logging queue.
|
||||
* This may be helpful to Logger classes for things like thread synchronization.
|
||||
**/
|
||||
@property (class, nonatomic, DISPATCH_QUEUE_REFERENCE_TYPE, readonly) dispatch_queue_t loggingQueue;
|
||||
|
||||
/**
|
||||
* Logging Primitive.
|
||||
*
|
||||
* This method is used by the macros or logging functions.
|
||||
* It is suggested you stick with the macros as they're easier to use.
|
||||
*
|
||||
* @param asynchronous YES if the logging is done async, NO if you want to force sync
|
||||
* @param level the log level
|
||||
* @param flag the log flag
|
||||
* @param context the context (if any is defined)
|
||||
* @param file the current file
|
||||
* @param function the current function
|
||||
* @param line the current code line
|
||||
* @param tag potential tag
|
||||
* @param format the log format
|
||||
*/
|
||||
+ (void)log:(BOOL)asynchronous
|
||||
level:(DDLogLevel)level
|
||||
flag:(DDLogFlag)flag
|
||||
context:(NSInteger)context
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(NSUInteger)line
|
||||
tag:(id __nullable)tag
|
||||
format:(NSString *)format, ... NS_FORMAT_FUNCTION(9,10);
|
||||
|
||||
/**
|
||||
* Logging Primitive.
|
||||
*
|
||||
* This method is used by the macros or logging functions.
|
||||
* It is suggested you stick with the macros as they're easier to use.
|
||||
*
|
||||
* @param asynchronous YES if the logging is done async, NO if you want to force sync
|
||||
* @param level the log level
|
||||
* @param flag the log flag
|
||||
* @param context the context (if any is defined)
|
||||
* @param file the current file
|
||||
* @param function the current function
|
||||
* @param line the current code line
|
||||
* @param tag potential tag
|
||||
* @param format the log format
|
||||
*/
|
||||
- (void)log:(BOOL)asynchronous
|
||||
level:(DDLogLevel)level
|
||||
flag:(DDLogFlag)flag
|
||||
context:(NSInteger)context
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(NSUInteger)line
|
||||
tag:(id __nullable)tag
|
||||
format:(NSString *)format, ... NS_FORMAT_FUNCTION(9,10);
|
||||
|
||||
/**
|
||||
* Logging Primitive.
|
||||
*
|
||||
* This method can be used if you have a prepared va_list.
|
||||
* Similar to `log:level:flag:context:file:function:line:tag:format:...`
|
||||
*
|
||||
* @param asynchronous YES if the logging is done async, NO if you want to force sync
|
||||
* @param level the log level
|
||||
* @param flag the log flag
|
||||
* @param context the context (if any is defined)
|
||||
* @param file the current file
|
||||
* @param function the current function
|
||||
* @param line the current code line
|
||||
* @param tag potential tag
|
||||
* @param format the log format
|
||||
* @param argList the arguments list as a va_list
|
||||
*/
|
||||
+ (void)log:(BOOL)asynchronous
|
||||
level:(DDLogLevel)level
|
||||
flag:(DDLogFlag)flag
|
||||
context:(NSInteger)context
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(NSUInteger)line
|
||||
tag:(id __nullable)tag
|
||||
format:(NSString *)format
|
||||
args:(va_list)argList NS_SWIFT_NAME(log(asynchronous:level:flag:context:file:function:line:tag:format:arguments:));
|
||||
|
||||
/**
|
||||
* Logging Primitive.
|
||||
*
|
||||
* This method can be used if you have a prepared va_list.
|
||||
* Similar to `log:level:flag:context:file:function:line:tag:format:...`
|
||||
*
|
||||
* @param asynchronous YES if the logging is done async, NO if you want to force sync
|
||||
* @param level the log level
|
||||
* @param flag the log flag
|
||||
* @param context the context (if any is defined)
|
||||
* @param file the current file
|
||||
* @param function the current function
|
||||
* @param line the current code line
|
||||
* @param tag potential tag
|
||||
* @param format the log format
|
||||
* @param argList the arguments list as a va_list
|
||||
*/
|
||||
- (void)log:(BOOL)asynchronous
|
||||
level:(DDLogLevel)level
|
||||
flag:(DDLogFlag)flag
|
||||
context:(NSInteger)context
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(NSUInteger)line
|
||||
tag:(id __nullable)tag
|
||||
format:(NSString *)format
|
||||
args:(va_list)argList NS_SWIFT_NAME(log(asynchronous:level:flag:context:file:function:line:tag:format:arguments:));
|
||||
|
||||
/**
|
||||
* Logging Primitive.
|
||||
*
|
||||
* This method can be used if you manualy prepared DDLogMessage.
|
||||
*
|
||||
* @param asynchronous YES if the logging is done async, NO if you want to force sync
|
||||
* @param logMessage the log message stored in a `DDLogMessage` model object
|
||||
*/
|
||||
+ (void)log:(BOOL)asynchronous
|
||||
message:(DDLogMessage *)logMessage NS_SWIFT_NAME(log(asynchronous:message:));
|
||||
|
||||
/**
|
||||
* Logging Primitive.
|
||||
*
|
||||
* This method can be used if you manualy prepared DDLogMessage.
|
||||
*
|
||||
* @param asynchronous YES if the logging is done async, NO if you want to force sync
|
||||
* @param logMessage the log message stored in a `DDLogMessage` model object
|
||||
*/
|
||||
- (void)log:(BOOL)asynchronous
|
||||
message:(DDLogMessage *)logMessage NS_SWIFT_NAME(log(asynchronous:message:));
|
||||
|
||||
/**
|
||||
* Since logging can be asynchronous, there may be times when you want to flush the logs.
|
||||
* The framework invokes this automatically when the application quits.
|
||||
**/
|
||||
+ (void)flushLog;
|
||||
|
||||
/**
|
||||
* Since logging can be asynchronous, there may be times when you want to flush the logs.
|
||||
* The framework invokes this automatically when the application quits.
|
||||
**/
|
||||
- (void)flushLog;
|
||||
|
||||
/**
|
||||
* Loggers
|
||||
*
|
||||
* In order for your log statements to go somewhere, you should create and add a logger.
|
||||
*
|
||||
* You can add multiple loggers in order to direct your log statements to multiple places.
|
||||
* And each logger can be configured separately.
|
||||
* So you could have, for example, verbose logging to the console, but a concise log file with only warnings & errors.
|
||||
**/
|
||||
|
||||
/**
|
||||
* Adds the logger to the system.
|
||||
*
|
||||
* This is equivalent to invoking `[DDLog addLogger:logger withLogLevel:DDLogLevelAll]`.
|
||||
**/
|
||||
+ (void)addLogger:(id <DDLogger>)logger;
|
||||
|
||||
/**
|
||||
* Adds the logger to the system.
|
||||
*
|
||||
* This is equivalent to invoking `[DDLog addLogger:logger withLogLevel:DDLogLevelAll]`.
|
||||
**/
|
||||
- (void)addLogger:(id <DDLogger>)logger;
|
||||
|
||||
/**
|
||||
* Adds the logger to the system.
|
||||
*
|
||||
* The level that you provide here is a preemptive filter (for performance).
|
||||
* That is, the level specified here will be used to filter out logMessages so that
|
||||
* the logger is never even invoked for the messages.
|
||||
*
|
||||
* More information:
|
||||
* When you issue a log statement, the logging framework iterates over each logger,
|
||||
* and checks to see if it should forward the logMessage to the logger.
|
||||
* This check is done using the level parameter passed to this method.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* `[DDLog addLogger:consoleLogger withLogLevel:DDLogLevelVerbose];`
|
||||
* `[DDLog addLogger:fileLogger withLogLevel:DDLogLevelWarning];`
|
||||
*
|
||||
* `DDLogError(@"oh no");` => gets forwarded to consoleLogger & fileLogger
|
||||
* `DDLogInfo(@"hi");` => gets forwarded to consoleLogger only
|
||||
*
|
||||
* It is important to remember that Lumberjack uses a BITMASK.
|
||||
* Many developers & third party frameworks may define extra log levels & flags.
|
||||
* For example:
|
||||
*
|
||||
* `#define SOME_FRAMEWORK_LOG_FLAG_TRACE (1 << 6) // 0...1000000`
|
||||
*
|
||||
* So if you specify `DDLogLevelVerbose` to this method, you won't see the framework's trace messages.
|
||||
*
|
||||
* `(SOME_FRAMEWORK_LOG_FLAG_TRACE & DDLogLevelVerbose) => (01000000 & 00011111) => NO`
|
||||
*
|
||||
* Consider passing `DDLogLevelAll` to this method, which has all bits set.
|
||||
* You can also use the exclusive-or bitwise operator to get a bitmask that has all flags set,
|
||||
* except the ones you explicitly don't want. For example, if you wanted everything except verbose & debug:
|
||||
*
|
||||
* `((DDLogLevelAll ^ DDLogLevelVerbose) | DDLogLevelInfo)`
|
||||
**/
|
||||
+ (void)addLogger:(id <DDLogger>)logger withLevel:(DDLogLevel)level;
|
||||
|
||||
/**
|
||||
* Adds the logger to the system.
|
||||
*
|
||||
* The level that you provide here is a preemptive filter (for performance).
|
||||
* That is, the level specified here will be used to filter out logMessages so that
|
||||
* the logger is never even invoked for the messages.
|
||||
*
|
||||
* More information:
|
||||
* When you issue a log statement, the logging framework iterates over each logger,
|
||||
* and checks to see if it should forward the logMessage to the logger.
|
||||
* This check is done using the level parameter passed to this method.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* `[DDLog addLogger:consoleLogger withLogLevel:DDLogLevelVerbose];`
|
||||
* `[DDLog addLogger:fileLogger withLogLevel:DDLogLevelWarning];`
|
||||
*
|
||||
* `DDLogError(@"oh no");` => gets forwarded to consoleLogger & fileLogger
|
||||
* `DDLogInfo(@"hi");` => gets forwarded to consoleLogger only
|
||||
*
|
||||
* It is important to remember that Lumberjack uses a BITMASK.
|
||||
* Many developers & third party frameworks may define extra log levels & flags.
|
||||
* For example:
|
||||
*
|
||||
* `#define SOME_FRAMEWORK_LOG_FLAG_TRACE (1 << 6) // 0...1000000`
|
||||
*
|
||||
* So if you specify `DDLogLevelVerbose` to this method, you won't see the framework's trace messages.
|
||||
*
|
||||
* `(SOME_FRAMEWORK_LOG_FLAG_TRACE & DDLogLevelVerbose) => (01000000 & 00011111) => NO`
|
||||
*
|
||||
* Consider passing `DDLogLevelAll` to this method, which has all bits set.
|
||||
* You can also use the exclusive-or bitwise operator to get a bitmask that has all flags set,
|
||||
* except the ones you explicitly don't want. For example, if you wanted everything except verbose & debug:
|
||||
*
|
||||
* `((DDLogLevelAll ^ DDLogLevelVerbose) | DDLogLevelInfo)`
|
||||
**/
|
||||
- (void)addLogger:(id <DDLogger>)logger withLevel:(DDLogLevel)level;
|
||||
|
||||
/**
|
||||
* Remove the logger from the system
|
||||
*/
|
||||
+ (void)removeLogger:(id <DDLogger>)logger;
|
||||
|
||||
/**
|
||||
* Remove the logger from the system
|
||||
*/
|
||||
- (void)removeLogger:(id <DDLogger>)logger;
|
||||
|
||||
/**
|
||||
* Remove all the current loggers
|
||||
*/
|
||||
+ (void)removeAllLoggers;
|
||||
|
||||
/**
|
||||
* Remove all the current loggers
|
||||
*/
|
||||
- (void)removeAllLoggers;
|
||||
|
||||
/**
|
||||
* Return all the current loggers
|
||||
*/
|
||||
@property (class, nonatomic, copy, readonly) NSArray<id<DDLogger>> *allLoggers;
|
||||
|
||||
/**
|
||||
* Return all the current loggers
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSArray<id<DDLogger>> *allLoggers;
|
||||
|
||||
/**
|
||||
* Return all the current loggers with their level (aka DDLoggerInformation).
|
||||
*/
|
||||
@property (class, nonatomic, copy, readonly) NSArray<DDLoggerInformation *> *allLoggersWithLevel;
|
||||
|
||||
/**
|
||||
* Return all the current loggers with their level (aka DDLoggerInformation).
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSArray<DDLoggerInformation *> *allLoggersWithLevel;
|
||||
|
||||
/**
|
||||
* Registered Dynamic Logging
|
||||
*
|
||||
* These methods allow you to obtain a list of classes that are using registered dynamic logging,
|
||||
* and also provides methods to get and set their log level during run time.
|
||||
**/
|
||||
|
||||
/**
|
||||
* Returns an array with the classes that are using registered dynamic logging
|
||||
*/
|
||||
@property (class, nonatomic, copy, readonly) NSArray<Class> *registeredClasses;
|
||||
|
||||
/**
|
||||
* Returns an array with the classes names that are using registered dynamic logging
|
||||
*/
|
||||
@property (class, nonatomic, copy, readonly) NSArray<NSString*> *registeredClassNames;
|
||||
|
||||
/**
|
||||
* Returns the current log level for a certain class
|
||||
*
|
||||
* @param aClass `Class` param
|
||||
*/
|
||||
+ (DDLogLevel)levelForClass:(Class)aClass;
|
||||
|
||||
/**
|
||||
* Returns the current log level for a certain class
|
||||
*
|
||||
* @param aClassName string param
|
||||
*/
|
||||
+ (DDLogLevel)levelForClassWithName:(NSString *)aClassName;
|
||||
|
||||
/**
|
||||
* Set the log level for a certain class
|
||||
*
|
||||
* @param level the new level
|
||||
* @param aClass `Class` param
|
||||
*/
|
||||
+ (void)setLevel:(DDLogLevel)level forClass:(Class)aClass;
|
||||
|
||||
/**
|
||||
* Set the log level for a certain class
|
||||
*
|
||||
* @param level the new level
|
||||
* @param aClassName string param
|
||||
*/
|
||||
+ (void)setLevel:(DDLogLevel)level forClassWithName:(NSString *)aClassName;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* This protocol describes a basic logger behavior.
|
||||
* Basically, it can log messages, store a logFormatter plus a bunch of optional behaviors.
|
||||
* (i.e. flush, get its loggerQueue, get its name, ...
|
||||
*/
|
||||
@protocol DDLogger <NSObject>
|
||||
|
||||
/**
|
||||
* The log message method
|
||||
*
|
||||
* @param logMessage the message (model)
|
||||
*/
|
||||
- (void)logMessage:(DDLogMessage *)logMessage NS_SWIFT_NAME(log(message:));
|
||||
|
||||
/**
|
||||
* Formatters may optionally be added to any logger.
|
||||
*
|
||||
* If no formatter is set, the logger simply logs the message as it is given in logMessage,
|
||||
* or it may use its own built in formatting style.
|
||||
**/
|
||||
@property (nonatomic, strong) id <DDLogFormatter> logFormatter;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Since logging is asynchronous, adding and removing loggers is also asynchronous.
|
||||
* In other words, the loggers are added and removed at appropriate times with regards to log messages.
|
||||
*
|
||||
* - Loggers will not receive log messages that were executed prior to when they were added.
|
||||
* - Loggers will not receive log messages that were executed after they were removed.
|
||||
*
|
||||
* These methods are executed in the logging thread/queue.
|
||||
* This is the same thread/queue that will execute every logMessage: invocation.
|
||||
* Loggers may use these methods for thread synchronization or other setup/teardown tasks.
|
||||
**/
|
||||
- (void)didAddLogger;
|
||||
|
||||
/**
|
||||
* Since logging is asynchronous, adding and removing loggers is also asynchronous.
|
||||
* In other words, the loggers are added and removed at appropriate times with regards to log messages.
|
||||
*
|
||||
* - Loggers will not receive log messages that were executed prior to when they were added.
|
||||
* - Loggers will not receive log messages that were executed after they were removed.
|
||||
*
|
||||
* These methods are executed in the logging thread/queue given in parameter.
|
||||
* This is the same thread/queue that will execute every logMessage: invocation.
|
||||
* Loggers may use the queue parameter to set specific values on the queue with dispatch_set_specific() function.
|
||||
**/
|
||||
- (void)didAddLoggerInQueue:(dispatch_queue_t)queue;
|
||||
|
||||
/**
|
||||
* See the above description for `didAddLoger`
|
||||
*/
|
||||
- (void)willRemoveLogger;
|
||||
|
||||
/**
|
||||
* Some loggers may buffer IO for optimization purposes.
|
||||
* For example, a database logger may only save occasionaly as the disk IO is slow.
|
||||
* In such loggers, this method should be implemented to flush any pending IO.
|
||||
*
|
||||
* This allows invocations of DDLog's flushLog method to be propogated to loggers that need it.
|
||||
*
|
||||
* Note that DDLog's flushLog method is invoked automatically when the application quits,
|
||||
* and it may be also invoked manually by the developer prior to application crashes, or other such reasons.
|
||||
**/
|
||||
- (void)flush;
|
||||
|
||||
/**
|
||||
* Each logger is executed concurrently with respect to the other loggers.
|
||||
* Thus, a dedicated dispatch queue is used for each logger.
|
||||
* Logger implementations may optionally choose to provide their own dispatch queue.
|
||||
**/
|
||||
@property (nonatomic, DISPATCH_QUEUE_REFERENCE_TYPE, readonly) dispatch_queue_t loggerQueue;
|
||||
|
||||
/**
|
||||
* If the logger implementation does not choose to provide its own queue,
|
||||
* one will automatically be created for it.
|
||||
* The created queue will receive its name from this method.
|
||||
* This may be helpful for debugging or profiling reasons.
|
||||
**/
|
||||
@property (nonatomic, readonly) NSString *loggerName;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* This protocol describes the behavior of a log formatter
|
||||
*/
|
||||
@protocol DDLogFormatter <NSObject>
|
||||
@required
|
||||
|
||||
/**
|
||||
* Formatters may optionally be added to any logger.
|
||||
* This allows for increased flexibility in the logging environment.
|
||||
* For example, log messages for log files may be formatted differently than log messages for the console.
|
||||
*
|
||||
* For more information about formatters, see the "Custom Formatters" page:
|
||||
* Documentation/CustomFormatters.md
|
||||
*
|
||||
* The formatter may also optionally filter the log message by returning nil,
|
||||
* in which case the logger will not log the message.
|
||||
**/
|
||||
- (NSString * __nullable)formatLogMessage:(DDLogMessage *)logMessage NS_SWIFT_NAME(format(message:));
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* A single formatter instance can be added to multiple loggers.
|
||||
* These methods provides hooks to notify the formatter of when it's added/removed.
|
||||
*
|
||||
* This is primarily for thread-safety.
|
||||
* If a formatter is explicitly not thread-safe, it may wish to throw an exception if added to multiple loggers.
|
||||
* Or if a formatter has potentially thread-unsafe code (e.g. NSDateFormatter),
|
||||
* it could possibly use these hooks to switch to thread-safe versions of the code.
|
||||
**/
|
||||
- (void)didAddToLogger:(id <DDLogger>)logger;
|
||||
|
||||
/**
|
||||
* A single formatter instance can be added to multiple loggers.
|
||||
* These methods provides hooks to notify the formatter of when it's added/removed.
|
||||
*
|
||||
* This is primarily for thread-safety.
|
||||
* If a formatter is explicitly not thread-safe, it may wish to throw an exception if added to multiple loggers.
|
||||
* Or if a formatter has potentially thread-unsafe code (e.g. NSDateFormatter),
|
||||
* it could possibly use these hooks to switch to thread-safe versions of the code or use dispatch_set_specific()
|
||||
.* to add its own specific values.
|
||||
**/
|
||||
- (void)didAddToLogger:(id <DDLogger>)logger inQueue:(dispatch_queue_t)queue;
|
||||
|
||||
/**
|
||||
* See the above description for `didAddToLogger:`
|
||||
*/
|
||||
- (void)willRemoveFromLogger:(id <DDLogger>)logger;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* This protocol describes a dynamic logging component
|
||||
*/
|
||||
@protocol DDRegisteredDynamicLogging
|
||||
|
||||
/**
|
||||
* Implement these methods to allow a file's log level to be managed from a central location.
|
||||
*
|
||||
* This is useful if you'd like to be able to change log levels for various parts
|
||||
* of your code from within the running application.
|
||||
*
|
||||
* Imagine pulling up the settings for your application,
|
||||
* and being able to configure the logging level on a per file basis.
|
||||
*
|
||||
* The implementation can be very straight-forward:
|
||||
*
|
||||
* ```
|
||||
* + (int)ddLogLevel
|
||||
* {
|
||||
* return ddLogLevel;
|
||||
* }
|
||||
*
|
||||
* + (void)ddSetLogLevel:(DDLogLevel)level
|
||||
* {
|
||||
* ddLogLevel = level;
|
||||
* }
|
||||
* ```
|
||||
**/
|
||||
@property (class, nonatomic, readwrite, setter=ddSetLogLevel:) DDLogLevel ddLogLevel;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef NS_DESIGNATED_INITIALIZER
|
||||
#define NS_DESIGNATED_INITIALIZER
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Log message options, allow copying certain log elements
|
||||
*/
|
||||
typedef NS_OPTIONS(NSInteger, DDLogMessageOptions){
|
||||
/**
|
||||
* Use this to use a copy of the file path
|
||||
*/
|
||||
DDLogMessageCopyFile = 1 << 0,
|
||||
/**
|
||||
* Use this to use a copy of the function name
|
||||
*/
|
||||
DDLogMessageCopyFunction = 1 << 1,
|
||||
/**
|
||||
* Use this to use avoid a copy of the message
|
||||
*/
|
||||
DDLogMessageDontCopyMessage = 1 << 2
|
||||
};
|
||||
|
||||
/**
|
||||
* The `DDLogMessage` class encapsulates information about the log message.
|
||||
* If you write custom loggers or formatters, you will be dealing with objects of this class.
|
||||
**/
|
||||
@interface DDLogMessage : NSObject <NSCopying>
|
||||
{
|
||||
// Direct accessors to be used only for performance
|
||||
@public
|
||||
NSString *_message;
|
||||
DDLogLevel _level;
|
||||
DDLogFlag _flag;
|
||||
NSInteger _context;
|
||||
NSString *_file;
|
||||
NSString *_fileName;
|
||||
NSString *_function;
|
||||
NSUInteger _line;
|
||||
id _tag;
|
||||
DDLogMessageOptions _options;
|
||||
NSDate *_timestamp;
|
||||
NSString *_threadID;
|
||||
NSString *_threadName;
|
||||
NSString *_queueLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default `init` for empty messages.
|
||||
*/
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Standard init method for a log message object.
|
||||
* Used by the logging primitives. (And the macros use the logging primitives.)
|
||||
*
|
||||
* If you find need to manually create logMessage objects, there is one thing you should be aware of:
|
||||
*
|
||||
* If no flags are passed, the method expects the file and function parameters to be string literals.
|
||||
* That is, it expects the given strings to exist for the duration of the object's lifetime,
|
||||
* and it expects the given strings to be immutable.
|
||||
* In other words, it does not copy these strings, it simply points to them.
|
||||
* This is due to the fact that __FILE__ and __FUNCTION__ are usually used to specify these parameters,
|
||||
* so it makes sense to optimize and skip the unnecessary allocations.
|
||||
* However, if you need them to be copied you may use the options parameter to specify this.
|
||||
*
|
||||
* @param message the message
|
||||
* @param level the log level
|
||||
* @param flag the log flag
|
||||
* @param context the context (if any is defined)
|
||||
* @param file the current file
|
||||
* @param function the current function
|
||||
* @param line the current code line
|
||||
* @param tag potential tag
|
||||
* @param options a bitmask which supports DDLogMessageCopyFile and DDLogMessageCopyFunction.
|
||||
* @param timestamp the log timestamp
|
||||
*
|
||||
* @return a new instance of a log message model object
|
||||
*/
|
||||
- (instancetype)initWithMessage:(NSString *)message
|
||||
level:(DDLogLevel)level
|
||||
flag:(DDLogFlag)flag
|
||||
context:(NSInteger)context
|
||||
file:(NSString *)file
|
||||
function:(NSString * __nullable)function
|
||||
line:(NSUInteger)line
|
||||
tag:(id __nullable)tag
|
||||
options:(DDLogMessageOptions)options
|
||||
timestamp:(NSDate * __nullable)timestamp NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Read-only properties
|
||||
**/
|
||||
|
||||
/**
|
||||
* The log message
|
||||
*/
|
||||
@property (readonly, nonatomic) NSString *message;
|
||||
@property (readonly, nonatomic) DDLogLevel level;
|
||||
@property (readonly, nonatomic) DDLogFlag flag;
|
||||
@property (readonly, nonatomic) NSInteger context;
|
||||
@property (readonly, nonatomic) NSString *file;
|
||||
@property (readonly, nonatomic) NSString *fileName;
|
||||
@property (readonly, nonatomic) NSString * __nullable function;
|
||||
@property (readonly, nonatomic) NSUInteger line;
|
||||
@property (readonly, nonatomic) id __nullable tag;
|
||||
@property (readonly, nonatomic) DDLogMessageOptions options;
|
||||
@property (readonly, nonatomic) NSDate *timestamp;
|
||||
@property (readonly, nonatomic) NSString *threadID; // ID as it appears in NSLog calculated from the machThreadID
|
||||
@property (readonly, nonatomic) NSString *threadName;
|
||||
@property (readonly, nonatomic) NSString *queueLabel;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The `DDLogger` protocol specifies that an optional formatter can be added to a logger.
|
||||
* Most (but not all) loggers will want to support formatters.
|
||||
*
|
||||
* However, writting getters and setters in a thread safe manner,
|
||||
* while still maintaining maximum speed for the logging process, is a difficult task.
|
||||
*
|
||||
* To do it right, the implementation of the getter/setter has strict requiremenets:
|
||||
* - Must NOT require the `logMessage:` method to acquire a lock.
|
||||
* - Must NOT require the `logMessage:` method to access an atomic property (also a lock of sorts).
|
||||
*
|
||||
* To simplify things, an abstract logger is provided that implements the getter and setter.
|
||||
*
|
||||
* Logger implementations may simply extend this class,
|
||||
* and they can ACCESS THE FORMATTER VARIABLE DIRECTLY from within their `logMessage:` method!
|
||||
**/
|
||||
@interface DDAbstractLogger : NSObject <DDLogger>
|
||||
{
|
||||
// Direct accessors to be used only for performance
|
||||
@public
|
||||
id <DDLogFormatter> _logFormatter;
|
||||
dispatch_queue_t _loggerQueue;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong, nullable) id <DDLogFormatter> logFormatter;
|
||||
@property (nonatomic, DISPATCH_QUEUE_REFERENCE_TYPE) dispatch_queue_t loggerQueue;
|
||||
|
||||
// For thread-safety assertions
|
||||
|
||||
/**
|
||||
* Return YES if the current logger uses a global queue for logging
|
||||
*/
|
||||
@property (nonatomic, readonly, getter=isOnGlobalLoggingQueue) BOOL onGlobalLoggingQueue;
|
||||
|
||||
/**
|
||||
* Return YES if the current logger uses the internal designated queue for logging
|
||||
*/
|
||||
@property (nonatomic, readonly, getter=isOnInternalLoggerQueue) BOOL onInternalLoggerQueue;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface DDLoggerInformation : NSObject
|
||||
|
||||
@property (nonatomic, readonly) id <DDLogger> logger;
|
||||
@property (nonatomic, readonly) DDLogLevel level;
|
||||
|
||||
+ (DDLoggerInformation *)informationWithLogger:(id <DDLogger>)logger
|
||||
andLevel:(DDLogLevel)level;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
1403
Examples/CloudKitTodo/Pods/CocoaLumberjack/Classes/DDLog.m
generated
1403
Examples/CloudKitTodo/Pods/CocoaLumberjack/Classes/DDLog.m
generated
File diff suppressed because it is too large
Load Diff
@ -1,101 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* The constant/variable/method responsible for controlling the current log level.
|
||||
**/
|
||||
#ifndef LOG_LEVEL_DEF
|
||||
#define LOG_LEVEL_DEF ddLogLevel
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Whether async should be used by log messages, excluding error messages that are always sent sync.
|
||||
**/
|
||||
#ifndef LOG_ASYNC_ENABLED
|
||||
#define LOG_ASYNC_ENABLED YES
|
||||
#endif
|
||||
|
||||
/**
|
||||
* These are the two macros that all other macros below compile into.
|
||||
* These big multiline macros makes all the other macros easier to read.
|
||||
**/
|
||||
#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \
|
||||
[DDLog log : isAsynchronous \
|
||||
level : lvl \
|
||||
flag : flg \
|
||||
context : ctx \
|
||||
file : __FILE__ \
|
||||
function : fnct \
|
||||
line : __LINE__ \
|
||||
tag : atag \
|
||||
format : (frmt), ## __VA_ARGS__]
|
||||
|
||||
#define LOG_MACRO_TO_DDLOG(ddlog, isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \
|
||||
[ddlog log : isAsynchronous \
|
||||
level : lvl \
|
||||
flag : flg \
|
||||
context : ctx \
|
||||
file : __FILE__ \
|
||||
function : fnct \
|
||||
line : __LINE__ \
|
||||
tag : atag \
|
||||
format : (frmt), ## __VA_ARGS__]
|
||||
|
||||
/**
|
||||
* Define version of the macro that only execute if the log level is above the threshold.
|
||||
* The compiled versions essentially look like this:
|
||||
*
|
||||
* if (logFlagForThisLogMsg & ddLogLevel) { execute log message }
|
||||
*
|
||||
* When LOG_LEVEL_DEF is defined as ddLogLevel.
|
||||
*
|
||||
* As shown further below, Lumberjack actually uses a bitmask as opposed to primitive log levels.
|
||||
* This allows for a great amount of flexibility and some pretty advanced fine grained logging techniques.
|
||||
*
|
||||
* Note that when compiler optimizations are enabled (as they are for your release builds),
|
||||
* the log messages above your logging threshold will automatically be compiled out.
|
||||
*
|
||||
* (If the compiler sees LOG_LEVEL_DEF/ddLogLevel declared as a constant, the compiler simply checks to see
|
||||
* if the 'if' statement would execute, and if not it strips it from the binary.)
|
||||
*
|
||||
* We also define shorthand versions for asynchronous and synchronous logging.
|
||||
**/
|
||||
#define LOG_MAYBE(async, lvl, flg, ctx, tag, fnct, frmt, ...) \
|
||||
do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, tag, fnct, frmt, ##__VA_ARGS__); } while(0)
|
||||
|
||||
#define LOG_MAYBE_TO_DDLOG(ddlog, async, lvl, flg, ctx, tag, fnct, frmt, ...) \
|
||||
do { if(lvl & flg) LOG_MACRO_TO_DDLOG(ddlog, async, lvl, flg, ctx, tag, fnct, frmt, ##__VA_ARGS__); } while(0)
|
||||
|
||||
/**
|
||||
* Ready to use log macros with no context or tag.
|
||||
**/
|
||||
#define DDLogError(frmt, ...) LOG_MAYBE(NO, LOG_LEVEL_DEF, DDLogFlagError, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
#define DDLogWarn(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagWarning, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
#define DDLogInfo(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
#define DDLogDebug(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
#define DDLogVerbose(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagVerbose, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define DDLogErrorToDDLog(ddlog, frmt, ...) LOG_MAYBE_TO_DDLOG(ddlog, NO, LOG_LEVEL_DEF, DDLogFlagError, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
#define DDLogWarnToDDLog(ddlog, frmt, ...) LOG_MAYBE_TO_DDLOG(ddlog, LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagWarning, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
#define DDLogInfoToDDLog(ddlog, frmt, ...) LOG_MAYBE_TO_DDLOG(ddlog, LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
#define DDLogDebugToDDLog(ddlog, frmt, ...) LOG_MAYBE_TO_DDLOG(ddlog, LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
#define DDLogVerboseToDDLog(ddlog, frmt, ...) LOG_MAYBE_TO_DDLOG(ddlog, LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagVerbose, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
@ -1,38 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* This class provides a logger for the Apple os_log facility.
|
||||
**/
|
||||
API_AVAILABLE(ios(10.0), macos(10.12), tvos(10.0), watchos(3.0))
|
||||
@interface DDOSLogger : DDAbstractLogger <DDLogger>
|
||||
|
||||
/**
|
||||
* Singleton method
|
||||
*
|
||||
* @return the shared instance
|
||||
*/
|
||||
@property (class, readonly, strong) DDOSLogger *sharedInstance;
|
||||
|
||||
@end
|
||||
@ -1,77 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import "DDOSLogger.h"
|
||||
#import <os/log.h>
|
||||
|
||||
static DDOSLogger *sharedInstance;
|
||||
|
||||
@implementation DDOSLogger
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static dispatch_once_t DDOSLoggerOnceToken;
|
||||
|
||||
dispatch_once(&DDOSLoggerOnceToken, ^{
|
||||
sharedInstance = [[[self class] alloc] init];
|
||||
});
|
||||
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
if (sharedInstance != nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (self = [super init]) {
|
||||
return self;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)logMessage:(DDLogMessage *)logMessage {
|
||||
// Skip captured log messages
|
||||
if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString * message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message;
|
||||
|
||||
if (message) {
|
||||
const char *msg = [message UTF8String];
|
||||
|
||||
switch (logMessage->_flag) {
|
||||
case DDLogFlagError :
|
||||
os_log_error(OS_LOG_DEFAULT, "%{public}s", msg);
|
||||
break;
|
||||
case DDLogFlagWarning :
|
||||
case DDLogFlagInfo :
|
||||
os_log_info(OS_LOG_DEFAULT, "%{public}s", msg);
|
||||
break;
|
||||
case DDLogFlagDebug :
|
||||
case DDLogFlagVerbose :
|
||||
default :
|
||||
os_log_debug(OS_LOG_DEFAULT, "%{public}s", msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)loggerName {
|
||||
return @"cocoa.lumberjack.osLogger";
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,178 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
#define LOG_CONTEXT_ALL INT_MAX
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-function"
|
||||
#if !(TARGET_OS_OSX)
|
||||
// iOS or tvOS or watchOS
|
||||
#import <UIKit/UIColor.h>
|
||||
typedef UIColor DDColor;
|
||||
static inline DDColor* DDMakeColor(CGFloat r, CGFloat g, CGFloat b) {return [DDColor colorWithRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f];}
|
||||
#elif defined(DD_CLI) || !__has_include(<AppKit/NSColor.h>)
|
||||
// OS X CLI
|
||||
#import "CLIColor.h"
|
||||
typedef CLIColor DDColor;
|
||||
static inline DDColor* DDMakeColor(CGFloat r, CGFloat g, CGFloat b) {return [DDColor colorWithCalibratedRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f];}
|
||||
#else
|
||||
// OS X with AppKit
|
||||
#import <AppKit/NSColor.h>
|
||||
typedef NSColor DDColor;
|
||||
static inline DDColor* DDMakeColor(CGFloat r, CGFloat g, CGFloat b) {return [DDColor colorWithCalibratedRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f];}
|
||||
#endif
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
|
||||
/**
|
||||
* This class provides a logger for Terminal output or Xcode console output,
|
||||
* depending on where you are running your code.
|
||||
*
|
||||
* As described in the "Getting Started" page,
|
||||
* the traditional NSLog() function directs it's output to two places:
|
||||
*
|
||||
* - Apple System Log (so it shows up in Console.app)
|
||||
* - StdErr (if stderr is a TTY, so log statements show up in Xcode console)
|
||||
*
|
||||
* To duplicate NSLog() functionality you can simply add this logger and an asl logger.
|
||||
* However, if you instead choose to use file logging (for faster performance),
|
||||
* you may choose to use only a file logger and a tty logger.
|
||||
**/
|
||||
@interface DDTTYLogger : DDAbstractLogger <DDLogger>
|
||||
|
||||
/**
|
||||
* Singleton method
|
||||
*/
|
||||
@property (class, readonly, strong) DDTTYLogger *sharedInstance;
|
||||
|
||||
/* Inherited from the DDLogger protocol:
|
||||
*
|
||||
* Formatters may optionally be added to any logger.
|
||||
*
|
||||
* If no formatter is set, the logger simply logs the message as it is given in logMessage,
|
||||
* or it may use its own built in formatting style.
|
||||
*
|
||||
* More information about formatters can be found here:
|
||||
* Documentation/CustomFormatters.md
|
||||
*
|
||||
* The actual implementation of these methods is inherited from DDAbstractLogger.
|
||||
|
||||
- (id <DDLogFormatter>)logFormatter;
|
||||
- (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Want to use different colors for different log levels?
|
||||
* Enable this property.
|
||||
*
|
||||
* If you run the application via the Terminal (not Xcode),
|
||||
* the logger will map colors to xterm-256color or xterm-color (if available).
|
||||
*
|
||||
* Xcode does NOT natively support colors in the Xcode debugging console.
|
||||
* You'll need to install the XcodeColors plugin to see colors in the Xcode console.
|
||||
* https://github.com/robbiehanson/XcodeColors
|
||||
*
|
||||
* The default value is NO.
|
||||
**/
|
||||
@property (readwrite, assign) BOOL colorsEnabled;
|
||||
|
||||
/**
|
||||
* When using a custom formatter you can set the `logMessage` method not to append
|
||||
* `\n` character after each output. This allows for some greater flexibility with
|
||||
* custom formatters. Default value is YES.
|
||||
**/
|
||||
@property (nonatomic, readwrite, assign) BOOL automaticallyAppendNewlineForCustomFormatters;
|
||||
|
||||
/**
|
||||
* The default color set (foregroundColor, backgroundColor) is:
|
||||
*
|
||||
* - DDLogFlagError = (red, nil)
|
||||
* - DDLogFlagWarning = (orange, nil)
|
||||
*
|
||||
* You can customize the colors however you see fit.
|
||||
* Please note that you are passing a flag, NOT a level.
|
||||
*
|
||||
* GOOD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:DDLogFlagInfo]; // <- Good :)
|
||||
* BAD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:DDLogLevelInfo]; // <- BAD! :(
|
||||
*
|
||||
* DDLogFlagInfo = 0...00100
|
||||
* DDLogLevelInfo = 0...00111 <- Would match DDLogFlagInfo and DDLogFlagWarning and DDLogFlagError
|
||||
*
|
||||
* If you run the application within Xcode, then the XcodeColors plugin is required.
|
||||
*
|
||||
* If you run the application from a shell, then DDTTYLogger will automatically map the given color to
|
||||
* the closest available color. (xterm-256color or xterm-color which have 256 and 16 supported colors respectively.)
|
||||
*
|
||||
* This method invokes setForegroundColor:backgroundColor:forFlag:context: and applies it to `LOG_CONTEXT_ALL`.
|
||||
**/
|
||||
- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask;
|
||||
|
||||
/**
|
||||
* Just like setForegroundColor:backgroundColor:flag, but allows you to specify a particular logging context.
|
||||
*
|
||||
* A logging context is often used to identify log messages coming from a 3rd party framework,
|
||||
* although logging context's can be used for many different functions.
|
||||
*
|
||||
* Use LOG_CONTEXT_ALL to set the deafult color for all contexts that have no specific color set defined.
|
||||
*
|
||||
* Logging context's are explained in further detail here:
|
||||
* Documentation/CustomContext.md
|
||||
**/
|
||||
- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask context:(NSInteger)ctxt;
|
||||
|
||||
/**
|
||||
* Similar to the methods above, but allows you to map DDLogMessage->tag to a particular color profile.
|
||||
* For example, you could do something like this:
|
||||
*
|
||||
* static NSString *const PurpleTag = @"PurpleTag";
|
||||
*
|
||||
* #define DDLogPurple(frmt, ...) LOG_OBJC_TAG_MACRO(NO, 0, 0, 0, PurpleTag, frmt, ##__VA_ARGS__)
|
||||
*
|
||||
* And then where you configure CocoaLumberjack:
|
||||
*
|
||||
* purple = DDMakeColor((64/255.0), (0/255.0), (128/255.0));
|
||||
*
|
||||
* or any UIColor/NSColor constructor.
|
||||
*
|
||||
* Note: For CLI OS X projects that don't link with AppKit use CLIColor objects instead
|
||||
*
|
||||
* [[DDTTYLogger sharedInstance] setForegroundColor:purple backgroundColor:nil forTag:PurpleTag];
|
||||
* [DDLog addLogger:[DDTTYLogger sharedInstance]];
|
||||
*
|
||||
* This would essentially give you a straight NSLog replacement that prints in purple:
|
||||
*
|
||||
* DDLogPurple(@"I'm a purple log message!");
|
||||
**/
|
||||
- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forTag:(id <NSCopying>)tag;
|
||||
|
||||
/**
|
||||
* Clearing color profiles.
|
||||
**/
|
||||
- (void)clearColorsForFlag:(DDLogFlag)mask;
|
||||
- (void)clearColorsForFlag:(DDLogFlag)mask context:(NSInteger)context;
|
||||
- (void)clearColorsForTag:(id <NSCopying>)tag;
|
||||
- (void)clearColorsForAllFlags;
|
||||
- (void)clearColorsForAllTags;
|
||||
- (void)clearAllColors;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,117 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* This class provides a log formatter that filters log statements from a logging context not on the whitelist.
|
||||
*
|
||||
* A log formatter can be added to any logger to format and/or filter its output.
|
||||
* You can learn more about log formatters here:
|
||||
* Documentation/CustomFormatters.md
|
||||
*
|
||||
* You can learn more about logging context's here:
|
||||
* Documentation/CustomContext.md
|
||||
*
|
||||
* But here's a quick overview / refresher:
|
||||
*
|
||||
* Every log statement has a logging context.
|
||||
* These come from the underlying logging macros defined in DDLog.h.
|
||||
* The default logging context is zero.
|
||||
* You can define multiple logging context's for use in your application.
|
||||
* For example, logically separate parts of your app each have a different logging context.
|
||||
* Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context.
|
||||
**/
|
||||
@interface DDContextWhitelistFilterLogFormatter : NSObject <DDLogFormatter>
|
||||
|
||||
/**
|
||||
* Designated default initializer
|
||||
*/
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Add a context to the whitelist
|
||||
*
|
||||
* @param loggingContext the context
|
||||
*/
|
||||
- (void)addToWhitelist:(NSUInteger)loggingContext;
|
||||
|
||||
/**
|
||||
* Remove context from whitelist
|
||||
*
|
||||
* @param loggingContext the context
|
||||
*/
|
||||
- (void)removeFromWhitelist:(NSUInteger)loggingContext;
|
||||
|
||||
/**
|
||||
* Return the whitelist
|
||||
*/
|
||||
@property (readonly, copy) NSArray<NSNumber *> *whitelist;
|
||||
|
||||
/**
|
||||
* Check if a context is on the whitelist
|
||||
*
|
||||
* @param loggingContext the context
|
||||
*/
|
||||
- (BOOL)isOnWhitelist:(NSUInteger)loggingContext;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* This class provides a log formatter that filters log statements from a logging context on the blacklist.
|
||||
**/
|
||||
@interface DDContextBlacklistFilterLogFormatter : NSObject <DDLogFormatter>
|
||||
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Add a context to the blacklist
|
||||
*
|
||||
* @param loggingContext the context
|
||||
*/
|
||||
- (void)addToBlacklist:(NSUInteger)loggingContext;
|
||||
|
||||
/**
|
||||
* Remove context from blacklist
|
||||
*
|
||||
* @param loggingContext the context
|
||||
*/
|
||||
- (void)removeFromBlacklist:(NSUInteger)loggingContext;
|
||||
|
||||
/**
|
||||
* Return the blacklist
|
||||
*/
|
||||
@property (readonly, copy) NSArray<NSNumber *> *blacklist;
|
||||
|
||||
|
||||
/**
|
||||
* Check if a context is on the blacklist
|
||||
*
|
||||
* @param loggingContext the context
|
||||
*/
|
||||
- (BOOL)isOnBlacklist:(NSUInteger)loggingContext;
|
||||
|
||||
@end
|
||||
@ -1,192 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import "DDContextFilterLogFormatter.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
@interface DDLoggingContextSet : NSObject
|
||||
|
||||
- (void)addToSet:(NSUInteger)loggingContext;
|
||||
- (void)removeFromSet:(NSUInteger)loggingContext;
|
||||
|
||||
@property (readonly, copy) NSArray *currentSet;
|
||||
|
||||
- (BOOL)isInSet:(NSUInteger)loggingContext;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface DDContextWhitelistFilterLogFormatter () {
|
||||
DDLoggingContextSet *_contextSet;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation DDContextWhitelistFilterLogFormatter
|
||||
|
||||
- (instancetype)init {
|
||||
if ((self = [super init])) {
|
||||
_contextSet = [[DDLoggingContextSet alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addToWhitelist:(NSUInteger)loggingContext {
|
||||
[_contextSet addToSet:loggingContext];
|
||||
}
|
||||
|
||||
- (void)removeFromWhitelist:(NSUInteger)loggingContext {
|
||||
[_contextSet removeFromSet:loggingContext];
|
||||
}
|
||||
|
||||
- (NSArray *)whitelist {
|
||||
return [_contextSet currentSet];
|
||||
}
|
||||
|
||||
- (BOOL)isOnWhitelist:(NSUInteger)loggingContext {
|
||||
return [_contextSet isInSet:loggingContext];
|
||||
}
|
||||
|
||||
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
|
||||
if ([self isOnWhitelist:logMessage->_context]) {
|
||||
return logMessage->_message;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface DDContextBlacklistFilterLogFormatter () {
|
||||
DDLoggingContextSet *_contextSet;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation DDContextBlacklistFilterLogFormatter
|
||||
|
||||
- (instancetype)init {
|
||||
if ((self = [super init])) {
|
||||
_contextSet = [[DDLoggingContextSet alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addToBlacklist:(NSUInteger)loggingContext {
|
||||
[_contextSet addToSet:loggingContext];
|
||||
}
|
||||
|
||||
- (void)removeFromBlacklist:(NSUInteger)loggingContext {
|
||||
[_contextSet removeFromSet:loggingContext];
|
||||
}
|
||||
|
||||
- (NSArray *)blacklist {
|
||||
return [_contextSet currentSet];
|
||||
}
|
||||
|
||||
- (BOOL)isOnBlacklist:(NSUInteger)loggingContext {
|
||||
return [_contextSet isInSet:loggingContext];
|
||||
}
|
||||
|
||||
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
|
||||
if ([self isOnBlacklist:logMessage->_context]) {
|
||||
return nil;
|
||||
} else {
|
||||
return logMessage->_message;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@interface DDLoggingContextSet () {
|
||||
OSSpinLock _lock;
|
||||
NSMutableSet *_set;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation DDLoggingContextSet
|
||||
|
||||
- (instancetype)init {
|
||||
if ((self = [super init])) {
|
||||
_set = [[NSMutableSet alloc] init];
|
||||
_lock = OS_SPINLOCK_INIT;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addToSet:(NSUInteger)loggingContext {
|
||||
OSSpinLockLock(&_lock);
|
||||
{
|
||||
[_set addObject:@(loggingContext)];
|
||||
}
|
||||
OSSpinLockUnlock(&_lock);
|
||||
}
|
||||
|
||||
- (void)removeFromSet:(NSUInteger)loggingContext {
|
||||
OSSpinLockLock(&_lock);
|
||||
{
|
||||
[_set removeObject:@(loggingContext)];
|
||||
}
|
||||
OSSpinLockUnlock(&_lock);
|
||||
}
|
||||
|
||||
- (NSArray *)currentSet {
|
||||
NSArray *result = nil;
|
||||
|
||||
OSSpinLockLock(&_lock);
|
||||
{
|
||||
result = [_set allObjects];
|
||||
}
|
||||
OSSpinLockUnlock(&_lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)isInSet:(NSUInteger)loggingContext {
|
||||
BOOL result = NO;
|
||||
|
||||
OSSpinLockLock(&_lock);
|
||||
{
|
||||
result = [_set containsObject:@(loggingContext)];
|
||||
}
|
||||
OSSpinLockUnlock(&_lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,178 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* Log formatter mode
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, DDDispatchQueueLogFormatterMode){
|
||||
/**
|
||||
* This is the default option, means the formatter can be reused between multiple loggers and therefore is thread-safe.
|
||||
* There is, of course, a performance cost for the thread-safety
|
||||
*/
|
||||
DDDispatchQueueLogFormatterModeShareble = 0,
|
||||
/**
|
||||
* If the formatter will only be used by a single logger, then the thread-safety can be removed
|
||||
* @note: there is an assert checking if the formatter is added to multiple loggers and the mode is non-shareble
|
||||
*/
|
||||
DDDispatchQueueLogFormatterModeNonShareble,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This class provides a log formatter that prints the dispatch_queue label instead of the mach_thread_id.
|
||||
*
|
||||
* A log formatter can be added to any logger to format and/or filter its output.
|
||||
* You can learn more about log formatters here:
|
||||
* Documentation/CustomFormatters.md
|
||||
*
|
||||
* A typical `NSLog` (or `DDTTYLogger`) prints detailed info as `[<process_id>:<thread_id>]`.
|
||||
* For example:
|
||||
*
|
||||
* `2011-10-17 20:21:45.435 AppName[19928:5207] Your log message here`
|
||||
*
|
||||
* Where:
|
||||
* `- 19928 = process id`
|
||||
* `- 5207 = thread id (mach_thread_id printed in hex)`
|
||||
*
|
||||
* When using grand central dispatch (GCD), this information is less useful.
|
||||
* This is because a single serial dispatch queue may be run on any thread from an internally managed thread pool.
|
||||
* For example:
|
||||
*
|
||||
* `2011-10-17 20:32:31.111 AppName[19954:4d07] Message from my_serial_dispatch_queue`
|
||||
* `2011-10-17 20:32:31.112 AppName[19954:5207] Message from my_serial_dispatch_queue`
|
||||
* `2011-10-17 20:32:31.113 AppName[19954:2c55] Message from my_serial_dispatch_queue`
|
||||
*
|
||||
* This formatter allows you to replace the standard `[box:info]` with the dispatch_queue name.
|
||||
* For example:
|
||||
*
|
||||
* `2011-10-17 20:32:31.111 AppName[img-scaling] Message from my_serial_dispatch_queue`
|
||||
* `2011-10-17 20:32:31.112 AppName[img-scaling] Message from my_serial_dispatch_queue`
|
||||
* `2011-10-17 20:32:31.113 AppName[img-scaling] Message from my_serial_dispatch_queue`
|
||||
*
|
||||
* If the dispatch_queue doesn't have a set name, then it falls back to the thread name.
|
||||
* If the current thread doesn't have a set name, then it falls back to the mach_thread_id in hex (like normal).
|
||||
*
|
||||
* Note: If manually creating your own background threads (via `NSThread/alloc/init` or `NSThread/detachNeThread`),
|
||||
* you can use `[[NSThread currentThread] setName:(NSString *)]`.
|
||||
**/
|
||||
@interface DDDispatchQueueLogFormatter : NSObject <DDLogFormatter>
|
||||
|
||||
/**
|
||||
* Standard init method.
|
||||
* Configure using properties as desired.
|
||||
**/
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Initializer with ability to set the queue mode
|
||||
*
|
||||
* @param mode choose between DDDispatchQueueLogFormatterModeShareble and DDDispatchQueueLogFormatterModeNonShareble, depending if the formatter is shared between several loggers or not
|
||||
*/
|
||||
- (instancetype)initWithMode:(DDDispatchQueueLogFormatterMode)mode;
|
||||
|
||||
/**
|
||||
* The minQueueLength restricts the minimum size of the [detail box].
|
||||
* If the minQueueLength is set to 0, there is no restriction.
|
||||
*
|
||||
* For example, say a dispatch_queue has a label of "diskIO":
|
||||
*
|
||||
* If the minQueueLength is 0: [diskIO]
|
||||
* If the minQueueLength is 4: [diskIO]
|
||||
* If the minQueueLength is 5: [diskIO]
|
||||
* If the minQueueLength is 6: [diskIO]
|
||||
* If the minQueueLength is 7: [diskIO ]
|
||||
* If the minQueueLength is 8: [diskIO ]
|
||||
*
|
||||
* The default minQueueLength is 0 (no minimum, so [detail box] won't be padded).
|
||||
*
|
||||
* If you want every [detail box] to have the exact same width,
|
||||
* set both minQueueLength and maxQueueLength to the same value.
|
||||
**/
|
||||
@property (assign, atomic) NSUInteger minQueueLength;
|
||||
|
||||
/**
|
||||
* The maxQueueLength restricts the number of characters that will be inside the [detail box].
|
||||
* If the maxQueueLength is 0, there is no restriction.
|
||||
*
|
||||
* For example, say a dispatch_queue has a label of "diskIO":
|
||||
*
|
||||
* If the maxQueueLength is 0: [diskIO]
|
||||
* If the maxQueueLength is 4: [disk]
|
||||
* If the maxQueueLength is 5: [diskI]
|
||||
* If the maxQueueLength is 6: [diskIO]
|
||||
* If the maxQueueLength is 7: [diskIO]
|
||||
* If the maxQueueLength is 8: [diskIO]
|
||||
*
|
||||
* The default maxQueueLength is 0 (no maximum, so [detail box] won't be truncated).
|
||||
*
|
||||
* If you want every [detail box] to have the exact same width,
|
||||
* set both minQueueLength and maxQueueLength to the same value.
|
||||
**/
|
||||
@property (assign, atomic) NSUInteger maxQueueLength;
|
||||
|
||||
/**
|
||||
* Sometimes queue labels have long names like "com.apple.main-queue",
|
||||
* but you'd prefer something shorter like simply "main".
|
||||
*
|
||||
* This method allows you to set such preferred replacements.
|
||||
* The above example is set by default.
|
||||
*
|
||||
* To remove/undo a previous replacement, invoke this method with nil for the 'shortLabel' parameter.
|
||||
**/
|
||||
- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel;
|
||||
|
||||
/**
|
||||
* See the `replacementStringForQueueLabel:` description
|
||||
*/
|
||||
- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Category on `DDDispatchQueueLogFormatter` to make method declarations easier to extend/modify
|
||||
**/
|
||||
@interface DDDispatchQueueLogFormatter (OverridableMethods)
|
||||
|
||||
/**
|
||||
* Date formatter default configuration
|
||||
*/
|
||||
- (void)configureDateFormatter:(NSDateFormatter *)dateFormatter;
|
||||
|
||||
/**
|
||||
* Formatter method to transfrom from date to string
|
||||
*/
|
||||
- (NSString *)stringFromDate:(NSDate *)date;
|
||||
|
||||
/**
|
||||
* Method to compute the queue thread label
|
||||
*/
|
||||
- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage;
|
||||
|
||||
/**
|
||||
* The actual method that formats a message (transforms a `DDLogMessage` model into a printable string)
|
||||
*/
|
||||
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage;
|
||||
|
||||
@end
|
||||
@ -1,278 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import "DDDispatchQueueLogFormatter.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
@interface DDDispatchQueueLogFormatter () {
|
||||
DDDispatchQueueLogFormatterMode _mode;
|
||||
NSString *_dateFormatterKey;
|
||||
|
||||
int32_t _atomicLoggerCount;
|
||||
NSDateFormatter *_threadUnsafeDateFormatter; // Use [self stringFromDate]
|
||||
|
||||
OSSpinLock _lock;
|
||||
|
||||
NSUInteger _minQueueLength; // _prefix == Only access via atomic property
|
||||
NSUInteger _maxQueueLength; // _prefix == Only access via atomic property
|
||||
NSMutableDictionary *_replacements; // _prefix == Only access from within spinlock
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation DDDispatchQueueLogFormatter
|
||||
|
||||
- (instancetype)init {
|
||||
if ((self = [super init])) {
|
||||
_mode = DDDispatchQueueLogFormatterModeShareble;
|
||||
|
||||
// We need to carefully pick the name for storing in thread dictionary to not
|
||||
// use a formatter configured by subclass and avoid surprises.
|
||||
Class cls = [self class];
|
||||
Class superClass = class_getSuperclass(cls);
|
||||
SEL configMethodName = @selector(configureDateFormatter:);
|
||||
Method configMethod = class_getInstanceMethod(cls, configMethodName);
|
||||
while (class_getInstanceMethod(superClass, configMethodName) == configMethod) {
|
||||
cls = superClass;
|
||||
superClass = class_getSuperclass(cls);
|
||||
}
|
||||
// now `cls` is the class that provides implementation for `configureDateFormatter:`
|
||||
_dateFormatterKey = [NSString stringWithFormat:@"%s_NSDateFormatter", class_getName(cls)];
|
||||
|
||||
_atomicLoggerCount = 0;
|
||||
_threadUnsafeDateFormatter = nil;
|
||||
|
||||
_minQueueLength = 0;
|
||||
_maxQueueLength = 0;
|
||||
_lock = OS_SPINLOCK_INIT;
|
||||
_replacements = [[NSMutableDictionary alloc] init];
|
||||
|
||||
// Set default replacements:
|
||||
|
||||
_replacements[@"com.apple.main-thread"] = @"main";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMode:(DDDispatchQueueLogFormatterMode)mode {
|
||||
if ((self = [self init])) {
|
||||
_mode = mode;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Configuration
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@synthesize minQueueLength = _minQueueLength;
|
||||
@synthesize maxQueueLength = _maxQueueLength;
|
||||
|
||||
- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel {
|
||||
NSString *result = nil;
|
||||
|
||||
OSSpinLockLock(&_lock);
|
||||
{
|
||||
result = _replacements[longLabel];
|
||||
}
|
||||
OSSpinLockUnlock(&_lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel {
|
||||
OSSpinLockLock(&_lock);
|
||||
{
|
||||
if (shortLabel) {
|
||||
_replacements[longLabel] = shortLabel;
|
||||
} else {
|
||||
[_replacements removeObjectForKey:longLabel];
|
||||
}
|
||||
}
|
||||
OSSpinLockUnlock(&_lock);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark DDLogFormatter
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSDateFormatter *)createDateFormatter {
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
[self configureDateFormatter:formatter];
|
||||
return formatter;
|
||||
}
|
||||
|
||||
- (void)configureDateFormatter:(NSDateFormatter *)dateFormatter {
|
||||
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
|
||||
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss:SSS"];
|
||||
[dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
||||
|
||||
NSString *calendarIdentifier = nil;
|
||||
#if defined(__IPHONE_8_0) || defined(__MAC_10_10)
|
||||
calendarIdentifier = NSCalendarIdentifierGregorian;
|
||||
#else
|
||||
calendarIdentifier = NSGregorianCalendar;
|
||||
#endif
|
||||
|
||||
[dateFormatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:calendarIdentifier]];
|
||||
}
|
||||
|
||||
- (NSString *)stringFromDate:(NSDate *)date {
|
||||
|
||||
NSDateFormatter *dateFormatter = nil;
|
||||
if (_mode == DDDispatchQueueLogFormatterModeNonShareble) {
|
||||
// Single-threaded mode.
|
||||
|
||||
dateFormatter = _threadUnsafeDateFormatter;
|
||||
if (dateFormatter == nil) {
|
||||
dateFormatter = [self createDateFormatter];
|
||||
_threadUnsafeDateFormatter = dateFormatter;
|
||||
}
|
||||
} else {
|
||||
// Multi-threaded mode.
|
||||
// NSDateFormatter is NOT thread-safe.
|
||||
|
||||
NSString *key = _dateFormatterKey;
|
||||
|
||||
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
|
||||
dateFormatter = threadDictionary[key];
|
||||
|
||||
if (dateFormatter == nil) {
|
||||
dateFormatter = [self createDateFormatter];
|
||||
threadDictionary[key] = dateFormatter;
|
||||
}
|
||||
}
|
||||
|
||||
return [dateFormatter stringFromDate:date];
|
||||
}
|
||||
|
||||
- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage {
|
||||
// As per the DDLogFormatter contract, this method is always invoked on the same thread/dispatch_queue
|
||||
|
||||
NSUInteger minQueueLength = self.minQueueLength;
|
||||
NSUInteger maxQueueLength = self.maxQueueLength;
|
||||
|
||||
// Get the name of the queue, thread, or machID (whichever we are to use).
|
||||
|
||||
NSString *queueThreadLabel = nil;
|
||||
|
||||
BOOL useQueueLabel = YES;
|
||||
BOOL useThreadName = NO;
|
||||
|
||||
if (logMessage->_queueLabel) {
|
||||
// If you manually create a thread, it's dispatch_queue will have one of the thread names below.
|
||||
// Since all such threads have the same name, we'd prefer to use the threadName or the machThreadID.
|
||||
|
||||
NSArray *names = @[
|
||||
@"com.apple.root.low-priority",
|
||||
@"com.apple.root.default-priority",
|
||||
@"com.apple.root.high-priority",
|
||||
@"com.apple.root.low-overcommit-priority",
|
||||
@"com.apple.root.default-overcommit-priority",
|
||||
@"com.apple.root.high-overcommit-priority"
|
||||
];
|
||||
|
||||
for (NSString * name in names) {
|
||||
if ([logMessage->_queueLabel isEqualToString:name]) {
|
||||
useQueueLabel = NO;
|
||||
useThreadName = [logMessage->_threadName length] > 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
useQueueLabel = NO;
|
||||
useThreadName = [logMessage->_threadName length] > 0;
|
||||
}
|
||||
|
||||
if (useQueueLabel || useThreadName) {
|
||||
NSString *fullLabel;
|
||||
NSString *abrvLabel;
|
||||
|
||||
if (useQueueLabel) {
|
||||
fullLabel = logMessage->_queueLabel;
|
||||
} else {
|
||||
fullLabel = logMessage->_threadName;
|
||||
}
|
||||
|
||||
OSSpinLockLock(&_lock);
|
||||
{
|
||||
abrvLabel = _replacements[fullLabel];
|
||||
}
|
||||
OSSpinLockUnlock(&_lock);
|
||||
|
||||
if (abrvLabel) {
|
||||
queueThreadLabel = abrvLabel;
|
||||
} else {
|
||||
queueThreadLabel = fullLabel;
|
||||
}
|
||||
} else {
|
||||
queueThreadLabel = logMessage->_threadID;
|
||||
}
|
||||
|
||||
// Now use the thread label in the output
|
||||
|
||||
NSUInteger labelLength = [queueThreadLabel length];
|
||||
|
||||
// labelLength > maxQueueLength : truncate
|
||||
// labelLength < minQueueLength : padding
|
||||
// : exact
|
||||
|
||||
if ((maxQueueLength > 0) && (labelLength > maxQueueLength)) {
|
||||
// Truncate
|
||||
|
||||
return [queueThreadLabel substringToIndex:maxQueueLength];
|
||||
} else if (labelLength < minQueueLength) {
|
||||
// Padding
|
||||
|
||||
NSUInteger numSpaces = minQueueLength - labelLength;
|
||||
|
||||
char spaces[numSpaces + 1];
|
||||
memset(spaces, ' ', numSpaces);
|
||||
spaces[numSpaces] = '\0';
|
||||
|
||||
return [NSString stringWithFormat:@"%@%s", queueThreadLabel, spaces];
|
||||
} else {
|
||||
// Exact
|
||||
|
||||
return queueThreadLabel;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
|
||||
NSString *timestamp = [self stringFromDate:(logMessage->_timestamp)];
|
||||
NSString *queueThreadLabel = [self queueThreadLabelForLogMessage:logMessage];
|
||||
|
||||
return [NSString stringWithFormat:@"%@ [%@] %@", timestamp, queueThreadLabel, logMessage->_message];
|
||||
}
|
||||
|
||||
- (void)didAddToLogger:(id <DDLogger> __attribute__((unused)))logger {
|
||||
int32_t count = 0;
|
||||
count = OSAtomicIncrement32(&_atomicLoggerCount);
|
||||
NSAssert(count <= 1 || _mode == DDDispatchQueueLogFormatterModeShareble, @"Can't reuse formatter with multiple loggers in non-shareable mode.");
|
||||
}
|
||||
|
||||
- (void)willRemoveFromLogger:(id <DDLogger> __attribute__((unused)))logger {
|
||||
OSAtomicDecrement32(&_atomicLoggerCount);
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,56 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Disable legacy macros
|
||||
#ifndef DD_LEGACY_MACROS
|
||||
#define DD_LEGACY_MACROS 0
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* This formatter can be used to chain different formatters together.
|
||||
* The log message will processed in the order of the formatters added.
|
||||
**/
|
||||
@interface DDMultiFormatter : NSObject <DDLogFormatter>
|
||||
|
||||
/**
|
||||
* Array of chained formatters
|
||||
*/
|
||||
@property (readonly) NSArray<id<DDLogFormatter>> *formatters;
|
||||
|
||||
/**
|
||||
* Add a new formatter
|
||||
*/
|
||||
- (void)addFormatter:(id<DDLogFormatter>)formatter NS_SWIFT_NAME(add(_:));
|
||||
|
||||
/**
|
||||
* Remove a formatter
|
||||
*/
|
||||
- (void)removeFormatter:(id<DDLogFormatter>)formatter NS_SWIFT_NAME(remove(_:));
|
||||
|
||||
/**
|
||||
* Remove all existing formatters
|
||||
*/
|
||||
- (void)removeAllFormatters NS_SWIFT_NAME(removeAll());
|
||||
|
||||
/**
|
||||
* Check if a certain formatter is used
|
||||
*/
|
||||
- (BOOL)isFormattingWithFormatter:(id<DDLogFormatter>)formatter;
|
||||
|
||||
@end
|
||||
@ -1,144 +0,0 @@
|
||||
// Software License Agreement (BSD License)
|
||||
//
|
||||
// Copyright (c) 2010-2016, 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.
|
||||
|
||||
#import "DDMultiFormatter.h"
|
||||
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
// Compiling for iOS
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
|
||||
#define NEEDS_DISPATCH_RETAIN_RELEASE 0
|
||||
#else // iOS 5.X or earlier
|
||||
#define NEEDS_DISPATCH_RETAIN_RELEASE 1
|
||||
#endif
|
||||
#elif TARGET_OS_WATCH || TARGET_OS_TV
|
||||
// Compiling for watchOS, tvOS
|
||||
#define NEEDS_DISPATCH_RETAIN_RELEASE 0
|
||||
#else
|
||||
// Compiling for Mac OS X
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
|
||||
#define NEEDS_DISPATCH_RETAIN_RELEASE 0
|
||||
#else // Mac OS X 10.7 or earlier
|
||||
#define NEEDS_DISPATCH_RETAIN_RELEASE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#if !__has_feature(objc_arc)
|
||||
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
|
||||
@interface DDMultiFormatter () {
|
||||
dispatch_queue_t _queue;
|
||||
NSMutableArray *_formatters;
|
||||
}
|
||||
|
||||
- (DDLogMessage *)logMessageForLine:(NSString *)line originalMessage:(DDLogMessage *)message;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation DDMultiFormatter
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
|
||||
_queue = dispatch_queue_create("cocoa.lumberjack.multiformatter", DISPATCH_QUEUE_CONCURRENT);
|
||||
#else
|
||||
_queue = dispatch_queue_create("cocoa.lumberjack.multiformatter", NULL);
|
||||
#endif
|
||||
_formatters = [NSMutableArray new];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#if NEEDS_DISPATCH_RETAIN_RELEASE
|
||||
- (void)dealloc {
|
||||
dispatch_release(_queue);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#pragma mark Processing
|
||||
|
||||
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
|
||||
__block NSString *line = logMessage->_message;
|
||||
|
||||
dispatch_sync(_queue, ^{
|
||||
for (id<DDLogFormatter> formatter in _formatters) {
|
||||
DDLogMessage *message = [self logMessageForLine:line originalMessage:logMessage];
|
||||
line = [formatter formatLogMessage:message];
|
||||
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
- (DDLogMessage *)logMessageForLine:(NSString *)line originalMessage:(DDLogMessage *)message {
|
||||
DDLogMessage *newMessage = [message copy];
|
||||
|
||||
newMessage->_message = line;
|
||||
return newMessage;
|
||||
}
|
||||
|
||||
#pragma mark Formatters
|
||||
|
||||
- (NSArray *)formatters {
|
||||
__block NSArray *formatters;
|
||||
|
||||
dispatch_sync(_queue, ^{
|
||||
formatters = [_formatters copy];
|
||||
});
|
||||
|
||||
return formatters;
|
||||
}
|
||||
|
||||
- (void)addFormatter:(id<DDLogFormatter>)formatter {
|
||||
dispatch_barrier_async(_queue, ^{
|
||||
[_formatters addObject:formatter];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)removeFormatter:(id<DDLogFormatter>)formatter {
|
||||
dispatch_barrier_async(_queue, ^{
|
||||
[_formatters removeObject:formatter];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)removeAllFormatters {
|
||||
dispatch_barrier_async(_queue, ^{
|
||||
[_formatters removeAllObjects];
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)isFormattingWithFormatter:(id<DDLogFormatter>)formatter {
|
||||
__block BOOL hasFormatter;
|
||||
|
||||
dispatch_sync(_queue, ^{
|
||||
hasFormatter = [_formatters containsObject:formatter];
|
||||
});
|
||||
|
||||
return hasFormatter;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,36 +0,0 @@
|
||||
framework module CocoaLumberjack {
|
||||
umbrella header "CocoaLumberjack.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
|
||||
textual header "DDLogMacros.h"
|
||||
|
||||
exclude header "DDLog+LOGV.h"
|
||||
exclude header "DDLegacyMacros.h"
|
||||
|
||||
explicit module DDContextFilterLogFormatter {
|
||||
header "DDContextFilterLogFormatter.h"
|
||||
export *
|
||||
}
|
||||
|
||||
explicit module DDDispatchQueueLogFormatter {
|
||||
header "DDDispatchQueueLogFormatter.h"
|
||||
export *
|
||||
}
|
||||
|
||||
explicit module DDMultiFormatter {
|
||||
header "DDMultiFormatter.h"
|
||||
export *
|
||||
}
|
||||
|
||||
explicit module DDASLLogCapture {
|
||||
header "DDASLLogCapture.h"
|
||||
export *
|
||||
}
|
||||
|
||||
explicit module DDAbstractDatabaseLogger {
|
||||
header "DDAbstractDatabaseLogger.h"
|
||||
export *
|
||||
}
|
||||
}
|
||||
194
Examples/CloudKitTodo/Pods/CocoaLumberjack/README.md
generated
194
Examples/CloudKitTodo/Pods/CocoaLumberjack/README.md
generated
@ -1,194 +0,0 @@
|
||||
<p align="center" >
|
||||
<img src="LumberjackLogo.png" title="Lumberjack logo" float=left>
|
||||
</p>
|
||||
|
||||
CocoaLumberjack
|
||||
===============
|
||||
[](https://travis-ci.org/CocoaLumberjack/CocoaLumberjack)
|
||||
[](http://cocoadocs.org/docsets/CocoaLumberjack/)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](http://cocoadocs.org/docsets/CocoaLumberjack/)
|
||||
[](http://opensource.org/licenses/BSD-3-Clause)
|
||||
[](https://www.versioneye.com/objective-c/cocoalumberjack/references)
|
||||
[](https://codecov.io/gh/CocoaLumberjack/CocoaLumberjack)
|
||||
|
||||
**CocoaLumberjack** is a fast & simple, yet powerful & flexible logging framework for Mac and iOS.
|
||||
|
||||
### How to get started
|
||||
- install via [CocoaPods](http://cocoapods.org)
|
||||
|
||||
##### Swift version via CocoaPods
|
||||
```ruby
|
||||
platform :ios, '8.0'
|
||||
|
||||
# You need to set target when you use CocoaPods 1.0.0 or later.
|
||||
target 'SampleTarget' do
|
||||
use_frameworks!
|
||||
pod 'CocoaLumberjack/Swift'
|
||||
end
|
||||
```
|
||||
Note: `Swift` is a subspec which will include all the Obj-C code plus the Swift one, so this is sufficient.
|
||||
For more details about how to use Swift with Lumberjack, see [this conversation](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/405).
|
||||
|
||||
##### Swift Usage
|
||||
|
||||
If you installed using CocoaPods or manually:
|
||||
```swift
|
||||
import CocoaLumberjack
|
||||
```
|
||||
|
||||
```swift
|
||||
DDLog.add(DDTTYLogger.sharedInstance) // TTY = Xcode console
|
||||
DDLog.add(DDASLLogger.sharedInstance) // ASL = Apple System Logs
|
||||
|
||||
let fileLogger: DDFileLogger = DDFileLogger() // File Logger
|
||||
fileLogger.rollingFrequency = TimeInterval(60*60*24) // 24 hours
|
||||
fileLogger.logFileManager.maximumNumberOfLogFiles = 7
|
||||
DDLog.add(fileLogger)
|
||||
|
||||
...
|
||||
|
||||
DDLogVerbose("Verbose");
|
||||
DDLogDebug("Debug");
|
||||
DDLogInfo("Info");
|
||||
DDLogWarn("Warn");
|
||||
DDLogError("Error");
|
||||
```
|
||||
|
||||
##### Obj-C version via CocoaPods
|
||||
|
||||
```ruby
|
||||
platform :ios, '7.0'
|
||||
pod 'CocoaLumberjack'
|
||||
```
|
||||
|
||||
##### Obj-C usage
|
||||
If you're using Lumberjack as a framework, you can `@import CocoaLumberjack`.
|
||||
|
||||
Otherwise, `#import <CocoaLumberjack/CocoaLumberjack.h>`
|
||||
|
||||
```objc
|
||||
[DDLog addLogger:[DDTTYLogger sharedInstance]]; // TTY = Xcode console
|
||||
[DDLog addLogger:[DDASLLogger sharedInstance]]; // ASL = Apple System Logs
|
||||
|
||||
DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
|
||||
fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
|
||||
fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
|
||||
[DDLog addLogger:fileLogger];
|
||||
|
||||
...
|
||||
|
||||
DDLogVerbose(@"Verbose");
|
||||
DDLogDebug(@"Debug");
|
||||
DDLogInfo(@"Info");
|
||||
DDLogWarn(@"Warn");
|
||||
DDLogError(@"Error");
|
||||
```
|
||||
|
||||
##### Installation with Carthage (iOS 8+)
|
||||
|
||||
[Carthage](https://github.com/Carthage/Carthage) is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods.
|
||||
|
||||
To install with Carthage, follow the instruction on [Carthage](https://github.com/Carthage/Carthage)
|
||||
|
||||
Cartfile
|
||||
```
|
||||
github "CocoaLumberjack/CocoaLumberjack"
|
||||
```
|
||||
|
||||
- or [install manually](Documentation/GettingStarted.md#manual-installation)
|
||||
- read the [Getting started](Documentation/GettingStarted.md) guide, check out the [FAQ](Documentation/FAQ.md) section or the other [docs](Documentation/)
|
||||
- if you find issues or want to suggest improvements, create an issue or a pull request
|
||||
- for all kinds of questions involving CocoaLumberjack, use the [Google group](http://groups.google.com/group/cocoalumberjack) or StackOverflow (use [#lumberjack](http://stackoverflow.com/questions/tagged/lumberjack)).
|
||||
|
||||
### CocoaLumberjack 3
|
||||
|
||||
#### Migrating to 3.x
|
||||
|
||||
* To be determined
|
||||
|
||||
### Features
|
||||
|
||||
#### Lumberjack is Fast & Simple, yet Powerful & Flexible.
|
||||
|
||||
It is similar in concept to other popular logging frameworks such as log4j, yet is designed specifically for Objective-C, and takes advantage of features such as multi-threading, grand central dispatch (if available), lockless atomic operations, and the dynamic nature of the Objective-C runtime.
|
||||
|
||||
#### Lumberjack is Fast
|
||||
|
||||
In most cases it is an order of magnitude faster than NSLog.
|
||||
|
||||
#### Lumberjack is Simple
|
||||
|
||||
It takes as little as a single line of code to configure lumberjack when your application launches. Then simply replace your NSLog statements with DDLog statements and that's about it. (And the DDLog macros have the exact same format and syntax as NSLog, so it's super easy.)
|
||||
|
||||
#### Lumberjack is Powerful:
|
||||
|
||||
One log statement can be sent to multiple loggers, meaning you can log to a file and the console simultaneously. Want more? Create your own loggers (it's easy) and send your log statements over the network. Or to a database or distributed file system. The sky is the limit.
|
||||
|
||||
#### Lumberjack is Flexible:
|
||||
|
||||
Configure your logging however you want. Change log levels per file (perfect for debugging). Change log levels per logger (verbose console, but concise log file). Change log levels per xcode configuration (verbose debug, but concise release). Have your log statements compiled out of the release build. Customize the number of log levels for your application. Add your own fine-grained logging. Dynamically change log levels during runtime. Choose how & when you want your log files to be rolled. Upload your log files to a central server. Compress archived log files to save disk space...
|
||||
|
||||
### This framework is for you if:
|
||||
|
||||
- You're looking for a way to track down that impossible-to-reproduce bug that keeps popping up in the field.
|
||||
- You're frustrated with the super short console log on the iPhone.
|
||||
- You're looking to take your application to the next level in terms of support and stability.
|
||||
- You're looking for an enterprise level logging solution for your application (Mac or iPhone).
|
||||
|
||||
### Documentation
|
||||
|
||||
- **[Get started using Lumberjack](Documentation/GettingStarted.md)**<br/>
|
||||
- [Different log levels for Debug and Release builds](Documentation/XcodeTricks.md)<br/>
|
||||
- [Different log levels for each logger](Documentation/PerLoggerLogLevels.md)<br/>
|
||||
- [Use colors in the Xcode debugging console](Documentation/XcodeColors.md)<br/>
|
||||
- [Write your own custom formatters](Documentation/CustomFormatters.md)<br/>
|
||||
- [FAQ](Documentation/FAQ.md)<br/>
|
||||
- [Analysis of performance with benchmarks](Documentation/Performance.md)<br/>
|
||||
- [Common issues you may encounter and their solutions](Documentation/ProblemSolution.md)<br/>
|
||||
- [AppCode support](Documentation/AppCode-support.md)
|
||||
- **[Full Lumberjack documentation](Documentation/)**<br/>
|
||||
|
||||
### Requirements
|
||||
The current version of Lumberjack requires:
|
||||
- Xcode 8 or later
|
||||
- Swift 3.0 or later
|
||||
- iOS 5 or later
|
||||
- OS X 10.7 or later
|
||||
- WatchOS 2 or later
|
||||
- TVOS 9 or later
|
||||
|
||||
#### Backwards compability
|
||||
- for Xcode 7.3 and Swift 2.3, use the 2.4.0 version
|
||||
- for Xcode 7.3 and Swift 2.2, use the 2.3.0 version
|
||||
- for Xcode 7.2 and 7.1, use the 2.2.0 version
|
||||
- for Xcode 7.0 or earlier, use the 2.1.0 version
|
||||
- for Xcode 6 or earlier, use the 2.0.x version
|
||||
- for OS X < 10.7 support, use the 1.6.0 version
|
||||
|
||||
### Communication
|
||||
|
||||
- If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/lumberjack). (Tag 'lumberjack')
|
||||
- If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/lumberjack).
|
||||
- If you **found a bug**, open an issue.
|
||||
- If you **have a feature request**, open an issue.
|
||||
- If you **want to contribute**, submit a pull request.
|
||||
|
||||
### Author
|
||||
- [Robbie Hanson](https://github.com/robbiehanson)
|
||||
- Love the project? Wanna buy me a coffee? (or a beer :D) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UZRA26JPJB3DA)
|
||||
|
||||
### Collaborators
|
||||
- [Ernesto Rivera](https://github.com/rivera-ernesto)
|
||||
- [Dmitry Vorobyov](https://github.com/dvor)
|
||||
- [Bogdan Poplauschi](https://github.com/bpoplauschi)
|
||||
- [C.W. Betts](https://github.com/MaddTheSane)
|
||||
|
||||
### License
|
||||
- CocoaLumberjack is available under the BSD license. See the [LICENSE file](https://github.com/CocoaLumberjack/CocoaLumberjack/blob/master/LICENSE.txt).
|
||||
|
||||
### Architecture
|
||||
|
||||
<p align="center" >
|
||||
<img src="Documentation/CocoaLumberjackClassDiagram.png" title="CocoaLumberjack class diagram">
|
||||
</p>
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/CocoaLumberjack.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDASLLogCapture.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDASLLogger.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDAbstractDatabaseLogger.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDAssertMacros.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/Extensions/DDContextFilterLogFormatter.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/Extensions/DDDispatchQueueLogFormatter.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDFileLogger.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDLegacyMacros.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDLog+LOGV.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDLog.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDLogMacros.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/Extensions/DDMultiFormatter.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDOSLogger.h
|
||||
@ -1 +0,0 @@
|
||||
../../../CocoaLumberjack/Classes/DDTTYLogger.h
|
||||
@ -1 +0,0 @@
|
||||
../../../Reachability/Reachability.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Internal/NSDate+YapDatabase.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Internal/NSDictionary+YapDatabase.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/CloudKit/Internal/YDBCKAttachRequest.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/CloudKit/Internal/YDBCKChangeQueue.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/CloudKit/Internal/YDBCKChangeRecord.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/CloudKit/Utilities/YDBCKChangeSet.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/CloudKit/Internal/YDBCKMappingTableInfo.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/CloudKit/Utilities/YDBCKMergeInfo.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/CloudKit/Utilities/YDBCKRecord.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/CloudKit/Utilities/YDBCKRecordInfo.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/CloudKit/Internal/YDBCKRecordTableInfo.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/ActionManager/YapActionItem.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/ActionManager/Internal/YapActionItemPrivate.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/ActionManager/YapActionable.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Utilities/YapBidirectionalCache.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Utilities/YapCache.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Utilities/YapCollectionKey.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/YapDatabase.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/ActionManager/YapDatabaseActionManager.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/ActionManager/YapDatabaseActionManagerConnection.h
|
||||
@ -1 +0,0 @@
|
||||
../../../../../../YapDatabase/Extensions/ActionManager/Internal/YapDatabaseActionManagerPrivate.h
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user