First Commit

This commit is contained in:
Giuseppe Nucifora 2016-02-24 15:45:24 +01:00
parent 1d4d9c2e6d
commit d281d765ea
124 changed files with 41873 additions and 9 deletions

View File

@ -20,7 +20,9 @@
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
6003F5BC195388D20070C39A /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* Tests.m */; };
749B868208C4550DF8D15244 /* Pods_PNXMPPFramework_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BC15E8E255445D1654538AD /* Pods_PNXMPPFramework_Example.framework */; };
873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; };
ED4A1BF717CEBC9857A3C141 /* Pods_PNXMPPFramework_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F3BF4B442CB9D5C33829E65 /* Pods_PNXMPPFramework_Tests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -34,7 +36,11 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
48A2780CBF4129B217C9FB60 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = README.md; path = ../README.md; sourceTree = "<group>"; };
07D73116CC9292248C2B77E0 /* Pods-PNXMPPFramework_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PNXMPPFramework_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PNXMPPFramework_Tests/Pods-PNXMPPFramework_Tests.debug.xcconfig"; sourceTree = "<group>"; };
0BC15E8E255445D1654538AD /* Pods_PNXMPPFramework_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PNXMPPFramework_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
48A2780CBF4129B217C9FB60 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
55F146B95BAE799C9D044DE9 /* Pods-PNXMPPFramework_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PNXMPPFramework_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-PNXMPPFramework_Tests/Pods-PNXMPPFramework_Tests.release.xcconfig"; sourceTree = "<group>"; };
5F3BF4B442CB9D5C33829E65 /* Pods_PNXMPPFramework_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PNXMPPFramework_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6003F58A195388D20070C39A /* PNXMPPFramework_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PNXMPPFramework_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@ -55,7 +61,9 @@
6003F5BB195388D20070C39A /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = "<group>"; };
606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = "<group>"; };
873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
9C9E2AE32D26AF684C48C9EF /* PNXMPPFramework.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = PNXMPPFramework.podspec; path = ../PNXMPPFramework.podspec; sourceTree = "<group>"; };
9C6A5AC04DA60C754426EF31 /* Pods-PNXMPPFramework_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PNXMPPFramework_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PNXMPPFramework_Example/Pods-PNXMPPFramework_Example.debug.xcconfig"; sourceTree = "<group>"; };
9C9E2AE32D26AF684C48C9EF /* PNXMPPFramework.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = PNXMPPFramework.podspec; path = ../PNXMPPFramework.podspec; sourceTree = "<group>"; };
C7E6916AC71C9FA4641759C4 /* Pods-PNXMPPFramework_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PNXMPPFramework_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-PNXMPPFramework_Example/Pods-PNXMPPFramework_Example.release.xcconfig"; sourceTree = "<group>"; };
FC794850FC701B180023C6D3 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -67,6 +75,7 @@
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */,
6003F592195388D20070C39A /* UIKit.framework in Frameworks */,
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */,
749B868208C4550DF8D15244 /* Pods_PNXMPPFramework_Example.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -77,6 +86,7 @@
6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */,
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */,
6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */,
ED4A1BF717CEBC9857A3C141 /* Pods_PNXMPPFramework_Tests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -91,6 +101,7 @@
6003F5B5195388D20070C39A /* Tests */,
6003F58C195388D20070C39A /* Frameworks */,
6003F58B195388D20070C39A /* Products */,
FA2286D5A97A795EA8C4D32A /* Pods */,
);
sourceTree = "<group>";
};
@ -110,6 +121,8 @@
6003F58F195388D20070C39A /* CoreGraphics.framework */,
6003F591195388D20070C39A /* UIKit.framework */,
6003F5AF195388D20070C39A /* XCTest.framework */,
0BC15E8E255445D1654538AD /* Pods_PNXMPPFramework_Example.framework */,
5F3BF4B442CB9D5C33829E65 /* Pods_PNXMPPFramework_Tests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -169,6 +182,17 @@
name = "Podspec Metadata";
sourceTree = "<group>";
};
FA2286D5A97A795EA8C4D32A /* Pods */ = {
isa = PBXGroup;
children = (
9C6A5AC04DA60C754426EF31 /* Pods-PNXMPPFramework_Example.debug.xcconfig */,
C7E6916AC71C9FA4641759C4 /* Pods-PNXMPPFramework_Example.release.xcconfig */,
07D73116CC9292248C2B77E0 /* Pods-PNXMPPFramework_Tests.debug.xcconfig */,
55F146B95BAE799C9D044DE9 /* Pods-PNXMPPFramework_Tests.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -176,9 +200,12 @@
isa = PBXNativeTarget;
buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "PNXMPPFramework_Example" */;
buildPhases = (
9176D54A4AF0F229C88A9A6E /* Check Pods Manifest.lock */,
6003F586195388D20070C39A /* Sources */,
6003F587195388D20070C39A /* Frameworks */,
6003F588195388D20070C39A /* Resources */,
BAF822DE65D2F60DDDDA6DB7 /* Embed Pods Frameworks */,
5B9320A0153767C5710AC930 /* Copy Pods Resources */,
);
buildRules = (
);
@ -193,9 +220,12 @@
isa = PBXNativeTarget;
buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "PNXMPPFramework_Tests" */;
buildPhases = (
DC6BEFF10A902FC868C2D943 /* Check Pods Manifest.lock */,
6003F5AA195388D20070C39A /* Sources */,
6003F5AB195388D20070C39A /* Frameworks */,
6003F5AC195388D20070C39A /* Resources */,
B5E49261AF5038095695C7E6 /* Embed Pods Frameworks */,
8CD7E296CF475455022D0388 /* Copy Pods Resources */,
);
buildRules = (
);
@ -222,7 +252,7 @@
};
};
};
buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "PROJECT" */;
buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "PNXMPPFramework" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
@ -262,6 +292,99 @@
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
5B9320A0153767C5710AC930 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PNXMPPFramework_Example/Pods-PNXMPPFramework_Example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
8CD7E296CF475455022D0388 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PNXMPPFramework_Tests/Pods-PNXMPPFramework_Tests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9176D54A4AF0F229C88A9A6E /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
B5E49261AF5038095695C7E6 /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PNXMPPFramework_Tests/Pods-PNXMPPFramework_Tests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
BAF822DE65D2F60DDDDA6DB7 /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PNXMPPFramework_Example/Pods-PNXMPPFramework_Example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
DC6BEFF10A902FC868C2D943 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
6003F586195388D20070C39A /* Sources */ = {
isa = PBXSourcesBuildPhase;
@ -386,6 +509,7 @@
};
6003F5C0195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9C6A5AC04DA60C754426EF31 /* Pods-PNXMPPFramework_Example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
@ -401,6 +525,7 @@
};
6003F5C1195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C7E6916AC71C9FA4641759C4 /* Pods-PNXMPPFramework_Example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
@ -416,6 +541,7 @@
};
6003F5C3195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 07D73116CC9292248C2B77E0 /* Pods-PNXMPPFramework_Tests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = (
@ -439,6 +565,7 @@
};
6003F5C4195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 55F146B95BAE799C9D044DE9 /* Pods-PNXMPPFramework_Tests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = (
@ -459,7 +586,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
6003F585195388D10070C39A /* Build configuration list for PBXProject "PROJECT" */ = {
6003F585195388D10070C39A /* Build configuration list for PBXProject "PNXMPPFramework" */ = {
isa = XCConfigurationList;
buildConfigurations = (
6003F5BD195388D20070C39A /* Debug */,

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:PNXMPPFramework.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,11 +1,11 @@
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
target 'PNXMPPFramework_Example', :exclusive => true do
target 'PNXMPPFramework_Example' do
pod 'PNXMPPFramework', :path => '../'
end
target 'PNXMPPFramework_Tests', :exclusive => true do
target 'PNXMPPFramework_Tests' do
pod 'PNXMPPFramework', :path => '../'
pod 'FBSnapshotTestCase'

49
Example/Podfile.lock Normal file
View File

@ -0,0 +1,49 @@
PODS:
- CocoaAsyncSocket (7.4.3):
- CocoaAsyncSocket/All (= 7.4.3)
- CocoaAsyncSocket/All (7.4.3):
- CocoaAsyncSocket/GCD
- CocoaAsyncSocket/RunLoop
- CocoaAsyncSocket/GCD (7.4.3)
- CocoaAsyncSocket/RunLoop (7.4.3)
- CocoaLumberjack (2.2.0):
- CocoaLumberjack/Default (= 2.2.0)
- CocoaLumberjack/Extensions (= 2.2.0)
- CocoaLumberjack/Core (2.2.0)
- CocoaLumberjack/Default (2.2.0):
- CocoaLumberjack/Core
- CocoaLumberjack/Extensions (2.2.0):
- CocoaLumberjack/Default
- FBSnapshotTestCase (2.0.7):
- FBSnapshotTestCase/SwiftSupport (= 2.0.7)
- FBSnapshotTestCase/Core (2.0.7)
- FBSnapshotTestCase/SwiftSupport (2.0.7):
- FBSnapshotTestCase/Core
- KissXML (5.0.3):
- KissXML/Standard (= 5.0.3)
- KissXML/Core (5.0.3)
- KissXML/Standard (5.0.3):
- KissXML/Core
- PNXMPPFramework (0.1.0):
- CocoaAsyncSocket
- CocoaLumberjack
- KissXML
DEPENDENCIES:
- FBSnapshotTestCase
- PNXMPPFramework (from `../`)
EXTERNAL SOURCES:
PNXMPPFramework:
:path: "../"
SPEC CHECKSUMS:
CocoaAsyncSocket: a18c75dca4b08723628a0bacca6e94803d90be91
CocoaLumberjack: 17fe8581f84914d5d7e6360f7c70022b173c3ae0
FBSnapshotTestCase: 7e85180d0d141a0cf472352edda7e80d7eaeb547
KissXML: d19dd6dc65e0dc721ba92b3077b8ebdd240f1c1e
PNXMPPFramework: 85a177de196fd742392f6ed0053c9cd2dd160f06
PODFILE CHECKSUM: c24dacdc80a49fe0e0fea049a6d762eb76667498
COCOAPODS: 1.0.0.beta.3

View File

@ -0,0 +1,121 @@
# CocoaAsyncSocket
[![Build Status](https://travis-ci.org/robbiehanson/CocoaAsyncSocket.svg?branch=master)](https://travis-ci.org/robbiehanson/CocoaAsyncSocket) [![Version Status](https://img.shields.io/cocoapods/v/CocoaAsyncSocket.svg?style=flat)](http://cocoadocs.org/docsets/CocoaAsyncSocket) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Platform](http://img.shields.io/cocoapods/p/CocoaAsyncSocket.svg?style=flat)](http://cocoapods.org/?q=CocoaAsyncSocket) [![license Public Domain](https://img.shields.io/badge/license-Public%20Domain-orange.svg?style=flat)](https://en.wikipedia.org/wiki/Public_domain)
CocoaAsyncSocket provides easy-to-use and powerful asynchronous socket libraries for Mac and iOS. The classes are described below.
## Installation
#### CocoaPods
Install using [CocoaPods](http://cocoapods.org) by adding this line to your Podfile:
````ruby
use_frameworks! # Add this if you are targeting iOS 8+ or using Swift
pod 'CocoaAsyncSocket'
````
#### Carthage
CocoaAsyncSocket is [Carthage](https://github.com/Carthage/Carthage) compatible. To include it add the following line to your `Cartfile`
```bash
github "robbiehanson/CocoaAsyncSocket" "master"
```
The project is currently configured to build for **iOS**, **tvOS** and **Mac**. After building with carthage the resultant frameworks will be stored in:
* `Carthage/Build/iOS/CocoaAsyncSocket.framework`
* `Carthage/Build/tvOS/CocoaAsyncSocket.framework`
* `Carthage/Build/Mac/CocoaAsyncSocket.framework`
Select the correct framework(s) and drag it into your project.
#### Manual
You can also include it into your project by adding the source files directly, but you should probably be using a dependency manager to keep up to date.
### Importing
Using Objective-C:
```obj-c
@import CocoaAsyncSocket; // When using iOS 8+ frameworks
// OR
#import "CocoaAsyncSocket.h" // When not using frameworks, targeting iOS 7 or below
```
Using Swift:
```swift
import CocoaAsyncSocket
```
## TCP
**GCDAsyncSocket** and **AsyncSocket** are TCP/IP socket networking libraries. Here are the key features available in both:
- Native objective-c, fully self-contained in one class.<br/>
_No need to muck around with sockets or streams. This class handles everything for you._
- Full delegate support<br/>
_Errors, connections, read completions, write completions, progress, and disconnections all result in a call to your delegate method._
- Queued non-blocking reads and writes, with optional timeouts.<br/>
_You tell it what to read or write, and it handles everything for you. Queueing, buffering, and searching for termination sequences within the stream - all handled for you automatically._
- Automatic socket acceptance.<br/>
_Spin up a server socket, tell it to accept connections, and it will call you with new instances of itself for each connection._
- Support for TCP streams over IPv4 and IPv6.<br/>
_Automatically connect to IPv4 or IPv6 hosts. Automatically accept incoming connections over both IPv4 and IPv6 with a single instance of this class. No more worrying about multiple sockets._
- Support for TLS / SSL<br/>
_Secure your socket with ease using just a single method call. Available for both client and server sockets._
**GCDAsyncSocket** is built atop Grand Central Dispatch:
- Fully GCD based and Thread-Safe<br/>
_It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._
- The Latest Technology & Performance Optimizations<br/>
_Internally the library takes advantage of technologies such as [kqueue's](http://en.wikipedia.org/wiki/Kqueue) to limit [system calls](http://en.wikipedia.org/wiki/System_call) and optimize buffer allocations. In other words, peak performance._
**AsyncSocket** wraps CFSocket and CFStream:
- Fully Run-loop based<br/>
_Use it on the main thread or a worker thread. It plugs into the NSRunLoop with configurable modes._
## UDP
**GCDAsyncUdpSocket** and **AsyncUdpSocket** are UDP/IP socket networking libraries. Here are the key features available in both:
- Native objective-c, fully self-contained in one class.<br/>
_No need to muck around with low-level sockets. This class handles everything for you._
- Full delegate support.<br/>
_Errors, send completions, receive completions, and disconnections all result in a call to your delegate method._
- Queued non-blocking send and receive operations, with optional timeouts.<br/>
_You tell it what to send or receive, and it handles everything for you. Queueing, buffering, waiting and checking errno - all handled for you automatically._
- Support for IPv4 and IPv6.<br/>
_Automatically send/recv using IPv4 and/or IPv6. No more worrying about multiple sockets._
**GCDAsyncUdpSocket** is built atop Grand Central Dispatch:
- Fully GCD based and Thread-Safe<br/>
_It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._
**AsyncUdpSocket** wraps CFSocket:
- Fully Run-loop based<br/>
_Use it on the main thread or a worker thread. It plugs into the NSRunLoop with configurable modes._
***
Can't find the answer to your question in any of the [wiki](https://github.com/robbiehanson/CocoaAsyncSocket/wiki) articles? Try the **[mailing list](http://groups.google.com/group/cocoaasyncsocket)**.
<br/>
<br/>
Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](http://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2M8C699FQ8AW2)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,657 @@
//
// AsyncSocket.h
//
// This class is in the public domain.
// Originally created by Dustin Voss on Wed Jan 29 2003.
// Updated and maintained by Deusty Designs and the Mac development community.
//
// http://code.google.com/p/cocoaasyncsocket/
//
#import <Foundation/Foundation.h>
@class AsyncSocket;
@class AsyncReadPacket;
@class AsyncWritePacket;
extern NSString *const AsyncSocketException;
extern NSString *const AsyncSocketErrorDomain;
typedef NS_ENUM(NSInteger, AsyncSocketError) {
AsyncSocketCFSocketError = kCFSocketError, // From CFSocketError enum.
AsyncSocketNoError = 0, // Never used.
AsyncSocketCanceledError, // onSocketWillConnect: returned NO.
AsyncSocketConnectTimeoutError,
AsyncSocketReadMaxedOutError, // Reached set maxLength without completing
AsyncSocketReadTimeoutError,
AsyncSocketWriteTimeoutError
};
@protocol AsyncSocketDelegate
@optional
/**
* In the event of an error, the socket is closed.
* You may call "unreadData" during this call-back to get the last bit of data off the socket.
* When connecting, this delegate method may be called
* before"onSocket:didAcceptNewSocket:" or "onSocket:didConnectToHost:".
**/
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err;
/**
* Called when a socket disconnects with or without error. If you want to release a socket after it disconnects,
* do so here. It is not safe to do that during "onSocket:willDisconnectWithError:".
*
* If you call the disconnect method, and the socket wasn't already disconnected,
* this delegate method will be called before the disconnect method returns.
**/
- (void)onSocketDidDisconnect:(AsyncSocket *)sock;
/**
* Called when a socket accepts a connection. Another socket is spawned to handle it. The new socket will have
* the same delegate and will call "onSocket:didConnectToHost:port:".
**/
- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket;
/**
* Called when a new socket is spawned to handle a connection. This method should return the run-loop of the
* thread on which the new socket and its delegate should operate. If omitted, [NSRunLoop currentRunLoop] is used.
**/
- (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket;
/**
* Called when a socket is about to connect. This method should return YES to continue, or NO to abort.
* If aborted, will result in AsyncSocketCanceledError.
*
* If the connectToHost:onPort:error: method was called, the delegate will be able to access and configure the
* CFReadStream and CFWriteStream as desired prior to connection.
*
* If the connectToAddress:error: method was called, the delegate will be able to access and configure the
* CFSocket and CFSocketNativeHandle (BSD socket) as desired prior to connection. You will be able to access and
* configure the CFReadStream and CFWriteStream in the onSocket:didConnectToHost:port: method.
**/
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock;
/**
* Called when a socket connects and is ready for reading and writing.
* The host parameter will be an IP address, not a DNS name.
**/
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port;
/**
* Called when a socket has completed reading the requested data into memory.
* Not called if there is an error.
**/
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;
/**
* Called when a socket has read in data, but has not yet completed the read.
* This would occur if using readToData: or readToLength: methods.
* It may be used to for things such as updating progress bars.
**/
- (void)onSocket:(AsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
/**
* Called when a socket has completed writing the requested data. Not called if there is an error.
**/
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag;
/**
* Called when a socket has written some data, but has not yet completed the entire write.
* It may be used to for things such as updating progress bars.
**/
- (void)onSocket:(AsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
/**
* Called if a read operation has reached its timeout without completing.
* This method allows you to optionally extend the timeout.
* If you return a positive time interval (> 0) the read's timeout will be extended by the given amount.
* If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual.
*
* The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
* The length parameter is the number of bytes that have been read so far for the read operation.
*
* Note that this method may be called multiple times for a single read if you return positive numbers.
**/
- (NSTimeInterval)onSocket:(AsyncSocket *)sock
shouldTimeoutReadWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed
bytesDone:(NSUInteger)length;
/**
* Called if a write operation has reached its timeout without completing.
* This method allows you to optionally extend the timeout.
* If you return a positive time interval (> 0) the write's timeout will be extended by the given amount.
* If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual.
*
* The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
* The length parameter is the number of bytes that have been written so far for the write operation.
*
* Note that this method may be called multiple times for a single write if you return positive numbers.
**/
- (NSTimeInterval)onSocket:(AsyncSocket *)sock
shouldTimeoutWriteWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed
bytesDone:(NSUInteger)length;
/**
* Called after the socket has successfully completed SSL/TLS negotiation.
* This method is not called unless you use the provided startTLS method.
*
* If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close,
* and the onSocket:willDisconnectWithError: delegate method will be called with the specific SSL error code.
**/
- (void)onSocketDidSecure:(AsyncSocket *)sock;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface AsyncSocket : NSObject
{
CFSocketNativeHandle theNativeSocket4;
CFSocketNativeHandle theNativeSocket6;
CFSocketRef theSocket4; // IPv4 accept or connect socket
CFSocketRef theSocket6; // IPv6 accept or connect socket
CFReadStreamRef theReadStream;
CFWriteStreamRef theWriteStream;
CFRunLoopSourceRef theSource4; // For theSocket4
CFRunLoopSourceRef theSource6; // For theSocket6
CFRunLoopRef theRunLoop;
CFSocketContext theContext;
NSArray *theRunLoopModes;
NSTimer *theConnectTimer;
NSMutableArray *theReadQueue;
AsyncReadPacket *theCurrentRead;
NSTimer *theReadTimer;
NSMutableData *partialReadBuffer;
NSMutableArray *theWriteQueue;
AsyncWritePacket *theCurrentWrite;
NSTimer *theWriteTimer;
id theDelegate;
UInt16 theFlags;
long theUserData;
}
- (id)init;
- (id)initWithDelegate:(id)delegate;
- (id)initWithDelegate:(id)delegate userData:(long)userData;
/* String representation is long but has no "\n". */
- (NSString *)description;
/**
* Use "canSafelySetDelegate" to see if there is any pending business (reads and writes) with the current delegate
* before changing it. It is, of course, safe to change the delegate before connecting or accepting connections.
**/
- (id)delegate;
- (BOOL)canSafelySetDelegate;
- (void)setDelegate:(id)delegate;
/* User data can be a long, or an id or void * cast to a long. */
- (long)userData;
- (void)setUserData:(long)userData;
/* Don't use these to read or write. And don't close them either! */
- (CFSocketRef)getCFSocket;
- (CFReadStreamRef)getCFReadStream;
- (CFWriteStreamRef)getCFWriteStream;
// Once one of the accept or connect methods are called, the AsyncSocket instance is locked in
// and the other accept/connect methods can't be called without disconnecting the socket first.
// If the attempt fails or times out, these methods either return NO or
// call "onSocket:willDisconnectWithError:" and "onSockedDidDisconnect:".
// When an incoming connection is accepted, AsyncSocket invokes several delegate methods.
// These methods are (in chronological order):
// 1. onSocket:didAcceptNewSocket:
// 2. onSocket:wantsRunLoopForNewSocket:
// 3. onSocketWillConnect:
//
// Your server code will need to retain the accepted socket (if you want to accept it).
// The best place to do this is probably in the onSocket:didAcceptNewSocket: method.
//
// After the read and write streams have been setup for the newly accepted socket,
// the onSocket:didConnectToHost:port: method will be called on the proper run loop.
//
// Multithreading Note: If you're going to be moving the newly accepted socket to another run
// loop by implementing onSocket:wantsRunLoopForNewSocket:, then you should wait until the
// onSocket:didConnectToHost:port: method before calling read, write, or startTLS methods.
// Otherwise read/write events are scheduled on the incorrect runloop, and chaos may ensue.
/**
* Tells the socket to begin listening and accepting connections on the given port.
* When a connection comes in, the AsyncSocket instance will call the various delegate methods (see above).
* The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
**/
- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr;
/**
* This method is the same as acceptOnPort:error: with the additional option
* of specifying which interface to listen on. So, for example, if you were writing code for a server that
* has multiple IP addresses, you could specify which address you wanted to listen on. Or you could use it
* to specify that the socket should only accept connections over ethernet, and not other interfaces such as wifi.
* You may also use the special strings "localhost" or "loopback" to specify that
* the socket only accept connections from the local machine.
*
* To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method.
**/
- (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr;
/**
* Connects to the given host and port.
* The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2")
**/
- (BOOL)connectToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr;
/**
* This method is the same as connectToHost:onPort:error: with an additional timeout option.
* To not time out use a negative time interval, or simply use the connectToHost:onPort:error: method.
**/
- (BOOL)connectToHost:(NSString *)hostname
onPort:(UInt16)port
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr;
/**
* Connects to the given address, specified as a sockaddr structure wrapped in a NSData object.
* For example, a NSData object returned from NSNetService's addresses method.
*
* If you have an existing struct sockaddr you can convert it to a NSData object like so:
* struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
* struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
**/
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
/**
* This method is the same as connectToAddress:error: with an additional timeout option.
* To not time out use a negative time interval, or simply use the connectToAddress:error: method.
**/
- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
- (BOOL)connectToAddress:(NSData *)remoteAddr
viaInterfaceAddress:(NSData *)interfaceAddr
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr;
/**
* Disconnects immediately. Any pending reads or writes are dropped.
* If the socket is not already disconnected, the onSocketDidDisconnect delegate method
* will be called immediately, before this method returns.
*
* Please note the recommended way of releasing an AsyncSocket instance (e.g. in a dealloc method)
* [asyncSocket setDelegate:nil];
* [asyncSocket disconnect];
* [asyncSocket release];
**/
- (void)disconnect;
/**
* Disconnects after all pending reads have completed.
* After calling this, the read and write methods will do nothing.
* The socket will disconnect even if there are still pending writes.
**/
- (void)disconnectAfterReading;
/**
* Disconnects after all pending writes have completed.
* After calling this, the read and write methods will do nothing.
* The socket will disconnect even if there are still pending reads.
**/
- (void)disconnectAfterWriting;
/**
* Disconnects after all pending reads and writes have completed.
* After calling this, the read and write methods will do nothing.
**/
- (void)disconnectAfterReadingAndWriting;
/* Returns YES if the socket and streams are open, connected, and ready for reading and writing. */
- (BOOL)isConnected;
/**
* Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected.
* The host will be an IP address.
**/
- (NSString *)connectedHost;
- (UInt16)connectedPort;
- (NSString *)localHost;
- (UInt16)localPort;
/**
* Returns the local or remote address to which this socket is connected,
* specified as a sockaddr structure wrapped in a NSData object.
*
* See also the connectedHost, connectedPort, localHost and localPort methods.
**/
- (NSData *)connectedAddress;
- (NSData *)localAddress;
/**
* Returns whether the socket is IPv4 or IPv6.
* An accepting socket may be both.
**/
- (BOOL)isIPv4;
- (BOOL)isIPv6;
// The readData and writeData methods won't block (they are asynchronous).
//
// When a read is complete the onSocket:didReadData:withTag: delegate method is called.
// When a write is complete the onSocket:didWriteDataWithTag: delegate method is called.
//
// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.)
// If a read/write opertion times out, the corresponding "onSocket:shouldTimeout..." delegate method
// is called to optionally allow you to extend the timeout.
// Upon a timeout, the "onSocket:willDisconnectWithError:" method is called, followed by "onSocketDidDisconnect".
//
// The tag is for your convenience.
// You can use it as an array index, step number, state id, pointer, etc.
/**
* Reads the first available bytes that become available on the socket.
*
* If the timeout value is negative, the read operation will not use a timeout.
**/
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* Reads the first available bytes that become available on the socket.
* The bytes will be appended to the given byte buffer starting at the given offset.
* The given buffer will automatically be increased in size if needed.
*
* If the timeout value is negative, the read operation will not use a timeout.
* If the buffer if nil, the socket will create a buffer for you.
*
* If the bufferOffset is greater than the length of the given buffer,
* the method will do nothing, and the delegate will not be called.
*
* If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
* After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer.
* That is, it will reference the bytes that were appended to the given buffer.
**/
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag;
/**
* Reads the first available bytes that become available on the socket.
* The bytes will be appended to the given byte buffer starting at the given offset.
* The given buffer will automatically be increased in size if needed.
* A maximum of length bytes will be read.
*
* If the timeout value is negative, the read operation will not use a timeout.
* If the buffer if nil, a buffer will automatically be created for you.
* If maxLength is zero, no length restriction is enforced.
*
* If the bufferOffset is greater than the length of the given buffer,
* the method will do nothing, and the delegate will not be called.
*
* If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
* After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer.
* That is, it will reference the bytes that were appended to the given buffer.
**/
- (void)readDataWithTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)length
tag:(long)tag;
/**
* Reads the given number of bytes.
*
* If the timeout value is negative, the read operation will not use a timeout.
*
* If the length is 0, this method does nothing and the delegate is not called.
**/
- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* Reads the given number of bytes.
* The bytes will be appended to the given byte buffer starting at the given offset.
* The given buffer will automatically be increased in size if needed.
*
* If the timeout value is negative, the read operation will not use a timeout.
* If the buffer if nil, a buffer will automatically be created for you.
*
* If the length is 0, this method does nothing and the delegate is not called.
* If the bufferOffset is greater than the length of the given buffer,
* the method will do nothing, and the delegate will not be called.
*
* If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
* After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer.
* That is, it will reference the bytes that were appended to the given buffer.
**/
- (void)readDataToLength:(NSUInteger)length
withTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag;
/**
* Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
*
* If the timeout value is negative, the read operation will not use a timeout.
*
* If you pass nil or zero-length data as the "data" parameter,
* the method will do nothing, and the delegate will not be called.
*
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
* Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for
* a character, the read will prematurely end.
**/
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
* The bytes will be appended to the given byte buffer starting at the given offset.
* The given buffer will automatically be increased in size if needed.
*
* If the timeout value is negative, the read operation will not use a timeout.
* If the buffer if nil, a buffer will automatically be created for you.
*
* If the bufferOffset is greater than the length of the given buffer,
* the method will do nothing, and the delegate will not be called.
*
* If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
* After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer.
* That is, it will reference the bytes that were appended to the given buffer.
*
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
* Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for
* a character, the read will prematurely end.
**/
- (void)readDataToData:(NSData *)data
withTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
tag:(long)tag;
/**
* Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
*
* If the timeout value is negative, the read operation will not use a timeout.
*
* If maxLength is zero, no length restriction is enforced.
* Otherwise if maxLength bytes are read without completing the read,
* it is treated similarly to a timeout - the socket is closed with a AsyncSocketReadMaxedOutError.
* The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
*
* If you pass nil or zero-length data as the "data" parameter,
* the method will do nothing, and the delegate will not be called.
* If you pass a maxLength parameter that is less than the length of the data parameter,
* the method will do nothing, and the delegate will not be called.
*
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
* Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for
* a character, the read will prematurely end.
**/
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag;
/**
* Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
* The bytes will be appended to the given byte buffer starting at the given offset.
* The given buffer will automatically be increased in size if needed.
* A maximum of length bytes will be read.
*
* If the timeout value is negative, the read operation will not use a timeout.
* If the buffer if nil, a buffer will automatically be created for you.
*
* If maxLength is zero, no length restriction is enforced.
* Otherwise if maxLength bytes are read without completing the read,
* it is treated similarly to a timeout - the socket is closed with a AsyncSocketReadMaxedOutError.
* The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
*
* If you pass a maxLength parameter that is less than the length of the data parameter,
* the method will do nothing, and the delegate will not be called.
* If the bufferOffset is greater than the length of the given buffer,
* the method will do nothing, and the delegate will not be called.
*
* If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
* After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer.
* That is, it will reference the bytes that were appended to the given buffer.
*
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
* Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for
* a character, the read will prematurely end.
**/
- (void)readDataToData:(NSData *)data
withTimeout:(NSTimeInterval)timeout
buffer:(NSMutableData *)buffer
bufferOffset:(NSUInteger)offset
maxLength:(NSUInteger)length
tag:(long)tag;
/**
* Writes data to the socket, and calls the delegate when finished.
*
* If you pass in nil or zero-length data, this method does nothing and the delegate will not be called.
* If the timeout value is negative, the write operation will not use a timeout.
**/
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* Returns progress of current read or write, from 0.0 to 1.0, or NaN if no read/write (use isnan() to check).
* "tag", "done" and "total" will be filled in if they aren't NULL.
**/
- (float)progressOfReadReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total;
- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total;
/**
* Secures the connection using SSL/TLS.
*
* This method may be called at any time, and the TLS handshake will occur after all pending reads and writes
* are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing
* the upgrade to TLS at the same time, without having to wait for the write to finish.
* Any reads or writes scheduled after this method is called will occur over the secured connection.
*
* The possible keys and values for the TLS settings are well documented.
* Some possible keys are:
* - kCFStreamSSLLevel
* - kCFStreamSSLAllowsExpiredCertificates
* - kCFStreamSSLAllowsExpiredRoots
* - kCFStreamSSLAllowsAnyRoot
* - kCFStreamSSLValidatesCertificateChain
* - kCFStreamSSLPeerName
* - kCFStreamSSLCertificates
* - kCFStreamSSLIsServer
*
* Please refer to Apple's documentation for associated values, as well as other possible keys.
*
* If you pass in nil or an empty dictionary, the default settings will be used.
*
* The default settings will check to make sure the remote party's certificate is signed by a
* trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired.
* However it will not verify the name on the certificate unless you
* give it a name to verify against via the kCFStreamSSLPeerName key.
* The security implications of this are important to understand.
* Imagine you are attempting to create a secure connection to MySecureServer.com,
* but your socket gets directed to MaliciousServer.com because of a hacked DNS server.
* If you simply use the default settings, and MaliciousServer.com has a valid certificate,
* the default settings will not detect any problems since the certificate is valid.
* To properly secure your connection in this particular scenario you
* should set the kCFStreamSSLPeerName property to "MySecureServer.com".
* If you do not know the peer name of the remote host in advance (for example, you're not sure
* if it will be "domain.com" or "www.domain.com"), then you can use the default settings to validate the
* certificate, and then use the X509Certificate class to verify the issuer after the socket has been secured.
* The X509Certificate class is part of the CocoaAsyncSocket open source project.
**/
- (void)startTLS:(NSDictionary *)tlsSettings;
/**
* For handling readDataToData requests, data is necessarily read from the socket in small increments.
* The performance can be much improved by allowing AsyncSocket to read larger chunks at a time and
* store any overflow in a small internal buffer.
* This is termed pre-buffering, as some data may be read for you before you ask for it.
* If you use readDataToData a lot, enabling pre-buffering will result in better performance, especially on the iPhone.
*
* The default pre-buffering state is controlled by the DEFAULT_PREBUFFERING definition.
* It is highly recommended one leave this set to YES.
*
* This method exists in case pre-buffering needs to be disabled by default for some unforeseen reason.
* In that case, this method exists to allow one to easily enable pre-buffering when ready.
**/
- (void)enablePreBuffering;
/**
* When you create an AsyncSocket, it is added to the runloop of the current thread.
* So for manually created sockets, it is easiest to simply create the socket on the thread you intend to use it.
*
* If a new socket is accepted, the delegate method onSocket:wantsRunLoopForNewSocket: is called to
* allow you to place the socket on a separate thread. This works best in conjunction with a thread pool design.
*
* If, however, you need to move the socket to a separate thread at a later time, this
* method may be used to accomplish the task.
*
* This method must be called from the thread/runloop the socket is currently running on.
*
* Note: After calling this method, all further method calls to this object should be done from the given runloop.
* Also, all delegate calls will be sent on the given runloop.
**/
- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop;
/**
* Allows you to configure which run loop modes the socket uses.
* The default set of run loop modes is NSDefaultRunLoopMode.
*
* If you'd like your socket to continue operation during other modes, you may want to add modes such as
* NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes.
*
* Accepted sockets will automatically inherit the same run loop modes as the listening socket.
*
* Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes.
**/
- (BOOL)setRunLoopModes:(NSArray *)runLoopModes;
- (BOOL)addRunLoopMode:(NSString *)runLoopMode;
- (BOOL)removeRunLoopMode:(NSString *)runLoopMode;
/**
* Returns the current run loop modes the AsyncSocket instance is operating in.
* The default set of run loop modes is NSDefaultRunLoopMode.
**/
- (NSArray *)runLoopModes;
/**
* In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read
* any data that's left on the socket.
**/
- (NSData *)unreadData;
/* A few common line separators, for use with the readDataToData:... methods. */
+ (NSData *)CRLFData; // 0x0D0A
+ (NSData *)CRData; // 0x0D
+ (NSData *)LFData; // 0x0A
+ (NSData *)ZeroData; // 0x00
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,368 @@
//
// AsyncUdpSocket.h
//
// This class is in the public domain.
// Originally created by Robbie Hanson on Wed Oct 01 2008.
// Updated and maintained by Deusty Designs and the Mac development community.
//
// http://code.google.com/p/cocoaasyncsocket/
//
#import <Foundation/Foundation.h>
@class AsyncSendPacket;
@class AsyncReceivePacket;
extern NSString *const AsyncUdpSocketException;
extern NSString *const AsyncUdpSocketErrorDomain;
typedef NS_ENUM(NSInteger, AsyncUdpSocketError) {
AsyncUdpSocketCFSocketError = kCFSocketError, // From CFSocketError enum
AsyncUdpSocketNoError = 0, // Never used
AsyncUdpSocketBadParameter, // Used if given a bad parameter (such as an improper address)
AsyncUdpSocketIPv4Unavailable, // Used if you bind/connect using IPv6 only
AsyncUdpSocketIPv6Unavailable, // Used if you bind/connect using IPv4 only (or iPhone)
AsyncUdpSocketSendTimeoutError,
AsyncUdpSocketReceiveTimeoutError
};
@interface AsyncUdpSocket : NSObject
{
CFSocketRef theSocket4; // IPv4 socket
CFSocketRef theSocket6; // IPv6 socket
CFRunLoopSourceRef theSource4; // For theSocket4
CFRunLoopSourceRef theSource6; // For theSocket6
CFRunLoopRef theRunLoop;
CFSocketContext theContext;
NSArray *theRunLoopModes;
NSMutableArray *theSendQueue;
AsyncSendPacket *theCurrentSend;
NSTimer *theSendTimer;
NSMutableArray *theReceiveQueue;
AsyncReceivePacket *theCurrentReceive;
NSTimer *theReceiveTimer;
id theDelegate;
UInt16 theFlags;
long theUserData;
NSString *cachedLocalHost;
UInt16 cachedLocalPort;
NSString *cachedConnectedHost;
UInt16 cachedConnectedPort;
UInt32 maxReceiveBufferSize;
}
/**
* Creates new instances of AsyncUdpSocket.
**/
- (id)init;
- (id)initWithDelegate:(id)delegate;
- (id)initWithDelegate:(id)delegate userData:(long)userData;
/**
* Creates new instances of AsyncUdpSocket that support only IPv4 or IPv6.
* The other init methods will support both, unless specifically binded or connected to one protocol.
* If you know you'll only be using one protocol, these init methods may be a bit more efficient.
**/
- (id)initIPv4;
- (id)initIPv6;
- (id)delegate;
- (void)setDelegate:(id)delegate;
- (long)userData;
- (void)setUserData:(long)userData;
/**
* Returns the local address info for the socket.
*
* Note: Address info may not be available until after the socket has been bind'ed,
* or until after data has been sent.
**/
- (NSString *)localHost;
- (UInt16)localPort;
/**
* Returns the remote address info for the socket.
*
* Note: Since UDP is connectionless by design, connected address info
* will not be available unless the socket is explicitly connected to a remote host/port
**/
- (NSString *)connectedHost;
- (UInt16)connectedPort;
/**
* Returns whether or not this socket has been connected to a single host.
* By design, UDP is a connectionless protocol, and connecting is not needed.
* If connected, the socket will only be able to send/receive data to/from the connected host.
**/
- (BOOL)isConnected;
/**
* Returns whether or not this socket has been closed.
* The only way a socket can be closed is if you explicitly call one of the close methods.
**/
- (BOOL)isClosed;
/**
* Returns whether or not this socket supports IPv4.
* By default this will be true, unless the socket is specifically initialized as IPv6 only,
* or is binded or connected to an IPv6 address.
**/
- (BOOL)isIPv4;
/**
* Returns whether or not this socket supports IPv6.
* By default this will be true, unless the socket is specifically initialized as IPv4 only,
* or is binded or connected to an IPv4 address.
*
* This method will also return false on platforms that do not support IPv6.
* Note: The iPhone does not currently support IPv6.
**/
- (BOOL)isIPv6;
/**
* Returns the mtu of the socket.
* If unknown, returns zero.
*
* Sending data larger than this may result in an error.
* This is an advanced topic, and one should understand the wide range of mtu's on networks and the internet.
* Therefore this method is only for reference and may be of little use in many situations.
**/
- (unsigned int)maximumTransmissionUnit;
/**
* Binds the UDP socket to the given port and optional address.
* Binding should be done for server sockets that receive data prior to sending it.
* Client sockets can skip binding,
* as the OS will automatically assign the socket an available port when it starts sending data.
*
* You cannot bind a socket after its been connected.
* You can only bind a socket once.
* You can still connect a socket (if desired) after binding.
*
* On success, returns YES.
* Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
**/
- (BOOL)bindToPort:(UInt16)port error:(NSError **)errPtr;
- (BOOL)bindToAddress:(NSString *)localAddr port:(UInt16)port error:(NSError **)errPtr;
/**
* Connects the UDP socket to the given host and port.
* By design, UDP is a connectionless protocol, and connecting is not needed.
*
* Choosing to connect to a specific host/port has the following effect:
* - You will only be able to send data to the connected host/port.
* - You will only be able to receive data from the connected host/port.
* - You will receive ICMP messages that come from the connected host/port, such as "connection refused".
*
* Connecting a UDP socket does not result in any communication on the socket.
* It simply changes the internal state of the socket.
*
* You cannot bind a socket after its been connected.
* You can only connect a socket once.
*
* On success, returns YES.
* Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
**/
- (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr;
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
/**
* Join multicast group
*
* Group should be an IP address (eg @"225.228.0.1")
**/
- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr;
- (BOOL)joinMulticastGroup:(NSString *)group withAddress:(NSString *)interface error:(NSError **)errPtr;
/**
* By default, the underlying socket in the OS will not allow you to send broadcast messages.
* In order to send broadcast messages, you need to enable this functionality in the socket.
*
* A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is
* delivered to every host on the network.
* The reason this is generally disabled by default is to prevent
* accidental broadcast messages from flooding the network.
**/
- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr;
/**
* Asynchronously sends the given data, with the given timeout and tag.
*
* This method may only be used with a connected socket.
*
* If data is nil or zero-length, this method does nothing and immediately returns NO.
* If the socket is not connected, this method does nothing and immediately returns NO.
**/
- (BOOL)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* Asynchronously sends the given data, with the given timeout and tag, to the given host and port.
*
* This method cannot be used with a connected socket.
*
* If data is nil or zero-length, this method does nothing and immediately returns NO.
* If the socket is connected, this method does nothing and immediately returns NO.
* If unable to resolve host to a valid IPv4 or IPv6 address, this method returns NO.
**/
- (BOOL)sendData:(NSData *)data toHost:(NSString *)host port:(UInt16)port withTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* Asynchronously sends the given data, with the given timeout and tag, to the given address.
*
* This method cannot be used with a connected socket.
*
* If data is nil or zero-length, this method does nothing and immediately returns NO.
* If the socket is connected, this method does nothing and immediately returns NO.
**/
- (BOOL)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* Asynchronously receives a single datagram packet.
*
* If the receive succeeds, the onUdpSocket:didReceiveData:fromHost:port:tag delegate method will be called.
* Otherwise, a timeout will occur, and the onUdpSocket:didNotReceiveDataWithTag: delegate method will be called.
**/
- (void)receiveWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* Closes the socket immediately. Any pending send or receive operations are dropped.
**/
- (void)close;
/**
* Closes after all pending send operations have completed.
* After calling this, the sendData: and receive: methods will do nothing.
* In other words, you won't be able to add any more send or receive operations to the queue.
* The socket will close even if there are still pending receive operations.
**/
- (void)closeAfterSending;
/**
* Closes after all pending receive operations have completed.
* After calling this, the sendData: and receive: methods will do nothing.
* In other words, you won't be able to add any more send or receive operations to the queue.
* The socket will close even if there are still pending send operations.
**/
- (void)closeAfterReceiving;
/**
* Closes after all pending send and receive operations have completed.
* After calling this, the sendData: and receive: methods will do nothing.
* In other words, you won't be able to add any more send or receive operations to the queue.
**/
- (void)closeAfterSendingAndReceiving;
/**
* Gets/Sets the maximum size of the buffer that will be allocated for receive operations.
* The default size is 9216 bytes.
*
* The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535.
* The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295.
*
* In practice, however, the size of UDP packets will be much smaller.
* Indeed most protocols will send and receive packets of only a few bytes,
* or will set a limit on the size of packets to prevent fragmentation in the IP layer.
*
* If you set the buffer size too small, the sockets API in the OS will silently discard
* any extra data, and you will not be notified of the error.
**/
- (UInt32)maxReceiveBufferSize;
- (void)setMaxReceiveBufferSize:(UInt32)max;
/**
* When you create an AsyncUdpSocket, it is added to the runloop of the current thread.
* So it is easiest to simply create the socket on the thread you intend to use it.
*
* If, however, you need to move the socket to a separate thread at a later time, this
* method may be used to accomplish the task.
*
* This method must be called from the thread/runloop the socket is currently running on.
*
* Note: After calling this method, all further method calls to this object should be done from the given runloop.
* Also, all delegate calls will be sent on the given runloop.
**/
- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop;
/**
* Allows you to configure which run loop modes the socket uses.
* The default set of run loop modes is NSDefaultRunLoopMode.
*
* If you'd like your socket to continue operation during other modes, you may want to add modes such as
* NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes.
*
* Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes.
**/
- (BOOL)setRunLoopModes:(NSArray *)runLoopModes;
/**
* Returns the current run loop modes the AsyncSocket instance is operating in.
* The default set of run loop modes is NSDefaultRunLoopMode.
**/
- (NSArray *)runLoopModes;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@protocol AsyncUdpSocketDelegate
@optional
/**
* Called when the datagram with the given tag has been sent.
**/
- (void)onUdpSocket:(AsyncUdpSocket *)sock didSendDataWithTag:(long)tag;
/**
* Called if an error occurs while trying to send a datagram.
* This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet.
**/
- (void)onUdpSocket:(AsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error;
/**
* Called when the socket has received the requested datagram.
*
* Due to the nature of UDP, you may occasionally receive undesired packets.
* These may be rogue UDP packets from unknown hosts,
* or they may be delayed packets arriving after retransmissions have already occurred.
* It's important these packets are properly ignored, while not interfering with the flow of your implementation.
* As an aid, this delegate method has a boolean return value.
* If you ever need to ignore a received packet, simply return NO,
* and AsyncUdpSocket will continue as if the packet never arrived.
* That is, the original receive request will still be queued, and will still timeout as usual if a timeout was set.
* For example, say you requested to receive data, and you set a timeout of 500 milliseconds, using a tag of 15.
* If rogue data arrives after 250 milliseconds, this delegate method would be invoked, and you could simply return NO.
* If the expected data then arrives within the next 250 milliseconds,
* this delegate method will be invoked, with a tag of 15, just as if the rogue data never appeared.
*
* Under normal circumstances, you simply return YES from this method.
**/
- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock
didReceiveData:(NSData *)data
withTag:(long)tag
fromHost:(NSString *)host
port:(UInt16)port;
/**
* Called if an error occurs while trying to receive a requested datagram.
* This is generally due to a timeout, but could potentially be something else if some kind of OS error occurred.
**/
- (void)onUdpSocket:(AsyncUdpSocket *)sock didNotReceiveDataWithTag:(long)tag dueToError:(NSError *)error;
/**
* Called when the socket is closed.
* A socket is only closed if you explicitly call one of the close methods.
**/
- (void)onUdpSocketDidClose:(AsyncUdpSocket *)sock;
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,81 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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"

View File

@ -0,0 +1,91 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2014-2015, 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 fromLogLevel(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: self.rawValue) {
return ourValid
} else {
let logFlag:DDLogFlag = self
if logFlag.contains(.Verbose) {
return .Verbose
} else if logFlag.contains(.Debug) {
return .Debug
} else if logFlag.contains(.Info) {
return .Info
} else if logFlag.contains(.Warning) {
return .Warning
} else if logFlag.contains(.Error) {
return .Error
} else {
return .Off
}
}
}
}
public var defaultDebugLevel = DDLogLevel.Verbose
public func resetDefaultDebugLevel() {
defaultDebugLevel = DDLogLevel.Verbose
}
public func SwiftLogMacro(isAsynchronous: Bool, level: DDLogLevel, flag flg: DDLogFlag, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, @autoclosure string: () -> String) {
if level.rawValue & flg.rawValue != 0 {
// Tell the DDLogMessage constructor to copy the C strings that get passed to it.
// Using string interpolation to prevent integer overflow warning when using StaticString.stringValue
let logMessage = DDLogMessage(message: string(), level: level, flag: flg, context: context, file: "\(file)", function: "\(function)", line: line, tag: tag, options: [.CopyFile, .CopyFunction], timestamp: nil)
DDLog.log(isAsynchronous, message: logMessage)
}
}
public func DDLogDebug(@autoclosure logText: () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, asynchronous async: Bool = true) {
SwiftLogMacro(async, level: level, flag: .Debug, context: context, file: file, function: function, line: line, tag: tag, string: logText)
}
public func DDLogInfo(@autoclosure logText: () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, asynchronous async: Bool = true) {
SwiftLogMacro(async, level: level, flag: .Info, context: context, file: file, function: function, line: line, tag: tag, string: logText)
}
public func DDLogWarn(@autoclosure logText: () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, asynchronous async: Bool = true) {
SwiftLogMacro(async, level: level, flag: .Warning, context: context, file: file, function: function, line: line, tag: tag, string: logText)
}
public func DDLogVerbose(@autoclosure logText: () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, asynchronous async: Bool = true) {
SwiftLogMacro(async, level: level, flag: .Verbose, context: context, file: file, function: function, line: line, tag: tag, string: logText)
}
public func DDLogError(@autoclosure logText: () -> String, level: DDLogLevel = defaultDebugLevel, context: Int = 0, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, tag: AnyObject? = nil, asynchronous async: Bool = false) {
SwiftLogMacro(async, level: level, flag: .Error, context: context, file: file, function: function, line: line, tag: tag, string: logText)
}
/// Analogous to the C preprocessor macro `THIS_FILE`.
public func CurrentFileName(fileName: StaticString = __FILE__) -> String {
// Using string interpolation to prevent integer overflow warning when using StaticString.stringValue
// This double-casting to NSString is necessary as changes to how Swift handles NSPathUtilities requres the string to be an NSString
return (("\(fileName)" as NSString).lastPathComponent as NSString).stringByDeletingPathExtension
}

View File

@ -0,0 +1,48 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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;
/**
* Returns the current capture level.
* @note Default log level: DDLogLevelVerbose (i.e. capture all ASL messages).
*/
+ (DDLogLevel)captureLevel;
/**
* Set the capture level
*
* @param level new level
*/
+ (void)setCaptureLevel:(DDLogLevel)level;
@end

View File

@ -0,0 +1,230 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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, &notifyToken, 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

View File

@ -0,0 +1,58 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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
*/
+ (instancetype)sharedInstance;
// Inherited from DDAbstractLogger
// - (id <DDLogFormatter>)logFormatter;
// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
@end

View File

@ -0,0 +1,121 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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 (logMessage) {
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
int 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

View File

@ -0,0 +1,123 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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

View File

@ -0,0 +1,660 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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 * NSEC_PER_SEC);
dispatch_time_t startTime = dispatch_time(_unsavedTime, interval);
dispatch_source_set_timer(_saveTimer, startTime, interval, 1.0);
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 * 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, 1.0);
}
}
- (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

View File

@ -0,0 +1,26 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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)

View File

@ -0,0 +1,487 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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)
*/
- (NSString *)logsDirectory;
/**
* Returns an array of `NSString` objects,
* each of which is the filePath to an existing log file on disk.
**/
- (NSArray *)unsortedLogFilePaths;
/**
* Returns an array of `NSString` objects,
* each of which is the fileName of an existing log file on disk.
**/
- (NSArray *)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.
**/
- (NSArray *)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.
**/
- (NSArray *)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.
**/
- (NSArray *)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.
**/
- (NSArray *)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;
/**
* Called when the roll action was executed and the log was archieved
*/
- (void)didRollAndArchiveLogFile:(NSString *)logFilePath;
@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:(NSString *)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;
/* 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>
/**
* Default initializer
*/
- (instancetype)init;
/**
* Designated initializer, requires a `DDLogFileManager` instance
*/
- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)logFileManager NS_DESIGNATED_INITIALIZER;
/**
* Log File Rolling:
*
* `maximumFileSize`:
* The approximate maximum size 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.
*
* 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 (^)())completionBlock;
/**
* 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.
**/
- (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;
@property (strong, nonatomic, readonly) NSDictionary *fileAttributes;
@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;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFilePath:(NSString *)filePath NS_DESIGNATED_INITIALIZER;
- (void)reset;
- (void)renameFile:(NSString *)newFileName;
#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

View File

@ -0,0 +1,75 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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

View File

@ -0,0 +1,83 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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)

View File

@ -0,0 +1,743 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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;
@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...00000 DDLogFlagError
*/
DDLogFlagError = (1 << 0),
/**
* 0...00001 DDLogFlagWarning
*/
DDLogFlagWarning = (1 << 1),
/**
* 0...00010 DDLogFlagInfo
*/
DDLogFlagInfo = (1 << 2),
/**
* 0...00100 DDLogFlagDebug
*/
DDLogFlagDebug = (1 << 3),
/**
* 0...01000 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
};
/**
* 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 * 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
/**
* Provides access to the underlying logging queue.
* This may be helpful to Logger classes for things like thread synchronization.
**/
+ (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)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)tag
format:(NSString *)format
args:(va_list)argList;
/**
* Logging Primitive.
*
* @param asynchronous YES if the logging is done async, NO if you want to force sync
* @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
*/
+ (void)log:(BOOL)asynchronous
message:(NSString *)message
level:(DDLogLevel)level
flag:(DDLogFlag)flag
context:(NSInteger)context
file:(const char *)file
function:(const char *)function
line:(NSUInteger)line
tag:(id)tag;
/**
* 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;
/**
* 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.
*
* 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 all the current loggers
*/
+ (void)removeAllLoggers;
/**
* Return all the current loggers
*/
+ (NSArray *)allLoggers;
/**
* 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
*/
+ (NSArray *)registeredClasses;
/**
* Returns an array with the classes names that are using registered dynamic logging
*/
+ (NSArray *)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;
/**
* 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;
/**
* 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 *)formatLogMessage:(DDLogMessage *)logMessage;
@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;
/**
* 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;
* }
* ```
**/
+ (DDLogLevel)ddLogLevel;
/**
* See the above description for `ddLogLevel`
*/
+ (void)ddSetLogLevel:(DDLogLevel)level;
@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
};
/**
* 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` is not available
*/
- (instancetype)init NS_UNAVAILABLE;
/**
* 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 *)function
line:(NSUInteger)line
tag:(id)tag
options:(DDLogMessageOptions)options
timestamp:(NSDate *)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 *function;
@property (readonly, nonatomic) NSUInteger line;
@property (readonly, nonatomic) id 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) 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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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 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 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)
/**
* 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__)

View File

@ -0,0 +1,178 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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_IPHONE
// iOS
#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
*/
+ (instancetype)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

View File

@ -0,0 +1,117 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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 *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 *blacklist;
/**
* Check if a context is on the blacklist
*
* @param loggingContext the context
*/
- (BOOL)isOnBlacklist:(NSUInteger)loggingContext;
@end

View File

@ -0,0 +1,191 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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];
}
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

View File

@ -0,0 +1,178 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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

View File

@ -0,0 +1,277 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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;
_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

View File

@ -0,0 +1,56 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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 *formatters;
/**
* Add a new formatter
*/
- (void)addFormatter:(id<DDLogFormatter>)formatter;
/**
* Remove a formatter
*/
- (void)removeFormatter:(id<DDLogFormatter>)formatter;
/**
* Remove all existing formatters
*/
- (void)removeAllFormatters;
/**
* Check if a certain formatter is used
*/
- (BOOL)isFormattingWithFormatter:(id<DDLogFormatter>)formatter;
@end

View File

@ -0,0 +1,144 @@
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2015, 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

View File

@ -0,0 +1,36 @@
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 *
}
}

18
Example/Pods/CocoaLumberjack/LICENSE.txt generated Normal file
View File

@ -0,0 +1,18 @@
Software License Agreement (BSD License)
Copyright (c) 2010-2015, Deusty, LLC
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Neither the name of Deusty nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of Deusty, LLC.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

197
Example/Pods/CocoaLumberjack/README.md generated Normal file
View File

@ -0,0 +1,197 @@
<p align="center" >
<img src="LumberjackLogo.png" title="Lumberjack logo" float=left>
</p>
CocoaLumberjack
===============
[![Build Status](https://travis-ci.org/CocoaLumberjack/CocoaLumberjack.svg?branch=master)](https://travis-ci.org/CocoaLumberjack/CocoaLumberjack)
[![Pod Version](http://img.shields.io/cocoapods/v/CocoaLumberjack.svg?style=flat)](http://cocoadocs.org/docsets/CocoaLumberjack/)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Pod Platform](http://img.shields.io/cocoapods/p/CocoaLumberjack.svg?style=flat)](http://cocoadocs.org/docsets/CocoaLumberjack/)
[![Pod License](http://img.shields.io/cocoapods/l/CocoaLumberjack.svg?style=flat)](http://opensource.org/licenses/BSD-3-Clause)
[![Reference Status](https://www.versioneye.com/objective-c/cocoalumberjack/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/cocoalumberjack/references)
**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'
pod 'CocoaLumberjack/Swift'
use_frameworks!
```
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 converation](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/405).
##### Swift Usage
If you installed using CocoaPods or manually:
```swift
import CocoaLumberjack
```
```swift
DDLog.addLogger(DDTTYLogger.sharedInstance()) // TTY = Xcode console
DDLog.addLogger(DDASLLogger.sharedInstance()) // ASL = Apple System Logs
let fileLogger: DDFileLogger = DDFileLogger() // File Logger
fileLogger.rollingFrequency = 60*60*24 // 24 hours
fileLogger.logFileManager.maximumNumberOfLogFiles = 7
DDLog.addLogger(fileLogger)
...
DDLogVerbose("Verbose");
DDLogDebug("Debug");
DDLogInfo("Info");
DDLogWarn("Warn");
DDLogError("Error");
```
##### Obj-C version via CocoaPods
```ruby
platform :ios, '7.0'
pod 'CocoaLumberjack'
```
##### Objc-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 2
#### Migrating to 2.x
* Replace `DDLog.h` imports by `#import <CocoaLumberjack/CocoaLumberjack.h>`.
Advanced users, third party libraries:
* Replace all `DDLogC` macros for regular `DDLog` macros.
* Replace log level (`LOG_LEVEL_*`) macros with `DDLogLevel` enum values
* Replace log flag (`LOG_FLAG_*`) macros with `DDLogFlag` enum values
* Replace `DDLogMessage` ivars and method calls to the new ivars and methods
* `logMsg` with `_message`
* `logLevel` with `_level`
* `logFlag` with `_flag`
* `logContext` with `_context`
* `lineNumber` with `_line` (type changed from `int` to `NSUInteger`)
* `file` with `_file` (`filename` contains just the file name, without the extension and the full path)
* `timestamp` with `_timestamp`
* `methodName` with `function`
* Replace `DDAbstractLogger` `formatter` to `logFormatter`
* `YSSingleFileLogger` ivars are no longer accesible, use the methods instead
* Replace `[DDLog addLogger:withLogLevel:]` with `[DDLog addLogger:withLevel:]`
#### Forcing 1.x
If an included library requires it, you can force CocoaLumberjack 1.x by setting the version before the conflicting library:
```ruby
pod 'CocoaLumberjack', '~> 1.9'
pod 'ConflictingLibrary'
```
### 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 7.1 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.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
### Author
- [Robbie Hanson](https://github.com/robbiehanson)
- Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](http://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](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)
### License
- CocoaLumberjack is available under the BSD license. See the [LICENSE file](https://github.com/CocoaLumberjack/CocoaLumberjack/blob/master/LICENSE.txt).

View File

@ -0,0 +1,15 @@
framework module FBSnapshotTestCase {
umbrella header "FBSnapshotTestCase.h"
export *
module * { export * }
header "FBSnapshotTestCase.h"
header "FBSnapshotTestCasePlatform.h"
header "FBSnapshotTestController.h"
private header "UIImage+Compare.h"
private header "UIImage+Diff.h"
private header "UIImage+Snapshot.h"
}

View File

@ -0,0 +1,37 @@
//
// Created by Gabriel Handford on 3/1/09.
// Copyright 2009-2013. All rights reserved.
// Created by John Boiles on 10/20/11.
// Copyright (c) 2011. All rights reserved
// Modified by Felix Schulze on 2/11/13.
// Copyright 2013. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <UIKit/UIKit.h>
@interface UIImage (Compare)
- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance;
@end

View File

@ -0,0 +1,134 @@
//
// Created by Gabriel Handford on 3/1/09.
// Copyright 2009-2013. All rights reserved.
// Created by John Boiles on 10/20/11.
// Copyright (c) 2011. All rights reserved
// Modified by Felix Schulze on 2/11/13.
// Copyright 2013. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <FBSnapshotTestCase/UIImage+Compare.h>
// This makes debugging much more fun
typedef union {
uint32_t raw;
unsigned char bytes[4];
struct {
char red;
char green;
char blue;
char alpha;
} __attribute__ ((packed)) pixels;
} FBComparePixel;
@implementation UIImage (Compare)
- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance
{
NSAssert(CGSizeEqualToSize(self.size, image.size), @"Images must be same size.");
CGSize referenceImageSize = CGSizeMake(CGImageGetWidth(self.CGImage), CGImageGetHeight(self.CGImage));
CGSize imageSize = CGSizeMake(CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage));
// The images have the equal size, so we could use the smallest amount of bytes because of byte padding
size_t minBytesPerRow = MIN(CGImageGetBytesPerRow(self.CGImage), CGImageGetBytesPerRow(image.CGImage));
size_t referenceImageSizeBytes = referenceImageSize.height * minBytesPerRow;
void *referenceImagePixels = calloc(1, referenceImageSizeBytes);
void *imagePixels = calloc(1, referenceImageSizeBytes);
if (!referenceImagePixels || !imagePixels) {
free(referenceImagePixels);
free(imagePixels);
return NO;
}
CGContextRef referenceImageContext = CGBitmapContextCreate(referenceImagePixels,
referenceImageSize.width,
referenceImageSize.height,
CGImageGetBitsPerComponent(self.CGImage),
minBytesPerRow,
CGImageGetColorSpace(self.CGImage),
(CGBitmapInfo)kCGImageAlphaPremultipliedLast
);
CGContextRef imageContext = CGBitmapContextCreate(imagePixels,
imageSize.width,
imageSize.height,
CGImageGetBitsPerComponent(image.CGImage),
minBytesPerRow,
CGImageGetColorSpace(image.CGImage),
(CGBitmapInfo)kCGImageAlphaPremultipliedLast
);
if (!referenceImageContext || !imageContext) {
CGContextRelease(referenceImageContext);
CGContextRelease(imageContext);
free(referenceImagePixels);
free(imagePixels);
return NO;
}
CGContextDrawImage(referenceImageContext, CGRectMake(0, 0, referenceImageSize.width, referenceImageSize.height), self.CGImage);
CGContextDrawImage(imageContext, CGRectMake(0, 0, imageSize.width, imageSize.height), image.CGImage);
CGContextRelease(referenceImageContext);
CGContextRelease(imageContext);
BOOL imageEqual = YES;
// Do a fast compare if we can
if (tolerance == 0) {
imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0);
} else {
// Go through each pixel in turn and see if it is different
const NSInteger pixelCount = referenceImageSize.width * referenceImageSize.height;
FBComparePixel *p1 = referenceImagePixels;
FBComparePixel *p2 = imagePixels;
NSInteger numDiffPixels = 0;
for (int n = 0; n < pixelCount; ++n) {
// If this pixel is different, increment the pixel diff count and see
// if we have hit our limit.
if (p1->raw != p2->raw) {
numDiffPixels ++;
CGFloat percent = (CGFloat)numDiffPixels / pixelCount;
if (percent > tolerance) {
imageEqual = NO;
break;
}
}
p1++;
p2++;
}
}
free(referenceImagePixels);
free(imagePixels);
return imageEqual;
}
@end

View File

@ -0,0 +1,37 @@
//
// Created by Gabriel Handford on 3/1/09.
// Copyright 2009-2013. All rights reserved.
// Created by John Boiles on 10/20/11.
// Copyright (c) 2011. All rights reserved
// Modified by Felix Schulze on 2/11/13.
// Copyright 2013. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <UIKit/UIKit.h>
@interface UIImage (Diff)
- (UIImage *)fb_diffWithImage:(UIImage *)image;
@end

View File

@ -0,0 +1,56 @@
//
// Created by Gabriel Handford on 3/1/09.
// Copyright 2009-2013. All rights reserved.
// Created by John Boiles on 10/20/11.
// Copyright (c) 2011. All rights reserved
// Modified by Felix Schulze on 2/11/13.
// Copyright 2013. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <FBSnapshotTestCase/UIImage+Diff.h>
@implementation UIImage (Diff)
- (UIImage *)fb_diffWithImage:(UIImage *)image
{
if (!image) {
return nil;
}
CGSize imageSize = CGSizeMake(MAX(self.size.width, image.size.width), MAX(self.size.height, image.size.height));
UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
[self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)];
CGContextSetAlpha(context, 0.5);
CGContextBeginTransparencyLayer(context, NULL);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
CGContextSetBlendMode(context, kCGBlendModeDifference);
CGContextSetFillColorWithColor(context,[UIColor whiteColor].CGColor);
CGContextFillRect(context, CGRectMake(0, 0, self.size.width, self.size.height));
CGContextEndTransparencyLayer(context);
UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return returnImage;
}
@end

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
@interface UIImage (Snapshot)
/// Uses renderInContext: to get a snapshot of the layer.
+ (UIImage *)fb_imageForLayer:(CALayer *)layer;
/// Uses renderInContext: to get a snapshot of the view layer.
+ (UIImage *)fb_imageForViewLayer:(UIView *)view;
/// Uses drawViewHierarchyInRect: to get a snapshot of the view and adds the view into a window if needed.
+ (UIImage *)fb_imageForView:(UIView *)view;
@end

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <FBSnapshotTestCase/UIImage+Snapshot.h>
@implementation UIImage (Snapshot)
+ (UIImage *)fb_imageForLayer:(CALayer *)layer
{
CGRect bounds = layer.bounds;
NSAssert1(CGRectGetWidth(bounds), @"Zero width for layer %@", layer);
NSAssert1(CGRectGetHeight(bounds), @"Zero height for layer %@", layer);
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
NSAssert1(context, @"Could not generate context for layer %@", layer);
CGContextSaveGState(context);
[layer layoutIfNeeded];
[layer renderInContext:context];
CGContextRestoreGState(context);
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshot;
}
+ (UIImage *)fb_imageForViewLayer:(UIView *)view
{
[view layoutIfNeeded];
return [self fb_imageForLayer:view.layer];
}
+ (UIImage *)fb_imageForView:(UIView *)view
{
CGRect bounds = view.bounds;
NSAssert1(CGRectGetWidth(bounds), @"Zero width for view %@", view);
NSAssert1(CGRectGetHeight(bounds), @"Zero height for view %@", view);
UIWindow *window = view.window;
if (window == nil) {
window = [[UIWindow alloc] initWithFrame:bounds];
[window addSubview:view];
[window makeKeyAndVisible];
}
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
[view layoutIfNeeded];
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES];
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshot;
}
@end

View File

@ -0,0 +1,200 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <FBSnapshotTestCase/FBSnapshotTestCasePlatform.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
/*
There are three ways of setting reference image directories.
1. Set the preprocessor macro FB_REFERENCE_IMAGE_DIR to a double quoted
c-string with the path.
2. Set an environment variable named FB_REFERENCE_IMAGE_DIR with the path. This
takes precedence over the preprocessor macro to allow for run-time override.
3. Keep everything unset, which will cause the reference images to be looked up
inside the bundle holding the current test, in the
Resources/ReferenceImages_* directories.
*/
#ifndef FB_REFERENCE_IMAGE_DIR
#define FB_REFERENCE_IMAGE_DIR ""
#endif
/**
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
@param view The view to snapshot
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@param suffixes An NSOrderedSet of strings for the different suffixes
@param tolerance The percentage of pixels that can differ and still count as an 'identical' view
*/
#define FBSnapshotVerifyViewWithOptions(view__, identifier__, suffixes__, tolerance__) \
FBSnapshotVerifyViewOrLayerWithOptions(View, view__, identifier__, suffixes__, tolerance__)
#define FBSnapshotVerifyView(view__, identifier__) \
FBSnapshotVerifyViewWithOptions(view__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0)
/**
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
@param layer The layer to snapshot
@param identifier An optional identifier, used is there are multiple snapshot tests in a given -test method.
@param suffixes An NSOrderedSet of strings for the different suffixes
@param tolerance The percentage of pixels that can differ and still count as an 'identical' layer
*/
#define FBSnapshotVerifyLayerWithOptions(layer__, identifier__, suffixes__, tolerance__) \
FBSnapshotVerifyViewOrLayerWithOptions(Layer, layer__, identifier__, suffixes__, tolerance__)
#define FBSnapshotVerifyLayer(layer__, identifier__) \
FBSnapshotVerifyLayerWithOptions(layer__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0)
#define FBSnapshotVerifyViewOrLayerWithOptions(what__, viewOrLayer__, identifier__, suffixes__, tolerance__) \
{ \
NSString *referenceImageDirectory = [self getReferenceImageDirectoryWithDefault:(@ FB_REFERENCE_IMAGE_DIR)]; \
XCTAssertNotNil(referenceImageDirectory, @"Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.");\
XCTAssertTrue((suffixes__.count > 0), @"Suffixes set cannot be empty %@", suffixes__); \
\
BOOL testSuccess__ = NO; \
NSError *error__ = nil; \
NSMutableArray *errors__ = [NSMutableArray array]; \
\
if (self.recordMode) { \
\
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffixes__.firstObject]; \
BOOL referenceImageSaved__ = [self compareSnapshotOf ## what__ :(viewOrLayer__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) tolerance:(tolerance__) error:&error__]; \
if (!referenceImageSaved__) { \
[errors__ addObject:error__]; \
} \
} else { \
\
for (NSString *suffix__ in suffixes__) { \
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffix__]; \
BOOL referenceImageAvailable = [self referenceImageRecordedInDirectory:referenceImagesDirectory__ identifier:(identifier__) error:&error__]; \
\
if (referenceImageAvailable) { \
BOOL comparisonSuccess__ = [self compareSnapshotOf ## what__ :(viewOrLayer__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) tolerance:(tolerance__) error:&error__]; \
[errors__ removeAllObjects]; \
if (comparisonSuccess__) { \
testSuccess__ = YES; \
break; \
} else { \
[errors__ addObject:error__]; \
} \
} else { \
[errors__ addObject:error__]; \
} \
} \
} \
XCTAssertTrue(testSuccess__, @"Snapshot comparison failed: %@", errors__.firstObject); \
XCTAssertFalse(self.recordMode, @"Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!"); \
}
/**
The base class of view snapshotting tests. If you have small UI component, it's often easier to configure it in a test
and compare an image of the view to a reference image that write lots of complex layout-code tests.
In order to flip the tests in your subclass to record the reference images set @c recordMode to @c YES.
@attention When recording, the reference image directory should be explicitly
set, otherwise the images may be written to somewhere inside the
simulator directory.
For example:
@code
- (void)setUp
{
[super setUp];
self.recordMode = YES;
}
@endcode
*/
@interface FBSnapshotTestCase : XCTestCase
/**
When YES, the test macros will save reference images, rather than performing an actual test.
*/
@property (readwrite, nonatomic, assign) BOOL recordMode;
/**
When @c YES appends the name of the device model and OS to the snapshot file name.
The default value is @c NO.
*/
@property (readwrite, nonatomic, assign, getter=isDeviceAgnostic) BOOL deviceAgnostic;
/**
When YES, renders a snapshot of the complete view hierarchy as visible onscreen.
There are several things that do not work if renderInContext: is used.
- UIVisualEffect #70
- UIAppearance #91
- Size Classes #92
@attention If the view does't belong to a UIWindow, it will create one and add the view as a subview.
*/
@property (readwrite, nonatomic, assign) BOOL usesDrawViewHierarchyInRect;
- (void)setUp NS_REQUIRES_SUPER;
- (void)tearDown NS_REQUIRES_SUPER;
/**
Performs the comparison or records a snapshot of the layer if recordMode is YES.
@param layer The Layer to snapshot
@param referenceImagesDirectory The directory in which reference images are stored.
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr;
/**
Performs the comparison or records a snapshot of the view if recordMode is YES.
@param view The view to snapshot
@param referenceImagesDirectory The directory in which reference images are stored.
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfView:(UIView *)view
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr;
/**
Checks if reference image with identifier based name exists in the reference images directory.
@param referenceImagesDirectory The directory in which reference images are stored.
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if reference image exists.
*/
- (BOOL)referenceImageRecordedInDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
Returns the reference image directory.
Helper function used to implement the assert macros.
@param dir directory to use if environment variable not specified. Ignored if null or empty.
*/
- (NSString *)getReferenceImageDirectoryWithDefault:(NSString *)dir;
@end

View File

@ -0,0 +1,136 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <FBSnapshotTestCase/FBSnapshotTestCase.h>
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
@implementation FBSnapshotTestCase
{
FBSnapshotTestController *_snapshotController;
}
#pragma mark - Overrides
- (void)setUp
{
[super setUp];
_snapshotController = [[FBSnapshotTestController alloc] initWithTestName:NSStringFromClass([self class])];
}
- (void)tearDown
{
_snapshotController = nil;
[super tearDown];
}
- (BOOL)recordMode
{
return _snapshotController.recordMode;
}
- (void)setRecordMode:(BOOL)recordMode
{
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
_snapshotController.recordMode = recordMode;
}
- (BOOL)isDeviceAgnostic
{
return _snapshotController.deviceAgnostic;
}
- (void)setDeviceAgnostic:(BOOL)deviceAgnostic
{
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
_snapshotController.deviceAgnostic = deviceAgnostic;
}
- (BOOL)usesDrawViewHierarchyInRect
{
return _snapshotController.usesDrawViewHierarchyInRect;
}
- (void)setUsesDrawViewHierarchyInRect:(BOOL)usesDrawViewHierarchyInRect
{
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
_snapshotController.usesDrawViewHierarchyInRect = usesDrawViewHierarchyInRect;
}
#pragma mark - Public API
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
return [self _compareSnapshotOfViewOrLayer:layer
referenceImagesDirectory:referenceImagesDirectory
identifier:identifier
tolerance:tolerance
error:errorPtr];
}
- (BOOL)compareSnapshotOfView:(UIView *)view
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
return [self _compareSnapshotOfViewOrLayer:view
referenceImagesDirectory:referenceImagesDirectory
identifier:identifier
tolerance:tolerance
error:errorPtr];
}
- (BOOL)referenceImageRecordedInDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
_snapshotController.referenceImagesDirectory = referenceImagesDirectory;
UIImage *referenceImage = [_snapshotController referenceImageForSelector:self.invocation.selector
identifier:identifier
error:errorPtr];
return (referenceImage != nil);
}
- (NSString *)getReferenceImageDirectoryWithDefault:(NSString *)dir
{
NSString *envReferenceImageDirectory = [NSProcessInfo processInfo].environment[@"FB_REFERENCE_IMAGE_DIR"];
if (envReferenceImageDirectory) {
return envReferenceImageDirectory;
}
if (dir && dir.length > 0) {
return dir;
}
return [[NSBundle bundleForClass:self.class].resourcePath stringByAppendingPathComponent:@"ReferenceImages"];
}
#pragma mark - Private API
- (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
_snapshotController.referenceImagesDirectory = referenceImagesDirectory;
return [_snapshotController compareSnapshotOfViewOrLayer:viewOrLayer
selector:self.invocation.selector
identifier:identifier
tolerance:tolerance
error:errorPtr];
}
@end

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
Returns a Boolean value that indicates whether the snapshot test is running in 64Bit.
This method is a convenience for creating the suffixes set based on the architecture
that the test is running.
@returns @c YES if the test is running in 64bit, otherwise @c NO.
*/
BOOL FBSnapshotTestCaseIs64Bit(void);
/**
Returns a default set of strings that is used to append a suffix based on the architectures.
@warning Do not modify this function, you can create your own and use it with @c FBSnapshotVerifyViewWithOptions()
@returns An @c NSOrderedSet object containing strings that are appended to the reference images directory.
*/
NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void);
/**
Returns a fully «normalized» file name.
Strips punctuation and spaces and replaces them with @c _. Also appends the device model, running OS and screen size to the file name.
@returns An @c NSString object containing the passed @c fileName with the device model, OS and screen size appended at the end.
*/
NSString *FBDeviceAgnosticNormalizedFileName(NSString *fileName);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <FBSnapshotTestCase/FBSnapshotTestCasePlatform.h>
#import <UIKit/UIKit.h>
BOOL FBSnapshotTestCaseIs64Bit(void)
{
#if __LP64__
return YES;
#else
return NO;
#endif
}
NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void)
{
NSMutableOrderedSet *suffixesSet = [[NSMutableOrderedSet alloc] init];
[suffixesSet addObject:@"_32"];
[suffixesSet addObject:@"_64"];
if (FBSnapshotTestCaseIs64Bit()) {
return [suffixesSet reversedOrderedSet];
}
return [suffixesSet copy];
}
NSString *FBDeviceAgnosticNormalizedFileName(NSString *fileName)
{
UIDevice *device = [UIDevice currentDevice];
CGSize screenSize = [[UIApplication sharedApplication] keyWindow].bounds.size;
NSString *os = device.systemVersion;
fileName = [NSString stringWithFormat:@"%@_%@%@_%.0fx%.0f", fileName, device.model, os, screenSize.width, screenSize.height];
NSMutableCharacterSet *invalidCharacters = [NSMutableCharacterSet new];
[invalidCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
[invalidCharacters formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
NSArray *validComponents = [fileName componentsSeparatedByCharactersInSet:invalidCharacters];
fileName = [validComponents componentsJoinedByString:@"_"];
return fileName;
}

View File

@ -0,0 +1,151 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, FBSnapshotTestControllerErrorCode) {
FBSnapshotTestControllerErrorCodeUnknown,
FBSnapshotTestControllerErrorCodeNeedsRecord,
FBSnapshotTestControllerErrorCodePNGCreationFailed,
FBSnapshotTestControllerErrorCodeImagesDifferentSizes,
FBSnapshotTestControllerErrorCodeImagesDifferent,
};
/**
Errors returned by the methods of FBSnapshotTestController use this domain.
*/
extern NSString *const FBSnapshotTestControllerErrorDomain;
/**
Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary.
*/
extern NSString *const FBReferenceImageFilePathKey;
/**
Provides the heavy-lifting for FBSnapshotTestCase. It loads and saves images, along with performing the actual pixel-
by-pixel comparison of images.
Instances are initialized with the test class, and directories to read and write to.
*/
@interface FBSnapshotTestController : NSObject
/**
Record snapshots.
*/
@property (readwrite, nonatomic, assign) BOOL recordMode;
/**
When @c YES appends the name of the device model and OS to the snapshot file name.
The default value is @c NO.
*/
@property (readwrite, nonatomic, assign, getter=isDeviceAgnostic) BOOL deviceAgnostic;
/**
Uses drawViewHierarchyInRect:afterScreenUpdates: to draw the image instead of renderInContext:
*/
@property (readwrite, nonatomic, assign) BOOL usesDrawViewHierarchyInRect;
/**
The directory in which referfence images are stored.
*/
@property (readwrite, nonatomic, copy) NSString *referenceImagesDirectory;
/**
@param testClass The subclass of FBSnapshotTestCase that is using this controller.
@returns An instance of FBSnapshotTestController.
*/
- (instancetype)initWithTestClass:(Class)testClass;
/**
Designated initializer.
@param testName The name of the tests.
@returns An instance of FBSnapshotTestController.
*/
- (instancetype)initWithTestName:(NSString *)testName;
/**
Performs the comparison of the layer.
@param layer The Layer to snapshot.
@param selector The test method being run.
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
Performs the comparison of the view.
@param view The view to snapshot.
@param selector The test method being run.
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfView:(UIView *)view
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
Performs the comparison of a view or layer.
@param view The view or layer to snapshot.
@param selector The test method being run.
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
@param tolerance The percentage of pixels that can differ and still be considered 'identical'
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr;
/**
Loads a reference image.
@param selector The test method being run.
@param identifier The optional identifier, used when multiple images are tested in a single -test method.
@param errorPtr An error, if this methods returns nil, the error will be something useful.
@returns An image.
*/
- (UIImage *)referenceImageForSelector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
Performs a pixel-by-pixel comparison of the two images with an allowable margin of error.
@param referenceImage The reference (correct) image.
@param image The image to test against the reference.
@param tolerance The percentage of pixels that can differ and still be considered 'identical'
@param errorPtr An error that indicates why the comparison failed if it does.
@returns YES if the comparison succeeded and the images are the same(ish).
*/
- (BOOL)compareReferenceImage:(UIImage *)referenceImage
toImage:(UIImage *)image
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr;
/**
Saves the reference image and the test image to `failedOutputDirectory`.
@param referenceImage The reference (correct) image.
@param testImage The image to test against the reference.
@param selector The test method being run.
@param identifier The optional identifier, used when multiple images are tested in a single -test method.
@param errorPtr An error that indicates why the comparison failed if it does.
@returns YES if the save succeeded.
*/
- (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage
testImage:(UIImage *)testImage
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
@end

View File

@ -0,0 +1,356 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
#import <FBSnapshotTestCase/FBSnapshotTestCasePlatform.h>
#import <FBSnapshotTestCase/UIImage+Compare.h>
#import <FBSnapshotTestCase/UIImage+Diff.h>
#import <FBSnapshotTestCase/UIImage+Snapshot.h>
#import <UIKit/UIKit.h>
NSString *const FBSnapshotTestControllerErrorDomain = @"FBSnapshotTestControllerErrorDomain";
NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey";
typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
FBTestSnapshotFileNameTypeReference,
FBTestSnapshotFileNameTypeFailedReference,
FBTestSnapshotFileNameTypeFailedTest,
FBTestSnapshotFileNameTypeFailedTestDiff,
};
@implementation FBSnapshotTestController
{
NSString *_testName;
NSFileManager *_fileManager;
}
#pragma mark - Initializers
- (instancetype)initWithTestClass:(Class)testClass;
{
return [self initWithTestName:NSStringFromClass(testClass)];
}
- (instancetype)initWithTestName:(NSString *)testName
{
if (self = [super init]) {
_testName = [testName copy];
_deviceAgnostic = NO;
_fileManager = [[NSFileManager alloc] init];
}
return self;
}
#pragma mark - Overrides
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ %@", [super description], _referenceImagesDirectory];
}
#pragma mark - Public API
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
return [self compareSnapshotOfViewOrLayer:layer
selector:selector
identifier:identifier
tolerance:0
error:errorPtr];
}
- (BOOL)compareSnapshotOfView:(UIView *)view
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
return [self compareSnapshotOfViewOrLayer:view
selector:selector
identifier:identifier
tolerance:0
error:errorPtr];
}
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
if (self.recordMode) {
return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
} else {
return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance error:errorPtr];
}
}
- (UIImage *)referenceImageForSelector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier];
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
if (nil == image && NULL != errorPtr) {
BOOL exists = [_fileManager fileExistsAtPath:filePath];
if (!exists) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodeNeedsRecord
userInfo:@{
FBReferenceImageFilePathKey: filePath,
NSLocalizedDescriptionKey: @"Unable to load reference image.",
NSLocalizedFailureReasonErrorKey: @"Reference image not found. You need to run the test in record mode",
}];
} else {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodeUnknown
userInfo:nil];
}
}
return image;
}
- (BOOL)compareReferenceImage:(UIImage *)referenceImage
toImage:(UIImage *)image
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
if (CGSizeEqualToSize(referenceImage.size, image.size)) {
BOOL imagesEqual = [referenceImage fb_compareWithImage:image tolerance:tolerance];
if (NULL != errorPtr) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodeImagesDifferent
userInfo:@{
NSLocalizedDescriptionKey: @"Images different",
}];
}
return imagesEqual;
}
if (NULL != errorPtr) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodeImagesDifferentSizes
userInfo:@{
NSLocalizedDescriptionKey: @"Images different sizes",
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"referenceImage:%@, image:%@",
NSStringFromCGSize(referenceImage.size),
NSStringFromCGSize(image.size)],
}];
}
return NO;
}
- (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage
testImage:(UIImage *)testImage
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
NSData *referencePNGData = UIImagePNGRepresentation(referenceImage);
NSData *testPNGData = UIImagePNGRepresentation(testImage);
NSString *referencePath = [self _failedFilePathForSelector:selector
identifier:identifier
fileNameType:FBTestSnapshotFileNameTypeFailedReference];
NSError *creationError = nil;
BOOL didCreateDir = [_fileManager createDirectoryAtPath:[referencePath stringByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:&creationError];
if (!didCreateDir) {
if (NULL != errorPtr) {
*errorPtr = creationError;
}
return NO;
}
if (![referencePNGData writeToFile:referencePath options:NSDataWritingAtomic error:errorPtr]) {
return NO;
}
NSString *testPath = [self _failedFilePathForSelector:selector
identifier:identifier
fileNameType:FBTestSnapshotFileNameTypeFailedTest];
if (![testPNGData writeToFile:testPath options:NSDataWritingAtomic error:errorPtr]) {
return NO;
}
NSString *diffPath = [self _failedFilePathForSelector:selector
identifier:identifier
fileNameType:FBTestSnapshotFileNameTypeFailedTestDiff];
UIImage *diffImage = [referenceImage fb_diffWithImage:testImage];
NSData *diffImageData = UIImagePNGRepresentation(diffImage);
if (![diffImageData writeToFile:diffPath options:NSDataWritingAtomic error:errorPtr]) {
return NO;
}
NSLog(@"If you have Kaleidoscope installed you can run this command to see an image diff:\n"
@"ksdiff \"%@\" \"%@\"", referencePath, testPath);
return YES;
}
#pragma mark - Private API
- (NSString *)_fileNameForSelector:(SEL)selector
identifier:(NSString *)identifier
fileNameType:(FBTestSnapshotFileNameType)fileNameType
{
NSString *fileName = nil;
switch (fileNameType) {
case FBTestSnapshotFileNameTypeFailedReference:
fileName = @"reference_";
break;
case FBTestSnapshotFileNameTypeFailedTest:
fileName = @"failed_";
break;
case FBTestSnapshotFileNameTypeFailedTestDiff:
fileName = @"diff_";
break;
default:
fileName = @"";
break;
}
fileName = [fileName stringByAppendingString:NSStringFromSelector(selector)];
if (0 < identifier.length) {
fileName = [fileName stringByAppendingFormat:@"_%@", identifier];
}
if (self.isDeviceAgnostic) {
fileName = FBDeviceAgnosticNormalizedFileName(fileName);
}
if ([[UIScreen mainScreen] scale] > 1) {
fileName = [fileName stringByAppendingFormat:@"@%.fx", [[UIScreen mainScreen] scale]];
}
fileName = [fileName stringByAppendingPathExtension:@"png"];
return fileName;
}
- (NSString *)_referenceFilePathForSelector:(SEL)selector
identifier:(NSString *)identifier
{
NSString *fileName = [self _fileNameForSelector:selector
identifier:identifier
fileNameType:FBTestSnapshotFileNameTypeReference];
NSString *filePath = [_referenceImagesDirectory stringByAppendingPathComponent:_testName];
filePath = [filePath stringByAppendingPathComponent:fileName];
return filePath;
}
- (NSString *)_failedFilePathForSelector:(SEL)selector
identifier:(NSString *)identifier
fileNameType:(FBTestSnapshotFileNameType)fileNameType
{
NSString *fileName = [self _fileNameForSelector:selector
identifier:identifier
fileNameType:fileNameType];
NSString *folderPath = NSTemporaryDirectory();
if (getenv("IMAGE_DIFF_DIR")) {
folderPath = @(getenv("IMAGE_DIFF_DIR"));
}
NSString *filePath = [folderPath stringByAppendingPathComponent:_testName];
filePath = [filePath stringByAppendingPathComponent:fileName];
return filePath;
}
- (BOOL)_performPixelComparisonWithViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr];
if (nil != referenceImage) {
UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer];
BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot tolerance:tolerance error:errorPtr];
if (!imagesSame) {
[self saveFailedReferenceImage:referenceImage
testImage:snapshot
selector:selector
identifier:identifier
error:errorPtr];
}
return imagesSame;
}
return NO;
}
- (BOOL)_recordSnapshotOfViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer];
return [self _saveReferenceImage:snapshot selector:selector identifier:identifier error:errorPtr];
}
- (BOOL)_saveReferenceImage:(UIImage *)image
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
BOOL didWrite = NO;
if (nil != image) {
NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier];
NSData *pngData = UIImagePNGRepresentation(image);
if (nil != pngData) {
NSError *creationError = nil;
BOOL didCreateDir = [_fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:&creationError];
if (!didCreateDir) {
if (NULL != errorPtr) {
*errorPtr = creationError;
}
return NO;
}
didWrite = [pngData writeToFile:filePath options:NSDataWritingAtomic error:errorPtr];
if (didWrite) {
NSLog(@"Reference image save at: %@", filePath);
}
} else {
if (nil != errorPtr) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodePNGCreationFailed
userInfo:@{
FBReferenceImageFilePathKey: filePath,
}];
}
}
}
return didWrite;
}
- (UIImage *)_imageForViewOrLayer:(id)viewOrLayer
{
if ([viewOrLayer isKindOfClass:[UIView class]]) {
if (_usesDrawViewHierarchyInRect) {
return [UIImage fb_imageForView:viewOrLayer];
} else {
return [UIImage fb_imageForViewLayer:viewOrLayer];
}
} else if ([viewOrLayer isKindOfClass:[CALayer class]]) {
return [UIImage fb_imageForLayer:viewOrLayer];
} else {
[NSException raise:@"Only UIView and CALayer classes can be snapshotted" format:@"%@", viewOrLayer];
}
return nil;
}
@end

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
public extension FBSnapshotTestCase {
public func FBSnapshotVerifyView(view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), file: String = __FILE__, line: UInt = __LINE__) {
FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes)
}
public func FBSnapshotVerifyLayer(layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), file: String = __FILE__, line: UInt = __LINE__) {
FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes)
}
private func FBSnapshotVerifyViewOrLayer(viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), file: String = __FILE__, line: UInt = __LINE__) {
let envReferenceImageDirectory = self.getReferenceImageDirectoryWithDefault(FB_REFERENCE_IMAGE_DIR)
var error: NSError?
var comparisonSuccess = false
if let envReferenceImageDirectory = envReferenceImageDirectory {
for suffix in suffixes {
let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)"
if viewOrLayer.isKindOfClass(UIView) {
do {
try compareSnapshotOfView(viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: 0)
comparisonSuccess = true
} catch let error1 as NSError {
error = error1
comparisonSuccess = false
}
} else if viewOrLayer.isKindOfClass(CALayer) {
do {
try compareSnapshotOfLayer(viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: 0)
comparisonSuccess = true
} catch let error1 as NSError {
error = error1
comparisonSuccess = false
}
} else {
assertionFailure("Only UIView and CALayer classes can be snapshotted")
}
assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line)
if comparisonSuccess || recordMode {
break
}
assert(comparisonSuccess, message: "Snapshot comparison failed: \(error)", file: file, line: line)
}
} else {
XCTFail("Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.")
}
}
func assert(assertion: Bool, message: String, file: String, line: UInt) {
if !assertion {
XCTFail(message, file: file, line: line)
}
}
}

29
Example/Pods/FBSnapshotTestCase/LICENSE generated Normal file
View File

@ -0,0 +1,29 @@
BSD License
For the FBSnapshotTestCase software
Copyright (c) 2013, Facebook, Inc.
All rights reserved.
Redistribution and use 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.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,97 @@
FBSnapshotTestCase
======================
[![Build Status](https://travis-ci.org/facebook/ios-snapshot-test-case.svg)](https://travis-ci.org/facebook/ios-snapshot-test-case) [![Cocoa Pod Version](https://cocoapod-badges.herokuapp.com/v/FBSnapshotTestCase/badge.svg)](http://cocoadocs.org/docsets/FBSnapshotTestCase/)
What it does
------------
A "snapshot test case" takes a configured `UIView` or `CALayer` and uses the
`renderInContext:` method to get an image snapshot of its contents. It
compares this snapshot to a "reference image" stored in your source code
repository and fails the test if the two images don't match.
Why?
----
At Facebook we write a lot of UI code. As you might imagine, each type of
feed story is rendered using a subclass of `UIView`. There are a lot of edge
cases that we want to handle correctly:
- What if there is more text than can fit in the space available?
- What if an image doesn't match the size of an image view?
- What should the highlighted state look like?
It's straightforward to test logic code, but less obvious how you should test
views. You can do a lot of rectangle asserts, but these are hard to understand
or visualize. Looking at an image diff shows you exactly what changed and how
it will look to users.
We developed `FBSnapshotTestCase` to make snapshot tests easy.
Installation with CocoaPods
---------------------------
1. Add the following lines to your Podfile:
```
target "Tests" do
pod 'FBSnapshotTestCase'
end
```
If you support iOS 7 use `FBSnapshotTestCase/Core` instead, which doesn't contain Swift support.
Replace "Tests" with the name of your test project.
2. There are [three ways](https://github.com/facebook/ios-snapshot-test-case/blob/master/FBSnapshotTestCase/FBSnapshotTestCase.h#L19-L29) of setting reference image directories, the recommended one is to define `FB_REFERENCE_IMAGE_DIR` in your scheme. This should point to the directory where you want reference images to be stored. At Facebook, we normally use this:
|Name|Value|
|:---|:----|
|`FB_REFERENCE_IMAGE_DIR`|`$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages`|
![](FBSnapshotTestCaseDemo/Scheme_FB_REFERENCE_IMAGE_DIR.png)
Creating a snapshot test
------------------------
1. Subclass `FBSnapshotTestCase` instead of `XCTestCase`.
2. From within your test, use `FBSnapshotVerifyView`.
3. Run the test once with `self.recordMode = YES;` in the test's `-setUp`
method. (This creates the reference images on disk.)
4. Remove the line enabling record mode and run the test.
Features
--------
- Automatically names reference images on disk according to test class and
selector.
- Prints a descriptive error message to the console on failure. (Bonus:
failure message includes a one-line command to see an image diff if
you have [Kaleidoscope](http://www.kaleidoscopeapp.com) installed.)
- Supply an optional "identifier" if you want to perform multiple snapshots
in a single test method.
- Support for `CALayer` via `FBSnapshotVerifyLayer`.
- `usesDrawViewHierarchyInRect` to handle cases like `UIVisualEffect`, `UIAppearance` and Size Classes.
- `isDeviceAgnostic` to allow appending the device model (`iPhone`, `iPad`, `iPod Touch`, etc), OS version and screen size to the images (allowing to have multiple tests for the same «snapshot» for different `OS`s and devices).
Notes
-----
Your unit test must be an "application test", not a "logic test." (That is, it
must be run within the Simulator so that it has access to UIKit.) In Xcode 5
and later new projects only offer application tests, but older projects will
have separate targets for the two types.
Authors
-------
`FBSnapshotTestCase` was written at Facebook by
[Jonathan Dann](https://facebook.com/j.p.dann) with significant contributions by
[Todd Krabach](https://facebook.com/toddkrabach).
License
-------
`FBSnapshotTestCase` is BSD-licensed. See `LICENSE`.

View File

@ -0,0 +1,24 @@
#import <Foundation/Foundation.h>
#import "DDXML.h"
// These methods are not part of the standard NSXML API.
// But any developer working extensively with XML will likely appreciate them.
@interface DDXMLElement (DDAdditions)
+ (DDXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns;
- (DDXMLElement *)elementForName:(NSString *)name;
- (DDXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns;
- (NSString *)xmlns;
- (void)setXmlns:(NSString *)ns;
- (NSString *)prettyXMLString;
- (NSString *)compactXMLString;
- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string;
- (NSDictionary *)attributesAsDictionary;
@end

View File

@ -0,0 +1,131 @@
#import "DDXMLElementAdditions.h"
@implementation DDXMLElement (DDAdditions)
/**
* Quick method to create an element
**/
+ (DDXMLElement *)elementWithName:(NSString *)name xmlns:(NSString *)ns
{
DDXMLElement *element = [DDXMLElement elementWithName:name];
[element setXmlns:ns];
return element;
}
/**
* This method returns the first child element for the given name.
* If no child element exists for the given name, returns nil.
**/
- (DDXMLElement *)elementForName:(NSString *)name
{
NSArray *elements = [self elementsForName:name];
if([elements count] > 0)
{
return [elements objectAtIndex:0];
}
else
{
// Note: If you port this code to work with Apple's NSXML, beware of the following:
//
// There is a bug in the NSXMLElement elementsForName: method.
// Consider the following XML fragment:
//
// <query xmlns="jabber:iq:private">
// <x xmlns="some:other:namespace"></x>
// </query>
//
// Calling [query elementsForName:@"x"] results in an empty array!
//
// However, it will work properly if you use the following:
// [query elementsForLocalName:@"x" URI:@"some:other:namespace"]
//
// The trouble with this is that we may not always know the xmlns in advance,
// so in this particular case there is no way to access the element without looping through the children.
//
// This bug was submitted to apple on June 1st, 2007 and was classified as "serious".
//
// --!!-- This bug does NOT exist in DDXML --!!--
return nil;
}
}
/**
* This method returns the first child element for the given name and given xmlns.
* If no child elements exist for the given name and given xmlns, returns nil.
**/
- (DDXMLElement *)elementForName:(NSString *)name xmlns:(NSString *)xmlns
{
NSArray *elements = [self elementsForLocalName:name URI:xmlns];
if([elements count] > 0)
{
return [elements objectAtIndex:0];
}
else
{
return nil;
}
}
/**
* Returns the common xmlns "attribute", which is only accessible via the namespace methods.
* The xmlns value is often used in jabber elements.
**/
- (NSString *)xmlns
{
return [[self namespaceForPrefix:@""] stringValue];
}
- (void)setXmlns:(NSString *)ns
{
// If you use setURI: then the xmlns won't be displayed in the XMLString.
// Adding the namespace this way works properly.
//
// This applies to both Apple's NSXML and DDXML.
[self addNamespace:[DDXMLNode namespaceWithName:@"" stringValue:ns]];
}
/**
* Shortcut to get a pretty (formatted) string representation of the element.
**/
- (NSString *)prettyXMLString
{
return [self XMLStringWithOptions:(DDXMLNodePrettyPrint | DDXMLNodeCompactEmptyElement)];
}
/**
* Shortcut to get a compact string representation of the element.
**/
- (NSString *)compactXMLString
{
return [self XMLStringWithOptions:DDXMLNodeCompactEmptyElement];
}
/**
* Shortcut to avoid having to manually create a DDXMLNode everytime.
**/
- (void)addAttributeWithName:(NSString *)name stringValue:(NSString *)string
{
[self addAttribute:[DDXMLNode attributeWithName:name stringValue:string]];
}
/**
* Returns all the attributes as a dictionary.
**/
- (NSDictionary *)attributesAsDictionary
{
NSArray *attributes = [self attributes];
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[attributes count]];
uint i;
for(i = 0; i < [attributes count]; i++)
{
DDXMLNode *node = [attributes objectAtIndex:i];
[result setObject:[node stringValue] forKey:[node name]];
}
return result;
}
@end

View File

@ -0,0 +1,24 @@
#import <Foundation/Foundation.h>
#if DDXML_LIBXML_MODULE_ENABLED
#if TARGET_OS_IOS && TARGET_OS_EMBEDDED
@import libxml;
#elif TARGET_IPHONE_SIMULATOR
@import libxmlSimu;
#elif TARGET_OS_MAC
@import libxmlMac;
#endif
#else
#import <libxml/tree.h>
#endif
@interface NSString (DDXML)
/**
* xmlChar - A basic replacement for char, a byte in a UTF-8 encoded string.
**/
- (const xmlChar *)xmlChar;
- (NSString *)stringByTrimming;
@end

View File

@ -0,0 +1,31 @@
#import "NSString+DDXML.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
@implementation NSString (DDXML)
- (const xmlChar *)xmlChar
{
return (const xmlChar *)[self UTF8String];
}
#ifdef GNUSTEP
- (NSString *)stringByTrimming
{
return [self stringByTrimmingSpaces];
}
#else
- (NSString *)stringByTrimming
{
NSMutableString *mStr = [self mutableCopy];
CFStringTrimWhitespace((__bridge CFMutableStringRef)mStr);
NSString *result = [mStr copy];
return result;
}
#endif
@end

194
Example/Pods/KissXML/KissXML/DDXML.h generated Normal file
View File

@ -0,0 +1,194 @@
/**
* Welcome to KissXML.
*
* The project page has documentation if you have questions.
* https://github.com/robbiehanson/KissXML
*
* If you're new to the project you may wish to read the "Getting Started" wiki.
* https://github.com/robbiehanson/KissXML/wiki/GettingStarted
*
* KissXML provides a drop-in replacement for Apple's NSXML class cluster.
* The goal is to get the exact same behavior as the NSXML classes.
*
* For API Reference, see Apple's excellent documentation,
* either via Xcode's Mac OS X documentation, or via the web:
*
* https://github.com/robbiehanson/KissXML/wiki/Reference
**/
#import "DDXMLNode.h"
#import "DDXMLElement.h"
#import "DDXMLDocument.h"
#if TARGET_OS_IPHONE
// Since KissXML is a drop in replacement for NSXML,
// it may be desireable (when writing cross-platform code to be used on both Mac OS X and iOS)
// to use the NSXML prefixes instead of the DDXML prefix.
//
// This way, on Mac OS X it uses NSXML, and on iOS it uses KissXML.
#ifndef NSXMLNode
#define NSXMLNode DDXMLNode
#endif
#ifndef NSXMLElement
#define NSXMLElement DDXMLElement
#endif
#ifndef NSXMLDocument
#define NSXMLDocument DDXMLDocument
#endif
#ifndef NSXMLInvalidKind
#define NSXMLInvalidKind DDXMLInvalidKind
#endif
#ifndef NSXMLDocumentKind
#define NSXMLDocumentKind DDXMLDocumentKind
#endif
#ifndef NSXMLElementKind
#define NSXMLElementKind DDXMLElementKind
#endif
#ifndef NSXMLAttributeKind
#define NSXMLAttributeKind DDXMLAttributeKind
#endif
#ifndef NSXMLNamespaceKind
#define NSXMLNamespaceKind DDXMLNamespaceKind
#endif
#ifndef NSXMLProcessingInstructionKind
#define NSXMLProcessingInstructionKind DDXMLProcessingInstructionKind
#endif
#ifndef NSXMLCommentKind
#define NSXMLCommentKind DDXMLCommentKind
#endif
#ifndef NSXMLTextKind
#define NSXMLTextKind DDXMLTextKind
#endif
#ifndef NSXMLDTDKind
#define NSXMLDTDKind DDXMLDTDKind
#endif
#ifndef NSXMLEntityDeclarationKind
#define NSXMLEntityDeclarationKind DDXMLEntityDeclarationKind
#endif
#ifndef NSXMLAttributeDeclarationKind
#define NSXMLAttributeDeclarationKind DDXMLAttributeDeclarationKind
#endif
#ifndef NSXMLElementDeclarationKind
#define NSXMLElementDeclarationKind DDXMLElementDeclarationKind
#endif
#ifndef NSXMLNotationDeclarationKind
#define NSXMLNotationDeclarationKind DDXMLNotationDeclarationKind
#endif
#ifndef NSXMLNodeOptionsNone
#define NSXMLNodeOptionsNone DDXMLNodeOptionsNone
#endif
#ifndef NSXMLNodeExpandEmptyElement
#define NSXMLNodeExpandEmptyElement DDXMLNodeExpandEmptyElement
#endif
#ifndef NSXMLNodeCompactEmptyElement
#define NSXMLNodeCompactEmptyElement DDXMLNodeCompactEmptyElement
#endif
#ifndef NSXMLNodePrettyPrint
#define NSXMLNodePrettyPrint DDXMLNodePrettyPrint
#endif
#endif // #if TARGET_OS_IPHONE
// KissXML has rather straight-forward memory management:
// https://github.com/robbiehanson/KissXML/wiki/MemoryManagementThreadSafety
//
// There are 3 important concepts to keep in mind when working with KissXML:
//
//
// 1.) KissXML provides a light-weight wrapper around libxml.
//
// The parsing, creation, storage, etc of the xml tree is all done via libxml.
// This is a fast low-level C library that's been around for ages, and comes pre-installed on Mac OS X and iOS.
// KissXML provides an easy-to-use Objective-C library atop libxml.
// So a DDXMLNode, DDXMLElement, or DDXMLDocument are simply objective-c objects
// with pointers to the underlying libxml C structure.
// Then only time you need to be aware of any of this is when it comes to equality.
// In order to maximize speed and provide read-access thread-safety,
// the library may create multiple DDXML wrapper objects that point to the same underlying xml node.
// So don't assume you can test for equality with "==".
// Instead use the isEqual method (as you should generally do with objects anyway).
//
//
// 2.) XML is implicitly a tree heirarchy, and the XML API's are designed to allow traversal up & down the tree.
//
// The tree heirarchy and API contract have an implicit impact concerning memory management.
//
// <starbucks>
// <latte/>
// </starbucks>
//
// Imagine you have a DDXMLNode corresponding to the starbucks node,
// and you have a DDXMLNode corresponding to the latte node.
// Now imagine you release the starbucks node, but you retain a reference to the latte node.
// What happens?
// Well the latte node is a part of the xml tree heirarchy.
// So if the latte node is still around, the xml tree heirarchy must stick around as well.
// So even though the DDXMLNode corresponding to the starbucks node may get deallocated,
// the underlying xml tree structure won't be freed until the latte node gets dealloacated.
//
// In general, this means that KissXML remains thread-safe when reading and processing a tree.
// If you traverse a tree and fork off asynchronous tasks to process subnodes,
// the tree will remain properly in place until all your asynchronous tasks have completed.
// In other words, it just works.
//
// However, if you parse a huge document into memory, and retain a single node from the giant xml tree...
// Well you should see the problem this creates.
// Instead, in this situation, copy or detach the node if you want to keep it around.
// Or just extract the info you need from it.
//
//
// 3.) KissXML is read-access thread-safe, but write-access thread-unsafe (designed for speed).
//
// <starbucks>
// <latte/>
// </starbucks>
//
// Imagine you have a DDXMLNode corresponding to the starbucks node,
// and you have a DDXMLNode corresponding to the latte node.
// What happens if you invoke [starbucks removeChildAtIndex:0]?
// Well the undelying xml tree will remove the latte node, and release the associated memory.
// And what if you still have a reference to the DDXMLNode that corresponds to the latte node?
// Well the short answer is that you shouldn't use it. At all.
// This is pretty obvious when you think about it from the context of this simple example.
// But in the real world, you might have multiple threads running in parallel,
// and you might accidently modify a node while another thread is processing it.
//
// To completely fix this problem, and provide write-access thread-safety, would require extensive overhead.
// This overhead is completely unwanted in the majority of cases.
// Most XML usage patterns are heavily read-only.
// And in the case of xml creation or modification, it is generally done on the same thread.
// Thus the KissXML library is write-access thread-unsafe, but provides speedier performance.
//
// However, when such a bug does creep up, it produces horrible side-effects.
// Essentially the pointer to the underlying xml structure becomes a dangling pointer,
// which means that accessing the dangling pointer might give you the correct results, or completely random results.
// And attempting to make modifications to non-existant xml nodes via the dangling pointer might do nothing,
// or completely corrupt your heap and cause un-explainable crashes in random parts of your library.
// Heap corruption is one of the worst problems to track down.
// So to help out, the library provides a debugging macro to track down these problems.
// That is, if you invalidate the write-access thread-unsafe rule,
// this macro will tell you when you're trying to access a now-dangling pointer.
//
// How does it work?
// Well everytime a DDXML wrapper object is created atop a libxml structure,
// it marks the linkage in a table.
// And everytime a libxml structure is freed, it destorys all corresponding linkages in the table.
// So everytime a DDXML wrapper objects is about to dereference it's pointer,
// it first ensures the linkage still exists in the table.
//
// Set to 1 to enable
// Set to 0 to disable (this is the default)
//
// The debugging macro adds a significant amount of overhead, and should NOT be enabled on production builds.
#if DEBUG
#define DDXML_DEBUG_MEMORY_ISSUES 0
#else
#define DDXML_DEBUG_MEMORY_ISSUES 0 // Don't change me!
#endif

View File

@ -0,0 +1,84 @@
#import <Foundation/Foundation.h>
#import "DDXMLElement.h"
#import "DDXMLNode.h"
/**
* Welcome to KissXML.
*
* The project page has documentation if you have questions.
* https://github.com/robbiehanson/KissXML
*
* If you're new to the project you may wish to read the "Getting Started" wiki.
* https://github.com/robbiehanson/KissXML/wiki/GettingStarted
*
* KissXML provides a drop-in replacement for Apple's NSXML class cluster.
* The goal is to get the exact same behavior as the NSXML classes.
*
* For API Reference, see Apple's excellent documentation,
* either via Xcode's Mac OS X documentation, or via the web:
*
* https://github.com/robbiehanson/KissXML/wiki/Reference
**/
enum {
DDXMLDocumentXMLKind = 0,
DDXMLDocumentXHTMLKind,
DDXMLDocumentHTMLKind,
DDXMLDocumentTextKind
};
typedef NSUInteger DDXMLDocumentContentKind;
@interface DDXMLDocument : DDXMLNode
{
}
- (id)initWithXMLString:(NSString *)string options:(NSUInteger)mask error:(NSError **)error;
//- (id)initWithContentsOfURL:(NSURL *)url options:(NSUInteger)mask error:(NSError **)error;
- (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)error;
//- (id)initWithRootElement:(DDXMLElement *)element;
//+ (Class)replacementClassForClass:(Class)cls;
//- (void)setCharacterEncoding:(NSString *)encoding; //primitive
//- (NSString *)characterEncoding; //primitive
//- (void)setVersion:(NSString *)version;
//- (NSString *)version;
//- (void)setStandalone:(BOOL)standalone;
//- (BOOL)isStandalone;
//- (void)setDocumentContentKind:(DDXMLDocumentContentKind)kind;
//- (DDXMLDocumentContentKind)documentContentKind;
//- (void)setMIMEType:(NSString *)MIMEType;
//- (NSString *)MIMEType;
//- (void)setDTD:(DDXMLDTD *)documentTypeDeclaration;
//- (DDXMLDTD *)DTD;
//- (void)setRootElement:(DDXMLNode *)root;
- (DDXMLElement *)rootElement;
//- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index;
//- (void)insertChildren:(NSArray *)children atIndex:(NSUInteger)index;
//- (void)removeChildAtIndex:(NSUInteger)index;
//- (void)setChildren:(NSArray *)children;
//- (void)addChild:(DDXMLNode *)child;
//- (void)replaceChildAtIndex:(NSUInteger)index withNode:(DDXMLNode *)node;
- (NSData *)XMLData;
- (NSData *)XMLDataWithOptions:(NSUInteger)options;
//- (id)objectByApplyingXSLT:(NSData *)xslt arguments:(NSDictionary *)arguments error:(NSError **)error;
//- (id)objectByApplyingXSLTString:(NSString *)xslt arguments:(NSDictionary *)arguments error:(NSError **)error;
//- (id)objectByApplyingXSLTAtURL:(NSURL *)xsltURL arguments:(NSDictionary *)argument error:(NSError **)error;
//- (BOOL)validateAndReturnError:(NSError **)error;
@end

View File

@ -0,0 +1,140 @@
#import "DDXMLPrivate.h"
#import "NSString+DDXML.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
/**
* Welcome to KissXML.
*
* The project page has documentation if you have questions.
* https://github.com/robbiehanson/KissXML
*
* If you're new to the project you may wish to read the "Getting Started" wiki.
* https://github.com/robbiehanson/KissXML/wiki/GettingStarted
*
* KissXML provides a drop-in replacement for Apple's NSXML class cluster.
* The goal is to get the exact same behavior as the NSXML classes.
*
* For API Reference, see Apple's excellent documentation,
* either via Xcode's Mac OS X documentation, or via the web:
*
* https://github.com/robbiehanson/KissXML/wiki/Reference
**/
@implementation DDXMLDocument
/**
* Returns a DDXML wrapper object for the given primitive node.
* The given node MUST be non-NULL and of the proper type.
**/
+ (id)nodeWithDocPrimitive:(xmlDocPtr)doc owner:(DDXMLNode *)owner
{
return [[DDXMLDocument alloc] initWithDocPrimitive:doc owner:owner];
}
- (id)initWithDocPrimitive:(xmlDocPtr)doc owner:(DDXMLNode *)inOwner
{
self = [super initWithPrimitive:(xmlKindPtr)doc owner:inOwner];
return self;
}
+ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes
NSAssert(NO, @"Use nodeWithDocPrimitive:owner:");
return nil;
}
- (id)initWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)inOwner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes.
NSAssert(NO, @"Use initWithDocPrimitive:owner:");
return nil;
}
/**
* Initializes and returns a DDXMLDocument object created from an NSData object.
*
* Returns an initialized DDXMLDocument object, or nil if initialization fails
* because of parsing errors or other reasons.
**/
- (id)initWithXMLString:(NSString *)string options:(NSUInteger)mask error:(NSError **)error
{
return [self initWithData:[string dataUsingEncoding:NSUTF8StringEncoding]
options:mask
error:error];
}
/**
* Initializes and returns a DDXMLDocument object created from an NSData object.
*
* Returns an initialized DDXMLDocument object, or nil if initialization fails
* because of parsing errors or other reasons.
**/
- (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)error
{
if (data == nil || [data length] == 0)
{
if (error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:0 userInfo:nil];
return nil;
}
// Even though xmlKeepBlanksDefault(0) is called in DDXMLNode's initialize method,
// it has been documented that this call seems to get reset on the iPhone:
// http://code.google.com/p/kissxml/issues/detail?id=8
//
// Therefore, we call it again here just to be safe.
xmlKeepBlanksDefault(0);
xmlDocPtr doc = xmlParseMemory([data bytes], (int)[data length]);
if (doc == NULL)
{
if (error) *error = [NSError errorWithDomain:@"DDXMLErrorDomain" code:1 userInfo:nil];
return nil;
}
return [self initWithDocPrimitive:doc owner:nil];
}
/**
* Returns the root element of the receiver.
**/
- (DDXMLElement *)rootElement
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlDocPtr doc = (xmlDocPtr)genericPtr;
// doc->children is a list containing possibly comments, DTDs, etc...
xmlNodePtr rootNode = xmlDocGetRootElement(doc);
if (rootNode != NULL)
return [DDXMLElement nodeWithElementPrimitive:rootNode owner:self];
else
return nil;
}
- (NSData *)XMLData
{
// Zombie test occurs in XMLString
return [[self XMLString] dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSData *)XMLDataWithOptions:(NSUInteger)options
{
// Zombie test occurs in XMLString
return [[self XMLStringWithOptions:options] dataUsingEncoding:NSUTF8StringEncoding];
}
@end

View File

@ -0,0 +1,66 @@
#import <Foundation/Foundation.h>
#import "DDXMLNode.h"
/**
* Welcome to KissXML.
*
* The project page has documentation if you have questions.
* https://github.com/robbiehanson/KissXML
*
* If you're new to the project you may wish to read the "Getting Started" wiki.
* https://github.com/robbiehanson/KissXML/wiki/GettingStarted
*
* KissXML provides a drop-in replacement for Apple's NSXML class cluster.
* The goal is to get the exact same behavior as the NSXML classes.
*
* For API Reference, see Apple's excellent documentation,
* either via Xcode's Mac OS X documentation, or via the web:
*
* https://github.com/robbiehanson/KissXML/wiki/Reference
**/
@interface DDXMLElement : DDXMLNode
{
}
- (id)initWithName:(NSString *)name;
- (id)initWithName:(NSString *)name URI:(NSString *)URI;
- (id)initWithName:(NSString *)name stringValue:(NSString *)string;
- (id)initWithXMLString:(NSString *)string error:(NSError **)error;
#pragma mark --- Elements by name ---
- (NSArray *)elementsForName:(NSString *)name;
- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI;
#pragma mark --- Attributes ---
- (void)addAttribute:(DDXMLNode *)attribute;
- (void)removeAttributeForName:(NSString *)name;
- (void)setAttributes:(NSArray *)attributes;
//- (void)setAttributesAsDictionary:(NSDictionary *)attributes;
- (NSArray *)attributes;
- (DDXMLNode *)attributeForName:(NSString *)name;
//- (DDXMLNode *)attributeForLocalName:(NSString *)localName URI:(NSString *)URI;
#pragma mark --- Namespaces ---
- (void)addNamespace:(DDXMLNode *)aNamespace;
- (void)removeNamespaceForPrefix:(NSString *)name;
- (void)setNamespaces:(NSArray *)namespaces;
- (NSArray *)namespaces;
- (DDXMLNode *)namespaceForPrefix:(NSString *)prefix;
- (DDXMLNode *)resolveNamespaceForName:(NSString *)name;
- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI;
#pragma mark --- Children ---
- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index;
//- (void)insertChildren:(NSArray *)children atIndex:(NSUInteger)index;
- (void)removeChildAtIndex:(NSUInteger)index;
- (void)setChildren:(NSArray *)children;
- (void)addChild:(DDXMLNode *)child;
//- (void)replaceChildAtIndex:(NSUInteger)index withNode:(DDXMLNode *)node;
//- (void)normalizeAdjacentTextNodesPreservingCDATA:(BOOL)preserve;
@end

View File

@ -0,0 +1,801 @@
#import "DDXMLPrivate.h"
#import "NSString+DDXML.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
/**
* Welcome to KissXML.
*
* The project page has documentation if you have questions.
* https://github.com/robbiehanson/KissXML
*
* If you're new to the project you may wish to read the "Getting Started" wiki.
* https://github.com/robbiehanson/KissXML/wiki/GettingStarted
*
* KissXML provides a drop-in replacement for Apple's NSXML class cluster.
* The goal is to get the exact same behavior as the NSXML classes.
*
* For API Reference, see Apple's excellent documentation,
* either via Xcode's Mac OS X documentation, or via the web:
*
* https://github.com/robbiehanson/KissXML/wiki/Reference
**/
@implementation DDXMLElement
/**
* Returns a DDXML wrapper object for the given primitive node.
* The given node MUST be non-NULL and of the proper type.
**/
+ (id)nodeWithElementPrimitive:(xmlNodePtr)node owner:(DDXMLNode *)owner
{
return [[DDXMLElement alloc] initWithElementPrimitive:node owner:owner];
}
- (id)initWithElementPrimitive:(xmlNodePtr)node owner:(DDXMLNode *)inOwner
{
self = [super initWithPrimitive:(xmlKindPtr)node owner:inOwner];
return self;
}
+ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes
NSAssert(NO, @"Use nodeWithElementPrimitive:owner:");
return nil;
}
- (id)initWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)inOwner
{
// Promote initializers which use proper parameter types to enable compiler to catch more mistakes.
NSAssert(NO, @"Use initWithElementPrimitive:owner:");
return nil;
}
- (id)initWithName:(NSString *)name
{
// Note: Make every guarantee that genericPtr is not null
xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]);
if (node == NULL)
{
return nil;
}
return [self initWithElementPrimitive:node owner:nil];
}
- (id)initWithName:(NSString *)name URI:(NSString *)URI
{
// Note: Make every guarantee that genericPtr is not null
xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]);
if (node == NULL)
{
return nil;
}
DDXMLElement *result = [self initWithElementPrimitive:node owner:nil];
[result setURI:URI];
return result;
}
- (id)initWithName:(NSString *)name stringValue:(NSString *)string
{
// Note: Make every guarantee that genericPtr is not null
xmlNodePtr node = xmlNewNode(NULL, [name xmlChar]);
if (node == NULL)
{
return nil;
}
DDXMLElement *result = [self initWithElementPrimitive:node owner:nil];
[result setStringValue:string];
return result;
}
- (id)initWithXMLString:(NSString *)string error:(NSError **)error
{
DDXMLDocument *doc = [[DDXMLDocument alloc] initWithXMLString:string options:0 error:error];
if (doc == nil)
{
return nil;
}
DDXMLElement *result = [doc rootElement];
[result detach];
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Elements by name
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Helper method elementsForName and elementsForLocalName:URI: so work isn't duplicated.
* The name parameter is required, all others are optional.
**/
- (NSArray *)_elementsForName:(NSString *)name
localName:(NSString *)localName
prefix:(NSString *)prefix
uri:(NSString *)uri
{
// This is a private/internal method
// Rule : !uri => match: name
// Rule : uri && hasPrefix => match: name || (localName && uri)
// Rule : uri && !hasPefix => match: name && uri
xmlNodePtr node = (xmlNodePtr)genericPtr;
NSMutableArray *result = [NSMutableArray array];
BOOL hasPrefix = [prefix length] > 0;
const xmlChar *xmlName = [name xmlChar];
const xmlChar *xmlLocalName = [localName xmlChar];
const xmlChar *xmlUri = [uri xmlChar];
xmlNodePtr child = node->children;
while (child)
{
if (IsXmlNodePtr(child))
{
BOOL match = NO;
if (uri == nil)
{
match = xmlStrEqual(child->name, xmlName);
}
else
{
BOOL nameMatch = xmlStrEqual(child->name, xmlName);
BOOL localNameMatch = xmlStrEqual(child->name, xmlLocalName);
BOOL uriMatch = NO;
if (child->ns)
{
uriMatch = xmlStrEqual(child->ns->href, xmlUri);
}
if (hasPrefix)
match = nameMatch || (localNameMatch && uriMatch);
else
match = nameMatch && uriMatch;
}
if (match)
{
[result addObject:[DDXMLElement nodeWithElementPrimitive:child owner:self]];
}
}
child = child->next;
}
return result;
}
/**
* Returns the child element nodes (as DDXMLElement objects) of the receiver that have a specified name.
*
* If name is a qualified name, then this method invokes elementsForLocalName:URI: with the URI parameter set to
* the URI associated with the prefix. Otherwise comparison is based on string equality of the qualified or
* non-qualified name.
**/
- (NSArray *)elementsForName:(NSString *)name
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (name == nil) return [NSArray array];
// We need to check to see if name has a prefix.
// If it does have a prefix, we need to figure out what the corresponding URI is for that prefix,
// and then search for any elements that have the same name (including prefix) OR have the same URI.
// Otherwise we loop through the children as usual and do a string compare on the name
NSString *prefix;
NSString *localName;
[DDXMLNode getPrefix:&prefix localName:&localName forName:name];
if ([prefix length] > 0)
{
xmlNodePtr node = (xmlNodePtr)genericPtr;
// Note: We use xmlSearchNs instead of resolveNamespaceForName: because
// we want to avoid creating wrapper objects when possible.
xmlNsPtr ns = xmlSearchNs(node->doc, node, [prefix xmlChar]);
if (ns)
{
NSString *uri = [NSString stringWithUTF8String:((const char *)ns->href)];
return [self _elementsForName:name localName:localName prefix:prefix uri:uri];
}
}
return [self _elementsForName:name localName:localName prefix:prefix uri:nil];
}
- (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)uri
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
if (localName == nil) return [NSArray array];
// We need to figure out what the prefix is for this URI.
// Then we search for elements that are named prefix:localName OR (named localName AND have the given URI).
NSString *prefix = [self _recursiveResolvePrefixForURI:uri atNode:(xmlNodePtr)genericPtr];
if (prefix)
{
NSString *name = [NSString stringWithFormat:@"%@:%@", prefix, localName];
return [self _elementsForName:name localName:localName prefix:prefix uri:uri];
}
else
{
NSString *prefix;
NSString *realLocalName;
[DDXMLNode getPrefix:&prefix localName:&realLocalName forName:localName];
return [self _elementsForName:localName localName:realLocalName prefix:prefix uri:uri];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Attributes
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)_hasAttributeWithName:(NSString *)name
{
// This is a private/internal method
xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
if (attr)
{
const xmlChar *xmlName = [name xmlChar];
do
{
if (xmlStrEqual(attr->name, xmlName))
{
return YES;
}
attr = attr->next;
} while (attr);
}
return NO;
}
- (void)_removeAttributeForName:(NSString *)name
{
// This is a private/internal method
xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
if (attr)
{
const xmlChar *xmlName = [name xmlChar];
do
{
if (xmlStrEqual(attr->name, xmlName))
{
[DDXMLNode removeAttribute:attr];
return;
}
attr = attr->next;
} while(attr);
}
}
- (void)addAttribute:(DDXMLNode *)attribute
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// NSXML version uses this same assertion
DDXMLAssert([attribute _hasParent] == NO, @"Cannot add an attribute with a parent; detach or copy first");
DDXMLAssert(IsXmlAttrPtr(attribute->genericPtr), @"Not an attribute");
[self _removeAttributeForName:[attribute name]];
// xmlNodePtr xmlAddChild(xmlNodePtr parent, xmlNodePtr cur)
// Add a new node to @parent, at the end of the child (or property) list merging
// adjacent TEXT nodes (in which case @cur is freed). If the new node is ATTRIBUTE, it is added
// into properties instead of children. If there is an attribute with equal name, it is first destroyed.
xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)attribute->genericPtr);
// The attribute is now part of the xml tree heirarchy
attribute->owner = self;
}
- (void)removeAttributeForName:(NSString *)name
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
[self _removeAttributeForName:name];
}
- (NSArray *)attributes
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
NSMutableArray *result = [NSMutableArray array];
xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
while (attr != NULL)
{
[result addObject:[DDXMLAttributeNode nodeWithAttrPrimitive:attr owner:self]];
attr = attr->next;
}
return result;
}
- (DDXMLNode *)attributeForName:(NSString *)name
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
xmlAttrPtr attr = ((xmlNodePtr)genericPtr)->properties;
if (attr)
{
const xmlChar *xmlName = [name xmlChar];
do
{
if (attr->ns && attr->ns->prefix)
{
// If the attribute name was originally something like "xml:quack",
// then attr->name is "quack" and attr->ns->prefix is "xml".
//
// So if the user is searching for "xml:quack" we need to take the prefix into account.
// Note that "xml:quack" is what would be printed if we output the attribute.
if (xmlStrQEqual(attr->ns->prefix, attr->name, xmlName))
{
return [DDXMLAttributeNode nodeWithAttrPrimitive:attr owner:self];
}
}
else
{
if (xmlStrEqual(attr->name, xmlName))
{
return [DDXMLAttributeNode nodeWithAttrPrimitive:attr owner:self];
}
}
attr = attr->next;
} while (attr);
}
return nil;
}
/**
* Sets the list of attributes for the element.
* Any previously set attributes are removed.
**/
- (void)setAttributes:(NSArray *)attributes
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
[DDXMLNode removeAllAttributesFromNode:(xmlNodePtr)genericPtr];
NSUInteger i;
for (i = 0; i < [attributes count]; i++)
{
DDXMLNode *attribute = [attributes objectAtIndex:i];
[self addAttribute:attribute];
// Note: The addAttributes method properly sets the freeOnDealloc ivar.
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Namespaces
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)_removeNamespaceForPrefix:(NSString *)name
{
xmlNodePtr node = (xmlNodePtr)genericPtr;
// If name is nil or the empty string, the user is wishing to remove the default namespace
const xmlChar *xmlName = [name length] > 0 ? [name xmlChar] : NULL;
xmlNsPtr ns = node->nsDef;
while (ns != NULL)
{
if (xmlStrEqual(ns->prefix, xmlName))
{
[DDXMLNode removeNamespace:ns fromNode:node];
break;
}
ns = ns->next;
}
// Note: The removeNamespace method properly handles the situation where the namespace is the default namespace
}
- (void)_addNamespace:(DDXMLNode *)namespace
{
// NSXML version uses this same assertion
DDXMLAssert([namespace _hasParent] == NO, @"Cannot add a namespace with a parent; detach or copy first");
DDXMLAssert(IsXmlNsPtr(namespace->genericPtr), @"Not a namespace");
xmlNodePtr node = (xmlNodePtr)genericPtr;
xmlNsPtr ns = (xmlNsPtr)namespace->genericPtr;
// Beware: [namespace prefix] does NOT return what you might expect. Use [namespace name] instead.
NSString *namespaceName = [namespace name];
[self _removeNamespaceForPrefix:namespaceName];
xmlNsPtr currentNs = node->nsDef;
if (currentNs == NULL)
{
node->nsDef = ns;
}
else
{
while (currentNs->next != NULL)
{
currentNs = currentNs->next;
}
currentNs->next = ns;
}
// The namespace is now part of the xml tree heirarchy
namespace->owner = self;
if ([namespace isKindOfClass:[DDXMLNamespaceNode class]])
{
DDXMLNamespaceNode *ddNamespace = (DDXMLNamespaceNode *)namespace;
// The xmlNs structure doesn't contain a reference to the parent, so we manage our own reference
[ddNamespace _setNsParentPtr:node];
}
// Did we just add a default namespace
if ([namespaceName isEqualToString:@""])
{
node->ns = ns;
// Note: The removeNamespaceForPrefix method above properly handled removing any previous default namespace
}
}
- (void)addNamespace:(DDXMLNode *)namespace
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
[self _addNamespace:namespace];
}
- (void)removeNamespaceForPrefix:(NSString *)name
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
[self _removeNamespaceForPrefix:name];
}
- (NSArray *)namespaces
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
NSMutableArray *result = [NSMutableArray array];
xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef;
while (ns != NULL)
{
[result addObject:[DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr owner:self]];
ns = ns->next;
}
return result;
}
- (DDXMLNode *)namespaceForPrefix:(NSString *)prefix
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// If the prefix is nil or the empty string, the user is requesting the default namespace
if ([prefix length] == 0)
{
// Requesting the default namespace
xmlNsPtr ns = ((xmlNodePtr)genericPtr)->ns;
if (ns != NULL)
{
return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr owner:self];
}
}
else
{
xmlNsPtr ns = ((xmlNodePtr)genericPtr)->nsDef;
if (ns)
{
const xmlChar *xmlPrefix = [prefix xmlChar];
do
{
if (xmlStrEqual(ns->prefix, xmlPrefix))
{
return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:(xmlNodePtr)genericPtr owner:self];
}
ns = ns->next;
} while (ns);
}
}
return nil;
}
- (void)setNamespaces:(NSArray *)namespaces
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
[DDXMLNode removeAllNamespacesFromNode:(xmlNodePtr)genericPtr];
NSUInteger i;
for (i = 0; i < [namespaces count]; i++)
{
DDXMLNode *namespace = [namespaces objectAtIndex:i];
[self _addNamespace:namespace];
// Note: The addNamespace method properly sets the freeOnDealloc ivar.
}
}
/**
* Recursively searches the given node for the given namespace
**/
- (DDXMLNode *)_recursiveResolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr
{
// This is a private/internal method
if (nodePtr == NULL) return nil;
xmlNsPtr ns = nodePtr->nsDef;
if (ns)
{
const xmlChar *xmlPrefix = [prefix xmlChar];
do
{
if (xmlStrEqual(ns->prefix, xmlPrefix))
{
return [DDXMLNamespaceNode nodeWithNsPrimitive:ns nsParent:nodePtr owner:self];
}
ns = ns->next;
} while(ns);
}
return [self _recursiveResolveNamespaceForPrefix:prefix atNode:nodePtr->parent];
}
/**
* Returns the namespace node with the prefix matching the given qualified name.
* Eg: You pass it "a:dog", it returns the namespace (defined in this node or parent nodes) that has the "a" prefix.
**/
- (DDXMLNode *)resolveNamespaceForName:(NSString *)name
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// If the user passes nil or an empty string for name, they're looking for the default namespace.
if ([name length] == 0)
{
return [self _recursiveResolveNamespaceForPrefix:nil atNode:(xmlNodePtr)genericPtr];
}
NSString *prefix = [[self class] prefixForName:name];
if ([prefix length] > 0)
{
// Unfortunately we can't use xmlSearchNs because it returns an xmlNsPtr.
// This gives us mostly what we want, except we also need to know the nsParent.
// So we do the recursive search ourselves.
return [self _recursiveResolveNamespaceForPrefix:prefix atNode:(xmlNodePtr)genericPtr];
}
return nil;
}
/**
* Recursively searches the given node for a namespace with the given URI, and a set prefix.
**/
- (NSString *)_recursiveResolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr
{
// This is a private/internal method
if (nodePtr == NULL) return nil;
xmlNsPtr ns = nodePtr->nsDef;
if (ns)
{
const xmlChar *xmlUri = [uri xmlChar];
do
{
if (xmlStrEqual(ns->href, xmlUri))
{
if (ns->prefix != NULL)
{
return [NSString stringWithUTF8String:((const char *)ns->prefix)];
}
}
ns = ns->next;
} while (ns);
}
return [self _recursiveResolvePrefixForURI:uri atNode:nodePtr->parent];
}
/**
* Returns the prefix associated with the specified URI.
* Returns a string that is the matching prefix or nil if it finds no matching prefix.
**/
- (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// We can't use xmlSearchNsByHref because it will return xmlNsPtr's with NULL prefixes.
// We're looking for a definitive prefix for the given URI.
return [self _recursiveResolvePrefixForURI:namespaceURI atNode:(xmlNodePtr)genericPtr];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Children
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)addChild:(DDXMLNode *)child
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// NSXML version uses these same assertions
DDXMLAssert([child _hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first");
DDXMLAssert(IsXmlNodePtr(child->genericPtr),
@"Elements can only have text, elements, processing instructions, and comments as children");
xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)child->genericPtr);
// The node is now part of the xml tree heirarchy
child->owner = self;
}
- (void)insertChild:(DDXMLNode *)child atIndex:(NSUInteger)index
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
// NSXML version uses these same assertions
DDXMLAssert([child _hasParent] == NO, @"Cannot add a child that has a parent; detach or copy first");
DDXMLAssert(IsXmlNodePtr(child->genericPtr),
@"Elements can only have text, elements, processing instructions, and comments as children");
NSUInteger i = 0;
xmlNodePtr childNodePtr = ((xmlNodePtr)genericPtr)->children;
while (childNodePtr != NULL)
{
// Ignore all but element, comment, text, or processing instruction nodes
if (IsXmlNodePtr(childNodePtr))
{
if (i == index)
{
xmlAddPrevSibling(childNodePtr, (xmlNodePtr)child->genericPtr);
child->owner = self;
return;
}
i++;
}
childNodePtr = childNodePtr->next;
}
if (i == index)
{
xmlAddChild((xmlNodePtr)genericPtr, (xmlNodePtr)child->genericPtr);
child->owner = self;
return;
}
// NSXML version uses this same assertion
DDXMLAssert(NO, @"index (%u) beyond bounds (%u)", (unsigned)index, (unsigned)++i);
}
- (void)removeChildAtIndex:(NSUInteger)index
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
NSUInteger i = 0;
xmlNodePtr child = ((xmlNodePtr)genericPtr)->children;
while (child != NULL)
{
// Ignore all but element, comment, text, or processing instruction nodes
if (IsXmlNodePtr(child))
{
if (i == index)
{
[DDXMLNode removeChild:child];
return;
}
i++;
}
child = child->next;
}
}
- (void)setChildren:(NSArray *)children
{
#if DDXML_DEBUG_MEMORY_ISSUES
DDXMLNotZombieAssert();
#endif
[DDXMLNode removeAllChildrenFromNode:(xmlNodePtr)genericPtr];
NSUInteger i;
for (i = 0; i < [children count]; i++)
{
DDXMLNode *child = [children objectAtIndex:i];
[self addChild:child];
// Note: The addChild method properly sets the freeOnDealloc ivar.
}
}
@end

167
Example/Pods/KissXML/KissXML/DDXMLNode.h generated Normal file
View File

@ -0,0 +1,167 @@
#import <Foundation/Foundation.h>
#if DDXML_LIBXML_MODULE_ENABLED
#if TARGET_OS_IOS && TARGET_OS_EMBEDDED
@import libxml;
#elif TARGET_IPHONE_SIMULATOR
@import libxmlSimu;
#elif TARGET_OS_MAC
@import libxmlMac;
#endif
#else
#import <libxml/tree.h>
#endif
@class DDXMLDocument;
/**
* Welcome to KissXML.
*
* The project page has documentation if you have questions.
* https://github.com/robbiehanson/KissXML
*
* If you're new to the project you may wish to read the "Getting Started" wiki.
* https://github.com/robbiehanson/KissXML/wiki/GettingStarted
*
* KissXML provides a drop-in replacement for Apple's NSXML class cluster.
* The goal is to get the exact same behavior as the NSXML classes.
*
* For API Reference, see Apple's excellent documentation,
* either via Xcode's Mac OS X documentation, or via the web:
*
* https://github.com/robbiehanson/KissXML/wiki/Reference
**/
enum {
DDXMLInvalidKind = 0,
DDXMLDocumentKind = XML_DOCUMENT_NODE,
DDXMLElementKind = XML_ELEMENT_NODE,
DDXMLAttributeKind = XML_ATTRIBUTE_NODE,
DDXMLNamespaceKind = XML_NAMESPACE_DECL,
DDXMLProcessingInstructionKind = XML_PI_NODE,
DDXMLCommentKind = XML_COMMENT_NODE,
DDXMLTextKind = XML_TEXT_NODE,
DDXMLDTDKind = XML_DTD_NODE,
DDXMLEntityDeclarationKind = XML_ENTITY_DECL,
DDXMLAttributeDeclarationKind = XML_ATTRIBUTE_DECL,
DDXMLElementDeclarationKind = XML_ELEMENT_DECL,
DDXMLNotationDeclarationKind = XML_NOTATION_NODE
};
typedef NSUInteger DDXMLNodeKind;
enum {
DDXMLNodeOptionsNone = 0,
DDXMLNodeExpandEmptyElement = 1 << 1,
DDXMLNodeCompactEmptyElement = 1 << 2,
DDXMLNodePrettyPrint = 1 << 17,
};
//extern struct _xmlKind;
@interface DDXMLNode : NSObject <NSCopying>
{
// Every DDXML object is simply a wrapper around an underlying libxml node
struct _xmlKind *genericPtr;
// Every libxml node resides somewhere within an xml tree heirarchy.
// We cannot free the tree heirarchy until all referencing nodes have been released.
// So all nodes retain a reference to the node that created them,
// and when the last reference is released the tree gets freed.
DDXMLNode *owner;
}
//- (id)initWithKind:(DDXMLNodeKind)kind;
//- (id)initWithKind:(DDXMLNodeKind)kind options:(NSUInteger)options;
//+ (id)document;
//+ (id)documentWithRootElement:(DDXMLElement *)element;
+ (id)elementWithName:(NSString *)name;
+ (id)elementWithName:(NSString *)name URI:(NSString *)URI;
+ (id)elementWithName:(NSString *)name stringValue:(NSString *)string;
+ (id)elementWithName:(NSString *)name children:(NSArray *)children attributes:(NSArray *)attributes;
+ (id)attributeWithName:(NSString *)name stringValue:(NSString *)stringValue;
+ (id)attributeWithName:(NSString *)name URI:(NSString *)URI stringValue:(NSString *)stringValue;
+ (id)namespaceWithName:(NSString *)name stringValue:(NSString *)stringValue;
+ (id)processingInstructionWithName:(NSString *)name stringValue:(NSString *)stringValue;
+ (id)commentWithStringValue:(NSString *)stringValue;
+ (id)textWithStringValue:(NSString *)stringValue;
//+ (id)DTDNodeWithXMLString:(NSString *)string;
#pragma mark --- Properties ---
- (DDXMLNodeKind)kind;
- (void)setName:(NSString *)name;
- (NSString *)name;
//- (void)setObjectValue:(id)value;
//- (id)objectValue;
- (void)setStringValue:(NSString *)string;
//- (void)setStringValue:(NSString *)string resolvingEntities:(BOOL)resolve;
- (NSString *)stringValue;
#pragma mark --- Tree Navigation ---
- (NSUInteger)index;
- (NSUInteger)level;
- (DDXMLDocument *)rootDocument;
- (DDXMLNode *)parent;
- (NSUInteger)childCount;
- (NSArray *)children;
- (DDXMLNode *)childAtIndex:(NSUInteger)index;
- (DDXMLNode *)previousSibling;
- (DDXMLNode *)nextSibling;
- (DDXMLNode *)previousNode;
- (DDXMLNode *)nextNode;
- (void)detach;
- (NSString *)XPath;
#pragma mark --- QNames ---
- (NSString *)localName;
- (NSString *)prefix;
- (void)setURI:(NSString *)URI;
- (NSString *)URI;
+ (NSString *)localNameForName:(NSString *)name;
+ (NSString *)prefixForName:(NSString *)name;
//+ (DDXMLNode *)predefinedNamespaceForPrefix:(NSString *)name;
#pragma mark --- Output ---
- (NSString *)description;
- (NSString *)XMLString;
- (NSString *)XMLStringWithOptions:(NSUInteger)options;
//- (NSString *)canonicalXMLStringPreservingComments:(BOOL)comments;
#pragma mark --- XPath/XQuery ---
- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error;
//- (NSArray *)objectsForXQuery:(NSString *)xquery constants:(NSDictionary *)constants error:(NSError **)error;
//- (NSArray *)objectsForXQuery:(NSString *)xquery error:(NSError **)error;
@end

2905
Example/Pods/KissXML/KissXML/DDXMLNode.m generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,229 @@
#import "DDXML.h"
// We can't rely solely on NSAssert, because many developers disable them for release builds.
// Our API contract requires us to keep these assertions intact.
#define DDXMLAssert(condition, desc, ...) \
do{ \
if(!(condition)) { \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
object:self \
file:[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__ \
description:(desc), ##__VA_ARGS__]; \
} \
}while(NO)
// Create assertion to ensure xml node is not a zombie.
#if DDXML_DEBUG_MEMORY_ISSUES
#define DDXMLNotZombieAssert() \
do{ \
if(DDXMLIsZombie(genericPtr, self)) { \
NSString *desc = @"XML node is a zombie - It's parent structure has been freed!"; \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
object:self \
file:[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__ \
description:desc]; \
} \
}while(NO)
#endif
#define DDLastErrorKey @"DDXML:LastError"
/**
* DDXMLNode can represent several underlying types, such as xmlNodePtr, xmlDocPtr, xmlAttrPtr, xmlNsPtr, etc.
* All of these are pointers to structures, and all of those structures start with a pointer, and a type.
* The xmlKind struct is used as a generic structure, and a stepping stone.
* We use it to check the type of a structure, and then perform the appropriate cast.
*
* For example:
* if(genericPtr->type == XML_ATTRIBUTE_NODE)
* {
* xmlAttrPtr attr = (xmlAttrPtr)genericPtr;
* // Do something with attr
* }
**/
struct _xmlKind {
void * ignore;
xmlElementType type;
};
typedef struct _xmlKind *xmlKindPtr;
/**
* Most xml types all start with this standard structure. In fact, all do except the xmlNsPtr.
* We will occasionally take advantage of this to simplify code when the code wouldn't vary from type to type.
* Obviously, you cannnot cast a xmlNsPtr to a xmlStdPtr.
**/
struct _xmlStd {
void * _private;
xmlElementType type;
const xmlChar *name;
struct _xmlNode *children;
struct _xmlNode *last;
struct _xmlNode *parent;
struct _xmlStd *next;
struct _xmlStd *prev;
struct _xmlDoc *doc;
};
typedef struct _xmlStd *xmlStdPtr;
NS_INLINE BOOL IsXmlAttrPtr(void *kindPtr)
{
return ((xmlKindPtr)kindPtr)->type == XML_ATTRIBUTE_NODE;
}
NS_INLINE BOOL IsXmlNodePtr(void *kindPtr)
{
switch (((xmlKindPtr)kindPtr)->type)
{
case XML_ELEMENT_NODE :
case XML_PI_NODE :
case XML_COMMENT_NODE :
case XML_TEXT_NODE :
case XML_CDATA_SECTION_NODE : return YES;
default : return NO;
}
}
NS_INLINE BOOL IsXmlDocPtr(void *kindPtr)
{
return ((xmlKindPtr)kindPtr)->type == XML_DOCUMENT_NODE;
}
NS_INLINE BOOL IsXmlDtdPtr(void *kindPtr)
{
return ((xmlKindPtr)kindPtr)->type == XML_DTD_NODE;
}
NS_INLINE BOOL IsXmlNsPtr(void *kindPtr)
{
return ((xmlKindPtr)kindPtr)->type == XML_NAMESPACE_DECL;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface DDXMLNamespaceNode : DDXMLNode
{
// The xmlNsPtr type doesn't store a reference to it's parent.
// This is here to fix the problem, and make this class more compatible with the NSXML classes.
xmlNodePtr nsParentPtr;
}
+ (id)nodeWithNsPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent owner:(DDXMLNode *)owner;
- (id)initWithNsPrimitive:(xmlNsPtr)ns nsParent:(xmlNodePtr)parent owner:(DDXMLNode *)owner;
- (xmlNodePtr)_nsParentPtr;
- (void)_setNsParentPtr:(xmlNodePtr)parentPtr;
// Overrides several methods in DDXMLNode
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface DDXMLAttributeNode : DDXMLNode
{
// The xmlAttrPtr type doesn't allow for ownership of a namespace.
//
// In other types, such as xmlNodePtr:
// - nsDef stores namespaces that are owned by the node (have been alloced by the node).
// - ns is simply a pointer to the default namespace of the node, which may or may not reside in its own nsDef list.
//
// The xmlAttrPtr only has a ns, it doesn't have a nsDef list.
// Which completely makes sense really, since namespaces have to be defined elsewhere.
//
// This is here to maintain compatibility with the NSXML classes,
// where one can assign a namespace to an attribute independently.
xmlNsPtr attrNsPtr;
}
+ (id)nodeWithAttrPrimitive:(xmlAttrPtr)attr owner:(DDXMLNode *)owner;
- (id)initWithAttrPrimitive:(xmlAttrPtr)attr owner:(DDXMLNode *)owner;
// Overrides several methods in DDXMLNode
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface DDXMLInvalidNode : DDXMLNode
{
}
// Overrides several methods in DDXMLNode
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface DDXMLNode (PrivateAPI)
+ (id)nodeWithUnknownPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner;
+ (id)nodeWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner;
- (id)initWithPrimitive:(xmlKindPtr)kindPtr owner:(DDXMLNode *)owner;
- (BOOL)_hasParent;
+ (void)getHasPrefix:(BOOL *)hasPrefixPtr localName:(NSString **)localNamePtr forName:(NSString *)name;
+ (void)getPrefix:(NSString **)prefixPtr localName:(NSString **)localNamePtr forName:(NSString *)name;
+ (void)recursiveStripDocPointersFromNode:(xmlNodePtr)node;
+ (void)detachNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node;
+ (void)removeNamespace:(xmlNsPtr)ns fromNode:(xmlNodePtr)node;
+ (void)removeAllNamespacesFromNode:(xmlNodePtr)node;
+ (void)detachAttribute:(xmlAttrPtr)attr andClean:(BOOL)clean;
+ (void)detachAttribute:(xmlAttrPtr)attr;
+ (void)removeAttribute:(xmlAttrPtr)attr;
+ (void)removeAllAttributesFromNode:(xmlNodePtr)node;
+ (void)detachChild:(xmlNodePtr)child andClean:(BOOL)clean andFixNamespaces:(BOOL)fixNamespaces;
+ (void)detachChild:(xmlNodePtr)child;
+ (void)removeChild:(xmlNodePtr)child;
+ (void)removeAllChildrenFromNode:(xmlNodePtr)node;
BOOL DDXMLIsZombie(void *xmlPtr, DDXMLNode *wrapper);
+ (NSError *)lastError;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface DDXMLElement (PrivateAPI)
+ (id)nodeWithElementPrimitive:(xmlNodePtr)node owner:(DDXMLNode *)owner;
- (id)initWithElementPrimitive:(xmlNodePtr)node owner:(DDXMLNode *)owner;
- (DDXMLNode *)_recursiveResolveNamespaceForPrefix:(NSString *)prefix atNode:(xmlNodePtr)nodePtr;
- (NSString *)_recursiveResolvePrefixForURI:(NSString *)uri atNode:(xmlNodePtr)nodePtr;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface DDXMLDocument (PrivateAPI)
+ (id)nodeWithDocPrimitive:(xmlDocPtr)doc owner:(DDXMLNode *)owner;
- (id)initWithDocPrimitive:(xmlDocPtr)doc owner:(DDXMLNode *)owner;
@end

8
Example/Pods/KissXML/LICENSE.txt generated Normal file
View File

@ -0,0 +1,8 @@
Copyright (c) 2012, Robbie Hanson
All rights reserved.
Redistribution and use 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

34
Example/Pods/KissXML/README.markdown generated Normal file
View File

@ -0,0 +1,34 @@
# [KissXML](https://github.com/robbiehanson/KissXML)
[![CI Status](http://img.shields.io/travis/robbiehanson/KissXML.svg?style=flat)](https://travis-ci.org/robbiehanson/KissXML)
[![Version](https://img.shields.io/cocoapods/v/KissXML.svg?style=flat)](http://cocoapods.org/pods/KissXML)
[![License](https://img.shields.io/cocoapods/l/KissXML.svg?style=flat)](http://cocoapods.org/pods/KissXML)
[![Platform](https://img.shields.io/cocoapods/p/KissXML.svg?style=flat)](http://cocoapods.org/pods/KissXML)
KissXML provides a drop-in replacement for Apple's NSXML class culster in environments without NSXML (e.g. iOS).
It is implemented atop the defacto libxml2 C library, which comes pre-installed on Mac & iOS.
But it shields you from all the nasty low-level C pointers and malloc's, and provides an easy-to-use Objective-C library.
It is designed for speed and reliability, so it's read-access thread-safe and will "just-work".
That is, KissXML provides an API that follows "what-you-would-expect" rules from an Objective-C library.
So feel free to do things like parallel processing of an xml document using blocks.
It will "just work" so you can get back to designing the rest of your app.
KissXML is a mature library used in thousands of products. It's also used in other libraries, such as [XMPPFramework](http://code.google.com/p/xmppframework/) (an objective-c library for real-time xml streaming). It's even used in hospital applications.
KissXML was inspired by the TouchXML project, but was created to add full support for generating XML as well as supporting the entire NSXML API.
**[Get started using KissXML](https://github.com/robbiehanson/KissXML/wiki/GettingStarted)**<br/>
**[Learn more about KissXML](https://github.com/robbiehanson/KissXML/wiki)**<br/>
<br/>
Can't find the answer to your question in any of the [wiki](https://github.com/robbiehanson/KissXML/wiki) articles? Try the **[mailing list](http://groups.google.com/group/kissxml)**.
<br/>
<br/>
Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](http://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=69SPF7R4ZF69J)
## Changelog
* 5.0.2 - Jan 26 2016 - Enable Swift support via `libxml/module.modulemap` and `DDXML_LIBXML_MODULE_ENABLED` macro. You can use the `KissXML/libxml_module` CocoaPods subspec to enable this feature.
* 5.0.1 - Jan 21 2016 - Run tests on iOS and Mac targets.

View File

@ -0,0 +1,39 @@
{
"name": "PNXMPPFramework",
"version": "0.1.0",
"summary": "A short of PNXMPPFramework.",
"homepage": "https://github.com/<GITHUB_USERNAME>/PNXMPPFramework",
"license": "MIT",
"authors": {
"Giuseppe Nucifora": "me@giuseppenucifora.com"
},
"source": {
"git": "https://github.com/<GITHUB_USERNAME>/PNXMPPFramework.git",
"tag": "0.1.0"
},
"platforms": {
"ios": "7.0"
},
"requires_arc": true,
"source_files": "Pod/Classes/**/*",
"resource_bundles": {
"PNXMPPFramework": [
"Pod/Assets/*.png"
]
},
"dependencies": {
"CocoaLumberjack": [
],
"CocoaAsyncSocket": [
]
},
"ios": {
"dependencies": {
"KissXML": [
]
}
}
}

49
Example/Pods/Manifest.lock generated Normal file
View File

@ -0,0 +1,49 @@
PODS:
- CocoaAsyncSocket (7.4.3):
- CocoaAsyncSocket/All (= 7.4.3)
- CocoaAsyncSocket/All (7.4.3):
- CocoaAsyncSocket/GCD
- CocoaAsyncSocket/RunLoop
- CocoaAsyncSocket/GCD (7.4.3)
- CocoaAsyncSocket/RunLoop (7.4.3)
- CocoaLumberjack (2.2.0):
- CocoaLumberjack/Default (= 2.2.0)
- CocoaLumberjack/Extensions (= 2.2.0)
- CocoaLumberjack/Core (2.2.0)
- CocoaLumberjack/Default (2.2.0):
- CocoaLumberjack/Core
- CocoaLumberjack/Extensions (2.2.0):
- CocoaLumberjack/Default
- FBSnapshotTestCase (2.0.7):
- FBSnapshotTestCase/SwiftSupport (= 2.0.7)
- FBSnapshotTestCase/Core (2.0.7)
- FBSnapshotTestCase/SwiftSupport (2.0.7):
- FBSnapshotTestCase/Core
- KissXML (5.0.3):
- KissXML/Standard (= 5.0.3)
- KissXML/Core (5.0.3)
- KissXML/Standard (5.0.3):
- KissXML/Core
- PNXMPPFramework (0.1.0):
- CocoaAsyncSocket
- CocoaLumberjack
- KissXML
DEPENDENCIES:
- FBSnapshotTestCase
- PNXMPPFramework (from `../`)
EXTERNAL SOURCES:
PNXMPPFramework:
:path: "../"
SPEC CHECKSUMS:
CocoaAsyncSocket: a18c75dca4b08723628a0bacca6e94803d90be91
CocoaLumberjack: 17fe8581f84914d5d7e6360f7c70022b173c3ae0
FBSnapshotTestCase: 7e85180d0d141a0cf472352edda7e80d7eaeb547
KissXML: d19dd6dc65e0dc721ba92b3077b8ebdd240f1c1e
PNXMPPFramework: 85a177de196fd742392f6ed0053c9cd2dd160f06
PODFILE CHECKSUM: c24dacdc80a49fe0e0fea049a6d762eb76667498
COCOAPODS: 1.0.0.beta.3

1842
Example/Pods/Pods.xcodeproj/project.pbxproj generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES">
<BuildableReference
BuildableIdentifier = 'primary'
BlueprintIdentifier = '5A35D899250B8C7553F3AC9DE053FA3F'
BlueprintName = 'PNXMPPFramework'
ReferencedContainer = 'container:Pods.xcodeproj'
BuildableName = 'PNXMPPFramework.framework'>
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_CocoaAsyncSocket : NSObject
@end
@implementation PodsDummy_CocoaAsyncSocket
@end

View File

@ -0,0 +1,4 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif

View File

@ -0,0 +1,10 @@
#import <UIKit/UIKit.h>
#import "GCDAsyncSocket.h"
#import "GCDAsyncUdpSocket.h"
#import "AsyncSocket.h"
#import "AsyncUdpSocket.h"
FOUNDATION_EXPORT double CocoaAsyncSocketVersionNumber;
FOUNDATION_EXPORT const unsigned char CocoaAsyncSocketVersionString[];

View File

@ -0,0 +1,6 @@
framework module CocoaAsyncSocket {
umbrella header "CocoaAsyncSocket-umbrella.h"
export *
module * { export * }
}

View File

@ -0,0 +1,6 @@
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_LDFLAGS = -framework "CFNetwork" -framework "Security"
PODS_ROOT = ${SRCROOT}
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES

View File

@ -0,0 +1,26 @@
<?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>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>7.4.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_CocoaLumberjack : NSObject
@end
@implementation PodsDummy_CocoaLumberjack
@end

View File

@ -0,0 +1,4 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif

View File

@ -0,0 +1,36 @@
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 *
}
}

View File

@ -0,0 +1,5 @@
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
PODS_ROOT = ${SRCROOT}
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES

View File

@ -0,0 +1,26 @@
<?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>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.2.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_FBSnapshotTestCase : NSObject
@end
@implementation PodsDummy_FBSnapshotTestCase
@end

View File

@ -0,0 +1,4 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif

View File

@ -0,0 +1,15 @@
framework module FBSnapshotTestCase {
umbrella header "FBSnapshotTestCase.h"
export *
module * { export * }
header "FBSnapshotTestCase.h"
header "FBSnapshotTestCasePlatform.h"
header "FBSnapshotTestController.h"
private header "UIImage+Compare.h"
private header "UIImage+Diff.h"
private header "UIImage+Snapshot.h"
}

View File

@ -0,0 +1,9 @@
ENABLE_BITCODE = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_LDFLAGS = -framework "Foundation" -framework "QuartzCore" -framework "UIKit" -framework "XCTest"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_ROOT = ${SRCROOT}
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES

View File

@ -0,0 +1,26 @@
<?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>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.0.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,26 @@
<?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>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>5.0.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_KissXML : NSObject
@end
@implementation PodsDummy_KissXML
@end

View File

@ -0,0 +1,4 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif

View File

@ -0,0 +1,13 @@
#import <UIKit/UIKit.h>
#import "DDXMLElementAdditions.h"
#import "NSString+DDXML.h"
#import "DDXML.h"
#import "DDXMLDocument.h"
#import "DDXMLElement.h"
#import "DDXMLNode.h"
#import "DDXMLPrivate.h"
FOUNDATION_EXPORT double KissXMLVersionNumber;
FOUNDATION_EXPORT const unsigned char KissXMLVersionString[];

View File

@ -0,0 +1,6 @@
framework module KissXML {
umbrella header "KissXML-umbrella.h"
export *
module * { export * }
}

View File

@ -0,0 +1,8 @@
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" $(SDKROOT)/usr/include/libxml2
OTHER_CFLAGS = $(inherited) -DDDXML_LIBXML_MODULE_ENABLED=0
OTHER_LDFLAGS = -l"xml2"
PODS_ROOT = ${SRCROOT}
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES

View File

@ -0,0 +1,26 @@
<?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>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_PNXMPPFramework : NSObject
@end
@implementation PodsDummy_PNXMPPFramework
@end

View File

@ -0,0 +1,4 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif

View File

@ -0,0 +1,6 @@
#import <UIKit/UIKit.h>
FOUNDATION_EXPORT double PNXMPPFrameworkVersionNumber;
FOUNDATION_EXPORT const unsigned char PNXMPPFrameworkVersionString[];

Some files were not shown because too many files have changed in this diff Show More